list-query-builder.e2e-spec.ts 23 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753
  1. import { mergeConfig } from '@vendure/core';
  2. import { createTestEnvironment } from '@vendure/testing';
  3. import gql from 'graphql-tag';
  4. import path from 'path';
  5. import { initialData } from '../../../e2e-common/e2e-initial-data';
  6. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  7. import { ListQueryPlugin } from './fixtures/test-plugins/list-query-plugin';
  8. import { LanguageCode, SortOrder } from './graphql/generated-e2e-admin-types';
  9. import { assertThrowsWithMessage } from './utils/assert-throws-with-message';
  10. import { fixPostgresTimezone } from './utils/fix-pg-timezone';
  11. fixPostgresTimezone();
  12. describe('ListQueryBuilder', () => {
  13. const { server, adminClient, shopClient } = createTestEnvironment(
  14. mergeConfig(testConfig, {
  15. apiOptions: {
  16. shopListQueryLimit: 10,
  17. adminListQueryLimit: 30,
  18. },
  19. plugins: [ListQueryPlugin],
  20. }),
  21. );
  22. beforeAll(async () => {
  23. await server.init({
  24. initialData,
  25. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  26. customerCount: 1,
  27. });
  28. await adminClient.asSuperAdmin();
  29. }, TEST_SETUP_TIMEOUT_MS);
  30. afterAll(async () => {
  31. await server.destroy();
  32. });
  33. function getItemLabels(items: any[]): string[] {
  34. return items.map((x: any) => x.label).sort();
  35. }
  36. describe('pagination', () => {
  37. it('all en', async () => {
  38. const { testEntities } = await adminClient.query(
  39. GET_LIST,
  40. {
  41. options: {},
  42. },
  43. { languageCode: LanguageCode.en },
  44. );
  45. expect(testEntities.totalItems).toBe(6);
  46. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'C', 'D', 'E', 'F']);
  47. expect(testEntities.items.map((i: any) => i.name)).toEqual([
  48. 'apple',
  49. 'bike',
  50. 'cake',
  51. 'dog',
  52. 'egg',
  53. 'baum', // if default en lang does not exist, use next available lang
  54. ]);
  55. });
  56. it('all de', async () => {
  57. const { testEntities } = await adminClient.query(
  58. GET_LIST,
  59. {
  60. options: {},
  61. },
  62. { languageCode: LanguageCode.de },
  63. );
  64. expect(testEntities.totalItems).toBe(6);
  65. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'C', 'D', 'E', 'F']);
  66. expect(testEntities.items.map((i: any) => i.name)).toEqual([
  67. 'apfel',
  68. 'fahrrad',
  69. 'kuchen',
  70. 'hund',
  71. 'egg', // falls back to en translation when de doesn't exist
  72. 'baum',
  73. ]);
  74. });
  75. it('take', async () => {
  76. const { testEntities } = await adminClient.query(GET_LIST, {
  77. options: {
  78. take: 2,
  79. },
  80. });
  81. expect(testEntities.totalItems).toBe(6);
  82. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
  83. });
  84. it('skip', async () => {
  85. const { testEntities } = await adminClient.query(GET_LIST, {
  86. options: {
  87. skip: 2,
  88. },
  89. });
  90. expect(testEntities.totalItems).toBe(6);
  91. expect(getItemLabels(testEntities.items)).toEqual(['C', 'D', 'E', 'F']);
  92. });
  93. it('skip negative is ignored', async () => {
  94. const { testEntities } = await adminClient.query(GET_LIST, {
  95. options: {
  96. skip: -1,
  97. },
  98. });
  99. expect(testEntities.totalItems).toBe(6);
  100. expect(testEntities.items.length).toBe(6);
  101. });
  102. it('take zero is ignored', async () => {
  103. const { testEntities } = await adminClient.query(GET_LIST, {
  104. options: {
  105. take: 0,
  106. },
  107. });
  108. expect(testEntities.totalItems).toBe(6);
  109. expect(testEntities.items.length).toBe(6);
  110. });
  111. it('take negative is ignored', async () => {
  112. const { testEntities } = await adminClient.query(GET_LIST, {
  113. options: {
  114. take: -1,
  115. },
  116. });
  117. expect(testEntities.totalItems).toBe(6);
  118. expect(testEntities.items.length).toBe(6);
  119. });
  120. it(
  121. 'take beyond adminListQueryLimit',
  122. assertThrowsWithMessage(async () => {
  123. await adminClient.query(GET_LIST, {
  124. options: {
  125. take: 50,
  126. },
  127. });
  128. }, 'Cannot take more than 30 results from a list query'),
  129. );
  130. it(
  131. 'take beyond shopListQueryLimit',
  132. assertThrowsWithMessage(async () => {
  133. await shopClient.query(GET_LIST, {
  134. options: {
  135. take: 50,
  136. },
  137. });
  138. }, 'Cannot take more than 10 results from a list query'),
  139. );
  140. });
  141. describe('string filtering', () => {
  142. it('eq', async () => {
  143. const { testEntities } = await adminClient.query(GET_LIST, {
  144. options: {
  145. filter: {
  146. label: {
  147. eq: 'B',
  148. },
  149. },
  150. },
  151. });
  152. expect(getItemLabels(testEntities.items)).toEqual(['B']);
  153. });
  154. it('notEq', async () => {
  155. const { testEntities } = await adminClient.query(GET_LIST, {
  156. options: {
  157. filter: {
  158. label: {
  159. notEq: 'B',
  160. },
  161. },
  162. },
  163. });
  164. expect(getItemLabels(testEntities.items)).toEqual(['A', 'C', 'D', 'E', 'F']);
  165. });
  166. it('contains', async () => {
  167. const { testEntities } = await adminClient.query(GET_LIST, {
  168. options: {
  169. filter: {
  170. description: {
  171. contains: 'adip',
  172. },
  173. },
  174. },
  175. });
  176. expect(getItemLabels(testEntities.items)).toEqual(['C']);
  177. });
  178. it('notContains', async () => {
  179. const { testEntities } = await adminClient.query(GET_LIST, {
  180. options: {
  181. filter: {
  182. description: {
  183. notContains: 'te',
  184. },
  185. },
  186. },
  187. });
  188. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'E', 'F']);
  189. });
  190. it('in', async () => {
  191. const { testEntities } = await adminClient.query(GET_LIST, {
  192. options: {
  193. filter: {
  194. label: {
  195. in: ['A', 'C'],
  196. },
  197. },
  198. },
  199. });
  200. expect(getItemLabels(testEntities.items)).toEqual(['A', 'C']);
  201. });
  202. it('notIn', async () => {
  203. const { testEntities } = await adminClient.query(GET_LIST, {
  204. options: {
  205. filter: {
  206. label: {
  207. notIn: ['A', 'C'],
  208. },
  209. },
  210. },
  211. });
  212. expect(getItemLabels(testEntities.items)).toEqual(['B', 'D', 'E', 'F']);
  213. });
  214. describe('regex', () => {
  215. it('simple substring', async () => {
  216. const { testEntities } = await adminClient.query(GET_LIST, {
  217. options: {
  218. filter: {
  219. description: {
  220. regex: 'or',
  221. },
  222. },
  223. },
  224. });
  225. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'D']);
  226. });
  227. it('start of string', async () => {
  228. const { testEntities } = await adminClient.query(GET_LIST, {
  229. options: {
  230. filter: {
  231. description: {
  232. regex: '^in',
  233. },
  234. },
  235. },
  236. });
  237. expect(getItemLabels(testEntities.items)).toEqual(['E']);
  238. });
  239. it('end of string', async () => {
  240. const { testEntities } = await adminClient.query(GET_LIST, {
  241. options: {
  242. filter: {
  243. description: {
  244. regex: 'or$',
  245. },
  246. },
  247. },
  248. });
  249. expect(getItemLabels(testEntities.items)).toEqual(['D']);
  250. });
  251. it('alternation', async () => {
  252. const { testEntities } = await adminClient.query(GET_LIST, {
  253. options: {
  254. filter: {
  255. description: {
  256. regex: 'dolor|tempor',
  257. },
  258. },
  259. },
  260. });
  261. expect(getItemLabels(testEntities.items)).toEqual(['B', 'D']);
  262. });
  263. it('complex', async () => {
  264. const { testEntities } = await adminClient.query(GET_LIST, {
  265. options: {
  266. filter: {
  267. description: {
  268. regex: '(dolor|tempor)|inc[i]?d[^a]d.*nt',
  269. },
  270. },
  271. },
  272. });
  273. expect(getItemLabels(testEntities.items)).toEqual(['B', 'D', 'E']);
  274. });
  275. });
  276. });
  277. describe('boolean filtering', () => {
  278. it('eq', async () => {
  279. const { testEntities } = await adminClient.query(GET_LIST, {
  280. options: {
  281. filter: {
  282. active: {
  283. eq: false,
  284. },
  285. },
  286. },
  287. });
  288. expect(getItemLabels(testEntities.items)).toEqual(['C', 'E', 'F']);
  289. });
  290. });
  291. describe('number filtering', () => {
  292. it('eq', async () => {
  293. const { testEntities } = await adminClient.query(GET_LIST, {
  294. options: {
  295. filter: {
  296. order: {
  297. eq: 1,
  298. },
  299. },
  300. },
  301. });
  302. expect(getItemLabels(testEntities.items)).toEqual(['B']);
  303. });
  304. it('lt', async () => {
  305. const { testEntities } = await adminClient.query(GET_LIST, {
  306. options: {
  307. filter: {
  308. order: {
  309. lt: 1,
  310. },
  311. },
  312. },
  313. });
  314. expect(getItemLabels(testEntities.items)).toEqual(['A']);
  315. });
  316. it('lte', async () => {
  317. const { testEntities } = await adminClient.query(GET_LIST, {
  318. options: {
  319. filter: {
  320. order: {
  321. lte: 1,
  322. },
  323. },
  324. },
  325. });
  326. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
  327. });
  328. it('gt', async () => {
  329. const { testEntities } = await adminClient.query(GET_LIST, {
  330. options: {
  331. filter: {
  332. order: {
  333. gt: 1,
  334. },
  335. },
  336. },
  337. });
  338. expect(getItemLabels(testEntities.items)).toEqual(['C', 'D', 'E', 'F']);
  339. });
  340. it('gte', async () => {
  341. const { testEntities } = await adminClient.query(GET_LIST, {
  342. options: {
  343. filter: {
  344. order: {
  345. gte: 1,
  346. },
  347. },
  348. },
  349. });
  350. expect(getItemLabels(testEntities.items)).toEqual(['B', 'C', 'D', 'E', 'F']);
  351. });
  352. it('between', async () => {
  353. const { testEntities } = await adminClient.query(GET_LIST, {
  354. options: {
  355. filter: {
  356. order: {
  357. between: {
  358. start: 2,
  359. end: 4,
  360. },
  361. },
  362. },
  363. },
  364. });
  365. expect(getItemLabels(testEntities.items)).toEqual(['C', 'D', 'E']);
  366. });
  367. });
  368. describe('date filtering', () => {
  369. it('before', async () => {
  370. const { testEntities } = await adminClient.query(GET_LIST, {
  371. options: {
  372. filter: {
  373. date: {
  374. before: '2020-01-20T10:00:00.000Z',
  375. },
  376. },
  377. },
  378. });
  379. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
  380. });
  381. it('before on same date', async () => {
  382. const { testEntities } = await adminClient.query(GET_LIST, {
  383. options: {
  384. filter: {
  385. date: {
  386. before: '2020-01-15T17:00:00.000Z',
  387. },
  388. },
  389. },
  390. });
  391. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
  392. });
  393. it('after', async () => {
  394. const { testEntities } = await adminClient.query(GET_LIST, {
  395. options: {
  396. filter: {
  397. date: {
  398. after: '2020-01-20T10:00:00.000Z',
  399. },
  400. },
  401. },
  402. });
  403. expect(getItemLabels(testEntities.items)).toEqual(['C', 'D', 'E', 'F']);
  404. });
  405. it('after on same date', async () => {
  406. const { testEntities } = await adminClient.query(GET_LIST, {
  407. options: {
  408. filter: {
  409. date: {
  410. after: '2020-01-25T09:00:00.000Z',
  411. },
  412. },
  413. },
  414. });
  415. expect(getItemLabels(testEntities.items)).toEqual(['C', 'D', 'E', 'F']);
  416. });
  417. it('between', async () => {
  418. const { testEntities } = await adminClient.query(GET_LIST, {
  419. options: {
  420. filter: {
  421. date: {
  422. between: {
  423. start: '2020-01-10T10:00:00.000Z',
  424. end: '2020-01-20T10:00:00.000Z',
  425. },
  426. },
  427. },
  428. },
  429. });
  430. expect(getItemLabels(testEntities.items)).toEqual(['B']);
  431. });
  432. });
  433. describe('sorting', () => {
  434. it('sort by string', async () => {
  435. const { testEntities } = await adminClient.query(GET_LIST, {
  436. options: {
  437. sort: {
  438. label: SortOrder.DESC,
  439. },
  440. },
  441. });
  442. expect(testEntities.items.map((x: any) => x.label)).toEqual(['F', 'E', 'D', 'C', 'B', 'A']);
  443. });
  444. it('sort by number', async () => {
  445. const { testEntities } = await adminClient.query(GET_LIST, {
  446. options: {
  447. sort: {
  448. order: SortOrder.DESC,
  449. },
  450. },
  451. });
  452. expect(testEntities.items.map((x: any) => x.label)).toEqual(['F', 'E', 'D', 'C', 'B', 'A']);
  453. });
  454. it('sort by date', async () => {
  455. const { testEntities } = await adminClient.query(GET_LIST, {
  456. options: {
  457. sort: {
  458. date: SortOrder.DESC,
  459. },
  460. },
  461. });
  462. expect(testEntities.items.map((x: any) => x.label)).toEqual(['F', 'E', 'D', 'C', 'B', 'A']);
  463. });
  464. it('sort by ID', async () => {
  465. const { testEntities } = await adminClient.query(GET_LIST, {
  466. options: {
  467. sort: {
  468. id: SortOrder.DESC,
  469. },
  470. },
  471. });
  472. expect(testEntities.items.map((x: any) => x.label)).toEqual(['F', 'E', 'D', 'C', 'B', 'A']);
  473. });
  474. it('sort by translated field en', async () => {
  475. const { testEntities } = await adminClient.query(GET_LIST, {
  476. options: {
  477. sort: {
  478. name: SortOrder.ASC,
  479. },
  480. },
  481. });
  482. expect(testEntities.items.map((x: any) => x.name)).toEqual([
  483. 'apple',
  484. 'baum', // falling back to de here
  485. 'bike',
  486. 'cake',
  487. 'dog',
  488. 'egg',
  489. ]);
  490. });
  491. it('sort by translated field de', async () => {
  492. const { testEntities } = await adminClient.query(
  493. GET_LIST,
  494. {
  495. options: {
  496. sort: {
  497. name: SortOrder.ASC,
  498. },
  499. },
  500. },
  501. { languageCode: LanguageCode.de },
  502. );
  503. expect(testEntities.items.map((x: any) => x.name)).toEqual([
  504. 'apfel',
  505. 'baum',
  506. 'egg',
  507. 'fahrrad',
  508. 'hund',
  509. 'kuchen',
  510. ]);
  511. });
  512. it('sort by translated field en with take', async () => {
  513. const { testEntities } = await adminClient.query(GET_LIST, {
  514. options: {
  515. sort: {
  516. name: SortOrder.ASC,
  517. },
  518. take: 4,
  519. },
  520. });
  521. expect(testEntities.items.map((x: any) => x.name)).toEqual(['apple', 'baum', 'bike', 'cake']);
  522. });
  523. it('sort by translated field de with take', async () => {
  524. const { testEntities } = await adminClient.query(
  525. GET_LIST,
  526. {
  527. options: {
  528. sort: {
  529. name: SortOrder.ASC,
  530. },
  531. take: 4,
  532. },
  533. },
  534. { languageCode: LanguageCode.de },
  535. );
  536. expect(testEntities.items.map((x: any) => x.name)).toEqual(['apfel', 'baum', 'egg', 'fahrrad']);
  537. });
  538. });
  539. describe('calculated fields', () => {
  540. it('filter by simple calculated property', async () => {
  541. const { testEntities } = await adminClient.query(GET_LIST, {
  542. options: {
  543. filter: {
  544. descriptionLength: {
  545. lt: 12,
  546. },
  547. },
  548. },
  549. });
  550. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B']);
  551. });
  552. it('filter by calculated property with join', async () => {
  553. const { testEntities } = await adminClient.query(GET_LIST, {
  554. options: {
  555. filter: {
  556. price: {
  557. lt: 14,
  558. },
  559. },
  560. },
  561. });
  562. expect(getItemLabels(testEntities.items)).toEqual(['A', 'B', 'E']);
  563. });
  564. it('sort by simple calculated property', async () => {
  565. const { testEntities } = await adminClient.query(GET_LIST, {
  566. options: {
  567. sort: {
  568. descriptionLength: SortOrder.ASC,
  569. },
  570. },
  571. });
  572. expect(testEntities.items.map((x: any) => x.label)).toEqual(['B', 'A', 'E', 'D', 'C', 'F']);
  573. });
  574. it('sort by calculated property with join', async () => {
  575. const { testEntities } = await adminClient.query(GET_LIST, {
  576. options: {
  577. sort: {
  578. price: SortOrder.ASC,
  579. },
  580. },
  581. });
  582. expect(testEntities.items.map((x: any) => x.label)).toEqual(['B', 'A', 'E', 'D', 'C', 'F']);
  583. });
  584. });
  585. describe('multiple clauses', () => {
  586. it('sort by translated field en & filter', async () => {
  587. const { testEntities } = await adminClient.query(GET_LIST, {
  588. options: {
  589. sort: {
  590. name: SortOrder.ASC,
  591. },
  592. filter: {
  593. order: {
  594. gte: 1,
  595. },
  596. },
  597. },
  598. });
  599. expect(testEntities.items.map((x: any) => x.name)).toEqual([
  600. 'baum',
  601. 'bike',
  602. 'cake',
  603. 'dog',
  604. 'egg',
  605. ]);
  606. });
  607. it('sort by translated field de & filter', async () => {
  608. const { testEntities } = await adminClient.query(
  609. GET_LIST,
  610. {
  611. options: {
  612. sort: {
  613. name: SortOrder.ASC,
  614. },
  615. filter: {
  616. order: {
  617. gte: 1,
  618. },
  619. },
  620. },
  621. },
  622. { languageCode: LanguageCode.de },
  623. );
  624. expect(testEntities.items.map((x: any) => x.name)).toEqual([
  625. 'baum',
  626. 'egg',
  627. 'fahrrad',
  628. 'hund',
  629. 'kuchen',
  630. ]);
  631. });
  632. it('sort by translated field de & filter & pagination', async () => {
  633. const { testEntities } = await adminClient.query(
  634. GET_LIST,
  635. {
  636. options: {
  637. sort: {
  638. name: SortOrder.ASC,
  639. },
  640. filter: {
  641. order: {
  642. gte: 1,
  643. },
  644. },
  645. take: 2,
  646. skip: 1,
  647. },
  648. },
  649. { languageCode: LanguageCode.de },
  650. );
  651. expect(testEntities.items.map((x: any) => x.name)).toEqual(['egg', 'fahrrad']);
  652. });
  653. });
  654. });
  655. const GET_LIST = gql`
  656. query GetTestEntities($options: TestEntityListOptions) {
  657. testEntities(options: $options) {
  658. totalItems
  659. items {
  660. id
  661. label
  662. name
  663. date
  664. }
  665. }
  666. }
  667. `;