facet.e2e-spec.ts 33 KB

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