default-search-plugin.e2e-spec.ts 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851
  1. /* tslint:disable:no-non-null-assertion */
  2. import { pick } from '@vendure/common/lib/pick';
  3. import {
  4. DefaultJobQueuePlugin,
  5. DefaultSearchPlugin,
  6. facetValueCollectionFilter,
  7. mergeConfig,
  8. } from '@vendure/core';
  9. import { createTestEnvironment, E2E_DEFAULT_CHANNEL_TOKEN, SimpleGraphQLClient } from '@vendure/testing';
  10. import gql from 'graphql-tag';
  11. import path from 'path';
  12. import { initialData } from '../../../e2e-common/e2e-initial-data';
  13. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  14. import {
  15. AssignProductsToChannel,
  16. CreateChannel,
  17. CreateCollection,
  18. CreateFacet,
  19. CurrencyCode,
  20. DeleteProduct,
  21. DeleteProductVariant,
  22. LanguageCode,
  23. RemoveProductsFromChannel,
  24. SearchFacetValues,
  25. SearchGetAssets,
  26. SearchGetPrices,
  27. SearchInput,
  28. SortOrder,
  29. UpdateAsset,
  30. UpdateCollection,
  31. UpdateProduct,
  32. UpdateProductVariants,
  33. UpdateTaxRate,
  34. } from './graphql/generated-e2e-admin-types';
  35. import { SearchProductsShop } from './graphql/generated-e2e-shop-types';
  36. import {
  37. ASSIGN_PRODUCT_TO_CHANNEL,
  38. CREATE_CHANNEL,
  39. CREATE_COLLECTION,
  40. CREATE_FACET,
  41. DELETE_PRODUCT,
  42. DELETE_PRODUCT_VARIANT,
  43. REMOVE_PRODUCT_FROM_CHANNEL,
  44. UPDATE_ASSET,
  45. UPDATE_COLLECTION,
  46. UPDATE_PRODUCT,
  47. UPDATE_PRODUCT_VARIANTS,
  48. UPDATE_TAX_RATE,
  49. } from './graphql/shared-definitions';
  50. import { SEARCH_PRODUCTS_SHOP } from './graphql/shop-definitions';
  51. import { awaitRunningJobs } from './utils/await-running-jobs';
  52. describe('Default search plugin', () => {
  53. const { server, adminClient, shopClient } = createTestEnvironment(
  54. mergeConfig(testConfig, { plugins: [DefaultSearchPlugin, DefaultJobQueuePlugin] }),
  55. );
  56. beforeAll(async () => {
  57. await server.init({
  58. initialData,
  59. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  60. customerCount: 1,
  61. });
  62. await adminClient.asSuperAdmin();
  63. }, TEST_SETUP_TIMEOUT_MS);
  64. afterAll(async () => {
  65. await server.destroy();
  66. });
  67. function doAdminSearchQuery(input: SearchInput) {
  68. return adminClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(SEARCH_PRODUCTS, {
  69. input,
  70. });
  71. }
  72. async function testGroupByProduct(client: SimpleGraphQLClient) {
  73. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  74. SEARCH_PRODUCTS_SHOP,
  75. {
  76. input: {
  77. groupByProduct: true,
  78. },
  79. },
  80. );
  81. expect(result.search.totalItems).toBe(20);
  82. }
  83. async function testNoGrouping(client: SimpleGraphQLClient) {
  84. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  85. SEARCH_PRODUCTS_SHOP,
  86. {
  87. input: {
  88. groupByProduct: false,
  89. },
  90. },
  91. );
  92. expect(result.search.totalItems).toBe(34);
  93. }
  94. async function testMatchSearchTerm(client: SimpleGraphQLClient) {
  95. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  96. SEARCH_PRODUCTS_SHOP,
  97. {
  98. input: {
  99. term: 'camera',
  100. groupByProduct: true,
  101. sort: {
  102. name: SortOrder.ASC,
  103. },
  104. },
  105. },
  106. );
  107. expect(result.search.items.map((i) => i.productName)).toEqual([
  108. 'Camera Lens',
  109. 'Instant Camera',
  110. 'Slr Camera',
  111. ]);
  112. }
  113. async function testMatchFacetIds(client: SimpleGraphQLClient) {
  114. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  115. SEARCH_PRODUCTS_SHOP,
  116. {
  117. input: {
  118. facetValueIds: ['T_1', 'T_2'],
  119. groupByProduct: true,
  120. },
  121. },
  122. );
  123. expect(result.search.items.map((i) => i.productName)).toEqual([
  124. 'Laptop',
  125. 'Curvy Monitor',
  126. 'Gaming PC',
  127. 'Hard Drive',
  128. 'Clacky Keyboard',
  129. 'USB Cable',
  130. ]);
  131. }
  132. async function testMatchCollectionId(client: SimpleGraphQLClient) {
  133. const result = await client.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  134. SEARCH_PRODUCTS_SHOP,
  135. {
  136. input: {
  137. collectionId: 'T_2',
  138. groupByProduct: true,
  139. },
  140. },
  141. );
  142. expect(result.search.items.map((i) => i.productName)).toEqual([
  143. 'Spiky Cactus',
  144. 'Orchid',
  145. 'Bonsai Tree',
  146. ]);
  147. }
  148. async function testSinglePrices(client: SimpleGraphQLClient) {
  149. const result = await client.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  150. SEARCH_GET_PRICES,
  151. {
  152. input: {
  153. groupByProduct: false,
  154. take: 3,
  155. } as SearchInput,
  156. },
  157. );
  158. expect(result.search.items).toEqual([
  159. {
  160. price: { value: 129900 },
  161. priceWithTax: { value: 155880 },
  162. },
  163. {
  164. price: { value: 139900 },
  165. priceWithTax: { value: 167880 },
  166. },
  167. {
  168. price: { value: 219900 },
  169. priceWithTax: { value: 263880 },
  170. },
  171. ]);
  172. }
  173. async function testPriceRanges(client: SimpleGraphQLClient) {
  174. const result = await client.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  175. SEARCH_GET_PRICES,
  176. {
  177. input: {
  178. groupByProduct: true,
  179. take: 3,
  180. } as SearchInput,
  181. },
  182. );
  183. expect(result.search.items).toEqual([
  184. {
  185. price: { min: 129900, max: 229900 },
  186. priceWithTax: { min: 155880, max: 275880 },
  187. },
  188. {
  189. price: { min: 14374, max: 16994 },
  190. priceWithTax: { min: 17249, max: 20393 },
  191. },
  192. {
  193. price: { min: 93120, max: 109995 },
  194. priceWithTax: { min: 111744, max: 131994 },
  195. },
  196. ]);
  197. }
  198. describe('shop api', () => {
  199. it('group by product', () => testGroupByProduct(shopClient));
  200. it('no grouping', () => testNoGrouping(shopClient));
  201. it('matches search term', () => testMatchSearchTerm(shopClient));
  202. it('matches by facetId', () => testMatchFacetIds(shopClient));
  203. it('matches by collectionId', () => testMatchCollectionId(shopClient));
  204. it('single prices', () => testSinglePrices(shopClient));
  205. it('price ranges', () => testPriceRanges(shopClient));
  206. it('returns correct facetValues when not grouped by product', async () => {
  207. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  208. SEARCH_GET_FACET_VALUES,
  209. {
  210. input: {
  211. groupByProduct: false,
  212. },
  213. },
  214. );
  215. expect(result.search.facetValues).toEqual([
  216. { count: 21, facetValue: { id: 'T_1', name: 'electronics' } },
  217. { count: 17, facetValue: { id: 'T_2', name: 'computers' } },
  218. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  219. { count: 10, facetValue: { id: 'T_4', name: 'sports equipment' } },
  220. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  221. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  222. ]);
  223. });
  224. it('returns correct facetValues when grouped by product', async () => {
  225. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  226. SEARCH_GET_FACET_VALUES,
  227. {
  228. input: {
  229. groupByProduct: true,
  230. },
  231. },
  232. );
  233. expect(result.search.facetValues).toEqual([
  234. { count: 10, facetValue: { id: 'T_1', name: 'electronics' } },
  235. { count: 6, facetValue: { id: 'T_2', name: 'computers' } },
  236. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  237. { count: 7, facetValue: { id: 'T_4', name: 'sports equipment' } },
  238. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  239. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  240. ]);
  241. });
  242. it('omits facetValues of private facets', async () => {
  243. const { createFacet } = await adminClient.query<CreateFacet.Mutation, CreateFacet.Variables>(
  244. CREATE_FACET,
  245. {
  246. input: {
  247. code: 'profit-margin',
  248. isPrivate: true,
  249. translations: [{ languageCode: LanguageCode.en, name: 'Profit Margin' }],
  250. values: [
  251. {
  252. code: 'massive',
  253. translations: [{ languageCode: LanguageCode.en, name: 'massive' }],
  254. },
  255. ],
  256. },
  257. },
  258. );
  259. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  260. input: {
  261. id: 'T_2',
  262. // T_1 & T_2 are the existing facetValues (electronics & photo)
  263. facetValueIds: ['T_1', 'T_2', createFacet.values[0].id],
  264. },
  265. });
  266. await awaitRunningJobs(adminClient);
  267. const result = await shopClient.query<SearchFacetValues.Query, SearchFacetValues.Variables>(
  268. SEARCH_GET_FACET_VALUES,
  269. {
  270. input: {
  271. groupByProduct: true,
  272. },
  273. },
  274. );
  275. expect(result.search.facetValues).toEqual([
  276. { count: 10, facetValue: { id: 'T_1', name: 'electronics' } },
  277. { count: 6, facetValue: { id: 'T_2', name: 'computers' } },
  278. { count: 4, facetValue: { id: 'T_3', name: 'photo' } },
  279. { count: 7, facetValue: { id: 'T_4', name: 'sports equipment' } },
  280. { count: 3, facetValue: { id: 'T_5', name: 'home & garden' } },
  281. { count: 3, facetValue: { id: 'T_6', name: 'plants' } },
  282. ]);
  283. });
  284. it('encodes the productId and productVariantId', async () => {
  285. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  286. SEARCH_PRODUCTS_SHOP,
  287. {
  288. input: {
  289. groupByProduct: false,
  290. take: 1,
  291. },
  292. },
  293. );
  294. expect(pick(result.search.items[0], ['productId', 'productVariantId'])).toEqual({
  295. productId: 'T_1',
  296. productVariantId: 'T_1',
  297. });
  298. });
  299. it('omits results for disabled ProductVariants', async () => {
  300. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  301. UPDATE_PRODUCT_VARIANTS,
  302. {
  303. input: [{ id: 'T_3', enabled: false }],
  304. },
  305. );
  306. await awaitRunningJobs(adminClient);
  307. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  308. SEARCH_PRODUCTS_SHOP,
  309. {
  310. input: {
  311. groupByProduct: false,
  312. take: 3,
  313. },
  314. },
  315. );
  316. expect(result.search.items.map((i) => i.productVariantId)).toEqual(['T_1', 'T_2', 'T_4']);
  317. });
  318. it('encodes collectionIds', async () => {
  319. const result = await shopClient.query<SearchProductsShop.Query, SearchProductsShop.Variables>(
  320. SEARCH_PRODUCTS_SHOP,
  321. {
  322. input: {
  323. groupByProduct: false,
  324. term: 'cactus',
  325. take: 1,
  326. },
  327. },
  328. );
  329. expect(result.search.items[0].collectionIds).toEqual(['T_2']);
  330. });
  331. });
  332. describe('admin api', () => {
  333. it('group by product', () => testGroupByProduct(adminClient));
  334. it('no grouping', () => testNoGrouping(adminClient));
  335. it('matches search term', () => testMatchSearchTerm(adminClient));
  336. it('matches by facetId', () => testMatchFacetIds(adminClient));
  337. it('matches by collectionId', () => testMatchCollectionId(adminClient));
  338. it('single prices', () => testSinglePrices(adminClient));
  339. it('price ranges', () => testPriceRanges(adminClient));
  340. describe('updating the index', () => {
  341. it('updates index when ProductVariants are changed', async () => {
  342. await awaitRunningJobs(adminClient);
  343. const { search } = await doAdminSearchQuery({ term: 'drive', groupByProduct: false });
  344. expect(search.items.map((i) => i.sku)).toEqual([
  345. 'IHD455T1',
  346. 'IHD455T2',
  347. 'IHD455T3',
  348. 'IHD455T4',
  349. 'IHD455T6',
  350. ]);
  351. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  352. UPDATE_PRODUCT_VARIANTS,
  353. {
  354. input: search.items.map((i) => ({
  355. id: i.productVariantId,
  356. sku: i.sku + '_updated',
  357. })),
  358. },
  359. );
  360. await awaitRunningJobs(adminClient);
  361. const { search: search2 } = await doAdminSearchQuery({
  362. term: 'drive',
  363. groupByProduct: false,
  364. });
  365. expect(search2.items.map((i) => i.sku)).toEqual([
  366. 'IHD455T1_updated',
  367. 'IHD455T2_updated',
  368. 'IHD455T3_updated',
  369. 'IHD455T4_updated',
  370. 'IHD455T6_updated',
  371. ]);
  372. });
  373. it('updates index when ProductVariants are deleted', async () => {
  374. await awaitRunningJobs(adminClient);
  375. const { search } = await doAdminSearchQuery({ term: 'drive', groupByProduct: false });
  376. await adminClient.query<DeleteProductVariant.Mutation, DeleteProductVariant.Variables>(
  377. DELETE_PRODUCT_VARIANT,
  378. {
  379. id: search.items[0].productVariantId,
  380. },
  381. );
  382. await awaitRunningJobs(adminClient);
  383. const { search: search2 } = await doAdminSearchQuery({
  384. term: 'drive',
  385. groupByProduct: false,
  386. });
  387. expect(search2.items.map((i) => i.sku)).toEqual([
  388. 'IHD455T2_updated',
  389. 'IHD455T3_updated',
  390. 'IHD455T4_updated',
  391. 'IHD455T6_updated',
  392. ]);
  393. });
  394. it('updates index when a Product is changed', async () => {
  395. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  396. input: {
  397. id: 'T_1',
  398. facetValueIds: [],
  399. },
  400. });
  401. await awaitRunningJobs(adminClient);
  402. const result = await doAdminSearchQuery({ facetValueIds: ['T_2'], groupByProduct: true });
  403. expect(result.search.items.map((i) => i.productName)).toEqual([
  404. 'Curvy Monitor',
  405. 'Gaming PC',
  406. 'Hard Drive',
  407. 'Clacky Keyboard',
  408. 'USB Cable',
  409. ]);
  410. });
  411. it('updates index when a Product is deleted', async () => {
  412. const { search } = await doAdminSearchQuery({ facetValueIds: ['T_2'], groupByProduct: true });
  413. expect(search.items.map((i) => i.productId)).toEqual(['T_2', 'T_3', 'T_4', 'T_5', 'T_6']);
  414. await adminClient.query<DeleteProduct.Mutation, DeleteProduct.Variables>(DELETE_PRODUCT, {
  415. id: 'T_5',
  416. });
  417. await awaitRunningJobs(adminClient);
  418. const { search: search2 } = await doAdminSearchQuery({
  419. facetValueIds: ['T_2'],
  420. groupByProduct: true,
  421. });
  422. expect(search2.items.map((i) => i.productId)).toEqual(['T_2', 'T_3', 'T_4', 'T_6']);
  423. });
  424. it('updates index when a Collection is changed', async () => {
  425. await adminClient.query<UpdateCollection.Mutation, UpdateCollection.Variables>(
  426. UPDATE_COLLECTION,
  427. {
  428. input: {
  429. id: 'T_2',
  430. filters: [
  431. {
  432. code: facetValueCollectionFilter.code,
  433. arguments: [
  434. {
  435. name: 'facetValueIds',
  436. value: `["T_4"]`,
  437. type: 'facetValueIds',
  438. },
  439. {
  440. name: 'containsAny',
  441. value: `false`,
  442. type: 'boolean',
  443. },
  444. ],
  445. },
  446. ],
  447. },
  448. },
  449. );
  450. await awaitRunningJobs(adminClient);
  451. // add an additional check for the collection filters to update
  452. await awaitRunningJobs(adminClient);
  453. const result = await doAdminSearchQuery({ collectionId: 'T_2', groupByProduct: true });
  454. expect(result.search.items.map((i) => i.productName)).toEqual([
  455. 'Road Bike',
  456. 'Skipping Rope',
  457. 'Boxing Gloves',
  458. 'Tent',
  459. 'Cruiser Skateboard',
  460. 'Football',
  461. 'Running Shoe',
  462. ]);
  463. });
  464. it('updates index when a Collection created', async () => {
  465. const { createCollection } = await adminClient.query<
  466. CreateCollection.Mutation,
  467. CreateCollection.Variables
  468. >(CREATE_COLLECTION, {
  469. input: {
  470. translations: [
  471. {
  472. languageCode: LanguageCode.en,
  473. name: 'Photo',
  474. description: '',
  475. },
  476. ],
  477. filters: [
  478. {
  479. code: facetValueCollectionFilter.code,
  480. arguments: [
  481. {
  482. name: 'facetValueIds',
  483. value: `["T_3"]`,
  484. type: 'facetValueIds',
  485. },
  486. {
  487. name: 'containsAny',
  488. value: `false`,
  489. type: 'boolean',
  490. },
  491. ],
  492. },
  493. ],
  494. },
  495. });
  496. await awaitRunningJobs(adminClient);
  497. // add an additional check for the collection filters to update
  498. await awaitRunningJobs(adminClient);
  499. const result = await doAdminSearchQuery({
  500. collectionId: createCollection.id,
  501. groupByProduct: true,
  502. });
  503. expect(result.search.items.map((i) => i.productName)).toEqual([
  504. 'Instant Camera',
  505. 'Camera Lens',
  506. 'Tripod',
  507. 'Slr Camera',
  508. ]);
  509. });
  510. it('updates index when a taxRate is changed', async () => {
  511. await adminClient.query<UpdateTaxRate.Mutation, UpdateTaxRate.Variables>(UPDATE_TAX_RATE, {
  512. input: {
  513. // Default Channel's defaultTaxZone is Europe (id 2) and the id of the standard TaxRate
  514. // to Europe is 2.
  515. id: 'T_2',
  516. value: 50,
  517. },
  518. });
  519. await awaitRunningJobs(adminClient);
  520. const result = await adminClient.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  521. SEARCH_GET_PRICES,
  522. {
  523. input: {
  524. groupByProduct: true,
  525. term: 'laptop',
  526. } as SearchInput,
  527. },
  528. );
  529. expect(result.search.items).toEqual([
  530. {
  531. price: { min: 129900, max: 229900 },
  532. priceWithTax: { min: 194850, max: 344850 },
  533. },
  534. ]);
  535. });
  536. it('updates index when asset focalPoint is changed', async () => {
  537. function doSearch() {
  538. return adminClient.query<SearchGetAssets.Query, SearchGetAssets.Variables>(
  539. SEARCH_GET_ASSETS,
  540. {
  541. input: {
  542. term: 'laptop',
  543. take: 1,
  544. },
  545. },
  546. );
  547. }
  548. const { search: search1 } = await doSearch();
  549. expect(search1.items[0].productAsset!.id).toBe('T_1');
  550. expect(search1.items[0].productAsset!.focalPoint).toBeNull();
  551. await adminClient.query<UpdateAsset.Mutation, UpdateAsset.Variables>(UPDATE_ASSET, {
  552. input: {
  553. id: 'T_1',
  554. focalPoint: {
  555. x: 0.42,
  556. y: 0.42,
  557. },
  558. },
  559. });
  560. await awaitRunningJobs(adminClient);
  561. const { search: search2 } = await doSearch();
  562. expect(search2.items[0].productAsset!.id).toBe('T_1');
  563. expect(search2.items[0].productAsset!.focalPoint).toEqual({ x: 0.42, y: 0.42 });
  564. });
  565. it('does not include deleted ProductVariants in index', async () => {
  566. const { search: s1 } = await doAdminSearchQuery({
  567. term: 'hard drive',
  568. groupByProduct: false,
  569. });
  570. const { deleteProductVariant } = await adminClient.query<
  571. DeleteProductVariant.Mutation,
  572. DeleteProductVariant.Variables
  573. >(DELETE_PRODUCT_VARIANT, { id: s1.items[0].productVariantId });
  574. await awaitRunningJobs(adminClient);
  575. const { search } = await adminClient.query<SearchGetPrices.Query, SearchGetPrices.Variables>(
  576. SEARCH_GET_PRICES,
  577. { input: { term: 'hard drive', groupByProduct: true } },
  578. );
  579. expect(search.items[0].price).toEqual({
  580. min: 7896,
  581. max: 13435,
  582. });
  583. });
  584. it('returns enabled field when not grouped', async () => {
  585. const result = await doAdminSearchQuery({ groupByProduct: false, take: 3 });
  586. expect(result.search.items.map(pick(['productVariantId', 'enabled']))).toEqual([
  587. { productVariantId: 'T_1', enabled: true },
  588. { productVariantId: 'T_2', enabled: true },
  589. { productVariantId: 'T_3', enabled: false },
  590. ]);
  591. });
  592. it('when grouped, enabled is true if at least one variant is enabled', async () => {
  593. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  594. UPDATE_PRODUCT_VARIANTS,
  595. {
  596. input: [
  597. { id: 'T_1', enabled: false },
  598. { id: 'T_2', enabled: false },
  599. ],
  600. },
  601. );
  602. await awaitRunningJobs(adminClient);
  603. const result = await doAdminSearchQuery({ groupByProduct: true, take: 3 });
  604. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  605. { productId: 'T_1', enabled: true },
  606. { productId: 'T_2', enabled: true },
  607. { productId: 'T_3', enabled: true },
  608. ]);
  609. });
  610. it('when grouped, enabled is false if all variants are disabled', async () => {
  611. await adminClient.query<UpdateProductVariants.Mutation, UpdateProductVariants.Variables>(
  612. UPDATE_PRODUCT_VARIANTS,
  613. {
  614. input: [{ id: 'T_4', enabled: false }],
  615. },
  616. );
  617. await awaitRunningJobs(adminClient);
  618. const result = await doAdminSearchQuery({ groupByProduct: true, take: 3 });
  619. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  620. { productId: 'T_1', enabled: false },
  621. { productId: 'T_2', enabled: true },
  622. { productId: 'T_3', enabled: true },
  623. ]);
  624. });
  625. it('when grouped, enabled is false if product is disabled', async () => {
  626. await adminClient.query<UpdateProduct.Mutation, UpdateProduct.Variables>(UPDATE_PRODUCT, {
  627. input: {
  628. id: 'T_3',
  629. enabled: false,
  630. },
  631. });
  632. await awaitRunningJobs(adminClient);
  633. const result = await doAdminSearchQuery({ groupByProduct: true, take: 3 });
  634. expect(result.search.items.map(pick(['productId', 'enabled']))).toEqual([
  635. { productId: 'T_1', enabled: false },
  636. { productId: 'T_2', enabled: true },
  637. { productId: 'T_3', enabled: false },
  638. ]);
  639. });
  640. });
  641. describe('channel handling', () => {
  642. const SECOND_CHANNEL_TOKEN = 'second-channel-token';
  643. let secondChannel: CreateChannel.CreateChannel;
  644. beforeAll(async () => {
  645. const { createChannel } = await adminClient.query<
  646. CreateChannel.Mutation,
  647. CreateChannel.Variables
  648. >(CREATE_CHANNEL, {
  649. input: {
  650. code: 'second-channel',
  651. token: SECOND_CHANNEL_TOKEN,
  652. defaultLanguageCode: LanguageCode.en,
  653. currencyCode: CurrencyCode.GBP,
  654. pricesIncludeTax: true,
  655. defaultTaxZoneId: 'T_1',
  656. defaultShippingZoneId: 'T_1',
  657. },
  658. });
  659. secondChannel = createChannel;
  660. });
  661. it('adding product to channel', async () => {
  662. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  663. await adminClient.query<AssignProductsToChannel.Mutation, AssignProductsToChannel.Variables>(
  664. ASSIGN_PRODUCT_TO_CHANNEL,
  665. {
  666. input: { channelId: secondChannel.id, productIds: ['T_1', 'T_2'] },
  667. },
  668. );
  669. await awaitRunningJobs(adminClient);
  670. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  671. const { search } = await doAdminSearchQuery({ groupByProduct: true });
  672. expect(search.items.map((i) => i.productId)).toEqual(['T_1', 'T_2']);
  673. }, 10000);
  674. it('removing product from channel', async () => {
  675. adminClient.setChannelToken(E2E_DEFAULT_CHANNEL_TOKEN);
  676. const { removeProductsFromChannel } = await adminClient.query<
  677. RemoveProductsFromChannel.Mutation,
  678. RemoveProductsFromChannel.Variables
  679. >(REMOVE_PRODUCT_FROM_CHANNEL, {
  680. input: {
  681. productIds: ['T_2'],
  682. channelId: secondChannel.id,
  683. },
  684. });
  685. await awaitRunningJobs(adminClient);
  686. adminClient.setChannelToken(SECOND_CHANNEL_TOKEN);
  687. const { search } = await doAdminSearchQuery({ groupByProduct: true });
  688. expect(search.items.map((i) => i.productId)).toEqual(['T_1']);
  689. }, 10000);
  690. });
  691. });
  692. });
  693. export const SEARCH_PRODUCTS = gql`
  694. query SearchProductsAdmin($input: SearchInput!) {
  695. search(input: $input) {
  696. totalItems
  697. items {
  698. enabled
  699. productId
  700. productName
  701. productPreview
  702. productVariantId
  703. productVariantName
  704. productVariantPreview
  705. sku
  706. }
  707. }
  708. }
  709. `;
  710. export const SEARCH_GET_FACET_VALUES = gql`
  711. query SearchFacetValues($input: SearchInput!) {
  712. search(input: $input) {
  713. totalItems
  714. facetValues {
  715. count
  716. facetValue {
  717. id
  718. name
  719. }
  720. }
  721. }
  722. }
  723. `;
  724. export const SEARCH_GET_ASSETS = gql`
  725. query SearchGetAssets($input: SearchInput!) {
  726. search(input: $input) {
  727. totalItems
  728. items {
  729. productId
  730. productName
  731. productVariantName
  732. productAsset {
  733. id
  734. preview
  735. focalPoint {
  736. x
  737. y
  738. }
  739. }
  740. productVariantAsset {
  741. id
  742. preview
  743. focalPoint {
  744. x
  745. y
  746. }
  747. }
  748. }
  749. }
  750. }
  751. `;
  752. export const SEARCH_GET_PRICES = gql`
  753. query SearchGetPrices($input: SearchInput!) {
  754. search(input: $input) {
  755. items {
  756. price {
  757. ... on PriceRange {
  758. min
  759. max
  760. }
  761. ... on SinglePrice {
  762. value
  763. }
  764. }
  765. priceWithTax {
  766. ... on PriceRange {
  767. min
  768. max
  769. }
  770. ... on SinglePrice {
  771. value
  772. }
  773. }
  774. }
  775. }
  776. }
  777. `;