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

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181
  1. import {
  2. Asset,
  3. Collection,
  4. Country,
  5. CustomFields,
  6. DefaultLogger,
  7. defaultShippingCalculator,
  8. defaultShippingEligibilityChecker,
  9. Facet,
  10. FacetValue,
  11. LogLevel,
  12. manualFulfillmentHandler,
  13. mergeConfig,
  14. Product,
  15. ProductOption,
  16. ProductOptionGroup,
  17. ProductVariant,
  18. ShippingMethod,
  19. } from '@vendure/core';
  20. import { createTestEnvironment } from '@vendure/testing';
  21. import gql from 'graphql-tag';
  22. import path from 'path';
  23. import { initialData } from '../../../e2e-common/e2e-initial-data';
  24. import { testConfig, TEST_SETUP_TIMEOUT_MS } from '../../../e2e-common/test-config';
  25. import { testSuccessfulPaymentMethod } from './fixtures/test-payment-methods';
  26. import { TestPlugin1636_1664 } from './fixtures/test-plugins/issue-1636-1664/issue-1636-1664-plugin';
  27. import { AddItemToOrder } from './graphql/generated-e2e-shop-types';
  28. import { ADD_ITEM_TO_ORDER } from './graphql/shop-definitions';
  29. import { sortById } from './utils/test-order-utils';
  30. // From https://github.com/microsoft/TypeScript/issues/13298#issuecomment-654906323
  31. // to ensure that we _always_ test all entities which support custom fields
  32. type ValueOf<T> = T[keyof T];
  33. type NonEmptyArray<T> = [T, ...T[]];
  34. type MustInclude<T, U extends T[]> = [T] extends [ValueOf<U>] ? U : never;
  35. const enumerate =
  36. <T>() =>
  37. <U extends NonEmptyArray<T>>(...elements: MustInclude<T, U>) =>
  38. elements;
  39. const entitiesWithCustomFields = enumerate<keyof CustomFields>()(
  40. 'Address',
  41. 'Administrator',
  42. 'Asset',
  43. 'Channel',
  44. 'Collection',
  45. 'Country',
  46. 'Customer',
  47. 'CustomerGroup',
  48. 'Facet',
  49. 'FacetValue',
  50. 'Fulfillment',
  51. 'GlobalSettings',
  52. 'Order',
  53. 'OrderLine',
  54. 'PaymentMethod',
  55. 'Product',
  56. 'ProductOption',
  57. 'ProductOptionGroup',
  58. 'ProductVariant',
  59. 'Promotion',
  60. 'ShippingMethod',
  61. 'TaxCategory',
  62. 'TaxRate',
  63. 'User',
  64. 'Zone',
  65. );
  66. const customFieldConfig: CustomFields = {};
  67. for (const entity of entitiesWithCustomFields) {
  68. customFieldConfig[entity] = [
  69. { name: 'single', type: 'relation', entity: Asset, graphQLType: 'Asset', list: false },
  70. { name: 'multi', type: 'relation', entity: Asset, graphQLType: 'Asset', list: true },
  71. ];
  72. }
  73. customFieldConfig.Product?.push(
  74. { name: 'cfCollection', type: 'relation', entity: Collection, list: false },
  75. { name: 'cfCountry', type: 'relation', entity: Country, list: false },
  76. { name: 'cfFacetValue', type: 'relation', entity: FacetValue, list: false },
  77. { name: 'cfFacet', type: 'relation', entity: Facet, list: false },
  78. { name: 'cfProductOptionGroup', type: 'relation', entity: ProductOptionGroup, list: false },
  79. { name: 'cfProductOption', type: 'relation', entity: ProductOption, list: false },
  80. { name: 'cfProductVariant', type: 'relation', entity: ProductVariant, list: false },
  81. { name: 'cfProduct', type: 'relation', entity: Product, list: false },
  82. { name: 'cfShippingMethod', type: 'relation', entity: ShippingMethod, list: false },
  83. { name: 'cfInternalAsset', type: 'relation', entity: Asset, list: false, internal: true },
  84. );
  85. customFieldConfig.ProductVariant?.push({
  86. name: 'cfRelatedProducts',
  87. type: 'relation',
  88. entity: Product,
  89. list: true,
  90. internal: false,
  91. public: true,
  92. });
  93. const customConfig = mergeConfig(testConfig(), {
  94. paymentOptions: {
  95. paymentMethodHandlers: [testSuccessfulPaymentMethod],
  96. },
  97. // logger: new DefaultLogger({ level: LogLevel.Debug }),
  98. dbConnectionOptions: {
  99. timezone: 'Z',
  100. },
  101. customFields: customFieldConfig,
  102. plugins: [TestPlugin1636_1664],
  103. });
  104. describe('Custom field relations', () => {
  105. const { server, adminClient, shopClient } = createTestEnvironment(customConfig);
  106. beforeAll(async () => {
  107. await server.init({
  108. initialData,
  109. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-full.csv'),
  110. customerCount: 3,
  111. });
  112. await adminClient.asSuperAdmin();
  113. }, TEST_SETUP_TIMEOUT_MS);
  114. afterAll(async () => {
  115. await server.destroy();
  116. });
  117. it('customFieldConfig query returns entity and scalar fields', async () => {
  118. const { globalSettings } = await adminClient.query(gql`
  119. query {
  120. globalSettings {
  121. serverConfig {
  122. customFieldConfig {
  123. Customer {
  124. ... on RelationCustomFieldConfig {
  125. name
  126. entity
  127. scalarFields
  128. }
  129. }
  130. }
  131. }
  132. }
  133. }
  134. `);
  135. const single = globalSettings.serverConfig.customFieldConfig.Customer[0];
  136. expect(single.entity).toBe('Asset');
  137. expect(single.scalarFields).toEqual([
  138. 'id',
  139. 'createdAt',
  140. 'updatedAt',
  141. 'name',
  142. 'type',
  143. 'fileSize',
  144. 'mimeType',
  145. 'width',
  146. 'height',
  147. 'source',
  148. 'preview',
  149. ]);
  150. });
  151. describe('special data resolution', () => {
  152. let productId: string;
  153. const productCustomFieldRelationsSelection = `
  154. id
  155. customFields {
  156. cfCollection {
  157. languageCode
  158. name
  159. }
  160. cfCountry {
  161. languageCode
  162. name
  163. }
  164. cfFacetValue {
  165. languageCode
  166. name
  167. }
  168. cfFacet {
  169. languageCode
  170. name
  171. }
  172. cfProductOptionGroup {
  173. languageCode
  174. name
  175. }
  176. cfProductOption {
  177. languageCode
  178. name
  179. }
  180. cfProductVariant {
  181. languageCode
  182. name
  183. }
  184. cfProduct {
  185. languageCode
  186. name
  187. }
  188. cfShippingMethod {
  189. languageCode
  190. name
  191. }
  192. }`;
  193. function assertTranslatableCustomFieldValues(product: { customFields: any }) {
  194. expect(product.customFields.cfCollection).toEqual({
  195. languageCode: 'en',
  196. name: '__root_collection__',
  197. });
  198. expect(product.customFields.cfCountry).toEqual({ languageCode: 'en', name: 'Australia' });
  199. expect(product.customFields.cfFacetValue).toEqual({
  200. languageCode: 'en',
  201. name: 'electronics',
  202. });
  203. expect(product.customFields.cfFacet).toEqual({ languageCode: 'en', name: 'category' });
  204. expect(product.customFields.cfProductOptionGroup).toEqual({
  205. languageCode: 'en',
  206. name: 'screen size',
  207. });
  208. expect(product.customFields.cfProductOption).toEqual({
  209. languageCode: 'en',
  210. name: '13 inch',
  211. });
  212. expect(product.customFields.cfProductVariant).toEqual({
  213. languageCode: 'en',
  214. name: 'Laptop 13 inch 8GB',
  215. });
  216. expect(product.customFields.cfProduct).toEqual({ languageCode: 'en', name: 'Laptop' });
  217. expect(product.customFields.cfShippingMethod).toEqual({
  218. languageCode: 'en',
  219. name: 'Standard Shipping',
  220. });
  221. }
  222. it('translatable entities get translated', async () => {
  223. const { createProduct } = await adminClient.query(gql`
  224. mutation {
  225. createProduct(
  226. input: {
  227. translations: [
  228. {
  229. languageCode: en
  230. name: "Test product"
  231. description: ""
  232. slug: "test-product"
  233. }
  234. ]
  235. customFields: {
  236. cfCollectionId: "T_1"
  237. cfCountryId: "T_1"
  238. cfFacetValueId: "T_1"
  239. cfFacetId: "T_1"
  240. cfProductOptionGroupId: "T_1"
  241. cfProductOptionId: "T_1"
  242. cfProductVariantId: "T_1"
  243. cfProductId: "T_1"
  244. cfShippingMethodId: "T_1"
  245. }
  246. }
  247. ) { ${productCustomFieldRelationsSelection} }
  248. }
  249. `);
  250. productId = createProduct.id;
  251. assertTranslatableCustomFieldValues(createProduct);
  252. });
  253. it('translatable entities get translated on findOneInChannel', async () => {
  254. const { product } = await adminClient.query(gql`
  255. query {
  256. product(id: "${productId}") { ${productCustomFieldRelationsSelection} }
  257. }
  258. `);
  259. assertTranslatableCustomFieldValues(product);
  260. });
  261. it('ProductVariant prices get resolved', async () => {
  262. const { product } = await adminClient.query(gql`
  263. query {
  264. product(id: "${productId}") {
  265. id
  266. customFields {
  267. cfProductVariant {
  268. price
  269. currencyCode
  270. priceWithTax
  271. }
  272. }
  273. }
  274. }`);
  275. expect(product.customFields.cfProductVariant).toEqual({
  276. price: 129900,
  277. currencyCode: 'USD',
  278. priceWithTax: 155880,
  279. });
  280. });
  281. });
  282. describe('entity-specific implementation', () => {
  283. function assertCustomFieldIds(customFields: any, single: string, multi: string[]) {
  284. expect(customFields.single).toEqual({ id: single });
  285. expect(customFields.multi.sort(sortById)).toEqual(multi.map(id => ({ id })));
  286. }
  287. const customFieldsSelection = `
  288. customFields {
  289. single {
  290. id
  291. }
  292. multi {
  293. id
  294. }
  295. }`;
  296. describe('Address entity', () => {
  297. it('admin createCustomerAddress', async () => {
  298. const { createCustomerAddress } = await adminClient.query(gql`
  299. mutation {
  300. createCustomerAddress(
  301. customerId: "T_1"
  302. input: {
  303. countryCode: "GB"
  304. streetLine1: "Test Street"
  305. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  306. }
  307. ) {
  308. id
  309. ${customFieldsSelection}
  310. }
  311. }
  312. `);
  313. assertCustomFieldIds(createCustomerAddress.customFields, 'T_1', ['T_1', 'T_2']);
  314. });
  315. it('shop createCustomerAddress', async () => {
  316. await shopClient.asUserWithCredentials('hayden.zieme12@hotmail.com', 'test');
  317. const { createCustomerAddress } = await shopClient.query(gql`
  318. mutation {
  319. createCustomerAddress(
  320. input: {
  321. countryCode: "GB"
  322. streetLine1: "Test Street"
  323. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  324. }
  325. ) {
  326. id
  327. ${customFieldsSelection}
  328. }
  329. }
  330. `);
  331. assertCustomFieldIds(createCustomerAddress.customFields, 'T_1', ['T_1', 'T_2']);
  332. });
  333. it('admin updateCustomerAddress', async () => {
  334. const { updateCustomerAddress } = await adminClient.query(gql`
  335. mutation {
  336. updateCustomerAddress(
  337. input: { id: "T_1", customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] } }
  338. ) {
  339. id
  340. ${customFieldsSelection}
  341. }
  342. }
  343. `);
  344. assertCustomFieldIds(updateCustomerAddress.customFields, 'T_2', ['T_3', 'T_4']);
  345. });
  346. it('shop updateCustomerAddress', async () => {
  347. const { updateCustomerAddress } = await shopClient.query(gql`
  348. mutation {
  349. updateCustomerAddress(
  350. input: { id: "T_1", customFields: { singleId: "T_3", multiIds: ["T_4", "T_2"] } }
  351. ) {
  352. id
  353. ${customFieldsSelection}
  354. }
  355. }
  356. `);
  357. assertCustomFieldIds(updateCustomerAddress.customFields, 'T_3', ['T_2', 'T_4']);
  358. });
  359. });
  360. describe('Collection entity', () => {
  361. let collectionId: string;
  362. it('admin createCollection', async () => {
  363. const { createCollection } = await adminClient.query(gql`
  364. mutation {
  365. createCollection(
  366. input: {
  367. translations: [
  368. { languageCode: en, name: "Test", description: "test", slug: "test" }
  369. ]
  370. filters: []
  371. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  372. }
  373. ) {
  374. id
  375. ${customFieldsSelection}
  376. }
  377. }
  378. `);
  379. assertCustomFieldIds(createCollection.customFields, 'T_1', ['T_1', 'T_2']);
  380. collectionId = createCollection.id;
  381. });
  382. it('admin updateCollection', async () => {
  383. const { updateCollection } = await adminClient.query(gql`
  384. mutation {
  385. updateCollection(
  386. input: {
  387. id: "${collectionId}"
  388. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  389. }
  390. ) {
  391. id
  392. ${customFieldsSelection}
  393. }
  394. }
  395. `);
  396. assertCustomFieldIds(updateCollection.customFields, 'T_2', ['T_3', 'T_4']);
  397. });
  398. });
  399. describe('Customer entity', () => {
  400. let customerId: string;
  401. it('admin createCustomer', async () => {
  402. const { createCustomer } = await adminClient.query(gql`
  403. mutation {
  404. createCustomer(
  405. input: {
  406. emailAddress: "test@test.com"
  407. firstName: "Test"
  408. lastName: "Person"
  409. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  410. }
  411. ) {
  412. ... on Customer {
  413. id
  414. ${customFieldsSelection}
  415. }
  416. }
  417. }
  418. `);
  419. assertCustomFieldIds(createCustomer.customFields, 'T_1', ['T_1', 'T_2']);
  420. customerId = createCustomer.id;
  421. });
  422. it('admin updateCustomer', async () => {
  423. const { updateCustomer } = await adminClient.query(gql`
  424. mutation {
  425. updateCustomer(
  426. input: {
  427. id: "${customerId}"
  428. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  429. }
  430. ) {
  431. ...on Customer {
  432. id
  433. ${customFieldsSelection}
  434. }
  435. }
  436. }
  437. `);
  438. assertCustomFieldIds(updateCustomer.customFields, 'T_2', ['T_3', 'T_4']);
  439. });
  440. it('shop updateCustomer', async () => {
  441. const { updateCustomer } = await shopClient.query(gql`
  442. mutation {
  443. updateCustomer(input: { customFields: { singleId: "T_4", multiIds: ["T_2", "T_4"] } }) {
  444. id
  445. ${customFieldsSelection}
  446. }
  447. }
  448. `);
  449. assertCustomFieldIds(updateCustomer.customFields, 'T_4', ['T_2', 'T_4']);
  450. });
  451. });
  452. describe('Facet entity', () => {
  453. let facetId: string;
  454. it('admin createFacet', async () => {
  455. const { createFacet } = await adminClient.query(gql`
  456. mutation {
  457. createFacet(
  458. input: {
  459. code: "test"
  460. isPrivate: false
  461. translations: [{ languageCode: en, name: "test" }]
  462. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  463. }
  464. ) {
  465. id
  466. ${customFieldsSelection}
  467. }
  468. }
  469. `);
  470. assertCustomFieldIds(createFacet.customFields, 'T_1', ['T_1', 'T_2']);
  471. facetId = createFacet.id;
  472. });
  473. it('admin updateFacet', async () => {
  474. const { updateFacet } = await adminClient.query(gql`
  475. mutation {
  476. updateFacet(
  477. input: {
  478. id: "${facetId}"
  479. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  480. }
  481. ) {
  482. id
  483. ${customFieldsSelection}
  484. }
  485. }
  486. `);
  487. assertCustomFieldIds(updateFacet.customFields, 'T_2', ['T_3', 'T_4']);
  488. });
  489. });
  490. describe('FacetValue entity', () => {
  491. let facetValueId: string;
  492. it('admin createFacetValues', async () => {
  493. const { createFacetValues } = await adminClient.query(gql`
  494. mutation {
  495. createFacetValues(
  496. input: {
  497. code: "test"
  498. facetId: "T_1"
  499. translations: [{ languageCode: en, name: "test" }]
  500. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  501. }
  502. ) {
  503. id
  504. ${customFieldsSelection}
  505. }
  506. }
  507. `);
  508. assertCustomFieldIds(createFacetValues[0].customFields, 'T_1', ['T_1', 'T_2']);
  509. facetValueId = createFacetValues[0].id;
  510. });
  511. it('admin updateFacetValues', async () => {
  512. const { updateFacetValues } = await adminClient.query(gql`
  513. mutation {
  514. updateFacetValues(
  515. input: {
  516. id: "${facetValueId}"
  517. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  518. }
  519. ) {
  520. id
  521. ${customFieldsSelection}
  522. }
  523. }
  524. `);
  525. assertCustomFieldIds(updateFacetValues[0].customFields, 'T_2', ['T_3', 'T_4']);
  526. });
  527. });
  528. describe('Fulfillment entity', () => {
  529. // Currently no GraphQL API to set customFields on fulfillments
  530. });
  531. describe('GlobalSettings entity', () => {
  532. it('admin updateGlobalSettings', async () => {
  533. const { updateGlobalSettings } = await adminClient.query(gql`
  534. mutation {
  535. updateGlobalSettings(
  536. input: {
  537. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  538. }
  539. ) {
  540. ... on GlobalSettings {
  541. id
  542. ${customFieldsSelection}
  543. }
  544. }
  545. }
  546. `);
  547. assertCustomFieldIds(updateGlobalSettings.customFields, 'T_2', ['T_3', 'T_4']);
  548. });
  549. });
  550. describe('Order entity', () => {
  551. let orderId: string;
  552. beforeAll(async () => {
  553. const { addItemToOrder } = await shopClient.query<any, AddItemToOrder.Variables>(
  554. ADD_ITEM_TO_ORDER,
  555. {
  556. productVariantId: 'T_1',
  557. quantity: 1,
  558. },
  559. );
  560. orderId = addItemToOrder.id;
  561. });
  562. it('shop setOrderCustomFields', async () => {
  563. const { setOrderCustomFields } = await shopClient.query(gql`
  564. mutation {
  565. setOrderCustomFields(
  566. input: {
  567. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  568. }
  569. ) {
  570. ... on Order {
  571. id
  572. ${customFieldsSelection}
  573. }
  574. }
  575. }
  576. `);
  577. assertCustomFieldIds(setOrderCustomFields.customFields, 'T_2', ['T_3', 'T_4']);
  578. });
  579. it('admin setOrderCustomFields', async () => {
  580. const { setOrderCustomFields } = await adminClient.query(gql`
  581. mutation {
  582. setOrderCustomFields(
  583. input: {
  584. id: "${orderId}"
  585. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  586. }
  587. ) {
  588. ... on Order {
  589. id
  590. ${customFieldsSelection}
  591. }
  592. }
  593. }
  594. `);
  595. assertCustomFieldIds(setOrderCustomFields.customFields, 'T_1', ['T_1', 'T_2']);
  596. });
  597. // https://github.com/vendure-ecommerce/vendure/issues/1664#issuecomment-1320872627
  598. it('admin order query with eager-loaded custom field relation', async () => {
  599. const { order } = await adminClient.query(gql`
  600. query {
  601. order(id: 1) {
  602. id
  603. customFields {
  604. productOwner {
  605. id
  606. }
  607. }
  608. }
  609. }
  610. `);
  611. // we're just making sure it does not throw here.
  612. expect(order).toEqual({
  613. customFields: {
  614. productOwner: null,
  615. },
  616. id: 'T_1',
  617. });
  618. });
  619. });
  620. describe('OrderLine entity', () => {
  621. it('shop addItemToOrder', async () => {
  622. const { addItemToOrder } = await shopClient.query(
  623. gql`mutation {
  624. addItemToOrder(productVariantId: "T_1", quantity: 1, customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }) {
  625. ... on Order {
  626. id
  627. ${customFieldsSelection}
  628. }
  629. }
  630. }`,
  631. );
  632. assertCustomFieldIds(addItemToOrder.customFields, 'T_1', ['T_1', 'T_2']);
  633. });
  634. });
  635. describe('Product, ProductVariant entity', () => {
  636. let productId: string;
  637. it('admin createProduct', async () => {
  638. const { createProduct } = await adminClient.query(gql`
  639. mutation {
  640. createProduct(
  641. input: {
  642. translations: [{ languageCode: en, name: "test" slug: "test" description: "test" }]
  643. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  644. }
  645. ) {
  646. id
  647. ${customFieldsSelection}
  648. }
  649. }
  650. `);
  651. assertCustomFieldIds(createProduct.customFields, 'T_1', ['T_1', 'T_2']);
  652. productId = createProduct.id;
  653. });
  654. it('admin updateProduct', async () => {
  655. const { updateProduct } = await adminClient.query(gql`
  656. mutation {
  657. updateProduct(
  658. input: {
  659. id: "${productId}"
  660. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  661. }
  662. ) {
  663. id
  664. ${customFieldsSelection}
  665. }
  666. }
  667. `);
  668. assertCustomFieldIds(updateProduct.customFields, 'T_2', ['T_3', 'T_4']);
  669. });
  670. let productVariantId: string;
  671. it('admin createProductVariant', async () => {
  672. const { createProductVariants } = await adminClient.query(gql`
  673. mutation {
  674. createProductVariants(
  675. input: [{
  676. sku: "TEST01"
  677. productId: "${productId}"
  678. translations: [{ languageCode: en, name: "test" }]
  679. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  680. }]
  681. ) {
  682. id
  683. ${customFieldsSelection}
  684. }
  685. }
  686. `);
  687. assertCustomFieldIds(createProductVariants[0].customFields, 'T_1', ['T_1', 'T_2']);
  688. productVariantId = createProductVariants[0].id;
  689. });
  690. it('admin updateProductVariant', async () => {
  691. const { updateProductVariants } = await adminClient.query(gql`
  692. mutation {
  693. updateProductVariants(
  694. input: [{
  695. id: "${productVariantId}"
  696. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  697. }]
  698. ) {
  699. id
  700. ${customFieldsSelection}
  701. }
  702. }
  703. `);
  704. assertCustomFieldIds(updateProductVariants[0].customFields, 'T_2', ['T_3', 'T_4']);
  705. });
  706. describe('issue 1664', () => {
  707. // https://github.com/vendure-ecommerce/vendure/issues/1664
  708. it('successfully gets product by id with eager-loading custom field relation', async () => {
  709. const { product } = await shopClient.query(gql`
  710. query {
  711. product(id: "T_1") {
  712. id
  713. customFields {
  714. cfVendor {
  715. featuredProduct {
  716. id
  717. }
  718. }
  719. }
  720. }
  721. }
  722. `);
  723. expect(product).toBeDefined();
  724. });
  725. // https://github.com/vendure-ecommerce/vendure/issues/1664
  726. it('successfully gets product by id with nested eager-loading custom field relation', async () => {
  727. const { customer } = await adminClient.query(gql`
  728. query {
  729. customer(id: "T_1") {
  730. id
  731. firstName
  732. lastName
  733. emailAddress
  734. phoneNumber
  735. user {
  736. customFields {
  737. cfVendor {
  738. id
  739. }
  740. }
  741. }
  742. }
  743. }
  744. `);
  745. expect(customer).toBeDefined();
  746. });
  747. // https://github.com/vendure-ecommerce/vendure/issues/1664
  748. it('successfully gets product.variants with nested custom field relation', async () => {
  749. await adminClient.query(gql`
  750. mutation {
  751. updateProductVariants(
  752. input: [{ id: "T_1", customFields: { cfRelatedProductsIds: ["T_2"] } }]
  753. ) {
  754. id
  755. }
  756. }
  757. `);
  758. const { product } = await adminClient.query(gql`
  759. query {
  760. product(id: "T_1") {
  761. variants {
  762. id
  763. customFields {
  764. cfRelatedProducts {
  765. featuredAsset {
  766. id
  767. }
  768. }
  769. }
  770. }
  771. }
  772. }
  773. `);
  774. expect(product).toBeDefined();
  775. expect(product.variants[0].customFields.cfRelatedProducts).toEqual([
  776. {
  777. featuredAsset: { id: 'T_2' },
  778. },
  779. ]);
  780. });
  781. it('successfully gets product by slug with eager-loading custom field relation', async () => {
  782. const { product } = await shopClient.query(gql`
  783. query {
  784. product(slug: "laptop") {
  785. id
  786. customFields {
  787. cfVendor {
  788. featuredProduct {
  789. id
  790. }
  791. }
  792. }
  793. }
  794. }
  795. `);
  796. expect(product).toBeDefined();
  797. });
  798. it('does not error on custom field relation with eager custom field relation', async () => {
  799. const { product } = await adminClient.query(gql`
  800. query {
  801. product(slug: "laptop") {
  802. name
  803. customFields {
  804. owner {
  805. id
  806. code
  807. customFields {
  808. profile {
  809. id
  810. name
  811. }
  812. }
  813. }
  814. }
  815. }
  816. }
  817. `);
  818. expect(product).toBeDefined();
  819. });
  820. });
  821. });
  822. describe('ProductOptionGroup, ProductOption entity', () => {
  823. let productOptionGroupId: string;
  824. it('admin createProductOptionGroup', async () => {
  825. const { createProductOptionGroup } = await adminClient.query(gql`
  826. mutation {
  827. createProductOptionGroup(
  828. input: {
  829. code: "test"
  830. options: []
  831. translations: [{ languageCode: en, name: "test" }]
  832. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  833. }
  834. ) {
  835. id
  836. ${customFieldsSelection}
  837. }
  838. }
  839. `);
  840. assertCustomFieldIds(createProductOptionGroup.customFields, 'T_1', ['T_1', 'T_2']);
  841. productOptionGroupId = createProductOptionGroup.id;
  842. });
  843. it('admin updateProductOptionGroup', async () => {
  844. const { updateProductOptionGroup } = await adminClient.query(gql`
  845. mutation {
  846. updateProductOptionGroup(
  847. input: {
  848. id: "${productOptionGroupId}"
  849. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  850. }
  851. ) {
  852. id
  853. ${customFieldsSelection}
  854. }
  855. }
  856. `);
  857. assertCustomFieldIds(updateProductOptionGroup.customFields, 'T_2', ['T_3', 'T_4']);
  858. });
  859. let productOptionId: string;
  860. it('admin createProductOption', async () => {
  861. const { createProductOption } = await adminClient.query(gql`
  862. mutation {
  863. createProductOption(
  864. input: {
  865. productOptionGroupId: "${productOptionGroupId}"
  866. code: "test-option"
  867. translations: [{ languageCode: en, name: "test-option" }]
  868. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  869. }
  870. ) {
  871. id
  872. ${customFieldsSelection}
  873. }
  874. }
  875. `);
  876. assertCustomFieldIds(createProductOption.customFields, 'T_1', ['T_1', 'T_2']);
  877. productOptionId = createProductOption.id;
  878. });
  879. it('admin updateProductOption', async () => {
  880. const { updateProductOption } = await adminClient.query(gql`
  881. mutation {
  882. updateProductOption(
  883. input: {
  884. id: "${productOptionId}"
  885. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  886. }
  887. ) {
  888. id
  889. ${customFieldsSelection}
  890. }
  891. }
  892. `);
  893. assertCustomFieldIds(updateProductOption.customFields, 'T_2', ['T_3', 'T_4']);
  894. });
  895. });
  896. describe('User entity', () => {
  897. // Currently no GraphQL API to set User custom fields
  898. });
  899. describe('ShippingMethod entity', () => {
  900. let shippingMethodId: string;
  901. it('admin createShippingMethod', async () => {
  902. const { createShippingMethod } = await adminClient.query(gql`
  903. mutation {
  904. createShippingMethod(
  905. input: {
  906. code: "test"
  907. calculator: {
  908. code: "${defaultShippingCalculator.code}"
  909. arguments: [
  910. { name: "rate" value: "10"},
  911. { name: "includesTax" value: "true"},
  912. { name: "taxRate" value: "10"},
  913. ]
  914. }
  915. checker: {
  916. code: "${defaultShippingEligibilityChecker.code}"
  917. arguments: [
  918. { name: "orderMinimum" value: "0"},
  919. ]
  920. }
  921. fulfillmentHandler: "${manualFulfillmentHandler.code}"
  922. translations: [{ languageCode: en, name: "test" description: "test" }]
  923. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  924. }
  925. ) {
  926. id
  927. ${customFieldsSelection}
  928. }
  929. }
  930. `);
  931. assertCustomFieldIds(createShippingMethod.customFields, 'T_1', ['T_1', 'T_2']);
  932. shippingMethodId = createShippingMethod.id;
  933. });
  934. it('admin updateShippingMethod', async () => {
  935. const { updateShippingMethod } = await adminClient.query(gql`
  936. mutation {
  937. updateShippingMethod(
  938. input: {
  939. id: "${shippingMethodId}"
  940. translations: []
  941. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  942. }
  943. ) {
  944. id
  945. ${customFieldsSelection}
  946. }
  947. }
  948. `);
  949. assertCustomFieldIds(updateShippingMethod.customFields, 'T_2', ['T_3', 'T_4']);
  950. });
  951. it('shop eligibleShippingMethods (ShippingMethodQuote)', async () => {
  952. const { eligibleShippingMethods } = await shopClient.query(gql`
  953. query {
  954. eligibleShippingMethods {
  955. id
  956. name
  957. code
  958. description
  959. ${customFieldsSelection}
  960. }
  961. }
  962. `);
  963. const testShippingMethodQuote = eligibleShippingMethods.find(
  964. (quote: any) => quote.code === 'test',
  965. );
  966. assertCustomFieldIds(testShippingMethodQuote.customFields, 'T_2', ['T_3', 'T_4']);
  967. });
  968. });
  969. describe('PaymentMethod entity', () => {
  970. let paymentMethodId: string;
  971. it('admin createShippingMethod', async () => {
  972. const { createPaymentMethod } = await adminClient.query(gql`
  973. mutation {
  974. createPaymentMethod(
  975. input: {
  976. name: "test"
  977. code: "test"
  978. enabled: true
  979. handler: {
  980. code: "${testSuccessfulPaymentMethod.code}"
  981. arguments: []
  982. }
  983. customFields: { singleId: "T_1", multiIds: ["T_1", "T_2"] }
  984. }
  985. ) {
  986. id
  987. ${customFieldsSelection}
  988. }
  989. }
  990. `);
  991. assertCustomFieldIds(createPaymentMethod.customFields, 'T_1', ['T_1', 'T_2']);
  992. paymentMethodId = createPaymentMethod.id;
  993. });
  994. it('admin updatePaymentMethod', async () => {
  995. const { updatePaymentMethod } = await adminClient.query(gql`
  996. mutation {
  997. updatePaymentMethod(
  998. input: {
  999. id: "${paymentMethodId}"
  1000. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  1001. }
  1002. ) {
  1003. id
  1004. ${customFieldsSelection}
  1005. }
  1006. }
  1007. `);
  1008. assertCustomFieldIds(updatePaymentMethod.customFields, 'T_2', ['T_3', 'T_4']);
  1009. });
  1010. it('shop eligiblePaymentMethods (PaymentMethodQuote)', async () => {
  1011. const { eligiblePaymentMethods } = await shopClient.query(gql`
  1012. query {
  1013. eligiblePaymentMethods {
  1014. id
  1015. name
  1016. description
  1017. ${customFieldsSelection}
  1018. }
  1019. }
  1020. `);
  1021. assertCustomFieldIds(eligiblePaymentMethods[0].customFields, 'T_2', ['T_3', 'T_4']);
  1022. });
  1023. });
  1024. describe('Asset entity', () => {
  1025. it('set custom field relations on Asset', async () => {
  1026. const { updateAsset } = await adminClient.query(gql`
  1027. mutation {
  1028. updateAsset(
  1029. input: {
  1030. id: "T_1",
  1031. customFields: { singleId: "T_2", multiIds: ["T_3", "T_4"] }
  1032. }
  1033. ) {
  1034. id
  1035. ${customFieldsSelection}
  1036. }
  1037. }
  1038. `);
  1039. assertCustomFieldIds(updateAsset.customFields, 'T_2', ['T_3', 'T_4']);
  1040. });
  1041. it('findOne on Asset', async () => {
  1042. const { asset } = await adminClient.query(gql`
  1043. query {
  1044. asset(id: "T_1") {
  1045. id
  1046. ${customFieldsSelection}
  1047. }
  1048. }
  1049. `);
  1050. expect(asset.customFields.single.id).toBe('T_2');
  1051. expect(asset.customFields.multi.length).toEqual(2);
  1052. });
  1053. // https://github.com/vendure-ecommerce/vendure/issues/1636
  1054. it('calling TransactionalConnection.findOneInChannel() returns custom field relations', async () => {
  1055. TestPlugin1636_1664.testResolverSpy.mockReset();
  1056. await shopClient.query(gql`
  1057. query {
  1058. getAssetTest(id: "T_1")
  1059. }
  1060. `);
  1061. const args = TestPlugin1636_1664.testResolverSpy.mock.calls[0];
  1062. expect(args[0].customFields.single.id).toEqual(2);
  1063. expect(args[0].customFields.multi.length).toEqual(2);
  1064. });
  1065. });
  1066. });
  1067. it('null values', async () => {
  1068. const { updateCustomerAddress } = await adminClient.query(gql`
  1069. mutation {
  1070. updateCustomerAddress(
  1071. input: { id: "T_1", customFields: { singleId: null, multiIds: ["T_1", "null"] } }
  1072. ) {
  1073. id
  1074. customFields {
  1075. single {
  1076. id
  1077. }
  1078. multi {
  1079. id
  1080. }
  1081. }
  1082. }
  1083. }
  1084. `);
  1085. expect(updateCustomerAddress.customFields.single).toEqual(null);
  1086. expect(updateCustomerAddress.customFields.multi).toEqual([{ id: 'T_1' }]);
  1087. });
  1088. });