facet.e2e-spec.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899
  1. import { pick } from '@vendure/common/lib/pick';
  2. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN } from '@vendure/testing';
  3. import gql from 'graphql-tag';
  4. import path from 'path';
  5. import { initialData } from '../../../e2e-common/e2e-initial-data';
  6. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  7. import { FACET_VALUE_FRAGMENT, FACET_WITH_VALUES_FRAGMENT } from './graphql/fragments';
  8. import {
  9. AssignFacetsToChannelMutation,
  10. AssignFacetsToChannelMutationVariables,
  11. AssignProductsToChannel,
  12. ChannelFragment,
  13. CreateChannel,
  14. CreateFacet,
  15. CreateFacetValues,
  16. CurrencyCode,
  17. DeleteFacet,
  18. DeleteFacetValues,
  19. DeletionResult,
  20. FacetWithValues,
  21. GetFacetList,
  22. GetFacetListSimple,
  23. GetFacetListSimpleQuery,
  24. GetFacetWithValues,
  25. GetProductListWithVariants,
  26. GetProductWithFacetValues,
  27. GetProductWithVariants,
  28. LanguageCode,
  29. RemoveCollectionsFromChannelMutationVariables,
  30. RemoveFacetsFromChannelMutation,
  31. RemoveFacetsFromChannelMutationVariables,
  32. UpdateFacet,
  33. UpdateFacetValues,
  34. UpdateProduct,
  35. UpdateProductVariants,
  36. } from './graphql/generated-e2e-admin-types';
  37. import {
  38. ASSIGN_PRODUCT_TO_CHANNEL,
  39. CREATE_CHANNEL,
  40. CREATE_FACET,
  41. GET_FACET_LIST,
  42. GET_FACET_LIST_SIMPLE,
  43. GET_PRODUCT_WITH_VARIANTS,
  44. UPDATE_FACET,
  45. UPDATE_PRODUCT,
  46. UPDATE_PRODUCT_VARIANTS,
  47. } from './graphql/shared-definitions';
  48. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  49. // tslint:disable:no-non-null-assertion
  50. describe('Facet resolver', () => {
  51. const { server, adminClient, shopClient } = createTestEnvironment(testConfig());
  52. let brandFacet: FacetWithValues.Fragment;
  53. let speakerTypeFacet: FacetWithValues.Fragment;
  54. beforeAll(async () => {
  55. await server.init({
  56. initialData,
  57. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  58. customerCount: 1,
  59. });
  60. await adminClient.asSuperAdmin();
  61. }, TEST_SETUP_TIMEOUT_MS);
  62. afterAll(async () => {
  63. await server.destroy();
  64. });
  65. it('createFacet', async () => {
  66. const result = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(CREATE_FACET, {
  67. input: {
  68. isPrivate: false,
  69. code: 'speaker-type',
  70. translations: [{ languageCode: LanguageCode.en, name: 'Speaker Type' }],
  71. values: [
  72. {
  73. code: 'portable',
  74. translations: [{ languageCode: LanguageCode.en, name: 'Portable' }],
  75. },
  76. ],
  77. },
  78. });
  79. speakerTypeFacet = result.createFacet;
  80. expect(speakerTypeFacet).toMatchSnapshot();
  81. });
  82. it('updateFacet', async () => {
  83. const result = await adminClient.query<UpdateFacet.Mutation, UpdateFacet.Variables>(UPDATE_FACET, {
  84. input: {
  85. id: speakerTypeFacet.id,
  86. translations: [{ languageCode: LanguageCode.en, name: 'Speaker Category' }],
  87. isPrivate: true,
  88. },
  89. });
  90. expect(result.updateFacet.name).toBe('Speaker Category');
  91. });
  92. it('createFacetValues', async () => {
  93. const { createFacetValues } = await adminClient.query<
  94. CreateFacetValues.Mutation,
  95. CreateFacetValues.Variables
  96. >(CREATE_FACET_VALUES, {
  97. input: [
  98. {
  99. facetId: speakerTypeFacet.id,
  100. code: 'pc',
  101. translations: [{ languageCode: LanguageCode.en, name: 'PC Speakers' }],
  102. },
  103. {
  104. facetId: speakerTypeFacet.id,
  105. code: 'hi-fi',
  106. translations: [{ languageCode: LanguageCode.en, name: 'Hi Fi Speakers' }],
  107. },
  108. ],
  109. });
  110. expect(createFacetValues.length).toBe(2);
  111. expect(pick(createFacetValues.find(fv => fv.code === 'pc')!, ['code', 'facet', 'name'])).toEqual({
  112. code: 'pc',
  113. facet: {
  114. id: 'T_2',
  115. name: 'Speaker Category',
  116. },
  117. name: 'PC Speakers',
  118. });
  119. expect(pick(createFacetValues.find(fv => fv.code === 'hi-fi')!, ['code', 'facet', 'name'])).toEqual({
  120. code: 'hi-fi',
  121. facet: {
  122. id: 'T_2',
  123. name: 'Speaker Category',
  124. },
  125. name: 'Hi Fi Speakers',
  126. });
  127. });
  128. it('updateFacetValues', async () => {
  129. const portableFacetValue = speakerTypeFacet.values.find(v => v.code === 'portable')!;
  130. const result = await adminClient.query<UpdateFacetValues.Mutation, UpdateFacetValues.Variables>(
  131. UPDATE_FACET_VALUES,
  132. {
  133. input: [
  134. {
  135. id: portableFacetValue.id,
  136. code: 'compact',
  137. },
  138. ],
  139. },
  140. );
  141. expect(result.updateFacetValues[0].code).toBe('compact');
  142. });
  143. it('facets', async () => {
  144. const result = await adminClient.query<GetFacetList.Query>(GET_FACET_LIST);
  145. const { items } = result.facets;
  146. expect(items.length).toBe(2);
  147. expect(items[0].name).toBe('category');
  148. expect(items[1].name).toBe('Speaker Category');
  149. brandFacet = items[0];
  150. speakerTypeFacet = items[1];
  151. });
  152. it('facets by shop-api', async () => {
  153. const result = await shopClient.query<GetFacetList.Query>(GET_FACET_LIST_SIMPLE);
  154. const { items } = result.facets;
  155. expect(items.length).toBe(1);
  156. expect(items[0].name).toBe('category');
  157. });
  158. it('facet', async () => {
  159. const result = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  160. GET_FACET_WITH_VALUES,
  161. {
  162. id: speakerTypeFacet.id,
  163. },
  164. );
  165. expect(result.facet!.name).toBe('Speaker Category');
  166. });
  167. it('product.facetValues resolver omits private facets in shop-api', async () => {
  168. const publicFacetValue = brandFacet.values[0];
  169. const privateFacetValue = speakerTypeFacet.values[0];
  170. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  171. input: {
  172. id: 'T_1',
  173. facetValueIds: [publicFacetValue.id, privateFacetValue.id],
  174. },
  175. });
  176. const { product } = await shopClient.query<
  177. GetProductWithFacetValues.Query,
  178. GetProductWithFacetValues.Variables
  179. >(GET_PRODUCT_WITH_FACET_VALUES, {
  180. id: 'T_1',
  181. });
  182. expect(product?.facetValues.map(v => v.id).includes(publicFacetValue.id)).toBe(true);
  183. expect(product?.facetValues.map(v => v.id).includes(privateFacetValue.id)).toBe(false);
  184. });
  185. it('productVariant.facetValues resolver omits private facets in shop-api', async () => {
  186. const publicFacetValue = brandFacet.values[0];
  187. const privateFacetValue = speakerTypeFacet.values[0];
  188. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  189. UPDATE_PRODUCT_VARIANTS,
  190. {
  191. input: [
  192. {
  193. id: 'T_1',
  194. facetValueIds: [publicFacetValue.id, privateFacetValue.id],
  195. },
  196. ],
  197. },
  198. );
  199. const { product } = await shopClient.query<
  200. GetProductWithFacetValues.Query,
  201. GetProductWithFacetValues.Variables
  202. >(GET_PRODUCT_WITH_FACET_VALUES, {
  203. id: 'T_1',
  204. });
  205. const productVariant1 = product?.variants.find(v => v.id === 'T_1');
  206. expect(productVariant1?.facetValues.map(v => v.id).includes(publicFacetValue.id)).toBe(true);
  207. expect(productVariant1?.facetValues.map(v => v.id).includes(privateFacetValue.id)).toBe(false);
  208. });
  209. describe('deletion', () => {
  210. let products: GetProductListWithVariants.Items[];
  211. beforeAll(async () => {
  212. // add the FacetValues to products and variants
  213. const result1 = await adminClient.query<GetProductListWithVariants.Query>(
  214. GET_PRODUCTS_LIST_WITH_VARIANTS,
  215. );
  216. products = result1.products.items;
  217. const pcFacetValue = speakerTypeFacet.values.find(v => v.code === 'pc')!;
  218. const hifiFacetValue = speakerTypeFacet.values.find(v => v.code === 'hi-fi')!;
  219. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  220. input: {
  221. id: products[0].id,
  222. facetValueIds: [pcFacetValue.id],
  223. },
  224. });
  225. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  226. UPDATE_PRODUCT_VARIANTS,
  227. {
  228. input: [
  229. {
  230. id: products[0].variants[0].id,
  231. facetValueIds: [pcFacetValue.id],
  232. },
  233. ],
  234. },
  235. );
  236. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  237. input: {
  238. id: products[1].id,
  239. facetValueIds: [hifiFacetValue.id],
  240. },
  241. });
  242. });
  243. it('deleteFacetValues deletes unused facetValue', async () => {
  244. const facetValueToDelete = speakerTypeFacet.values.find(v => v.code === 'compact')!;
  245. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  246. DELETE_FACET_VALUES,
  247. {
  248. ids: [facetValueToDelete.id],
  249. force: false,
  250. },
  251. );
  252. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  253. GET_FACET_WITH_VALUES,
  254. {
  255. id: speakerTypeFacet.id,
  256. },
  257. );
  258. expect(result1.deleteFacetValues).toEqual([
  259. {
  260. result: DeletionResult.DELETED,
  261. message: ``,
  262. },
  263. ]);
  264. expect(result2.facet!.values[0]).not.toEqual(facetValueToDelete);
  265. });
  266. it('deleteFacetValues for FacetValue in use returns NOT_DELETED', async () => {
  267. const facetValueToDelete = speakerTypeFacet.values.find(v => v.code === 'pc')!;
  268. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  269. DELETE_FACET_VALUES,
  270. {
  271. ids: [facetValueToDelete.id],
  272. force: false,
  273. },
  274. );
  275. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  276. GET_FACET_WITH_VALUES,
  277. {
  278. id: speakerTypeFacet.id,
  279. },
  280. );
  281. expect(result1.deleteFacetValues).toEqual([
  282. {
  283. result: DeletionResult.NOT_DELETED,
  284. message: `The FacetValue "pc" is assigned to 1 Product, 1 ProductVariant`,
  285. },
  286. ]);
  287. expect(result2.facet!.values.find(v => v.id === facetValueToDelete.id)).toBeDefined();
  288. });
  289. it('deleteFacetValues for FacetValue in use can be force deleted', async () => {
  290. const facetValueToDelete = speakerTypeFacet.values.find(v => v.code === 'pc')!;
  291. const result1 = await adminClient.query<DeleteFacetValues.Mutation, DeleteFacetValues.Variables>(
  292. DELETE_FACET_VALUES,
  293. {
  294. ids: [facetValueToDelete.id],
  295. force: true,
  296. },
  297. );
  298. expect(result1.deleteFacetValues).toEqual([
  299. {
  300. result: DeletionResult.DELETED,
  301. message: `The selected FacetValue was removed from 1 Product, 1 ProductVariant and deleted`,
  302. },
  303. ]);
  304. // FacetValue no longer in the Facet.values array
  305. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  306. GET_FACET_WITH_VALUES,
  307. {
  308. id: speakerTypeFacet.id,
  309. },
  310. );
  311. expect(result2.facet!.values[0]).not.toEqual(facetValueToDelete);
  312. // FacetValue no longer in the Product.facetValues array
  313. const result3 = await adminClient.query<
  314. GetProductWithVariants.Query,
  315. GetProductWithVariants.Variables
  316. >(GET_PRODUCT_WITH_VARIANTS, {
  317. id: products[0].id,
  318. });
  319. expect(result3.product!.facetValues).toEqual([]);
  320. });
  321. it('deleteFacet that is in use returns NOT_DELETED', async () => {
  322. const result1 = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  323. DELETE_FACET,
  324. {
  325. id: speakerTypeFacet.id,
  326. force: false,
  327. },
  328. );
  329. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  330. GET_FACET_WITH_VALUES,
  331. {
  332. id: speakerTypeFacet.id,
  333. },
  334. );
  335. expect(result1.deleteFacet).toEqual({
  336. result: DeletionResult.NOT_DELETED,
  337. message: `The Facet "speaker-type" includes FacetValues which are assigned to 1 Product`,
  338. });
  339. expect(result2.facet).not.toBe(null);
  340. });
  341. it('deleteFacet that is in use can be force deleted', async () => {
  342. const result1 = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  343. DELETE_FACET,
  344. {
  345. id: speakerTypeFacet.id,
  346. force: true,
  347. },
  348. );
  349. expect(result1.deleteFacet).toEqual({
  350. result: DeletionResult.DELETED,
  351. message: `The Facet was deleted and its FacetValues were removed from 1 Product`,
  352. });
  353. // FacetValue no longer in the Facet.values array
  354. const result2 = await adminClient.query<GetFacetWithValues.Query, GetFacetWithValues.Variables>(
  355. GET_FACET_WITH_VALUES,
  356. {
  357. id: speakerTypeFacet.id,
  358. },
  359. );
  360. expect(result2.facet).toBe(null);
  361. // FacetValue no longer in the Product.facetValues array
  362. const result3 = await adminClient.query<
  363. GetProductWithVariants.Query,
  364. GetProductWithVariants.Variables
  365. >(GET_PRODUCT_WITH_VARIANTS, {
  366. id: products[1].id,
  367. });
  368. expect(result3.product!.facetValues).toEqual([]);
  369. });
  370. it('deleteFacet with no FacetValues works', async () => {
  371. const { createFacet } = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(
  372. CREATE_FACET,
  373. {
  374. input: {
  375. code: 'test',
  376. isPrivate: false,
  377. translations: [{ languageCode: LanguageCode.en, name: 'Test' }],
  378. },
  379. },
  380. );
  381. const result = await adminClient.query<DeleteFacet.Mutation, DeleteFacet.Variables>(
  382. DELETE_FACET,
  383. {
  384. id: createFacet.id,
  385. force: false,
  386. },
  387. );
  388. expect(result.deleteFacet.result).toBe(DeletionResult.DELETED);
  389. });
  390. });
  391. describe('channels', () => {
  392. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  393. let secondChannel: ChannelFragment;
  394. let createdFacet: CreateFacet.CreateFacet;
  395. beforeAll(async () => {
  396. const { createChannel } = await adminClient.query<
  397. CreateChannel.Mutation,
  398. CreateChannel.Variables
  399. >(CREATE_CHANNEL, {
  400. input: {
  401. code: 'second-channel',
  402. token: SECOND_CHANNEL_TOKEN,
  403. defaultLanguageCode: LanguageCode.en,
  404. currencyCode: CurrencyCode.USD,
  405. pricesIncludeTax: true,
  406. defaultShippingZoneId: 'T_1',
  407. defaultTaxZoneId: 'T_1',
  408. },
  409. });
  410. secondChannel = createChannel as ChannelFragment;
  411. const { assignProductsToChannel } = await adminClient.query<
  412. AssignProductsToChannel.Mutation,
  413. AssignProductsToChannel.Variables
  414. >(ASSIGN_PRODUCT_TO_CHANNEL, {
  415. input: {
  416. channelId: secondChannel.id,
  417. productIds: ['T_1'],
  418. priceFactor: 0.5,
  419. },
  420. });
  421. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  422. });
  423. it('create Facet in channel', async () => {
  424. const { createFacet } = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(
  425. CREATE_FACET,
  426. {
  427. input: {
  428. isPrivate: false,
  429. code: 'channel-facet',
  430. translations: [{ languageCode: LanguageCode.en, name: 'Channel Facet' }],
  431. values: [
  432. {
  433. code: 'channel-value-1',
  434. translations: [{ languageCode: LanguageCode.en, name: 'Channel Value 1' }],
  435. },
  436. {
  437. code: 'channel-value-2',
  438. translations: [{ languageCode: LanguageCode.en, name: 'Channel Value 2' }],
  439. },
  440. ],
  441. },
  442. },
  443. );
  444. expect(createFacet.code).toBe('channel-facet');
  445. createdFacet = createFacet;
  446. });
  447. it('facets list in channel', async () => {
  448. const result = await adminClient.query<GetFacetList.Query>(GET_FACET_LIST);
  449. const { items } = result.facets;
  450. expect(items.length).toBe(1);
  451. expect(items.map(i => i.code)).toEqual(['channel-facet']);
  452. });
  453. it('Product.facetValues in channel', async () => {
  454. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  455. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  456. input: {
  457. id: 'T_1',
  458. facetValueIds: [brandFacet.values[0].id, ...createdFacet.values.map(v => v.id)],
  459. },
  460. });
  461. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  462. UPDATE_PRODUCT_VARIANTS,
  463. {
  464. input: [
  465. {
  466. id: 'T_1',
  467. facetValueIds: [brandFacet.values[0].id, ...createdFacet.values.map(v => v.id)],
  468. },
  469. ],
  470. },
  471. );
  472. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  473. const { product } = await adminClient.query<
  474. GetProductWithVariants.Query,
  475. GetProductWithVariants.Variables
  476. >(GET_PRODUCT_WITH_VARIANTS, {
  477. id: 'T_1',
  478. });
  479. expect(product?.facetValues.map(fv => fv.code).sort()).toEqual([
  480. 'channel-value-1',
  481. 'channel-value-2',
  482. ]);
  483. });
  484. it('ProductVariant.facetValues in channel', async () => {
  485. const { product } = await adminClient.query<
  486. GetProductWithVariants.Query,
  487. GetProductWithVariants.Variables
  488. >(GET_PRODUCT_WITH_VARIANTS, {
  489. id: 'T_1',
  490. });
  491. expect(product?.variants[0].facetValues.map(fv => fv.code).sort()).toEqual([
  492. 'channel-value-1',
  493. 'channel-value-2',
  494. ]);
  495. });
  496. it('updating Product facetValuesIds in channel only affects that channel', async () => {
  497. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  498. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  499. input: {
  500. id: 'T_1',
  501. facetValueIds: [createdFacet.values[0].id],
  502. },
  503. });
  504. const { product: productC2 } = await adminClient.query<
  505. GetProductWithVariants.Query,
  506. GetProductWithVariants.Variables
  507. >(GET_PRODUCT_WITH_VARIANTS, {
  508. id: 'T_1',
  509. });
  510. expect(productC2?.facetValues.map(fv => fv.code)).toEqual([createdFacet.values[0].code]);
  511. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  512. const { product: productCD } = await adminClient.query<
  513. GetProductWithVariants.Query,
  514. GetProductWithVariants.Variables
  515. >(GET_PRODUCT_WITH_VARIANTS, {
  516. id: 'T_1',
  517. });
  518. expect(productCD?.facetValues.map(fv => fv.code)).toEqual([
  519. brandFacet.values[0].code,
  520. createdFacet.values[0].code,
  521. ]);
  522. });
  523. it('updating ProductVariant facetValuesIds in channel only affects that channel', async () => {
  524. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  525. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  526. UPDATE_PRODUCT_VARIANTS,
  527. {
  528. input: [
  529. {
  530. id: 'T_1',
  531. facetValueIds: [createdFacet.values[0].id],
  532. },
  533. ],
  534. },
  535. );
  536. const { product: productC2 } = await adminClient.query<
  537. GetProductWithVariants.Query,
  538. GetProductWithVariants.Variables
  539. >(GET_PRODUCT_WITH_VARIANTS, {
  540. id: 'T_1',
  541. });
  542. expect(productC2?.variants.find(v => v.id === 'T_1')?.facetValues.map(fv => fv.code)).toEqual([
  543. createdFacet.values[0].code,
  544. ]);
  545. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  546. const { product: productCD } = await adminClient.query<
  547. GetProductWithVariants.Query,
  548. GetProductWithVariants.Variables
  549. >(GET_PRODUCT_WITH_VARIANTS, {
  550. id: 'T_1',
  551. });
  552. expect(productCD?.variants.find(v => v.id === 'T_1')?.facetValues.map(fv => fv.code)).toEqual([
  553. brandFacet.values[0].code,
  554. createdFacet.values[0].code,
  555. ]);
  556. });
  557. it(
  558. 'attempting to create FacetValue in Facet from another Channel throws',
  559. assertThrowsWithMessage(async () => {
  560. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  561. await adminClient.query<CreateFacetValues.Mutation, CreateFacetValues.Variables>(
  562. CREATE_FACET_VALUES,
  563. {
  564. input: [
  565. {
  566. facetId: brandFacet.id,
  567. code: 'channel-brand',
  568. translations: [{ languageCode: LanguageCode.en, name: 'Channel Brand' }],
  569. },
  570. ],
  571. },
  572. );
  573. }, `No Facet with the id '1' could be found`),
  574. );
  575. it('removing from channel with error', async () => {
  576. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  577. const { facets: before } = await adminClient.query<GetFacetListSimpleQuery>(
  578. GET_FACET_LIST_SIMPLE,
  579. );
  580. expect(before.items).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  581. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  582. const { removeFacetsFromChannel } = await adminClient.query<
  583. RemoveFacetsFromChannelMutation,
  584. RemoveFacetsFromChannelMutationVariables
  585. >(REMOVE_FACETS_FROM_CHANNEL, {
  586. input: {
  587. channelId: secondChannel.id,
  588. facetIds: [createdFacet.id],
  589. force: false,
  590. },
  591. });
  592. expect(removeFacetsFromChannel).toEqual([
  593. {
  594. errorCode: 'FACET_IN_USE_ERROR',
  595. message:
  596. 'The Facet "channel-facet" includes FacetValues which are assigned to 1 Product 1 ProductVariant',
  597. productCount: 1,
  598. variantCount: 1,
  599. },
  600. ]);
  601. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  602. const { facets: after } = await adminClient.query<GetFacetListSimpleQuery>(GET_FACET_LIST_SIMPLE);
  603. expect(after.items).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  604. });
  605. it('force removing from channel', async () => {
  606. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  607. const { facets: before } = await adminClient.query<GetFacetListSimpleQuery>(
  608. GET_FACET_LIST_SIMPLE,
  609. );
  610. expect(before.items).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  611. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  612. const { removeFacetsFromChannel } = await adminClient.query<
  613. RemoveFacetsFromChannelMutation,
  614. RemoveFacetsFromChannelMutationVariables
  615. >(REMOVE_FACETS_FROM_CHANNEL, {
  616. input: {
  617. channelId: secondChannel.id,
  618. facetIds: [createdFacet.id],
  619. force: true,
  620. },
  621. });
  622. expect(removeFacetsFromChannel).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  623. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  624. const { facets: after } = await adminClient.query<GetFacetListSimpleQuery>(GET_FACET_LIST_SIMPLE);
  625. expect(after.items).toEqual([]);
  626. });
  627. it('assigning to channel', async () => {
  628. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  629. const { facets: before } = await adminClient.query<GetFacetListSimpleQuery>(
  630. GET_FACET_LIST_SIMPLE,
  631. );
  632. expect(before.items).toEqual([]);
  633. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  634. const { assignFacetsToChannel } = await adminClient.query<
  635. AssignFacetsToChannelMutation,
  636. AssignFacetsToChannelMutationVariables
  637. >(ASSIGN_FACETS_TO_CHANNEL, {
  638. input: {
  639. channelId: secondChannel.id,
  640. facetIds: [createdFacet.id],
  641. },
  642. });
  643. expect(assignFacetsToChannel).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  644. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  645. const { facets: after } = await adminClient.query<GetFacetListSimpleQuery>(GET_FACET_LIST_SIMPLE);
  646. expect(after.items).toEqual([{ id: 'T_4', name: 'Channel Facet' }]);
  647. });
  648. });
  649. // https://github.com/vendure-ecommerce/vendure/issues/715
  650. describe('code conflicts', () => {
  651. function createFacetWithCode(code: string) {
  652. return adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(CREATE_FACET, {
  653. input: {
  654. isPrivate: false,
  655. code,
  656. translations: [{ languageCode: LanguageCode.en, name: `Test Facet (${code})` }],
  657. values: [],
  658. },
  659. });
  660. }
  661. // https://github.com/vendure-ecommerce/vendure/issues/831
  662. it('updateFacet with unchanged code', async () => {
  663. const { createFacet } = await createFacetWithCode('some-new-facet');
  664. const result = await adminClient.query<UpdateFacet.Mutation, UpdateFacet.Variables>(
  665. UPDATE_FACET,
  666. {
  667. input: {
  668. id: createFacet.id,
  669. code: createFacet.code,
  670. },
  671. },
  672. );
  673. expect(result.updateFacet.code).toBe(createFacet.code);
  674. });
  675. it('createFacet with conflicting slug gets renamed', async () => {
  676. const { createFacet: result1 } = await createFacetWithCode('test');
  677. expect(result1.code).toBe('test');
  678. const { createFacet: result2 } = await createFacetWithCode('test');
  679. expect(result2.code).toBe('test-2');
  680. });
  681. it('updateFacet with conflicting slug gets renamed', async () => {
  682. const { createFacet } = await createFacetWithCode('foo');
  683. expect(createFacet.code).toBe('foo');
  684. const { updateFacet } = await adminClient.query<UpdateFacet.Mutation, UpdateFacet.Variables>(
  685. UPDATE_FACET,
  686. {
  687. input: {
  688. id: createFacet.id,
  689. code: 'test-2',
  690. },
  691. },
  692. );
  693. expect(updateFacet.code).toBe('test-3');
  694. });
  695. });
  696. });
  697. export const GET_FACET_WITH_VALUES = gql`
  698. query GetFacetWithValues($id: ID!) {
  699. facet(id: $id) {
  700. ...FacetWithValues
  701. }
  702. }
  703. ${FACET_WITH_VALUES_FRAGMENT}
  704. `;
  705. const DELETE_FACET_VALUES = gql`
  706. mutation DeleteFacetValues($ids: [ID!]!, $force: Boolean) {
  707. deleteFacetValues(ids: $ids, force: $force) {
  708. result
  709. message
  710. }
  711. }
  712. `;
  713. const DELETE_FACET = gql`
  714. mutation DeleteFacet($id: ID!, $force: Boolean) {
  715. deleteFacet(id: $id, force: $force) {
  716. result
  717. message
  718. }
  719. }
  720. `;
  721. const GET_PRODUCT_WITH_FACET_VALUES = gql`
  722. query GetProductWithFacetValues($id: ID!) {
  723. product(id: $id) {
  724. id
  725. facetValues {
  726. id
  727. name
  728. code
  729. }
  730. variants {
  731. id
  732. facetValues {
  733. id
  734. name
  735. code
  736. }
  737. }
  738. }
  739. }
  740. `;
  741. const GET_PRODUCTS_LIST_WITH_VARIANTS = gql`
  742. query GetProductListWithVariants {
  743. products {
  744. items {
  745. id
  746. name
  747. variants {
  748. id
  749. name
  750. }
  751. }
  752. totalItems
  753. }
  754. }
  755. `;
  756. export const CREATE_FACET_VALUES = gql`
  757. mutation CreateFacetValues($input: [CreateFacetValueInput!]!) {
  758. createFacetValues(input: $input) {
  759. ...FacetValue
  760. }
  761. }
  762. ${FACET_VALUE_FRAGMENT}
  763. `;
  764. export const UPDATE_FACET_VALUES = gql`
  765. mutation UpdateFacetValues($input: [UpdateFacetValueInput!]!) {
  766. updateFacetValues(input: $input) {
  767. ...FacetValue
  768. }
  769. }
  770. ${FACET_VALUE_FRAGMENT}
  771. `;
  772. export const ASSIGN_FACETS_TO_CHANNEL = gql`
  773. mutation AssignFacetsToChannel($input: AssignFacetsToChannelInput!) {
  774. assignFacetsToChannel(input: $input) {
  775. id
  776. name
  777. }
  778. }
  779. `;
  780. export const REMOVE_FACETS_FROM_CHANNEL = gql`
  781. mutation RemoveFacetsFromChannel($input: RemoveFacetsFromChannelInput!) {
  782. removeFacetsFromChannel(input: $input) {
  783. ... on Facet {
  784. id
  785. name
  786. }
  787. ... on FacetInUseError {
  788. errorCode
  789. message
  790. productCount
  791. variantCount
  792. }
  793. }
  794. }
  795. `;