facet.e2e-spec.ts 33 KB

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