collection.e2e-spec.ts 101 KB

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