collection.e2e-spec.ts 94 KB

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