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

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