custom-field-relations.e2e-spec.ts 57 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968969970971972973974975976977978979980981982983984985986987988989990991992993994995996997998999100010011002100310041005100610071008100910101011101210131014101510161017101810191020102110221023102410251026102710281029103010311032103310341035103610371038103910401041104210431044104510461047104810491050105110521053105410551056105710581059106010611062106310641065106610671068106910701071107210731074107510761077107810791080108110821083108410851086108710881089109010911092109310941095109610971098109911001101110211031104110511061107110811091110111111121113111411151116111711181119112011211122112311241125112611271128112911301131113211331134113511361137113811391140114111421143114411451146114711481149115011511152115311541155115611571158115911601161116211631164116511661167116811691170117111721173117411751176117711781179118011811182118311841185118611871188118911901191119211931194119511961197119811991200120112021203120412051206120712081209121012111212121312141215121612171218121912201221122212231224122512261227122812291230123112321233123412351236123712381239124012411242124312441245124612471248124912501251125212531254125512561257125812591260126112621263126412651266126712681269127012711272127312741275127612771278127912801281128212831284128512861287128812891290129112921293129412951296129712981299130013011302130313041305130613071308130913101311131213131314131513161317131813191320132113221323132413251326132713281329133013311332133313341335133613371338133913401341134213431344134513461347134813491350135113521353135413551356135713581359136013611362136313641365136613671368136913701371137213731374137513761377137813791380138113821383138413851386138713881389139013911392139313941395139613971398139914001401140214031404140514061407140814091410141114121413141414151416141714181419142014211422142314241425142614271428142914301431143214331434143514361437143814391440144114421443144414451446144714481449145014511452145314541455145614571458145914601461146214631464
  1. // this file relies on defintions that are only available at runtime, therefore we still use gql here.
  2. import {
  3. assertFound,
  4. Asset,
  5. Collection,
  6. Country,
  7. CustomFields,
  8. defaultShippingCalculator,
  9. defaultShippingEligibilityChecker,
  10. Facet,
  11. FacetValue,
  12. manualFulfillmentHandler,
  13. mergeConfig,
  14. Product,
  15. ProductOption,
  16. ProductOptionGroup,
  17. ProductVariant,
  18. RequestContext,
  19. ShippingMethod,
  20. TransactionalConnection,
  21. } from '@vendure/core';
  22. import { createTestEnvironment } from '@vendure/testing';
  23. import gql from 'graphql-tag';
  24. import path from 'path';
  25. import { Repository } from 'typeorm';
  26. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  27. import { initialData } from '../../../e2e-common/e2e-initial-data';
  28. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  29. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  30. import { TestPlugin1636_1664 } from './fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin';
  31. import { PluginIssue2453 } from './fixtures/test-plugins/issue-2453/plugin-issue2453';
  32. import { TestCustomEntity, WithCustomEntity } from './fixtures/test-plugins/with-custom-entity';
  33. import { addItemToOrderDocument } from './graphql/shop-definitions';
  34. import { sortById } from './utils/test-order-utils';
  35. const entitiesWithCustomFields: Array<keyof CustomFields> = [
  36. 'Address',
  37. 'Administrator',
  38. 'Asset',
  39. 'Channel',
  40. 'Collection',
  41. 'Customer',
  42. 'CustomerGroup',
  43. 'Facet',
  44. 'FacetValue',
  45. 'Fulfillment',
  46. 'GlobalSettings',
  47. 'Order',
  48. 'OrderLine',
  49. 'PaymentMethod',
  50. 'Product',
  51. 'ProductOption',
  52. 'ProductOptionGroup',
  53. 'ProductVariant',
  54. 'Promotion',
  55. 'Region',
  56. 'Seller',
  57. 'ShippingMethod',
  58. 'TaxCategory',
  59. 'TaxRate',
  60. 'User',
  61. 'Zone',
  62. ];
  63. const customFieldConfig: CustomFields = {};
  64. for (const entity of entitiesWithCustomFields) {
  65. customFieldConfig[entity] = [
  66. { name: 'primitive', type: 'string', list: false, defaultValue: 'test' },
  67. { name: 'single', type: 'relation', entity: Asset, graphQLType: 'Asset', list: false },
  68. { name: 'multi', type: 'relation', entity: Asset, graphQLType: 'Asset', list: true },
  69. ];
  70. }
  71. customFieldConfig.Product?.push(
  72. { name: 'cfCollection', type: 'relation', entity: Collection, list: false },
  73. { name: 'cfCountry', type: 'relation', entity: Country, list: false },
  74. { name: 'cfFacetValue', type: 'relation', entity: FacetValue, list: false },
  75. { name: 'cfFacet', type: 'relation', entity: Facet, list: false },
  76. { name: 'cfProductOptionGroup', type: 'relation', entity: ProductOptionGroup, list: false },
  77. { name: 'cfProductOption', type: 'relation', entity: ProductOption, list: false },
  78. { name: 'cfProductVariant', type: 'relation', entity: ProductVariant, list: false },
  79. { name: 'cfProduct', type: 'relation', entity: Product, list: false },
  80. { name: 'cfShippingMethod', type: 'relation', entity: ShippingMethod, list: false },
  81. { name: 'cfInternalAsset', type: 'relation', entity: Asset, list: false, internal: true },
  82. );
  83. customFieldConfig.ProductVariant?.push({
  84. name: 'cfRelatedProducts',
  85. type: 'relation',
  86. entity: Product,
  87. list: true,
  88. internal: false,
  89. public: true,
  90. });
  91. const customConfig = mergeConfig(testConfig(), {
  92. paymentOptions: {
  93. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  94. },
  95. dbConnectionOptions: {
  96. timezone: 'Z',
  97. },
  98. customFields: customFieldConfig,
  99. plugins: [TestPlugin1636_1664, WithCustomEntity, PluginIssue2453],
  100. });
  101. describe('Custom field relations', () => {
  102. const { server, adminClient, shopClient } = createTestEnvironment(customConfig);
  103. beforeAll(async () => {
  104. await server.init({
  105. initialData,
  106. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  107. customerCount: 3,
  108. });
  109. await adminClient.asSuperAdmin();
  110. }, TEST_SETUP_TIMEOUT_MS);
  111. afterAll(async () => {
  112. await server.destroy();
  113. });
  114. it('customFieldConfig query returns entity and scalar fields', async () => {
  115. const { globalSettings } = await adminClient.query(gql`
  116. query {
  117. globalSettings {
  118. serverConfig {
  119. customFieldConfig {
  120. Customer {
  121. ... on RelationCustomFieldConfig {
  122. name
  123. entity
  124. scalarFields
  125. }
  126. }
  127. }
  128. }
  129. }
  130. }
  131. `);
  132. const single = globalSettings.serverConfig.customFieldConfig.Customer[1];
  133. expect(single.entity).toBe('Asset');
  134. expect(single.scalarFields).toEqual([
  135. 'id',
  136. 'createdAt',
  137. 'updatedAt',
  138. 'languageCode',
  139. 'name',
  140. 'type',
  141. 'fileSize',
  142. 'mimeType',
  143. 'width',
  144. 'height',
  145. 'source',
  146. 'preview',
  147. ]);
  148. });
  149. describe('special data resolution', () => {
  150. let productId: string;
  151. const productCustomFieldRelationsSelection = `
  152. id
  153. customFields {
  154. cfCollection {
  155. languageCode
  156. name
  157. }
  158. cfCountry {
  159. languageCode
  160. name
  161. }
  162. cfFacetValue {
  163. languageCode
  164. name
  165. }
  166. cfFacet {
  167. languageCode
  168. name
  169. }
  170. cfProductOptionGroup {
  171. languageCode
  172. name
  173. }
  174. cfProductOption {
  175. languageCode
  176. name
  177. }
  178. cfProductVariant {
  179. languageCode
  180. name
  181. }
  182. cfProduct {
  183. languageCode
  184. name
  185. }
  186. cfShippingMethod {
  187. languageCode
  188. name
  189. }
  190. }`;
  191. function assertTranslatableCustomFieldValues(product: { customFields: any }) {
  192. expect(product.customFields.cfCollection).toEqual({
  193. languageCode: 'en',
  194. name: '__root_collection__',
  195. });
  196. expect(product.customFields.cfCountry).toEqual({ languageCode: 'en', name: 'Australia' });
  197. expect(product.customFields.cfFacetValue).toEqual({
  198. languageCode: 'en',
  199. name: 'electronics',
  200. });
  201. expect(product.customFields.cfFacet).toEqual({ languageCode: 'en', name: 'category' });
  202. expect(product.customFields.cfProductOptionGroup).toEqual({
  203. languageCode: 'en',
  204. name: 'screen size',
  205. });
  206. expect(product.customFields.cfProductOption).toEqual({
  207. languageCode: 'en',
  208. name: '13 inch',
  209. });
  210. expect(product.customFields.cfProductVariant).toEqual({
  211. languageCode: 'en',
  212. name: 'Laptop 13 inch 8GB',
  213. });
  214. expect(product.customFields.cfProduct).toEqual({ languageCode: 'en', name: 'Laptop' });
  215. expect(product.customFields.cfShippingMethod).toEqual({
  216. languageCode: 'en',
  217. name: 'Standard Shipping',
  218. });
  219. }
  220. it('translatable entities get translated', async () => {
  221. const { createProduct } = await adminClient.query(gql`
  222. mutation {
  223. createProduct(
  224. input: {
  225. translations: [
  226. {
  227. languageCode: en
  228. name: "Test product"
  229. description: ""
  230. slug: "test-product"
  231. }
  232. ]
  233. customFields: {
  234. cfCollectionId: "T_1"
  235. cfCountryId: "T_1"
  236. cfFacetValueId: "T_1"
  237. cfFacetId: "T_1"
  238. cfProductOptionGroupId: "T_1"
  239. cfProductOptionId: "T_1"
  240. cfProductVariantId: "T_1"
  241. cfProductId: "T_1"
  242. cfShippingMethodId: "T_1"
  243. }
  244. }
  245. ) { ${productCustomFieldRelationsSelection} }
  246. }
  247. `);
  248. productId = createProduct.id;
  249. assertTranslatableCustomFieldValues(createProduct);
  250. });
  251. it('translatable entities get translated on findOneInChannel', async () => {
  252. const { product } = await adminClient.query(gql`
  253. query {
  254. product(id: "${productId}") { ${productCustomFieldRelationsSelection} }
  255. }
  256. `);
  257. assertTranslatableCustomFieldValues(product);
  258. });
  259. // https://github.com/vendurehq/vendure/issues/2453
  260. it('translatable eager-loaded relation works (issue 2453)', async () => {
  261. const { collections } = await adminClient.query(gql`
  262. query {
  263. collections(options: { sort: { name: DESC } }) {
  264. totalItems
  265. items {
  266. id
  267. name
  268. customFields {
  269. campaign {
  270. name
  271. languageCode
  272. }
  273. }
  274. }
  275. }
  276. }
  277. `);
  278. expect(collections.totalItems).toBe(3);
  279. expect(collections.items.find((c: any) => c.id === 'T_3')).toEqual({
  280. customFields: {
  281. campaign: {
  282. languageCode: 'en',
  283. name: 'Clearance Up to 70% Off frames',
  284. },
  285. },
  286. id: 'T_3',
  287. name: 'children collection',
  288. });
  289. });
  290. it('ProductVariant prices get resolved', async () => {
  291. const { product } = await adminClient.query(gql`
  292. query {
  293. product(id: "${productId}") {
  294. id
  295. customFields {
  296. cfProductVariant {
  297. price
  298. currencyCode
  299. priceWithTax
  300. }
  301. }
  302. }
  303. }`);
  304. expect(product.customFields.cfProductVariant).toEqual({
  305. price: 129900,
  306. currencyCode: 'USD',
  307. priceWithTax: 155880,
  308. });
  309. });
  310. });
  311. it('ProductVariant without a specified property value returns null', async () => {
  312. const { createProduct } = await adminClient.query(gql`
  313. mutation {
  314. createProduct(
  315. input: {
  316. translations: [
  317. {
  318. languageCode: en
  319. name: "Product with empty custom fields"
  320. description: ""
  321. slug: "product-with-empty-custom-fields"
  322. }
  323. ]
  324. }
  325. ) {
  326. id
  327. }
  328. }
  329. `);
  330. const { product } = await adminClient.query(gql`
  331. query {
  332. product(id: "${createProduct.id}") {
  333. id
  334. customFields {
  335. cfProductVariant{
  336. price
  337. currencyCode
  338. priceWithTax
  339. }
  340. }
  341. }
  342. }`);
  343. expect(product.customFields.cfProductVariant).toEqual(null);
  344. });
  345. describe('entity-specific implementation', () => {
  346. function assertCustomFieldIds(customFields: any, single: string, multi: string[]) {
  347. expect(customFields.single).toEqual({ id: single });
  348. expect(customFields.multi.sort(sortById)).toEqual(multi.map(id => ({ id })));
  349. }
  350. const customFieldsSelection = `
  351. customFields {
  352. primitive
  353. single {
  354. id
  355. }
  356. multi {
  357. id
  358. }
  359. }`;
  360. describe('Address entity', () => {
  361. it('admin createCustomerAddress', async () => {
  362. const { createCustomerAddress } = await adminClient.query(gql`
  363. mutation {
  364. createCustomerAddress(
  365. customerId: "T_1"
  366. input: {
  367. countryCode: "GB"
  368. streetLine1: "Test Street"
  369. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  370. }
  371. ) {
  372. id
  373. ${customFieldsSelection}
  374. }
  375. }
  376. `);
  377. assertCustomFieldIds(createCustomerAddress.customFields, 'T_1', ['T_1', 'T_2']);
  378. });
  379. it('shop createCustomerAddress', async () => {
  380. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  381. const { createCustomerAddress } = await shopClient.query(gql`
  382. mutation {
  383. createCustomerAddress(
  384. input: {
  385. countryCode: "GB"
  386. streetLine1: "Test Street"
  387. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  388. }
  389. ) {
  390. id
  391. ${customFieldsSelection}
  392. }
  393. }
  394. `);
  395. assertCustomFieldIds(createCustomerAddress.customFields, 'T_1', ['T_1', 'T_2']);
  396. });
  397. it('admin updateCustomerAddress', async () => {
  398. const { updateCustomerAddress } = await adminClient.query(gql`
  399. mutation {
  400. updateCustomerAddress(
  401. input: { id: "T_1", customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] } }
  402. ) {
  403. id
  404. ${customFieldsSelection}
  405. }
  406. }
  407. `);
  408. assertCustomFieldIds(updateCustomerAddress.customFields, 'T_2', ['T_3', 'T_4']);
  409. });
  410. it('shop updateCustomerAddress', async () => {
  411. const { updateCustomerAddress } = await shopClient.query(gql`
  412. mutation {
  413. updateCustomerAddress(
  414. input: { id: "T_1", customFields: { singleId: "T_3", multiIds: ["T_4", "T_2"] } }
  415. ) {
  416. id
  417. ${customFieldsSelection}
  418. }
  419. }
  420. `);
  421. assertCustomFieldIds(updateCustomerAddress.customFields, 'T_3', ['T_2', 'T_4']);
  422. });
  423. });
  424. describe('Collection entity', () => {
  425. let collectionId: string;
  426. it('admin createCollection', async () => {
  427. const { createCollection } = await adminClient.query(gql`
  428. mutation {
  429. createCollection(
  430. input: {
  431. translations: [
  432. { languageCode: en, name: "Test", description: "test", slug: "test" }
  433. ]
  434. filters: []
  435. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  436. }
  437. ) {
  438. id
  439. ${customFieldsSelection}
  440. }
  441. }
  442. `);
  443. assertCustomFieldIds(createCollection.customFields, 'T_1', ['T_1', 'T_2']);
  444. collectionId = createCollection.id;
  445. });
  446. it('admin updateCollection', async () => {
  447. const { updateCollection } = await adminClient.query(gql`
  448. mutation {
  449. updateCollection(
  450. input: {
  451. id: "${collectionId}"
  452. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  453. }
  454. ) {
  455. id
  456. ${customFieldsSelection}
  457. }
  458. }
  459. `);
  460. assertCustomFieldIds(updateCollection.customFields, 'T_2', ['T_3', 'T_4']);
  461. });
  462. // https://github.com/vendurehq/vendure/issues/2840
  463. it('updating custom field relation on Collection does not delete primitive values', async () => {
  464. const { updateCollection } = await adminClient.query(gql`
  465. mutation {
  466. updateCollection(
  467. input: {
  468. id: "${collectionId}"
  469. customFields: { singleId: "T_3" }
  470. }
  471. ) {
  472. id
  473. ${customFieldsSelection}
  474. }
  475. }
  476. `);
  477. expect(updateCollection.customFields.single).toEqual({ id: 'T_3' });
  478. expect(updateCollection.customFields.primitive).toBe('test');
  479. });
  480. });
  481. describe('Customer entity', () => {
  482. let customerId: string;
  483. it('admin createCustomer', async () => {
  484. const { createCustomer } = await adminClient.query(gql`
  485. mutation {
  486. createCustomer(
  487. input: {
  488. emailAddress: "test@test.com"
  489. firstName: "Test"
  490. lastName: "Person"
  491. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  492. }
  493. ) {
  494. ... on Customer {
  495. id
  496. ${customFieldsSelection}
  497. }
  498. }
  499. }
  500. `);
  501. assertCustomFieldIds(createCustomer.customFields, 'T_1', ['T_1', 'T_2']);
  502. customerId = createCustomer.id;
  503. });
  504. it('admin updateCustomer', async () => {
  505. const { updateCustomer } = await adminClient.query(gql`
  506. mutation {
  507. updateCustomer(
  508. input: {
  509. id: "${customerId}"
  510. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  511. }
  512. ) {
  513. ...on Customer {
  514. id
  515. ${customFieldsSelection}
  516. }
  517. }
  518. }
  519. `);
  520. assertCustomFieldIds(updateCustomer.customFields, 'T_2', ['T_3', 'T_4']);
  521. });
  522. it('shop updateCustomer', async () => {
  523. const { updateCustomer } = await shopClient.query(gql`
  524. mutation {
  525. updateCustomer(input: { customFields: { singleId: "T_4", multiIds: ["T_2", "T_4"] } }) {
  526. id
  527. ${customFieldsSelection}
  528. }
  529. }
  530. `);
  531. assertCustomFieldIds(updateCustomer.customFields, 'T_4', ['T_2', 'T_4']);
  532. });
  533. });
  534. describe('Facet entity', () => {
  535. let facetId: string;
  536. it('admin createFacet', async () => {
  537. const { createFacet } = await adminClient.query(gql`
  538. mutation {
  539. createFacet(
  540. input: {
  541. code: "test"
  542. isPrivate: false
  543. translations: [{ languageCode: en, name: "test" }]
  544. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  545. }
  546. ) {
  547. id
  548. ${customFieldsSelection}
  549. }
  550. }
  551. `);
  552. assertCustomFieldIds(createFacet.customFields, 'T_1', ['T_1', 'T_2']);
  553. facetId = createFacet.id;
  554. });
  555. it('admin updateFacet', async () => {
  556. const { updateFacet } = await adminClient.query(gql`
  557. mutation {
  558. updateFacet(
  559. input: {
  560. id: "${facetId}"
  561. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  562. }
  563. ) {
  564. id
  565. ${customFieldsSelection}
  566. }
  567. }
  568. `);
  569. assertCustomFieldIds(updateFacet.customFields, 'T_2', ['T_3', 'T_4']);
  570. });
  571. // https://github.com/vendurehq/vendure/issues/2840
  572. it('updating custom field relation on Facet does not delete primitive values', async () => {
  573. const { updateFacet } = await adminClient.query(gql`
  574. mutation {
  575. updateFacet(
  576. input: {
  577. id: "${facetId}"
  578. customFields: { singleId: "T_3" }
  579. }
  580. ) {
  581. id
  582. ${customFieldsSelection}
  583. }
  584. }
  585. `);
  586. expect(updateFacet.customFields.single).toEqual({ id: 'T_3' });
  587. expect(updateFacet.customFields.primitive).toBe('test');
  588. });
  589. });
  590. describe('FacetValue entity', () => {
  591. let facetValueId: string;
  592. it('admin createFacetValues', async () => {
  593. const { createFacetValues } = await adminClient.query(gql`
  594. mutation {
  595. createFacetValues(
  596. input: {
  597. code: "test"
  598. facetId: "T_1"
  599. translations: [{ languageCode: en, name: "test" }]
  600. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  601. }
  602. ) {
  603. id
  604. ${customFieldsSelection}
  605. }
  606. }
  607. `);
  608. assertCustomFieldIds(createFacetValues[0].customFields, 'T_1', ['T_1', 'T_2']);
  609. facetValueId = createFacetValues[0].id;
  610. });
  611. it('admin updateFacetValues', async () => {
  612. const { updateFacetValues } = await adminClient.query(gql`
  613. mutation {
  614. updateFacetValues(
  615. input: {
  616. id: "${facetValueId}"
  617. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  618. }
  619. ) {
  620. id
  621. ${customFieldsSelection}
  622. }
  623. }
  624. `);
  625. assertCustomFieldIds(updateFacetValues[0].customFields, 'T_2', ['T_3', 'T_4']);
  626. });
  627. // https://github.com/vendurehq/vendure/issues/2840
  628. it('updating custom field relation on FacetValue does not delete primitive values', async () => {
  629. const { updateFacetValues } = await adminClient.query(gql`
  630. mutation {
  631. updateFacetValues(
  632. input: {
  633. id: "${facetValueId}"
  634. customFields: { singleId: "T_3" }
  635. }
  636. ) {
  637. id
  638. ${customFieldsSelection}
  639. }
  640. }
  641. `);
  642. expect(updateFacetValues[0].customFields.single).toEqual({ id: 'T_3' });
  643. expect(updateFacetValues[0].customFields.primitive).toBe('test');
  644. });
  645. });
  646. describe('GlobalSettings entity', () => {
  647. it('admin updateGlobalSettings', async () => {
  648. const { updateGlobalSettings } = await adminClient.query(gql`
  649. mutation {
  650. updateGlobalSettings(
  651. input: {
  652. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  653. }
  654. ) {
  655. ... on GlobalSettings {
  656. id
  657. ${customFieldsSelection}
  658. }
  659. }
  660. }
  661. `);
  662. assertCustomFieldIds(updateGlobalSettings.customFields, 'T_2', ['T_3', 'T_4']);
  663. });
  664. });
  665. describe('Order entity', () => {
  666. let orderId: string;
  667. beforeAll(async () => {
  668. const { addItemToOrder } = await shopClient.query(addItemToOrderDocument, {
  669. productVariantId: 'T_1',
  670. quantity: 1,
  671. });
  672. orderId = (addItemToOrder as any).id;
  673. });
  674. it('shop setOrderCustomFields', async () => {
  675. const { setOrderCustomFields } = await shopClient.query(gql`
  676. mutation {
  677. setOrderCustomFields(
  678. input: {
  679. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  680. }
  681. ) {
  682. ... on Order {
  683. id
  684. ${customFieldsSelection}
  685. }
  686. }
  687. }
  688. `);
  689. assertCustomFieldIds(setOrderCustomFields.customFields, 'T_2', ['T_3', 'T_4']);
  690. });
  691. it('admin setOrderCustomFields', async () => {
  692. const { setOrderCustomFields } = await adminClient.query(gql`
  693. mutation {
  694. setOrderCustomFields(
  695. input: {
  696. id: "${orderId}"
  697. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  698. }
  699. ) {
  700. ... on Order {
  701. id
  702. ${customFieldsSelection}
  703. }
  704. }
  705. }
  706. `);
  707. assertCustomFieldIds(setOrderCustomFields.customFields, 'T_1', ['T_1', 'T_2']);
  708. });
  709. // https://github.com/vendurehq/vendure/issues/1664#issuecomment-1320872627
  710. it('admin order query with eager-loaded custom field relation', async () => {
  711. const { order } = await adminClient.query(gql`
  712. query {
  713. order(id: 1) {
  714. id
  715. customFields {
  716. productOwner {
  717. id
  718. }
  719. }
  720. }
  721. }
  722. `);
  723. // we're just making sure it does not throw here.
  724. expect(order).toEqual({
  725. customFields: {
  726. productOwner: null,
  727. },
  728. id: 'T_1',
  729. });
  730. });
  731. });
  732. describe('OrderLine entity', () => {
  733. it('shop addItemToOrder', async () => {
  734. const { addItemToOrder } = await shopClient.query(
  735. gql`mutation {
  736. addItemToOrder(productVariantId: "T_1", quantity: 1, customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }) {
  737. ... on Order {
  738. id
  739. ${customFieldsSelection}
  740. }
  741. }
  742. }`,
  743. );
  744. assertCustomFieldIds(addItemToOrder.customFields, 'T_1', ['T_1', 'T_2']);
  745. });
  746. });
  747. describe('Product, ProductVariant entity', () => {
  748. let productId: string;
  749. it('admin createProduct', async () => {
  750. const { createProduct } = await adminClient.query(gql`
  751. mutation {
  752. createProduct(
  753. input: {
  754. translations: [{ languageCode: en, name: "test" slug: "test" description: "test" }]
  755. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  756. }
  757. ) {
  758. id
  759. ${customFieldsSelection}
  760. }
  761. }
  762. `);
  763. assertCustomFieldIds(createProduct.customFields, 'T_1', ['T_1', 'T_2']);
  764. productId = createProduct.id;
  765. });
  766. it('admin updateProduct', async () => {
  767. const { updateProduct } = await adminClient.query(gql`
  768. mutation {
  769. updateProduct(
  770. input: {
  771. id: "${productId}"
  772. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  773. }
  774. ) {
  775. id
  776. ${customFieldsSelection}
  777. }
  778. }
  779. `);
  780. assertCustomFieldIds(updateProduct.customFields, 'T_2', ['T_3', 'T_4']);
  781. });
  782. // https://github.com/vendurehq/vendure/issues/2840
  783. it('updating custom field relation on Product does not delete primitive values', async () => {
  784. const { updateProduct } = await adminClient.query(gql`
  785. mutation {
  786. updateProduct(
  787. input: {
  788. id: "${productId}"
  789. customFields: { singleId: "T_3" }
  790. }
  791. ) {
  792. id
  793. ${customFieldsSelection}
  794. }
  795. }
  796. `);
  797. expect(updateProduct.customFields.single).toEqual({ id: 'T_3' });
  798. expect(updateProduct.customFields.primitive).toBe('test');
  799. });
  800. let productVariantId: string;
  801. it('admin createProductVariant', async () => {
  802. const { createProductVariants } = await adminClient.query(gql`
  803. mutation {
  804. createProductVariants(
  805. input: [{
  806. sku: "TEST01"
  807. productId: "${productId}"
  808. translations: [{ languageCode: en, name: "test" }]
  809. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  810. }]
  811. ) {
  812. id
  813. ${customFieldsSelection}
  814. }
  815. }
  816. `);
  817. assertCustomFieldIds(createProductVariants[0].customFields, 'T_1', ['T_1', 'T_2']);
  818. productVariantId = createProductVariants[0].id;
  819. });
  820. it('admin updateProductVariant', async () => {
  821. const { updateProductVariants } = await adminClient.query(gql`
  822. mutation {
  823. updateProductVariants(
  824. input: [{
  825. id: "${productVariantId}"
  826. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  827. }]
  828. ) {
  829. id
  830. ${customFieldsSelection}
  831. }
  832. }
  833. `);
  834. assertCustomFieldIds(updateProductVariants[0].customFields, 'T_2', ['T_3', 'T_4']);
  835. });
  836. // https://github.com/vendurehq/vendure/issues/2840
  837. it('updating custom field relation on ProductVariant does not delete primitive values', async () => {
  838. const { updateProductVariants } = await adminClient.query(gql`
  839. mutation {
  840. updateProductVariants(
  841. input: [{
  842. id: "${productVariantId}"
  843. customFields: { singleId: "T_3" }
  844. }]
  845. ) {
  846. id
  847. ${customFieldsSelection}
  848. }
  849. }
  850. `);
  851. expect(updateProductVariants[0].customFields.single).toEqual({ id: 'T_3' });
  852. expect(updateProductVariants[0].customFields.primitive).toBe('test');
  853. });
  854. describe('issue 1664', () => {
  855. // https://github.com/vendurehq/vendure/issues/1664
  856. it('successfully gets product by id with eager-loading custom field relation', async () => {
  857. const { product } = await shopClient.query(gql`
  858. query {
  859. product(id: "T_1") {
  860. id
  861. customFields {
  862. cfVendor {
  863. featuredProduct {
  864. id
  865. }
  866. }
  867. }
  868. }
  869. }
  870. `);
  871. expect(product).toBeDefined();
  872. });
  873. // https://github.com/vendurehq/vendure/issues/1664
  874. it('successfully gets product by id with nested eager-loading custom field relation', async () => {
  875. const { customer } = await adminClient.query(gql`
  876. query {
  877. customer(id: "T_1") {
  878. id
  879. firstName
  880. lastName
  881. emailAddress
  882. phoneNumber
  883. user {
  884. customFields {
  885. cfVendor {
  886. id
  887. }
  888. }
  889. }
  890. }
  891. }
  892. `);
  893. expect(customer).toBeDefined();
  894. });
  895. // https://github.com/vendurehq/vendure/issues/1664
  896. it('successfully gets product.variants with nested custom field relation', async () => {
  897. await adminClient.query(gql`
  898. mutation {
  899. updateProductVariants(
  900. input: [{ id: "T_1", customFields: { cfRelatedProductsIds: ["T_2"] } }]
  901. ) {
  902. id
  903. }
  904. }
  905. `);
  906. const { product } = await adminClient.query(gql`
  907. query {
  908. product(id: "T_1") {
  909. variants {
  910. id
  911. customFields {
  912. cfRelatedProducts {
  913. featuredAsset {
  914. id
  915. }
  916. }
  917. }
  918. }
  919. }
  920. }
  921. `);
  922. expect(product).toBeDefined();
  923. expect(product.variants[0].customFields.cfRelatedProducts).toEqual([
  924. {
  925. featuredAsset: { id: 'T_2' },
  926. },
  927. ]);
  928. });
  929. it('successfully gets product by slug with eager-loading custom field relation', async () => {
  930. const { product } = await shopClient.query(gql`
  931. query {
  932. product(slug: "laptop") {
  933. id
  934. customFields {
  935. cfVendor {
  936. featuredProduct {
  937. id
  938. }
  939. }
  940. }
  941. }
  942. }
  943. `);
  944. expect(product).toBeDefined();
  945. });
  946. it('does not error on custom field relation with eager custom field relation', async () => {
  947. const { product } = await adminClient.query(gql`
  948. query {
  949. product(slug: "laptop") {
  950. name
  951. customFields {
  952. owner {
  953. id
  954. code
  955. customFields {
  956. profile {
  957. id
  958. name
  959. }
  960. }
  961. }
  962. }
  963. }
  964. }
  965. `);
  966. expect(product).toBeDefined();
  967. });
  968. });
  969. });
  970. describe('ProductOptionGroup, ProductOption entity', () => {
  971. let productOptionGroupId: string;
  972. it('admin createProductOptionGroup', async () => {
  973. const { createProductOptionGroup } = await adminClient.query(gql`
  974. mutation {
  975. createProductOptionGroup(
  976. input: {
  977. code: "test"
  978. options: []
  979. translations: [{ languageCode: en, name: "test" }]
  980. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  981. }
  982. ) {
  983. id
  984. ${customFieldsSelection}
  985. }
  986. }
  987. `);
  988. assertCustomFieldIds(createProductOptionGroup.customFields, 'T_1', ['T_1', 'T_2']);
  989. productOptionGroupId = createProductOptionGroup.id;
  990. });
  991. it('admin updateProductOptionGroup', async () => {
  992. const { updateProductOptionGroup } = await adminClient.query(gql`
  993. mutation {
  994. updateProductOptionGroup(
  995. input: {
  996. id: "${productOptionGroupId}"
  997. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  998. }
  999. ) {
  1000. id
  1001. ${customFieldsSelection}
  1002. }
  1003. }
  1004. `);
  1005. assertCustomFieldIds(updateProductOptionGroup.customFields, 'T_2', ['T_3', 'T_4']);
  1006. });
  1007. // https://github.com/vendurehq/vendure/issues/2840
  1008. it('updating custom field relation on ProductOptionGroup does not delete primitive values', async () => {
  1009. const { updateProductOptionGroup } = await adminClient.query(gql`
  1010. mutation {
  1011. updateProductOptionGroup(
  1012. input: {
  1013. id: "${productOptionGroupId}"
  1014. customFields: { singleId: "T_3" }
  1015. }
  1016. ) {
  1017. id
  1018. ${customFieldsSelection}
  1019. }
  1020. }
  1021. `);
  1022. expect(updateProductOptionGroup.customFields.single).toEqual({ id: 'T_3' });
  1023. expect(updateProductOptionGroup.customFields.primitive).toBe('test');
  1024. });
  1025. let productOptionId: string;
  1026. it('admin createProductOption', async () => {
  1027. const { createProductOption } = await adminClient.query(gql`
  1028. mutation {
  1029. createProductOption(
  1030. input: {
  1031. productOptionGroupId: "${productOptionGroupId}"
  1032. code: "test-option"
  1033. translations: [{ languageCode: en, name: "test-option" }]
  1034. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  1035. }
  1036. ) {
  1037. id
  1038. ${customFieldsSelection}
  1039. }
  1040. }
  1041. `);
  1042. assertCustomFieldIds(createProductOption.customFields, 'T_1', ['T_1', 'T_2']);
  1043. productOptionId = createProductOption.id;
  1044. });
  1045. it('admin updateProductOption', async () => {
  1046. const { updateProductOption } = await adminClient.query(gql`
  1047. mutation {
  1048. updateProductOption(
  1049. input: {
  1050. id: "${productOptionId}"
  1051. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  1052. }
  1053. ) {
  1054. id
  1055. ${customFieldsSelection}
  1056. }
  1057. }
  1058. `);
  1059. assertCustomFieldIds(updateProductOption.customFields, 'T_2', ['T_3', 'T_4']);
  1060. });
  1061. // https://github.com/vendurehq/vendure/issues/2840
  1062. it('updating custom field relation on ProductOption does not delete primitive values', async () => {
  1063. const { updateProductOption } = await adminClient.query(gql`
  1064. mutation {
  1065. updateProductOption(
  1066. input: {
  1067. id: "${productOptionId}"
  1068. customFields: { singleId: "T_3" }
  1069. }
  1070. ) {
  1071. id
  1072. ${customFieldsSelection}
  1073. }
  1074. }
  1075. `);
  1076. expect(updateProductOption.customFields.single).toEqual({ id: 'T_3' });
  1077. expect(updateProductOption.customFields.primitive).toBe('test');
  1078. });
  1079. });
  1080. describe('ShippingMethod entity', () => {
  1081. let shippingMethodId: string;
  1082. it('admin createShippingMethod', async () => {
  1083. const { createShippingMethod } = await adminClient.query(gql`
  1084. mutation {
  1085. createShippingMethod(
  1086. input: {
  1087. code: "test"
  1088. calculator: {
  1089. code: "${defaultShippingCalculator.code}"
  1090. arguments: [
  1091. { name: "rate" value: "10"},
  1092. { name: "includesTax" value: "true"},
  1093. { name: "taxRate" value: "10"},
  1094. ]
  1095. }
  1096. checker: {
  1097. code: "${defaultShippingEligibilityChecker.code}"
  1098. arguments: [
  1099. { name: "orderMinimum" value: "0"},
  1100. ]
  1101. }
  1102. fulfillmentHandler: "${manualFulfillmentHandler.code}"
  1103. translations: [{ languageCode: en, name: "test" description: "test" }]
  1104. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  1105. }
  1106. ) {
  1107. id
  1108. ${customFieldsSelection}
  1109. }
  1110. }
  1111. `);
  1112. assertCustomFieldIds(createShippingMethod.customFields, 'T_1', ['T_1', 'T_2']);
  1113. shippingMethodId = createShippingMethod.id;
  1114. });
  1115. it('admin updateShippingMethod', async () => {
  1116. const { updateShippingMethod } = await adminClient.query(gql`
  1117. mutation {
  1118. updateShippingMethod(
  1119. input: {
  1120. id: "${shippingMethodId}"
  1121. translations: []
  1122. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  1123. }
  1124. ) {
  1125. id
  1126. ${customFieldsSelection}
  1127. }
  1128. }
  1129. `);
  1130. assertCustomFieldIds(updateShippingMethod.customFields, 'T_2', ['T_3', 'T_4']);
  1131. });
  1132. // https://github.com/vendurehq/vendure/issues/2840
  1133. it('updating custom field relation on ShippingMethod does not delete primitive values', async () => {
  1134. const { updateShippingMethod } = await adminClient.query(gql`
  1135. mutation {
  1136. updateShippingMethod(
  1137. input: {
  1138. id: "${shippingMethodId}"
  1139. translations: []
  1140. customFields: { singleId: "T_3" }
  1141. }
  1142. ) {
  1143. id
  1144. ${customFieldsSelection}
  1145. }
  1146. }
  1147. `);
  1148. expect(updateShippingMethod.customFields.single).toEqual({ id: 'T_3' });
  1149. expect(updateShippingMethod.customFields.primitive).toBe('test');
  1150. });
  1151. it('shop eligibleShippingMethods (ShippingMethodQuote)', async () => {
  1152. const { eligibleShippingMethods } = await shopClient.query(gql`
  1153. query {
  1154. eligibleShippingMethods {
  1155. id
  1156. name
  1157. code
  1158. description
  1159. ${customFieldsSelection}
  1160. }
  1161. }
  1162. `);
  1163. const testShippingMethodQuote = eligibleShippingMethods.find(
  1164. (quote: any) => quote.code === 'test',
  1165. );
  1166. assertCustomFieldIds(testShippingMethodQuote.customFields, 'T_3', ['T_3', 'T_4']);
  1167. });
  1168. });
  1169. describe('PaymentMethod entity', () => {
  1170. let paymentMethodId: string;
  1171. it('admin createShippingMethod', async () => {
  1172. const { createPaymentMethod } = await adminClient.query(gql`
  1173. mutation {
  1174. createPaymentMethod(
  1175. input: {
  1176. code: "test"
  1177. enabled: true
  1178. handler: {
  1179. code: "${testSuccessfulPaymentMethod.code}"
  1180. arguments: []
  1181. }
  1182. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] },
  1183. translations: [{ languageCode: en, name: "test" }]
  1184. }
  1185. ) {
  1186. id
  1187. ${customFieldsSelection}
  1188. }
  1189. }
  1190. `);
  1191. assertCustomFieldIds(createPaymentMethod.customFields, 'T_1', ['T_1', 'T_2']);
  1192. paymentMethodId = createPaymentMethod.id;
  1193. });
  1194. it('admin updatePaymentMethod', async () => {
  1195. const { updatePaymentMethod } = await adminClient.query(gql`
  1196. mutation {
  1197. updatePaymentMethod(
  1198. input: {
  1199. id: "${paymentMethodId}"
  1200. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] },
  1201. }
  1202. ) {
  1203. id
  1204. ${customFieldsSelection}
  1205. }
  1206. }
  1207. `);
  1208. assertCustomFieldIds(updatePaymentMethod.customFields, 'T_2', ['T_3', 'T_4']);
  1209. });
  1210. // https://github.com/vendurehq/vendure/issues/2840
  1211. it('updating custom field relation on PaymentMethod does not delete primitive values', async () => {
  1212. const { updatePaymentMethod } = await adminClient.query(gql`
  1213. mutation {
  1214. updatePaymentMethod(
  1215. input: {
  1216. id: "${paymentMethodId}"
  1217. customFields: { singleId: "T_3" }
  1218. }
  1219. ) {
  1220. id
  1221. ${customFieldsSelection}
  1222. }
  1223. }
  1224. `);
  1225. expect(updatePaymentMethod.customFields.single).toEqual({ id: 'T_3' });
  1226. expect(updatePaymentMethod.customFields.primitive).toBe('test');
  1227. });
  1228. it('shop eligiblePaymentMethods (PaymentMethodQuote)', async () => {
  1229. const { eligiblePaymentMethods } = await shopClient.query(gql`
  1230. query {
  1231. eligiblePaymentMethods {
  1232. id
  1233. name
  1234. description
  1235. ${customFieldsSelection}
  1236. }
  1237. }
  1238. `);
  1239. assertCustomFieldIds(eligiblePaymentMethods[0].customFields, 'T_3', ['T_3', 'T_4']);
  1240. });
  1241. });
  1242. describe('Asset entity', () => {
  1243. it('set custom field relations on Asset', async () => {
  1244. const { updateAsset } = await adminClient.query(gql`
  1245. mutation {
  1246. updateAsset(
  1247. input: {
  1248. id: "T_1",
  1249. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  1250. }
  1251. ) {
  1252. id
  1253. ${customFieldsSelection}
  1254. }
  1255. }
  1256. `);
  1257. assertCustomFieldIds(updateAsset.customFields, 'T_2', ['T_3', 'T_4']);
  1258. });
  1259. it('findOne on Asset', async () => {
  1260. const { asset } = await adminClient.query(gql`
  1261. query {
  1262. asset(id: "T_1") {
  1263. id
  1264. ${customFieldsSelection}
  1265. }
  1266. }
  1267. `);
  1268. expect(asset.customFields.single.id).toBe('T_2');
  1269. expect(asset.customFields.multi.length).toEqual(2);
  1270. });
  1271. // https://github.com/vendurehq/vendure/issues/1636
  1272. it('calling TransactionalConnection.findOneInChannel() returns custom field relations', async () => {
  1273. TestPlugin1636_1664.testResolverSpy.mockReset();
  1274. await shopClient.query(gql`
  1275. query {
  1276. getAssetTest(id: "T_1")
  1277. }
  1278. `);
  1279. const args = TestPlugin1636_1664.testResolverSpy.mock.calls[0];
  1280. expect(args[0].customFields.single.id).toEqual(2);
  1281. expect(args[0].customFields.multi.length).toEqual(2);
  1282. });
  1283. });
  1284. });
  1285. it('null values', async () => {
  1286. const { updateCustomerAddress } = await adminClient.query(gql`
  1287. mutation {
  1288. updateCustomerAddress(
  1289. input: { id: "T_1", customFields: { singleId: null, multiIds: ["T_1", "null"] } }
  1290. ) {
  1291. id
  1292. customFields {
  1293. single {
  1294. id
  1295. }
  1296. multi {
  1297. id
  1298. }
  1299. }
  1300. }
  1301. }
  1302. `);
  1303. expect(updateCustomerAddress.customFields.single).toEqual(null);
  1304. expect(updateCustomerAddress.customFields.multi).toEqual([{ id: 'T_1' }]);
  1305. });
  1306. describe('bi-direction relations', () => {
  1307. let customEntityRepository: Repository<TestCustomEntity>;
  1308. let customEntity: TestCustomEntity;
  1309. let collectionIdInternal: number;
  1310. beforeAll(async () => {
  1311. customEntityRepository = server.app
  1312. .get(TransactionalConnection)
  1313. .getRepository(RequestContext.empty(), TestCustomEntity);
  1314. const customEntityId = (await customEntityRepository.save({})).id;
  1315. const { createCollection } = await adminClient.query(gql`
  1316. mutation {
  1317. createCollection(
  1318. input: {
  1319. translations: [
  1320. { languageCode: en, name: "Test", description: "test", slug: "test" }
  1321. ]
  1322. filters: []
  1323. customFields: { customEntityListIds: [${customEntityId}] customEntityId: ${customEntityId} }
  1324. }
  1325. ) {
  1326. id
  1327. }
  1328. }
  1329. `);
  1330. collectionIdInternal = parseInt(createCollection.id.replace('T_', ''), 10);
  1331. customEntity = await assertFound(
  1332. customEntityRepository.findOne({
  1333. where: { id: customEntityId },
  1334. relations: {
  1335. customEntityInverse: true,
  1336. customEntityListInverse: true,
  1337. },
  1338. }),
  1339. );
  1340. });
  1341. it('can create inverse relation for list=false', () => {
  1342. expect(customEntity.customEntityInverse).toEqual([
  1343. expect.objectContaining({ id: collectionIdInternal }),
  1344. ]);
  1345. });
  1346. it('can create inverse relation for list=true', () => {
  1347. expect(customEntity.customEntityListInverse).toEqual([
  1348. expect.objectContaining({ id: collectionIdInternal }),
  1349. ]);
  1350. });
  1351. });
  1352. });