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

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