collection.e2e-spec.ts 92 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464146514661467146814691470147114721473147414751476147714781479148014811482148314841485148614871488148914901491149214931494149514961497149814991500150115021503150415051506150715081509151015111512151315141515151615171518151915201521152215231524152515261527152815291530153115321533153415351536153715381539154015411542154315441545154615471548154915501551155215531554155515561557155815591560156115621563156415651566156715681569157015711572157315741575157615771578157915801581158215831584158515861587158815891590159115921593159415951596159715981599160016011602160316041605160616071608160916101611161216131614161516161617161816191620162116221623162416251626162716281629163016311632163316341635163616371638163916401641164216431644164516461647164816491650165116521653165416551656165716581659166016611662166316641665166616671668166916701671167216731674167516761677167816791680168116821683168416851686168716881689169016911692169316941695169616971698169917001701170217031704170517061707170817091710171117121713171417151716171717181719172017211722172317241725172617271728172917301731173217331734173517361737173817391740174117421743174417451746174717481749175017511752175317541755175617571758175917601761176217631764176517661767176817691770177117721773177417751776177717781779178017811782178317841785178617871788178917901791179217931794179517961797179817991800180118021803180418051806180718081809181018111812181318141815181618171818181918201821182218231824182518261827182818291830183118321833183418351836183718381839184018411842184318441845184618471848184918501851185218531854185518561857185818591860186118621863186418651866186718681869187018711872187318741875187618771878187918801881188218831884188518861887188818891890189118921893189418951896189718981899190019011902190319041905190619071908190919101911191219131914191519161917191819191920192119221923192419251926192719281929193019311932193319341935193619371938193919401941194219431944194519461947194819491950195119521953195419551956195719581959196019611962196319641965196619671968196919701971197219731974197519761977197819791980198119821983198419851986198719881989199019911992199319941995199619971998199920002001200220032004200520062007200820092010201120122013201420152016201720182019202020212022202320242025202620272028202920302031203220332034203520362037203820392040204120422043204420452046204720482049205020512052205320542055205620572058205920602061206220632064206520662067206820692070207120722073207420752076207720782079208020812082208320842085208620872088208920902091209220932094209520962097209820992100210121022103210421052106210721082109211021112112211321142115211621172118211921202121212221232124212521262127212821292130213121322133213421352136213721382139214021412142214321442145214621472148214921502151215221532154215521562157215821592160216121622163216421652166216721682169217021712172217321742175217621772178217921802181218221832184218521862187218821892190219121922193219421952196219721982199220022012202220322042205220622072208220922102211221222132214221522162217221822192220222122222223222422252226222722282229223022312232223322342235223622372238223922402241224222432244224522462247224822492250225122522253225422552256225722582259226022612262226322642265226622672268226922702271227222732274227522762277227822792280228122822283228422852286228722882289229022912292229322942295229622972298229923002301230223032304230523062307230823092310231123122313231423152316231723182319232023212322232323242325232623272328232923302331233223332334233523362337233823392340234123422343234423452346234723482349235023512352235323542355235623572358235923602361236223632364236523662367236823692370237123722373237423752376
  1. /* eslint-disable @typescript-eslint/no-non-null-assertion */
  2. import { CurrencyCode, DeletionResult, LanguageCode, SortOrder } from '@vendure/common/lib/generated-types';
  3. import { ROOT_COLLECTION_NAME } from '@vendure/common/lib/shared-constants';
  4. import {
  5. DefaultJobQueuePlugin,
  6. facetValueCollectionFilter,
  7. productIdCollectionFilter,
  8. variantIdCollectionFilter,
  9. variantNameCollectionFilter,
  10. } from '@vendure/core';
  11. import {
  12. createErrorResultGuard,
  13. createTestEnvironment,
  14. E2E_DEFAULT_CHANNEL_TOKEN,
  15. ErrorResultGuard,
  16. } from '@vendure/testing';
  17. import path from 'node:path';
  18. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  19. import { initialData } from '../../../e2e-common/e2e-initial-data';
  20. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  21. import { pick } from '../../common/lib/pick';
  22. import { channelFragment, collectionFragment, facetValueFragment } from './graphql/fragments-admin';
  23. import { FragmentOf, graphql, ResultOf } from './graphql/graphql-admin';
  24. import {
  25. assignCollectionsToChannelDocument,
  26. createChannelDocument,
  27. createCollectionDocument,
  28. deleteProductDocument,
  29. deleteProductVariantDocument,
  30. getAssetListDocument,
  31. getCollectionDocument,
  32. getCollectionsDocument,
  33. updateCollectionDocument,
  34. updateProductDocument,
  35. updateProductVariantsDocument,
  36. } from './graphql/shared-definitions';
  37. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  38. import { awaitRunningJobs } from './utils/await-running-jobs';
  39. import { sortById } from './utils/test-order-utils';
  40. describe('Collection resolver', () => {
  41. const { server, adminClient, shopClient } = createTestEnvironment({
  42. ...testConfig(),
  43. plugins: [DefaultJobQueuePlugin],
  44. });
  45. let assets: ResultOf<typeof getAssetListDocument>['assets']['items'];
  46. let facetValues: Array<FragmentOf<typeof facetValueFragment>>;
  47. let electronicsCollection: FragmentOf<typeof collectionFragment>;
  48. let computersCollection: FragmentOf<typeof collectionFragment>;
  49. let pearCollection: FragmentOf<typeof collectionFragment>;
  50. let electronicsBreadcrumbsCollection: FragmentOf<typeof collectionFragment>;
  51. let computersBreadcrumbsCollection: FragmentOf<typeof collectionFragment>;
  52. let pearBreadcrumbsCollection: FragmentOf<typeof collectionFragment>;
  53. const SECOND_CHANNEL_TOKEN = 'second_channel_token';
  54. let secondChannel: FragmentOf<typeof channelFragment>;
  55. const channelGuard: ErrorResultGuard<FragmentOf<typeof channelFragment>> = createErrorResultGuard(
  56. input => !!input && !('errorCode' in input),
  57. );
  58. // Guard for collection query results
  59. type CollectionResult = NonNullable<ResultOf<typeof getCollectionProductVariantsDocument>['collection']>;
  60. const collectionResultGuard: ErrorResultGuard<CollectionResult> = createErrorResultGuard(
  61. input => !!input,
  62. );
  63. // Guard for product query results
  64. type ProductResult = NonNullable<ResultOf<typeof getProductCollectionsDocument>['product']>;
  65. const productResultGuard: ErrorResultGuard<ProductResult> = createErrorResultGuard(input => !!input);
  66. beforeAll(async () => {
  67. await server.init({
  68. initialData,
  69. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-collections.csv'),
  70. customerCount: 1,
  71. });
  72. await adminClient.asSuperAdmin();
  73. const assetsResult = await adminClient.query(getAssetListDocument, {
  74. options: {
  75. sort: {
  76. name: SortOrder.ASC,
  77. },
  78. },
  79. });
  80. assets = assetsResult.assets.items;
  81. const facetValuesResult = await adminClient.query(getFacetValuesDocument);
  82. facetValues = facetValuesResult.facets.items.reduce(
  83. (values, facet) => [...values, ...facet.values],
  84. [] as Array<FragmentOf<typeof facetValueFragment>>,
  85. );
  86. const { createChannel } = await adminClient.query(createChannelDocument, {
  87. input: {
  88. code: 'second-channel',
  89. token: SECOND_CHANNEL_TOKEN,
  90. defaultLanguageCode: LanguageCode.en,
  91. currencyCode: CurrencyCode.USD,
  92. pricesIncludeTax: true,
  93. defaultShippingZoneId: 'T_1',
  94. defaultTaxZoneId: 'T_1',
  95. },
  96. });
  97. channelGuard.assertSuccess(createChannel);
  98. secondChannel = createChannel;
  99. }, TEST_SETUP_TIMEOUT_MS);
  100. afterAll(async () => {
  101. await server.destroy();
  102. });
  103. /**
  104. * Test case for https://github.com/vendure-ecommerce/vendure/issues/97
  105. */
  106. it('collection breadcrumbs works after bootstrap', async () => {
  107. const result = await adminClient.query(getCollectionBreadcrumbsDocument, {
  108. id: 'T_1',
  109. });
  110. expect(result.collection?.breadcrumbs[0].name).toBe(ROOT_COLLECTION_NAME);
  111. });
  112. describe('createCollection', () => {
  113. it('creates a root collection', async () => {
  114. const result = await adminClient.query(createCollectionDocument, {
  115. input: {
  116. assetIds: [assets[0].id, assets[1].id],
  117. featuredAssetId: assets[1].id,
  118. filters: [
  119. {
  120. code: facetValueCollectionFilter.code,
  121. arguments: [
  122. {
  123. name: 'facetValueIds',
  124. value: `["${getFacetValueId('electronics')}"]`,
  125. },
  126. {
  127. name: 'containsAny',
  128. value: 'false',
  129. },
  130. ],
  131. },
  132. ],
  133. translations: [
  134. {
  135. languageCode: LanguageCode.en,
  136. name: 'Electronics',
  137. description: '',
  138. slug: 'electronics',
  139. },
  140. ],
  141. },
  142. });
  143. electronicsCollection = result.createCollection;
  144. expect(electronicsCollection).toMatchSnapshot();
  145. expect(electronicsCollection.parent!.name).toBe(ROOT_COLLECTION_NAME);
  146. });
  147. it('creates a nested collection', async () => {
  148. const result = await adminClient.query(createCollectionDocument, {
  149. input: {
  150. parentId: electronicsCollection.id,
  151. translations: [
  152. {
  153. languageCode: LanguageCode.en,
  154. name: 'Computers',
  155. description: '',
  156. slug: 'computers',
  157. },
  158. ],
  159. filters: [
  160. {
  161. code: facetValueCollectionFilter.code,
  162. arguments: [
  163. {
  164. name: 'facetValueIds',
  165. value: `["${getFacetValueId('computers')}"]`,
  166. },
  167. {
  168. name: 'containsAny',
  169. value: 'false',
  170. },
  171. ],
  172. },
  173. ],
  174. },
  175. });
  176. computersCollection = result.createCollection;
  177. expect(computersCollection.parent!.name).toBe(electronicsCollection.name);
  178. });
  179. it('creates a 2nd level nested collection', async () => {
  180. const result = await adminClient.query(createCollectionDocument, {
  181. input: {
  182. parentId: computersCollection.id,
  183. translations: [
  184. { languageCode: LanguageCode.en, name: 'Pear', description: '', slug: 'pear' },
  185. ],
  186. filters: [
  187. {
  188. code: facetValueCollectionFilter.code,
  189. arguments: [
  190. {
  191. name: 'facetValueIds',
  192. value: `["${getFacetValueId('pear')}"]`,
  193. },
  194. {
  195. name: 'containsAny',
  196. value: 'false',
  197. },
  198. ],
  199. },
  200. ],
  201. },
  202. });
  203. pearCollection = result.createCollection;
  204. expect(pearCollection.parent!.name).toBe(computersCollection.name);
  205. });
  206. it('slug is normalized to be url-safe', async () => {
  207. const { createCollection } = await adminClient.query(createCollectionDocument, {
  208. input: {
  209. translations: [
  210. {
  211. languageCode: LanguageCode.en,
  212. name: 'Accessories',
  213. description: '',
  214. slug: 'Accessories!',
  215. },
  216. {
  217. languageCode: LanguageCode.de,
  218. name: 'Zubehör',
  219. description: '',
  220. slug: 'Zubehör!',
  221. },
  222. ],
  223. filters: [],
  224. },
  225. });
  226. expect(createCollection.slug).toBe('accessories');
  227. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.en)?.slug).toBe(
  228. 'accessories',
  229. );
  230. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.de)?.slug).toBe(
  231. 'zubehoer',
  232. );
  233. });
  234. it('create with duplicate slug is renamed to be unique', async () => {
  235. const { createCollection } = await adminClient.query(createCollectionDocument, {
  236. input: {
  237. translations: [
  238. {
  239. languageCode: LanguageCode.en,
  240. name: 'Accessories',
  241. description: '',
  242. slug: 'Accessories',
  243. },
  244. {
  245. languageCode: LanguageCode.de,
  246. name: 'Zubehör',
  247. description: '',
  248. slug: 'Zubehör',
  249. },
  250. ],
  251. filters: [],
  252. },
  253. });
  254. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.en)?.slug).toBe(
  255. 'accessories-2',
  256. );
  257. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.de)?.slug).toBe(
  258. 'zubehoer-2',
  259. );
  260. });
  261. it('creates the duplicate slug without suffix in another channel', async () => {
  262. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  263. const { createCollection } = await adminClient.query(createCollectionDocument, {
  264. input: {
  265. translations: [
  266. {
  267. languageCode: LanguageCode.en,
  268. name: 'Accessories',
  269. description: '',
  270. slug: 'Accessories',
  271. },
  272. {
  273. languageCode: LanguageCode.de,
  274. name: 'Zubehör',
  275. description: '',
  276. slug: 'Zubehör',
  277. },
  278. ],
  279. filters: [],
  280. },
  281. });
  282. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.en)?.slug).toBe(
  283. 'accessories',
  284. );
  285. expect(createCollection.translations.find(t => t.languageCode === LanguageCode.de)?.slug).toBe(
  286. 'zubehoer',
  287. );
  288. });
  289. it('creates a root collection to become a 1st level collection later #779', async () => {
  290. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  291. const result = await adminClient.query(createCollectionDocument, {
  292. input: {
  293. assetIds: [assets[0].id, assets[1].id],
  294. featuredAssetId: assets[1].id,
  295. filters: [
  296. {
  297. code: facetValueCollectionFilter.code,
  298. arguments: [
  299. {
  300. name: 'facetValueIds',
  301. value: `["${getFacetValueId('computers')}"]`,
  302. },
  303. {
  304. name: 'containsAny',
  305. value: 'false',
  306. },
  307. ],
  308. },
  309. ],
  310. translations: [
  311. {
  312. languageCode: LanguageCode.en,
  313. name: 'Computers Breadcrumbs',
  314. description: '',
  315. slug: 'computers_breadcrumbs',
  316. },
  317. ],
  318. },
  319. });
  320. computersBreadcrumbsCollection = result.createCollection;
  321. expect(computersBreadcrumbsCollection.parent!.name).toBe(ROOT_COLLECTION_NAME);
  322. });
  323. it('creates a root collection to be a parent collection for 1st level collection with id greater than child collection #779', async () => {
  324. const result = await adminClient.query(createCollectionDocument, {
  325. input: {
  326. assetIds: [assets[0].id, assets[1].id],
  327. featuredAssetId: assets[1].id,
  328. filters: [
  329. {
  330. code: facetValueCollectionFilter.code,
  331. arguments: [
  332. {
  333. name: 'facetValueIds',
  334. value: `["${getFacetValueId('electronics')}"]`,
  335. },
  336. {
  337. name: 'containsAny',
  338. value: 'false',
  339. },
  340. ],
  341. },
  342. ],
  343. translations: [
  344. {
  345. languageCode: LanguageCode.en,
  346. name: 'Electronics Breadcrumbs',
  347. description: '',
  348. slug: 'electronics_breadcrumbs',
  349. },
  350. ],
  351. },
  352. });
  353. electronicsBreadcrumbsCollection = result.createCollection;
  354. expect(electronicsBreadcrumbsCollection.parent!.name).toBe(ROOT_COLLECTION_NAME);
  355. });
  356. it('creates a 2nd level nested collection #779', async () => {
  357. const result = await adminClient.query(createCollectionDocument, {
  358. input: {
  359. parentId: computersBreadcrumbsCollection.id,
  360. translations: [
  361. {
  362. languageCode: LanguageCode.en,
  363. name: 'Pear Breadcrumbs',
  364. description: '',
  365. slug: 'pear_breadcrumbs',
  366. },
  367. ],
  368. filters: [
  369. {
  370. code: facetValueCollectionFilter.code,
  371. arguments: [
  372. {
  373. name: 'facetValueIds',
  374. value: `["${getFacetValueId('pear')}"]`,
  375. },
  376. {
  377. name: 'containsAny',
  378. value: 'false',
  379. },
  380. ],
  381. },
  382. ],
  383. },
  384. });
  385. pearBreadcrumbsCollection = result.createCollection;
  386. expect(pearBreadcrumbsCollection.parent!.name).toBe(computersBreadcrumbsCollection.name);
  387. });
  388. });
  389. describe('updateCollection', () => {
  390. it('updates with assets', async () => {
  391. const { updateCollection } = await adminClient.query(updateCollectionDocument, {
  392. input: {
  393. id: pearCollection.id,
  394. assetIds: [assets[1].id, assets[2].id],
  395. featuredAssetId: assets[1].id,
  396. translations: [
  397. { languageCode: LanguageCode.en, description: 'Apple stuff ', slug: 'apple-stuff' },
  398. ],
  399. },
  400. });
  401. await awaitRunningJobs(adminClient, 5000);
  402. expect(updateCollection).toMatchSnapshot();
  403. pearCollection = updateCollection;
  404. });
  405. it('updating existing assets', async () => {
  406. const { updateCollection } = await adminClient.query(updateCollectionDocument, {
  407. input: {
  408. id: pearCollection.id,
  409. assetIds: [assets[3].id, assets[0].id],
  410. featuredAssetId: assets[3].id,
  411. },
  412. });
  413. await awaitRunningJobs(adminClient, 5000);
  414. expect(updateCollection.assets.map(a => a.id)).toEqual([assets[3].id, assets[0].id]);
  415. });
  416. it('removes all assets', async () => {
  417. const { updateCollection } = await adminClient.query(updateCollectionDocument, {
  418. input: {
  419. id: pearCollection.id,
  420. assetIds: [],
  421. },
  422. });
  423. await awaitRunningJobs(adminClient, 5000);
  424. expect(updateCollection.assets).toEqual([]);
  425. expect(updateCollection.featuredAsset).toBeNull();
  426. });
  427. });
  428. describe('querying', () => {
  429. it('collection by id', async () => {
  430. const result = await adminClient.query(getCollectionDocument, {
  431. id: computersCollection.id,
  432. });
  433. if (!result.collection) {
  434. fail('did not return the collection');
  435. return;
  436. }
  437. expect(result.collection.id).toBe(computersCollection.id);
  438. });
  439. it('collection by slug', async () => {
  440. const result = await adminClient.query(getCollectionDocument, {
  441. slug: computersCollection.slug,
  442. });
  443. if (!result.collection) {
  444. fail('did not return the collection');
  445. return;
  446. }
  447. expect(result.collection.id).toBe(computersCollection.id);
  448. });
  449. // https://github.com/vendure-ecommerce/vendure/issues/538
  450. it('falls back to default language slug', async () => {
  451. const result = await adminClient.query(
  452. getCollectionDocument,
  453. {
  454. slug: computersCollection.slug,
  455. },
  456. { languageCode: LanguageCode.de },
  457. );
  458. if (!result.collection) {
  459. fail('did not return the collection');
  460. return;
  461. }
  462. expect(result.collection.id).toBe(computersCollection.id);
  463. });
  464. it(
  465. 'throws if neither id nor slug provided',
  466. assertThrowsWithMessage(async () => {
  467. await adminClient.query(getCollectionDocument, {});
  468. }, 'Either the Collection id or slug must be provided'),
  469. );
  470. it(
  471. 'throws if id and slug do not refer to the same Product',
  472. assertThrowsWithMessage(async () => {
  473. await adminClient.query(getCollectionDocument, {
  474. id: computersCollection.id,
  475. slug: pearCollection.slug,
  476. });
  477. }, 'The provided id and slug refer to different Collections'),
  478. );
  479. it('parent field', async () => {
  480. const result = await adminClient.query(getCollectionDocument, {
  481. id: computersCollection.id,
  482. });
  483. if (!result.collection) {
  484. fail('did not return the collection');
  485. return;
  486. }
  487. expect(result.collection.parent!.name).toBe('Electronics');
  488. });
  489. // Tests fix for https://github.com/vendure-ecommerce/vendure/issues/361
  490. it('parent field resolved by CollectionEntityResolver', async () => {
  491. const { product } = await adminClient.query(getProductCollectionsWithParentDocument, {
  492. id: 'T_1',
  493. });
  494. expect(product?.collections.length).toBe(6);
  495. expect(product?.collections.sort(sortById)).toEqual([
  496. {
  497. id: 'T_10',
  498. name: 'Electronics Breadcrumbs',
  499. parent: {
  500. id: 'T_1',
  501. name: '__root_collection__',
  502. },
  503. },
  504. {
  505. id: 'T_11',
  506. name: 'Pear Breadcrumbs',
  507. parent: {
  508. id: 'T_9',
  509. name: 'Computers Breadcrumbs',
  510. },
  511. },
  512. {
  513. id: 'T_3',
  514. name: 'Electronics',
  515. parent: {
  516. id: 'T_1',
  517. name: '__root_collection__',
  518. },
  519. },
  520. {
  521. id: 'T_4',
  522. name: 'Computers',
  523. parent: {
  524. id: 'T_3',
  525. name: 'Electronics',
  526. },
  527. },
  528. {
  529. id: 'T_5',
  530. name: 'Pear',
  531. parent: {
  532. id: 'T_4',
  533. name: 'Computers',
  534. },
  535. },
  536. {
  537. id: 'T_9',
  538. name: 'Computers Breadcrumbs',
  539. parent: {
  540. id: 'T_1',
  541. name: '__root_collection__',
  542. },
  543. },
  544. ]);
  545. });
  546. // https://github.com/vendure-ecommerce/vendure/issues/981
  547. it('nested parent field in shop API', async () => {
  548. const { collections } = await shopClient.query(getCollectionNestedParentsDocument);
  549. expect(collections.items[0].parent?.name).toBe(ROOT_COLLECTION_NAME);
  550. });
  551. it('children field', async () => {
  552. const result = await adminClient.query(getCollectionDocument, {
  553. id: electronicsCollection.id,
  554. });
  555. if (!result.collection) {
  556. fail('did not return the collection');
  557. return;
  558. }
  559. expect(result.collection.children!.length).toBe(1);
  560. expect(result.collection.children![0].name).toBe('Computers');
  561. });
  562. it('breadcrumbs', async () => {
  563. const result = await adminClient.query(getCollectionBreadcrumbsDocument, {
  564. id: pearCollection.id,
  565. });
  566. if (!result.collection) {
  567. fail('did not return the collection');
  568. return;
  569. }
  570. expect(result.collection.breadcrumbs).toEqual([
  571. { id: 'T_1', name: ROOT_COLLECTION_NAME, slug: ROOT_COLLECTION_NAME },
  572. {
  573. id: electronicsCollection.id,
  574. name: electronicsCollection.name,
  575. slug: electronicsCollection.slug,
  576. },
  577. {
  578. id: computersCollection.id,
  579. name: computersCollection.name,
  580. slug: computersCollection.slug,
  581. },
  582. { id: pearCollection.id, name: pearCollection.name, slug: pearCollection.slug },
  583. ]);
  584. });
  585. it('breadcrumbs for root collection', async () => {
  586. const result = await adminClient.query(getCollectionBreadcrumbsDocument, {
  587. id: 'T_1',
  588. });
  589. if (!result.collection) {
  590. fail('did not return the collection');
  591. return;
  592. }
  593. expect(result.collection.breadcrumbs).toEqual([
  594. { id: 'T_1', name: ROOT_COLLECTION_NAME, slug: ROOT_COLLECTION_NAME },
  595. ]);
  596. });
  597. it('collections.assets', async () => {
  598. const { collections } = await adminClient.query(
  599. graphql(`
  600. query GetCollectionsWithAssets {
  601. collections {
  602. items {
  603. assets {
  604. name
  605. }
  606. }
  607. }
  608. }
  609. `),
  610. );
  611. expect(collections.items[0].assets).toBeDefined();
  612. });
  613. // https://github.com/vendure-ecommerce/vendure/issues/642
  614. it('sorting on Collection.productVariants.price', async () => {
  615. const { collection } = await adminClient.query(getCollectionDocument, {
  616. id: computersCollection.id,
  617. variantListOptions: {
  618. sort: {
  619. price: SortOrder.ASC,
  620. },
  621. },
  622. });
  623. collectionResultGuard.assertSuccess(collection);
  624. expect(collection.productVariants.items.map(i => i.price)).toEqual([
  625. 3799, 5374, 6900, 7489, 7896, 9299, 13435, 14374, 16994, 93120, 94920, 108720, 109995, 129900,
  626. 139900, 219900, 229900,
  627. ]);
  628. });
  629. // https://github.com/vendure-ecommerce/vendure/issues/3107
  630. it('collection list with translations, filtered by name', async () => {
  631. const { collections } = await adminClient.query(getCollectionListWithTranslationsDocument, {
  632. options: {
  633. filter: {
  634. name: {
  635. eq: 'Electronics',
  636. },
  637. },
  638. },
  639. });
  640. expect(collections.items.length).toBeDefined();
  641. });
  642. });
  643. describe('moveCollection', () => {
  644. it('moves a collection to a new parent', async () => {
  645. const result = await adminClient.query(moveCollectionDocument, {
  646. input: {
  647. collectionId: pearCollection.id,
  648. parentId: electronicsCollection.id,
  649. index: 0,
  650. },
  651. });
  652. expect(result.moveCollection.parent!.id).toBe(electronicsCollection.id);
  653. const positions = await getChildrenOf(electronicsCollection.id);
  654. expect(positions.map(i => i.id)).toEqual([pearCollection.id, computersCollection.id]);
  655. });
  656. it('re-evaluates Collection contents on move', async () => {
  657. await awaitRunningJobs(adminClient, 5000);
  658. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  659. id: pearCollection.id,
  660. });
  661. collectionResultGuard.assertSuccess(result.collection);
  662. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  663. 'Laptop 13 inch 8GB',
  664. 'Laptop 15 inch 8GB',
  665. 'Laptop 13 inch 16GB',
  666. 'Laptop 15 inch 16GB',
  667. 'Instant Camera',
  668. ]);
  669. });
  670. it('moves a 1st level collection to a new parent to check breadcrumbs', async () => {
  671. const result = await adminClient.query(moveCollectionDocument, {
  672. input: {
  673. collectionId: computersBreadcrumbsCollection.id,
  674. parentId: electronicsBreadcrumbsCollection.id,
  675. index: 0,
  676. },
  677. });
  678. expect(result.moveCollection.parent!.id).toBe(electronicsBreadcrumbsCollection.id);
  679. const positions = await getChildrenOf(electronicsBreadcrumbsCollection.id);
  680. expect(positions.map(i => i.id)).toEqual([computersBreadcrumbsCollection.id]);
  681. });
  682. it('breadcrumbs for collection with ids out of order', async () => {
  683. const result = await adminClient.query(getCollectionBreadcrumbsDocument, {
  684. id: pearBreadcrumbsCollection.id,
  685. });
  686. if (!result.collection) {
  687. fail('did not return the collection');
  688. return;
  689. }
  690. expect(result.collection.breadcrumbs).toEqual([
  691. { id: 'T_1', name: ROOT_COLLECTION_NAME, slug: ROOT_COLLECTION_NAME },
  692. {
  693. id: electronicsBreadcrumbsCollection.id,
  694. name: electronicsBreadcrumbsCollection.name,
  695. slug: electronicsBreadcrumbsCollection.slug,
  696. },
  697. {
  698. id: computersBreadcrumbsCollection.id,
  699. name: computersBreadcrumbsCollection.name,
  700. slug: computersBreadcrumbsCollection.slug,
  701. },
  702. {
  703. id: pearBreadcrumbsCollection.id,
  704. name: pearBreadcrumbsCollection.name,
  705. slug: pearBreadcrumbsCollection.slug,
  706. },
  707. ]);
  708. });
  709. it('alters the position in the current parent 1', async () => {
  710. await adminClient.query(moveCollectionDocument, {
  711. input: {
  712. collectionId: computersCollection.id,
  713. parentId: electronicsCollection.id,
  714. index: 0,
  715. },
  716. });
  717. const afterResult = await getChildrenOf(electronicsCollection.id);
  718. expect(afterResult.map(i => i.id)).toEqual([computersCollection.id, pearCollection.id]);
  719. });
  720. it('alters the position in the current parent 2', async () => {
  721. await adminClient.query(moveCollectionDocument, {
  722. input: {
  723. collectionId: pearCollection.id,
  724. parentId: electronicsCollection.id,
  725. index: 0,
  726. },
  727. });
  728. const afterResult = await getChildrenOf(electronicsCollection.id);
  729. expect(afterResult.map(i => i.id)).toEqual([pearCollection.id, computersCollection.id]);
  730. });
  731. it('corrects an out-of-bounds negative index value', async () => {
  732. await adminClient.query(moveCollectionDocument, {
  733. input: {
  734. collectionId: pearCollection.id,
  735. parentId: electronicsCollection.id,
  736. index: -3,
  737. },
  738. });
  739. const afterResult = await getChildrenOf(electronicsCollection.id);
  740. expect(afterResult.map(i => i.id)).toEqual([pearCollection.id, computersCollection.id]);
  741. });
  742. it('corrects an out-of-bounds positive index value', async () => {
  743. await adminClient.query(moveCollectionDocument, {
  744. input: {
  745. collectionId: pearCollection.id,
  746. parentId: electronicsCollection.id,
  747. index: 10,
  748. },
  749. });
  750. const afterResult = await getChildrenOf(electronicsCollection.id);
  751. expect(afterResult.map(i => i.id)).toEqual([computersCollection.id, pearCollection.id]);
  752. });
  753. it(
  754. 'throws if attempting to move into self',
  755. assertThrowsWithMessage(
  756. () =>
  757. adminClient.query(moveCollectionDocument, {
  758. input: {
  759. collectionId: pearCollection.id,
  760. parentId: pearCollection.id,
  761. index: 0,
  762. },
  763. }),
  764. 'Cannot move a Collection into itself',
  765. ),
  766. );
  767. it(
  768. 'throws if attempting to move into a descendant of self',
  769. assertThrowsWithMessage(
  770. () =>
  771. adminClient.query(moveCollectionDocument, {
  772. input: {
  773. collectionId: pearCollection.id,
  774. parentId: pearCollection.id,
  775. index: 0,
  776. },
  777. }),
  778. 'Cannot move a Collection into itself',
  779. ),
  780. );
  781. // https://github.com/vendure-ecommerce/vendure/issues/1595
  782. it('children correctly ordered', async () => {
  783. await adminClient.query(moveCollectionDocument, {
  784. input: {
  785. collectionId: computersCollection.id,
  786. parentId: 'T_1',
  787. index: 4,
  788. },
  789. });
  790. const result = await adminClient.query(getCollectionDocument, {
  791. id: 'T_1',
  792. });
  793. if (!result.collection) {
  794. fail('did not return the collection');
  795. return;
  796. }
  797. expect(result.collection.children?.map(c => (c as any).position)).toEqual([0, 1, 2, 3, 4, 5, 6]);
  798. });
  799. async function getChildrenOf(parentId: string): Promise<Array<{ name: string; id: string }>> {
  800. const result = await adminClient.query(getCollectionsDocument);
  801. return result.collections.items.filter(i => i.parent!.id === parentId);
  802. }
  803. });
  804. describe('deleteCollection', () => {
  805. let collectionToDeleteParent: ResultOf<typeof createCollectionDocument>['createCollection'];
  806. let collectionToDeleteChild: ResultOf<typeof createCollectionDocument>['createCollection'];
  807. let laptopProductId: string;
  808. beforeAll(async () => {
  809. const result1 = await adminClient.query(createCollectionDocument, {
  810. input: {
  811. filters: [
  812. {
  813. code: variantNameCollectionFilter.code,
  814. arguments: [
  815. {
  816. name: 'operator',
  817. value: 'contains',
  818. },
  819. {
  820. name: 'term',
  821. value: 'laptop',
  822. },
  823. ],
  824. },
  825. ],
  826. translations: [
  827. {
  828. languageCode: LanguageCode.en,
  829. name: 'Delete Me Parent',
  830. description: '',
  831. slug: 'delete-me-parent',
  832. },
  833. ],
  834. assetIds: ['T_1'],
  835. },
  836. });
  837. collectionToDeleteParent = result1.createCollection;
  838. const result2 = await adminClient.query(createCollectionDocument, {
  839. input: {
  840. filters: [],
  841. translations: [
  842. {
  843. languageCode: LanguageCode.en,
  844. name: 'Delete Me Child',
  845. description: '',
  846. slug: 'delete-me-child',
  847. },
  848. ],
  849. parentId: collectionToDeleteParent.id,
  850. assetIds: ['T_2'],
  851. },
  852. });
  853. collectionToDeleteChild = result2.createCollection;
  854. await awaitRunningJobs(adminClient, 5000);
  855. });
  856. it(
  857. 'throws for invalid collection id',
  858. assertThrowsWithMessage(async () => {
  859. await adminClient.query(deleteCollectionDocument, {
  860. id: 'T_999',
  861. });
  862. }, 'No Collection with the id "999" could be found'),
  863. );
  864. it('collection and product related prior to deletion', async () => {
  865. const { collection } = await adminClient.query(getCollectionProductVariantsDocument, {
  866. id: collectionToDeleteParent.id,
  867. });
  868. collectionResultGuard.assertSuccess(collection);
  869. expect(collection.productVariants.items.map(pick(['name']))).toEqual([
  870. { name: 'Laptop 13 inch 8GB' },
  871. { name: 'Laptop 15 inch 8GB' },
  872. { name: 'Laptop 13 inch 16GB' },
  873. { name: 'Laptop 15 inch 16GB' },
  874. ]);
  875. laptopProductId = collection.productVariants.items[0].productId;
  876. const { product } = await adminClient.query(getProductCollectionsDocument, {
  877. id: laptopProductId,
  878. });
  879. productResultGuard.assertSuccess(product);
  880. expect(product.collections).toEqual([
  881. {
  882. id: 'T_3',
  883. name: 'Electronics',
  884. },
  885. {
  886. id: 'T_4',
  887. name: 'Computers',
  888. },
  889. {
  890. id: 'T_5',
  891. name: 'Pear',
  892. },
  893. {
  894. id: 'T_9',
  895. name: 'Computers Breadcrumbs',
  896. },
  897. {
  898. id: 'T_10',
  899. name: 'Electronics Breadcrumbs',
  900. },
  901. {
  902. id: 'T_11',
  903. name: 'Pear Breadcrumbs',
  904. },
  905. {
  906. id: 'T_12',
  907. name: 'Delete Me Parent',
  908. },
  909. {
  910. id: 'T_13',
  911. name: 'Delete Me Child',
  912. },
  913. ]);
  914. });
  915. it('deleteCollection works', async () => {
  916. const { deleteCollection } = await adminClient.query(deleteCollectionDocument, {
  917. id: collectionToDeleteParent.id,
  918. });
  919. expect(deleteCollection.result).toBe(DeletionResult.DELETED);
  920. });
  921. it('deleted parent collection is null', async () => {
  922. const { collection } = await adminClient.query(getCollectionDocument, {
  923. id: collectionToDeleteParent.id,
  924. });
  925. expect(collection).toBeNull();
  926. });
  927. it('deleted child collection is null', async () => {
  928. const { collection } = await adminClient.query(getCollectionDocument, {
  929. id: collectionToDeleteChild.id,
  930. });
  931. expect(collection).toBeNull();
  932. });
  933. it('product no longer lists collection', async () => {
  934. const { product } = await adminClient.query(getProductCollectionsDocument, {
  935. id: laptopProductId,
  936. });
  937. productResultGuard.assertSuccess(product);
  938. expect(product.collections).toEqual([
  939. { id: 'T_3', name: 'Electronics' },
  940. { id: 'T_4', name: 'Computers' },
  941. { id: 'T_5', name: 'Pear' },
  942. {
  943. id: 'T_9',
  944. name: 'Computers Breadcrumbs',
  945. },
  946. {
  947. id: 'T_10',
  948. name: 'Electronics Breadcrumbs',
  949. },
  950. {
  951. id: 'T_11',
  952. name: 'Pear Breadcrumbs',
  953. },
  954. ]);
  955. });
  956. });
  957. describe('filters', () => {
  958. it('Collection with no filters has no productVariants', async () => {
  959. const result = await adminClient.query(createCollectionSelectVariantsDocument, {
  960. input: {
  961. translations: [
  962. { languageCode: LanguageCode.en, name: 'Empty', description: '', slug: 'empty' },
  963. ],
  964. filters: [],
  965. },
  966. });
  967. expect(result.createCollection.productVariants.totalItems).toBe(0);
  968. });
  969. describe('facetValue filter', () => {
  970. it('electronics', async () => {
  971. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  972. id: electronicsCollection.id,
  973. });
  974. collectionResultGuard.assertSuccess(result.collection);
  975. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  976. 'Laptop 13 inch 8GB',
  977. 'Laptop 15 inch 8GB',
  978. 'Laptop 13 inch 16GB',
  979. 'Laptop 15 inch 16GB',
  980. 'Curvy Monitor 24 inch',
  981. 'Curvy Monitor 27 inch',
  982. 'Gaming PC i7-8700 240GB SSD',
  983. 'Gaming PC R7-2700 240GB SSD',
  984. 'Gaming PC i7-8700 120GB SSD',
  985. 'Gaming PC R7-2700 120GB SSD',
  986. 'Hard Drive 1TB',
  987. 'Hard Drive 2TB',
  988. 'Hard Drive 3TB',
  989. 'Hard Drive 4TB',
  990. 'Hard Drive 6TB',
  991. 'Clacky Keyboard',
  992. 'USB Cable',
  993. 'Instant Camera',
  994. 'Camera Lens',
  995. 'Tripod',
  996. 'SLR Camera',
  997. ]);
  998. });
  999. it('computers', async () => {
  1000. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1001. id: computersCollection.id,
  1002. });
  1003. collectionResultGuard.assertSuccess(result.collection);
  1004. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1005. 'Laptop 13 inch 8GB',
  1006. 'Laptop 15 inch 8GB',
  1007. 'Laptop 13 inch 16GB',
  1008. 'Laptop 15 inch 16GB',
  1009. 'Curvy Monitor 24 inch',
  1010. 'Curvy Monitor 27 inch',
  1011. 'Gaming PC i7-8700 240GB SSD',
  1012. 'Gaming PC R7-2700 240GB SSD',
  1013. 'Gaming PC i7-8700 120GB SSD',
  1014. 'Gaming PC R7-2700 120GB SSD',
  1015. 'Hard Drive 1TB',
  1016. 'Hard Drive 2TB',
  1017. 'Hard Drive 3TB',
  1018. 'Hard Drive 4TB',
  1019. 'Hard Drive 6TB',
  1020. 'Clacky Keyboard',
  1021. 'USB Cable',
  1022. ]);
  1023. });
  1024. it('photo AND pear', async () => {
  1025. const result = await adminClient.query(createCollectionSelectVariantsDocument, {
  1026. input: {
  1027. translations: [
  1028. {
  1029. languageCode: LanguageCode.en,
  1030. name: 'Photo AND Pear',
  1031. description: '',
  1032. slug: 'photo-and-pear',
  1033. },
  1034. ],
  1035. filters: [
  1036. {
  1037. code: facetValueCollectionFilter.code,
  1038. arguments: [
  1039. {
  1040. name: 'facetValueIds',
  1041. value: `["${getFacetValueId('pear')}", "${getFacetValueId(
  1042. 'photo',
  1043. )}"]`,
  1044. },
  1045. {
  1046. name: 'containsAny',
  1047. value: 'false',
  1048. },
  1049. ],
  1050. },
  1051. ],
  1052. },
  1053. });
  1054. await awaitRunningJobs(adminClient, 5000);
  1055. const { collection } = await adminClient.query(getCollectionDocument, {
  1056. id: result.createCollection.id,
  1057. });
  1058. collectionResultGuard.assertSuccess(collection);
  1059. expect(collection.productVariants.items.map(i => i.name)).toEqual(['Instant Camera']);
  1060. });
  1061. it('photo OR pear', async () => {
  1062. const result = await adminClient.query(createCollectionSelectVariantsDocument, {
  1063. input: {
  1064. translations: [
  1065. {
  1066. languageCode: LanguageCode.en,
  1067. name: 'Photo OR Pear',
  1068. description: '',
  1069. slug: 'photo-or-pear',
  1070. },
  1071. ],
  1072. filters: [
  1073. {
  1074. code: facetValueCollectionFilter.code,
  1075. arguments: [
  1076. {
  1077. name: 'facetValueIds',
  1078. value: `["${getFacetValueId('pear')}", "${getFacetValueId(
  1079. 'photo',
  1080. )}"]`,
  1081. },
  1082. {
  1083. name: 'containsAny',
  1084. value: 'true',
  1085. },
  1086. ],
  1087. },
  1088. ],
  1089. },
  1090. });
  1091. await awaitRunningJobs(adminClient, 5000);
  1092. const { collection } = await adminClient.query(getCollectionDocument, {
  1093. id: result.createCollection.id,
  1094. });
  1095. collectionResultGuard.assertSuccess(collection);
  1096. expect(collection.productVariants.items.map(i => i.name)).toEqual([
  1097. 'Laptop 13 inch 8GB',
  1098. 'Laptop 15 inch 8GB',
  1099. 'Laptop 13 inch 16GB',
  1100. 'Laptop 15 inch 16GB',
  1101. 'Instant Camera',
  1102. 'Camera Lens',
  1103. 'Tripod',
  1104. 'SLR Camera',
  1105. 'Hat',
  1106. ]);
  1107. });
  1108. it('bell OR pear in computers', async () => {
  1109. const result = await adminClient.query(createCollectionSelectVariantsDocument, {
  1110. input: {
  1111. parentId: computersCollection.id,
  1112. translations: [
  1113. {
  1114. languageCode: LanguageCode.en,
  1115. name: 'Bell OR Pear Computers',
  1116. description: '',
  1117. slug: 'bell-or-pear',
  1118. },
  1119. ],
  1120. filters: [
  1121. {
  1122. code: facetValueCollectionFilter.code,
  1123. arguments: [
  1124. {
  1125. name: 'facetValueIds',
  1126. value: `["${getFacetValueId('pear')}", "${getFacetValueId('bell')}"]`,
  1127. },
  1128. {
  1129. name: 'containsAny',
  1130. value: 'true',
  1131. },
  1132. ],
  1133. },
  1134. ],
  1135. },
  1136. });
  1137. await awaitRunningJobs(adminClient, 5000);
  1138. const { collection } = await adminClient.query(getCollectionDocument, {
  1139. id: result.createCollection.id,
  1140. });
  1141. collectionResultGuard.assertSuccess(collection);
  1142. expect(collection.productVariants.items.map(i => i.name)).toEqual([
  1143. 'Laptop 13 inch 8GB',
  1144. 'Laptop 15 inch 8GB',
  1145. 'Laptop 13 inch 16GB',
  1146. 'Laptop 15 inch 16GB',
  1147. 'Curvy Monitor 24 inch',
  1148. 'Curvy Monitor 27 inch',
  1149. ]);
  1150. });
  1151. });
  1152. describe('variantName filter', () => {
  1153. async function createVariantNameFilteredCollection(
  1154. operator: string,
  1155. term: string,
  1156. parentId?: string,
  1157. ): Promise<FragmentOf<typeof collectionFragment>> {
  1158. const { createCollection } = await adminClient.query(createCollectionDocument, {
  1159. input: {
  1160. parentId,
  1161. translations: [
  1162. {
  1163. languageCode: LanguageCode.en,
  1164. name: `${operator} ${term}`,
  1165. description: '',
  1166. slug: `${operator} ${term}`,
  1167. },
  1168. ],
  1169. filters: [
  1170. {
  1171. code: variantNameCollectionFilter.code,
  1172. arguments: [
  1173. {
  1174. name: 'operator',
  1175. value: operator,
  1176. },
  1177. {
  1178. name: 'term',
  1179. value: term,
  1180. },
  1181. ],
  1182. },
  1183. ],
  1184. },
  1185. });
  1186. await awaitRunningJobs(adminClient, 5000);
  1187. return createCollection;
  1188. }
  1189. it('contains operator', async () => {
  1190. const collection = await createVariantNameFilteredCollection('contains', 'camera');
  1191. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1192. id: collection.id,
  1193. });
  1194. collectionResultGuard.assertSuccess(result.collection);
  1195. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1196. 'Instant Camera',
  1197. 'Camera Lens',
  1198. 'SLR Camera',
  1199. ]);
  1200. });
  1201. it('startsWith operator', async () => {
  1202. const collection = await createVariantNameFilteredCollection('startsWith', 'camera');
  1203. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1204. id: collection.id,
  1205. });
  1206. collectionResultGuard.assertSuccess(result.collection);
  1207. expect(result.collection.productVariants.items.map(i => i.name)).toEqual(['Camera Lens']);
  1208. });
  1209. it('endsWith operator', async () => {
  1210. const collection = await createVariantNameFilteredCollection('endsWith', 'camera');
  1211. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1212. id: collection.id,
  1213. });
  1214. collectionResultGuard.assertSuccess(result.collection);
  1215. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1216. 'Instant Camera',
  1217. 'SLR Camera',
  1218. ]);
  1219. });
  1220. it('doesNotContain operator', async () => {
  1221. const collection = await createVariantNameFilteredCollection('doesNotContain', 'camera');
  1222. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1223. id: collection.id,
  1224. });
  1225. collectionResultGuard.assertSuccess(result.collection);
  1226. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1227. 'Laptop 13 inch 8GB',
  1228. 'Laptop 15 inch 8GB',
  1229. 'Laptop 13 inch 16GB',
  1230. 'Laptop 15 inch 16GB',
  1231. 'Curvy Monitor 24 inch',
  1232. 'Curvy Monitor 27 inch',
  1233. 'Gaming PC i7-8700 240GB SSD',
  1234. 'Gaming PC R7-2700 240GB SSD',
  1235. 'Gaming PC i7-8700 120GB SSD',
  1236. 'Gaming PC R7-2700 120GB SSD',
  1237. 'Hard Drive 1TB',
  1238. 'Hard Drive 2TB',
  1239. 'Hard Drive 3TB',
  1240. 'Hard Drive 4TB',
  1241. 'Hard Drive 6TB',
  1242. 'Clacky Keyboard',
  1243. 'USB Cable',
  1244. 'Tripod',
  1245. 'Hat',
  1246. 'Boots',
  1247. ]);
  1248. });
  1249. // https://github.com/vendure-ecommerce/vendure/issues/927
  1250. it('nested variantName filter', async () => {
  1251. const parent = await createVariantNameFilteredCollection('contains', 'lap');
  1252. const parentResult = await adminClient.query(getCollectionProductVariantsDocument, {
  1253. id: parent.id,
  1254. });
  1255. expect(parentResult.collection?.productVariants.items.map(i => i.name)).toEqual([
  1256. 'Laptop 13 inch 8GB',
  1257. 'Laptop 15 inch 8GB',
  1258. 'Laptop 13 inch 16GB',
  1259. 'Laptop 15 inch 16GB',
  1260. ]);
  1261. const child = await createVariantNameFilteredCollection('contains', 'GB', parent.id);
  1262. const childResult = await adminClient.query(getCollectionProductVariantsDocument, {
  1263. id: child.id,
  1264. });
  1265. expect(childResult.collection?.productVariants.items.map(i => i.name)).toEqual([
  1266. 'Laptop 13 inch 8GB',
  1267. 'Laptop 15 inch 8GB',
  1268. 'Laptop 13 inch 16GB',
  1269. 'Laptop 15 inch 16GB',
  1270. ]);
  1271. });
  1272. });
  1273. describe('variantId filter', () => {
  1274. it('contains expects variants', async () => {
  1275. const { createCollection } = await adminClient.query(createCollectionDocument, {
  1276. input: {
  1277. translations: [
  1278. {
  1279. languageCode: LanguageCode.en,
  1280. name: 'variantId filter test',
  1281. description: '',
  1282. slug: 'variantId-filter-test',
  1283. },
  1284. ],
  1285. filters: [
  1286. {
  1287. code: variantIdCollectionFilter.code,
  1288. arguments: [
  1289. {
  1290. name: 'variantIds',
  1291. value: '["T_1", "T_4"]',
  1292. },
  1293. ],
  1294. },
  1295. ],
  1296. },
  1297. });
  1298. await awaitRunningJobs(adminClient, 5000);
  1299. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1300. id: createCollection.id,
  1301. });
  1302. collectionResultGuard.assertSuccess(result.collection);
  1303. expect(result.collection.productVariants.items.map(i => i.id).sort()).toEqual(['T_1', 'T_4']);
  1304. });
  1305. });
  1306. describe('productId filter', () => {
  1307. it('contains expects variants', async () => {
  1308. const { createCollection } = await adminClient.query(createCollectionDocument, {
  1309. input: {
  1310. translations: [
  1311. {
  1312. languageCode: LanguageCode.en,
  1313. name: 'productId filter test',
  1314. description: '',
  1315. slug: 'productId-filter-test',
  1316. },
  1317. ],
  1318. filters: [
  1319. {
  1320. code: productIdCollectionFilter.code,
  1321. arguments: [
  1322. {
  1323. name: 'productIds',
  1324. value: '["T_2"]',
  1325. },
  1326. ],
  1327. },
  1328. ],
  1329. },
  1330. });
  1331. await awaitRunningJobs(adminClient, 5000);
  1332. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1333. id: createCollection.id,
  1334. });
  1335. collectionResultGuard.assertSuccess(result.collection);
  1336. expect(result.collection.productVariants.items.map(i => i.id).sort()).toEqual(['T_5', 'T_6']);
  1337. });
  1338. });
  1339. describe('re-evaluation of contents on changes', () => {
  1340. const getProductsWithVariantIdsDocument = graphql(`
  1341. query GetProductsWithVariantIds {
  1342. products(options: { sort: { id: ASC } }) {
  1343. items {
  1344. id
  1345. name
  1346. variants {
  1347. id
  1348. name
  1349. }
  1350. }
  1351. }
  1352. }
  1353. `);
  1354. let products: ResultOf<typeof getProductsWithVariantIdsDocument>['products']['items'];
  1355. beforeAll(async () => {
  1356. const result = await adminClient.query(getProductsWithVariantIdsDocument);
  1357. products = result.products.items;
  1358. });
  1359. it('updates contents when Product is updated', async () => {
  1360. await adminClient.query(updateProductDocument, {
  1361. input: {
  1362. id: products[1].id,
  1363. facetValueIds: [
  1364. getFacetValueId('electronics'),
  1365. getFacetValueId('computers'),
  1366. getFacetValueId('pear'),
  1367. ],
  1368. },
  1369. });
  1370. await awaitRunningJobs(adminClient, 5000);
  1371. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1372. id: pearCollection.id,
  1373. });
  1374. collectionResultGuard.assertSuccess(result.collection);
  1375. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1376. 'Laptop 13 inch 8GB',
  1377. 'Laptop 15 inch 8GB',
  1378. 'Laptop 13 inch 16GB',
  1379. 'Laptop 15 inch 16GB',
  1380. 'Curvy Monitor 24 inch',
  1381. 'Curvy Monitor 27 inch',
  1382. 'Instant Camera',
  1383. ]);
  1384. });
  1385. it('updates contents when ProductVariant is updated', async () => {
  1386. const gamingPc240GB = products
  1387. .find(p => p.name === 'Gaming PC')!
  1388. .variants.find(v => v.name.includes('240GB'))!;
  1389. await adminClient.query(updateProductVariantsDocument, {
  1390. input: [
  1391. {
  1392. id: gamingPc240GB.id,
  1393. facetValueIds: [getFacetValueId('pear')],
  1394. },
  1395. ],
  1396. });
  1397. await awaitRunningJobs(adminClient, 5000);
  1398. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1399. id: pearCollection.id,
  1400. });
  1401. collectionResultGuard.assertSuccess(result.collection);
  1402. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1403. 'Laptop 13 inch 8GB',
  1404. 'Laptop 15 inch 8GB',
  1405. 'Laptop 13 inch 16GB',
  1406. 'Laptop 15 inch 16GB',
  1407. 'Curvy Monitor 24 inch',
  1408. 'Curvy Monitor 27 inch',
  1409. 'Gaming PC i7-8700 240GB SSD',
  1410. 'Instant Camera',
  1411. ]);
  1412. });
  1413. it('correctly filters when ProductVariant and Product both have matching FacetValue', async () => {
  1414. const gamingPc240GB = products
  1415. .find(p => p.name === 'Gaming PC')!
  1416. .variants.find(v => v.name.includes('240GB'))!;
  1417. await adminClient.query(updateProductVariantsDocument, {
  1418. input: [
  1419. {
  1420. id: gamingPc240GB.id,
  1421. facetValueIds: [getFacetValueId('electronics'), getFacetValueId('pear')],
  1422. },
  1423. ],
  1424. });
  1425. await awaitRunningJobs(adminClient, 5000);
  1426. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1427. id: pearCollection.id,
  1428. });
  1429. collectionResultGuard.assertSuccess(result.collection);
  1430. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1431. 'Laptop 13 inch 8GB',
  1432. 'Laptop 15 inch 8GB',
  1433. 'Laptop 13 inch 16GB',
  1434. 'Laptop 15 inch 16GB',
  1435. 'Curvy Monitor 24 inch',
  1436. 'Curvy Monitor 27 inch',
  1437. 'Gaming PC i7-8700 240GB SSD',
  1438. 'Instant Camera',
  1439. ]);
  1440. });
  1441. });
  1442. describe('filter inheritance', () => {
  1443. let clothesCollectionId: string;
  1444. it('filter inheritance of nested collections (issue #158)', async () => {
  1445. const { createCollection: pearElectronics } = await adminClient.query(
  1446. createCollectionSelectVariantsDocument,
  1447. {
  1448. input: {
  1449. parentId: electronicsCollection.id,
  1450. translations: [
  1451. {
  1452. languageCode: LanguageCode.en,
  1453. name: 'pear electronics',
  1454. description: '',
  1455. slug: 'pear-electronics',
  1456. },
  1457. ],
  1458. filters: [
  1459. {
  1460. code: facetValueCollectionFilter.code,
  1461. arguments: [
  1462. {
  1463. name: 'facetValueIds',
  1464. value: `["${getFacetValueId('pear')}"]`,
  1465. },
  1466. {
  1467. name: 'containsAny',
  1468. value: 'false',
  1469. },
  1470. ],
  1471. },
  1472. ],
  1473. },
  1474. },
  1475. );
  1476. await awaitRunningJobs(adminClient, 5000);
  1477. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1478. id: pearElectronics.id,
  1479. });
  1480. collectionResultGuard.assertSuccess(result.collection);
  1481. expect(result.collection.productVariants.items.map(i => i.name)).toEqual([
  1482. 'Laptop 13 inch 8GB',
  1483. 'Laptop 15 inch 8GB',
  1484. 'Laptop 13 inch 16GB',
  1485. 'Laptop 15 inch 16GB',
  1486. 'Curvy Monitor 24 inch',
  1487. 'Curvy Monitor 27 inch',
  1488. 'Gaming PC i7-8700 240GB SSD',
  1489. 'Instant Camera',
  1490. // no "Hat"
  1491. ]);
  1492. });
  1493. it('child collection with no inheritance', async () => {
  1494. const { createCollection: clothesCollection } = await adminClient.query(
  1495. createCollectionSelectVariantsDocument,
  1496. {
  1497. input: {
  1498. parentId: electronicsCollection.id,
  1499. translations: [
  1500. {
  1501. languageCode: LanguageCode.en,
  1502. name: 'clothes',
  1503. description: '',
  1504. slug: 'clothes',
  1505. },
  1506. ],
  1507. inheritFilters: false,
  1508. filters: [
  1509. {
  1510. code: facetValueCollectionFilter.code,
  1511. arguments: [
  1512. {
  1513. name: 'facetValueIds',
  1514. value: `["${getFacetValueId('clothing')}"]`,
  1515. },
  1516. {
  1517. name: 'containsAny',
  1518. value: 'false',
  1519. },
  1520. ],
  1521. },
  1522. ],
  1523. },
  1524. },
  1525. );
  1526. await awaitRunningJobs(adminClient, 5000);
  1527. clothesCollectionId = clothesCollection.id;
  1528. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1529. id: clothesCollection.id,
  1530. });
  1531. collectionResultGuard.assertSuccess(result.collection);
  1532. expect(result.collection.productVariants.items.map(i => i.name)).toEqual(['Hat', 'Boots']);
  1533. });
  1534. it('grandchild collection with inheritance (root -> no inherit -> inherit', async () => {
  1535. const { createCollection: footwearCollection } = await adminClient.query(
  1536. createCollectionSelectVariantsDocument,
  1537. {
  1538. input: {
  1539. parentId: clothesCollectionId,
  1540. translations: [
  1541. {
  1542. languageCode: LanguageCode.en,
  1543. name: 'footwear',
  1544. description: '',
  1545. slug: 'footwear',
  1546. },
  1547. ],
  1548. inheritFilters: true,
  1549. filters: [
  1550. {
  1551. code: facetValueCollectionFilter.code,
  1552. arguments: [
  1553. {
  1554. name: 'facetValueIds',
  1555. value: `["${getFacetValueId('footwear')}"]`,
  1556. },
  1557. {
  1558. name: 'containsAny',
  1559. value: 'false',
  1560. },
  1561. ],
  1562. },
  1563. ],
  1564. },
  1565. },
  1566. );
  1567. await awaitRunningJobs(adminClient, 5000);
  1568. const result = await adminClient.query(getCollectionProductVariantsDocument, {
  1569. id: footwearCollection.id,
  1570. });
  1571. collectionResultGuard.assertSuccess(result.collection);
  1572. expect(result.collection.productVariants.items.map(i => i.name)).toEqual(['Boots']);
  1573. });
  1574. });
  1575. describe('previewCollectionVariants', () => {
  1576. it('returns correct contents', async () => {
  1577. const { previewCollectionVariants } = await adminClient.query(
  1578. previewCollectionVariantsDocument,
  1579. {
  1580. input: {
  1581. parentId: electronicsCollection.parent?.id,
  1582. inheritFilters: true,
  1583. filters: [
  1584. {
  1585. code: facetValueCollectionFilter.code,
  1586. arguments: [
  1587. {
  1588. name: 'facetValueIds',
  1589. value: `["${getFacetValueId('electronics')}","${getFacetValueId(
  1590. 'pear',
  1591. )}"]`,
  1592. },
  1593. {
  1594. name: 'containsAny',
  1595. value: 'false',
  1596. },
  1597. ],
  1598. },
  1599. ],
  1600. },
  1601. },
  1602. );
  1603. expect(previewCollectionVariants.items.map(i => i.name).sort()).toEqual([
  1604. 'Curvy Monitor 24 inch',
  1605. 'Curvy Monitor 27 inch',
  1606. 'Gaming PC i7-8700 240GB SSD',
  1607. 'Instant Camera',
  1608. 'Laptop 13 inch 16GB',
  1609. 'Laptop 13 inch 8GB',
  1610. 'Laptop 15 inch 16GB',
  1611. 'Laptop 15 inch 8GB',
  1612. ]);
  1613. });
  1614. it('works with list options', async () => {
  1615. const { previewCollectionVariants } = await adminClient.query(
  1616. previewCollectionVariantsDocument,
  1617. {
  1618. input: {
  1619. parentId: electronicsCollection.parent?.id,
  1620. inheritFilters: true,
  1621. filters: [
  1622. {
  1623. code: facetValueCollectionFilter.code,
  1624. arguments: [
  1625. {
  1626. name: 'facetValueIds',
  1627. value: `["${getFacetValueId('electronics')}"]`,
  1628. },
  1629. {
  1630. name: 'containsAny',
  1631. value: 'false',
  1632. },
  1633. ],
  1634. },
  1635. ],
  1636. },
  1637. options: {
  1638. sort: {
  1639. name: SortOrder.ASC,
  1640. },
  1641. filter: {
  1642. name: {
  1643. contains: 'mon',
  1644. },
  1645. },
  1646. take: 5,
  1647. },
  1648. },
  1649. );
  1650. expect(previewCollectionVariants.items).toEqual([
  1651. { id: 'T_5', name: 'Curvy Monitor 24 inch' },
  1652. { id: 'T_6', name: 'Curvy Monitor 27 inch' },
  1653. ]);
  1654. });
  1655. it('takes parent filters into account', async () => {
  1656. const { previewCollectionVariants } = await adminClient.query(
  1657. previewCollectionVariantsDocument,
  1658. {
  1659. input: {
  1660. parentId: electronicsCollection.id,
  1661. inheritFilters: true,
  1662. filters: [
  1663. {
  1664. code: variantNameCollectionFilter.code,
  1665. arguments: [
  1666. {
  1667. name: 'operator',
  1668. value: 'startsWith',
  1669. },
  1670. {
  1671. name: 'term',
  1672. value: 'h',
  1673. },
  1674. ],
  1675. },
  1676. ],
  1677. },
  1678. },
  1679. );
  1680. expect(previewCollectionVariants.items.map(i => i.name).sort()).toEqual([
  1681. 'Hard Drive 1TB',
  1682. 'Hard Drive 2TB',
  1683. 'Hard Drive 3TB',
  1684. 'Hard Drive 4TB',
  1685. 'Hard Drive 6TB',
  1686. ]);
  1687. });
  1688. it('ignores parent filters id inheritFilters set to false', async () => {
  1689. const { previewCollectionVariants } = await adminClient.query(
  1690. previewCollectionVariantsDocument,
  1691. {
  1692. input: {
  1693. parentId: electronicsCollection.id,
  1694. inheritFilters: false,
  1695. filters: [
  1696. {
  1697. code: variantNameCollectionFilter.code,
  1698. arguments: [
  1699. {
  1700. name: 'operator',
  1701. value: 'startsWith',
  1702. },
  1703. {
  1704. name: 'term',
  1705. value: 'h',
  1706. },
  1707. ],
  1708. },
  1709. ],
  1710. },
  1711. },
  1712. );
  1713. expect(previewCollectionVariants.items.map(i => i.name).sort()).toEqual([
  1714. 'Hard Drive 1TB',
  1715. 'Hard Drive 2TB',
  1716. 'Hard Drive 3TB',
  1717. 'Hard Drive 4TB',
  1718. 'Hard Drive 6TB',
  1719. 'Hat',
  1720. ]);
  1721. });
  1722. it('with no parentId, operates at the root level', async () => {
  1723. const { previewCollectionVariants } = await adminClient.query(
  1724. previewCollectionVariantsDocument,
  1725. {
  1726. input: {
  1727. inheritFilters: true,
  1728. filters: [
  1729. {
  1730. code: variantNameCollectionFilter.code,
  1731. arguments: [
  1732. {
  1733. name: 'operator',
  1734. value: 'startsWith',
  1735. },
  1736. {
  1737. name: 'term',
  1738. value: 'h',
  1739. },
  1740. ],
  1741. },
  1742. ],
  1743. },
  1744. },
  1745. );
  1746. expect(previewCollectionVariants.items.map(i => i.name).sort()).toEqual([
  1747. 'Hard Drive 1TB',
  1748. 'Hard Drive 2TB',
  1749. 'Hard Drive 3TB',
  1750. 'Hard Drive 4TB',
  1751. 'Hard Drive 6TB',
  1752. 'Hat',
  1753. ]);
  1754. });
  1755. });
  1756. });
  1757. describe('Product collections property', () => {
  1758. it('returns all collections to which the Product belongs', async () => {
  1759. const result = await adminClient.query(getCollectionsForProductsDocument, { term: 'camera' });
  1760. expect(result.products.items[0].collections).toEqual([
  1761. {
  1762. id: 'T_3',
  1763. name: 'Electronics',
  1764. },
  1765. {
  1766. id: 'T_5',
  1767. name: 'Pear',
  1768. },
  1769. {
  1770. id: 'T_10',
  1771. name: 'Electronics Breadcrumbs',
  1772. },
  1773. {
  1774. id: 'T_15',
  1775. name: 'Photo AND Pear',
  1776. },
  1777. {
  1778. id: 'T_16',
  1779. name: 'Photo OR Pear',
  1780. },
  1781. {
  1782. id: 'T_18',
  1783. name: 'contains camera',
  1784. },
  1785. {
  1786. id: 'T_20',
  1787. name: 'endsWith camera',
  1788. },
  1789. {
  1790. id: 'T_26',
  1791. name: 'pear electronics',
  1792. },
  1793. ]);
  1794. });
  1795. });
  1796. describe('productVariants list', () => {
  1797. it('does not list variants from deleted products', async () => {
  1798. await adminClient.query(deleteProductDocument, {
  1799. id: 'T_2', // curvy monitor
  1800. });
  1801. await awaitRunningJobs(adminClient, 5000);
  1802. const { collection } = await adminClient.query(getCollectionProductVariantsDocument, {
  1803. id: pearCollection.id,
  1804. });
  1805. collectionResultGuard.assertSuccess(collection);
  1806. expect(collection.productVariants.items.map(i => i.name)).toEqual([
  1807. 'Laptop 13 inch 8GB',
  1808. 'Laptop 15 inch 8GB',
  1809. 'Laptop 13 inch 16GB',
  1810. 'Laptop 15 inch 16GB',
  1811. 'Gaming PC i7-8700 240GB SSD',
  1812. 'Instant Camera',
  1813. ]);
  1814. });
  1815. // https://github.com/vendure-ecommerce/vendure/issues/1213
  1816. it('does not list deleted variants', async () => {
  1817. await adminClient.query(deleteProductVariantDocument, {
  1818. id: 'T_18', // Instant Camera
  1819. });
  1820. await awaitRunningJobs(adminClient, 5000);
  1821. const { collection } = await adminClient.query(getCollectionProductVariantsDocument, {
  1822. id: pearCollection.id,
  1823. });
  1824. collectionResultGuard.assertSuccess(collection);
  1825. expect(collection.productVariants.items.map(i => i.name)).toEqual([
  1826. 'Laptop 13 inch 8GB',
  1827. 'Laptop 15 inch 8GB',
  1828. 'Laptop 13 inch 16GB',
  1829. 'Laptop 15 inch 16GB',
  1830. 'Gaming PC i7-8700 240GB SSD',
  1831. // 'Instant Camera',
  1832. ]);
  1833. });
  1834. it('does not list disabled variants in Shop API', async () => {
  1835. await adminClient.query(updateProductVariantsDocument, {
  1836. input: [{ id: 'T_1', enabled: false }],
  1837. });
  1838. await awaitRunningJobs(adminClient, 5000);
  1839. const { collection } = await shopClient.query(getCollectionProductVariantsDocument, {
  1840. id: pearCollection.id,
  1841. });
  1842. collectionResultGuard.assertSuccess(collection);
  1843. expect(collection.productVariants.items.map(i => i.id).includes('T_1')).toBe(false);
  1844. });
  1845. it('does not list variants of disabled products in Shop API', async () => {
  1846. await adminClient.query(updateProductDocument, {
  1847. input: { id: 'T_1', enabled: false },
  1848. });
  1849. await awaitRunningJobs(adminClient, 5000);
  1850. const { collection } = await shopClient.query(getCollectionProductVariantsDocument, {
  1851. id: pearCollection.id,
  1852. });
  1853. collectionResultGuard.assertSuccess(collection);
  1854. expect(collection.productVariants.items.map(i => i.id).includes('T_1')).toBe(false);
  1855. expect(collection.productVariants.items.map(i => i.id).includes('T_2')).toBe(false);
  1856. expect(collection.productVariants.items.map(i => i.id).includes('T_3')).toBe(false);
  1857. expect(collection.productVariants.items.map(i => i.id).includes('T_4')).toBe(false);
  1858. });
  1859. it('handles other languages', async () => {
  1860. await adminClient.query(updateProductDocument, {
  1861. input: { id: 'T_1', enabled: true },
  1862. });
  1863. await adminClient.query(updateProductVariantsDocument, {
  1864. input: [
  1865. {
  1866. id: 'T_2',
  1867. translations: [{ languageCode: LanguageCode.de, name: 'Taschenrechner 15 Zoll' }],
  1868. },
  1869. ],
  1870. });
  1871. const { collection } = await shopClient.query(
  1872. getCollectionProductVariantsDocument,
  1873. {
  1874. id: pearCollection.id,
  1875. },
  1876. { languageCode: LanguageCode.de },
  1877. );
  1878. collectionResultGuard.assertSuccess(collection);
  1879. expect(collection.productVariants.items.map(i => i.name)).toEqual([
  1880. 'Taschenrechner 15 Zoll',
  1881. 'Laptop 13 inch 16GB',
  1882. 'Laptop 15 inch 16GB',
  1883. 'Gaming PC i7-8700 240GB SSD',
  1884. ]);
  1885. });
  1886. });
  1887. describe('channel assignment & removal', () => {
  1888. let testCollection: FragmentOf<typeof collectionFragment>;
  1889. beforeAll(async () => {
  1890. const { createCollection } = await adminClient.query(createCollectionDocument, {
  1891. input: {
  1892. filters: [
  1893. {
  1894. code: facetValueCollectionFilter.code,
  1895. arguments: [
  1896. {
  1897. name: 'facetValueIds',
  1898. value: `["${getFacetValueId('electronics')}"]`,
  1899. },
  1900. {
  1901. name: 'containsAny',
  1902. value: 'false',
  1903. },
  1904. ],
  1905. },
  1906. ],
  1907. translations: [
  1908. {
  1909. languageCode: LanguageCode.en,
  1910. name: 'Channels Test Collection',
  1911. description: '',
  1912. slug: 'channels-test-collection',
  1913. },
  1914. ],
  1915. },
  1916. });
  1917. testCollection = createCollection;
  1918. });
  1919. it('assign to channel', async () => {
  1920. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  1921. const { collections: before } = await adminClient.query(getCollectionListDocument);
  1922. expect(before.items.length).toBe(1);
  1923. expect(before.items.map(i => i.id).includes(testCollection.id)).toBe(false);
  1924. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  1925. const { assignCollectionsToChannel } = await adminClient.query(
  1926. assignCollectionsToChannelDocument,
  1927. {
  1928. input: {
  1929. channelId: secondChannel.id,
  1930. collectionIds: [testCollection.id],
  1931. },
  1932. },
  1933. );
  1934. expect(assignCollectionsToChannel.map(c => c.id)).toEqual([testCollection.id]);
  1935. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  1936. const { collections: after } = await adminClient.query(getCollectionListDocument);
  1937. expect(after.items.length).toBe(2);
  1938. expect(after.items.map(i => i.id).includes(testCollection.id)).toBe(true);
  1939. });
  1940. it('remove from channel', async () => {
  1941. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  1942. const { removeCollectionsFromChannel } = await adminClient.query(
  1943. removeCollectionsFromChannelDocument,
  1944. {
  1945. input: {
  1946. channelId: secondChannel.id,
  1947. collectionIds: [testCollection.id],
  1948. },
  1949. },
  1950. );
  1951. expect(removeCollectionsFromChannel.map(c => c.id)).toEqual([testCollection.id]);
  1952. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  1953. const { collections: after } = await adminClient.query(getCollectionListDocument);
  1954. expect(after.items.length).toBe(1);
  1955. expect(after.items.map(i => i.id).includes(testCollection.id)).toBe(false);
  1956. });
  1957. });
  1958. describe('deleteCollections (multiple)', () => {
  1959. let top: FragmentOf<typeof collectionFragment>;
  1960. let child: FragmentOf<typeof collectionFragment>;
  1961. let grandchild: FragmentOf<typeof collectionFragment>;
  1962. beforeAll(async () => {
  1963. async function createNewCollection(name: string, parentId?: string) {
  1964. const { createCollection } = await adminClient.query(createCollectionDocument, {
  1965. input: {
  1966. translations: [
  1967. {
  1968. languageCode: LanguageCode.en,
  1969. name,
  1970. description: '',
  1971. slug: name,
  1972. },
  1973. ],
  1974. filters: [],
  1975. },
  1976. });
  1977. return createCollection;
  1978. }
  1979. top = await createNewCollection('top');
  1980. child = await createNewCollection('child', top.id);
  1981. grandchild = await createNewCollection('grandchild', child.id);
  1982. });
  1983. it('deletes all selected collections', async () => {
  1984. const { collections: before } = await adminClient.query(getCollectionListDocument);
  1985. expect(before.items.sort(sortById).map(pick(['name']))).toEqual([
  1986. { name: 'top' },
  1987. { name: 'child' },
  1988. { name: 'grandchild' },
  1989. { name: 'Accessories' },
  1990. ]);
  1991. const { deleteCollections } = await adminClient.query(deleteCollectionsBulkDocument, {
  1992. ids: [top.id, child.id, grandchild.id],
  1993. });
  1994. expect(deleteCollections).toEqual([
  1995. { result: DeletionResult.DELETED, message: null },
  1996. { result: DeletionResult.DELETED, message: null },
  1997. { result: DeletionResult.DELETED, message: null },
  1998. ]);
  1999. const { collections: after } = await adminClient.query(getCollectionListDocument);
  2000. expect(after.items.map(pick(['id', 'name'])).sort(sortById)).toEqual([
  2001. { id: 'T_8', name: 'Accessories' },
  2002. ]);
  2003. });
  2004. });
  2005. function getFacetValueId(code: string): string {
  2006. const match = facetValues.find(fv => fv.code === code);
  2007. if (!match) {
  2008. throw new Error(`Could not find a FacetValue with the code "${code}"`);
  2009. }
  2010. return match.id;
  2011. }
  2012. });
  2013. export const getCollectionListDocument = graphql(
  2014. `
  2015. query GetCollectionListAdmin($options: CollectionListOptions) {
  2016. collections(options: $options) {
  2017. items {
  2018. ...Collection
  2019. }
  2020. totalItems
  2021. }
  2022. }
  2023. `,
  2024. [collectionFragment],
  2025. );
  2026. export const getCollectionListWithTranslationsDocument = graphql(`
  2027. query GetCollectionListWithTranslations($options: CollectionListOptions) {
  2028. collections(options: $options) {
  2029. items {
  2030. id
  2031. name
  2032. translations {
  2033. id
  2034. name
  2035. }
  2036. }
  2037. }
  2038. }
  2039. `);
  2040. export const moveCollectionDocument = graphql(
  2041. `
  2042. mutation MoveCollection($input: MoveCollectionInput!) {
  2043. moveCollection(input: $input) {
  2044. ...Collection
  2045. }
  2046. }
  2047. `,
  2048. [collectionFragment],
  2049. );
  2050. const getFacetValuesDocument = graphql(
  2051. `
  2052. query GetFacetWithFacetValues {
  2053. facets {
  2054. items {
  2055. values {
  2056. ...FacetValue
  2057. }
  2058. }
  2059. }
  2060. }
  2061. `,
  2062. [facetValueFragment],
  2063. );
  2064. const getCollectionProductVariantsDocument = graphql(`
  2065. query GetCollectionProducts($id: ID!) {
  2066. collection(id: $id) {
  2067. productVariants(options: { sort: { id: ASC } }) {
  2068. items {
  2069. id
  2070. name
  2071. facetValues {
  2072. code
  2073. }
  2074. productId
  2075. }
  2076. }
  2077. }
  2078. }
  2079. `);
  2080. const createCollectionSelectVariantsDocument = graphql(`
  2081. mutation CreateCollectionSelectVariants($input: CreateCollectionInput!) {
  2082. createCollection(input: $input) {
  2083. id
  2084. productVariants {
  2085. items {
  2086. name
  2087. }
  2088. totalItems
  2089. }
  2090. }
  2091. }
  2092. `);
  2093. const getCollectionBreadcrumbsDocument = graphql(`
  2094. query GetCollectionBreadcrumbs($id: ID!) {
  2095. collection(id: $id) {
  2096. breadcrumbs {
  2097. id
  2098. name
  2099. slug
  2100. }
  2101. }
  2102. }
  2103. `);
  2104. const getCollectionsForProductsDocument = graphql(`
  2105. query GetCollectionsForProducts($term: String!) {
  2106. products(options: { filter: { name: { contains: $term } } }) {
  2107. items {
  2108. id
  2109. name
  2110. collections {
  2111. id
  2112. name
  2113. }
  2114. }
  2115. }
  2116. }
  2117. `);
  2118. const deleteCollectionDocument = graphql(`
  2119. mutation DeleteCollection($id: ID!) {
  2120. deleteCollection(id: $id) {
  2121. result
  2122. message
  2123. }
  2124. }
  2125. `);
  2126. const getProductCollectionsDocument = graphql(`
  2127. query GetProductCollections($id: ID!) {
  2128. product(id: $id) {
  2129. id
  2130. collections {
  2131. id
  2132. name
  2133. }
  2134. }
  2135. }
  2136. `);
  2137. const getProductCollectionsWithParentDocument = graphql(`
  2138. query GetProductCollectionsWithParent($id: ID!) {
  2139. product(id: $id) {
  2140. id
  2141. collections {
  2142. id
  2143. name
  2144. parent {
  2145. id
  2146. name
  2147. }
  2148. }
  2149. }
  2150. }
  2151. `);
  2152. const getCollectionNestedParentsDocument = graphql(`
  2153. query GetCollectionNestedParents {
  2154. collections {
  2155. items {
  2156. id
  2157. name
  2158. parent {
  2159. name
  2160. parent {
  2161. name
  2162. parent {
  2163. name
  2164. }
  2165. }
  2166. }
  2167. }
  2168. }
  2169. }
  2170. `);
  2171. const previewCollectionVariantsDocument = graphql(`
  2172. query PreviewCollectionVariants(
  2173. $input: PreviewCollectionVariantsInput!
  2174. $options: ProductVariantListOptions
  2175. ) {
  2176. previewCollectionVariants(input: $input, options: $options) {
  2177. items {
  2178. id
  2179. name
  2180. }
  2181. totalItems
  2182. }
  2183. }
  2184. `);
  2185. const removeCollectionsFromChannelDocument = graphql(
  2186. `
  2187. mutation RemoveCollectionsFromChannel($input: RemoveCollectionsFromChannelInput!) {
  2188. removeCollectionsFromChannel(input: $input) {
  2189. ...Collection
  2190. }
  2191. }
  2192. `,
  2193. [collectionFragment],
  2194. );
  2195. const deleteCollectionsBulkDocument = graphql(`
  2196. mutation DeleteCollectionsBulk($ids: [ID!]!) {
  2197. deleteCollections(ids: $ids) {
  2198. message
  2199. result
  2200. }
  2201. }
  2202. `);