custom-field-default-values.e2e-spec.ts 10 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261
  1. import { createErrorResultGuard, createTestEnvironment, ErrorResultGuard } from '@vendure/testing';
  2. import path from 'path';
  3. import { afterAll, beforeAll, describe, expect, it } from 'vitest';
  4. import { initialData } from '../../../e2e-common/e2e-initial-data';
  5. import { TEST_SETUP_TIMEOUT_MS, testConfig } from '../../../e2e-common/test-config';
  6. import { graphql } from './graphql/graphql-admin';
  7. /**
  8. * Tests for GitHub issue #3266: Custom field default values not applied when explicitly set to null
  9. * https://github.com/vendure-ecommerce/vendure/issues/3266
  10. */
  11. const customConfig = {
  12. ...testConfig(),
  13. customFields: {
  14. Product: [
  15. { name: 'stringWithDefault', type: 'string', defaultValue: 'hello' },
  16. { name: 'intWithDefault', type: 'int', defaultValue: 5 },
  17. { name: 'booleanWithDefault', type: 'boolean', defaultValue: true },
  18. ],
  19. Customer: [
  20. { name: 'stringWithDefault', type: 'string', defaultValue: 'customer-default' },
  21. { name: 'intWithDefault', type: 'int', defaultValue: 100 },
  22. { name: 'booleanWithDefault', type: 'boolean', defaultValue: false },
  23. ],
  24. },
  25. };
  26. describe('Custom field default values', () => {
  27. const { server, adminClient, shopClient } = createTestEnvironment(customConfig);
  28. // Test-specific documents: only fetch customFields, avoid over-fetching shared definitions
  29. const createProductDocument = graphql(`
  30. mutation CreateProduct($input: CreateProductInput!) {
  31. createProduct(input: $input) {
  32. id
  33. name
  34. customFields {
  35. stringWithDefault
  36. intWithDefault
  37. booleanWithDefault
  38. }
  39. }
  40. }
  41. `);
  42. const createCustomerDocument = graphql(`
  43. mutation CreateCustomer($input: CreateCustomerInput!) {
  44. createCustomer(input: $input) {
  45. ... on Customer {
  46. id
  47. firstName
  48. lastName
  49. emailAddress
  50. customFields {
  51. stringWithDefault
  52. intWithDefault
  53. booleanWithDefault
  54. }
  55. }
  56. ... on ErrorResult {
  57. errorCode
  58. message
  59. }
  60. }
  61. }
  62. `);
  63. type CustomerSuccess = {
  64. id: string;
  65. firstName: string;
  66. lastName: string;
  67. emailAddress: string;
  68. customFields: any;
  69. };
  70. const customerGuard: ErrorResultGuard<CustomerSuccess> = createErrorResultGuard(
  71. input => 'id' in input && 'firstName' in input,
  72. );
  73. beforeAll(async () => {
  74. await server.init({
  75. initialData,
  76. productsCsvPath: path.join(__dirname, 'fixtures/e2e-products-minimal.csv'),
  77. customerCount: 1,
  78. });
  79. await adminClient.asSuperAdmin();
  80. }, TEST_SETUP_TIMEOUT_MS);
  81. afterAll(async () => {
  82. await server.destroy();
  83. });
  84. describe('translatable entity (Product)', () => {
  85. it('should apply default values when creating product without custom fields', async () => {
  86. const { createProduct } = await adminClient.query(createProductDocument, {
  87. input: {
  88. translations: [
  89. {
  90. languageCode: 'en',
  91. name: 'Test Product 1',
  92. slug: 'test-product-1',
  93. description: 'Test',
  94. },
  95. ],
  96. },
  97. });
  98. expect(createProduct.customFields.stringWithDefault).toBe('hello');
  99. expect(createProduct.customFields.intWithDefault).toBe(5);
  100. expect(createProduct.customFields.booleanWithDefault).toBe(true);
  101. });
  102. it('should apply default values when creating product with empty custom fields', async () => {
  103. const { createProduct } = await adminClient.query(createProductDocument, {
  104. input: {
  105. translations: [
  106. {
  107. languageCode: 'en',
  108. name: 'Test Product 2',
  109. slug: 'test-product-2',
  110. description: 'Test',
  111. },
  112. ],
  113. customFields: {},
  114. },
  115. });
  116. expect(createProduct.customFields.stringWithDefault).toBe('hello');
  117. expect(createProduct.customFields.intWithDefault).toBe(5);
  118. expect(createProduct.customFields.booleanWithDefault).toBe(true);
  119. });
  120. it('should apply default values when custom fields are explicitly set to null', async () => {
  121. const { createProduct } = await adminClient.query(createProductDocument, {
  122. input: {
  123. translations: [
  124. {
  125. languageCode: 'en',
  126. name: 'Test Product Null',
  127. slug: 'test-product-null',
  128. description: 'Test',
  129. },
  130. ],
  131. customFields: {
  132. stringWithDefault: null,
  133. intWithDefault: null,
  134. booleanWithDefault: null,
  135. },
  136. },
  137. });
  138. // This is the core issue: when custom fields are explicitly set to null,
  139. // they should still get their default values
  140. expect(createProduct.customFields.stringWithDefault).toBe('hello');
  141. expect(createProduct.customFields.intWithDefault).toBe(5);
  142. expect(createProduct.customFields.booleanWithDefault).toBe(true);
  143. });
  144. it('should not override explicitly provided values', async () => {
  145. const { createProduct } = await adminClient.query(createProductDocument, {
  146. input: {
  147. translations: [
  148. {
  149. languageCode: 'en',
  150. name: 'Test Product Custom',
  151. slug: 'test-product-custom',
  152. description: 'Test',
  153. },
  154. ],
  155. customFields: {
  156. stringWithDefault: 'custom value',
  157. intWithDefault: 999,
  158. booleanWithDefault: false,
  159. },
  160. },
  161. });
  162. // When explicit values are provided, they should be used instead of defaults
  163. expect(createProduct.customFields.stringWithDefault).toBe('custom value');
  164. expect(createProduct.customFields.intWithDefault).toBe(999);
  165. expect(createProduct.customFields.booleanWithDefault).toBe(false);
  166. });
  167. });
  168. describe('non-translatable entity (Customer)', () => {
  169. it('should apply default values when creating customer without custom fields', async () => {
  170. const { createCustomer } = await adminClient.query(createCustomerDocument, {
  171. input: {
  172. firstName: 'John',
  173. lastName: 'Doe',
  174. emailAddress: 'john.doe@example.com',
  175. },
  176. });
  177. customerGuard.assertSuccess(createCustomer);
  178. expect(createCustomer.customFields.stringWithDefault).toBe('customer-default');
  179. expect(createCustomer.customFields.intWithDefault).toBe(100);
  180. expect(createCustomer.customFields.booleanWithDefault).toBe(false);
  181. });
  182. it('should apply default values when creating customer with empty custom fields', async () => {
  183. const { createCustomer } = await adminClient.query(createCustomerDocument, {
  184. input: {
  185. firstName: 'Jane',
  186. lastName: 'Smith',
  187. emailAddress: 'jane.smith@example.com',
  188. customFields: {},
  189. },
  190. });
  191. customerGuard.assertSuccess(createCustomer);
  192. expect(createCustomer.customFields.stringWithDefault).toBe('customer-default');
  193. expect(createCustomer.customFields.intWithDefault).toBe(100);
  194. expect(createCustomer.customFields.booleanWithDefault).toBe(false);
  195. });
  196. it('should apply default values when custom fields are explicitly set to null', async () => {
  197. const { createCustomer } = await adminClient.query(createCustomerDocument, {
  198. input: {
  199. firstName: 'Bob',
  200. lastName: 'Johnson',
  201. emailAddress: 'bob.johnson@example.com',
  202. customFields: {
  203. stringWithDefault: null,
  204. intWithDefault: null,
  205. booleanWithDefault: null,
  206. },
  207. },
  208. });
  209. customerGuard.assertSuccess(createCustomer);
  210. // This should reproduce the issue for non-translatable entities
  211. expect(createCustomer.customFields.stringWithDefault).toBe('customer-default');
  212. expect(createCustomer.customFields.intWithDefault).toBe(100);
  213. expect(createCustomer.customFields.booleanWithDefault).toBe(false);
  214. });
  215. it('should not override explicitly provided values', async () => {
  216. const { createCustomer } = await adminClient.query(createCustomerDocument, {
  217. input: {
  218. firstName: 'Alice',
  219. lastName: 'Wilson',
  220. emailAddress: 'alice.wilson@example.com',
  221. customFields: {
  222. stringWithDefault: 'custom customer value',
  223. intWithDefault: 777,
  224. booleanWithDefault: true,
  225. },
  226. },
  227. });
  228. customerGuard.assertSuccess(createCustomer);
  229. // When explicit values are provided, they should be used instead of defaults
  230. expect(createCustomer.customFields.stringWithDefault).toBe('custom customer value');
  231. expect(createCustomer.customFields.intWithDefault).toBe(777);
  232. expect(createCustomer.customFields.booleanWithDefault).toBe(true);
  233. });
  234. });
  235. });