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

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