@vendure/dashboard 3.4.1-master-202508050244 → 3.4.1-master-202508070243

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,143 @@
1
+ import { UniversalFieldDefinition } from './universal-field-definition.js';
2
+
3
+ export type ValueMode = 'native' | 'json-string';
4
+
5
+ /**
6
+ * Interface for transforming values between native JavaScript types and JSON strings
7
+ */
8
+ export interface ValueTransformer {
9
+ parse: (value: string, fieldDef: UniversalFieldDefinition) => any;
10
+ serialize: (value: any, fieldDef: UniversalFieldDefinition) => string;
11
+ }
12
+
13
+ /**
14
+ * Native value transformer - passes values through unchanged
15
+ */
16
+ export const nativeValueTransformer: ValueTransformer = {
17
+ parse: (value: string, fieldDef: UniversalFieldDefinition) => {
18
+ // For native mode, values are already in their correct JavaScript type
19
+ return value;
20
+ },
21
+ serialize: (value: any, fieldDef: UniversalFieldDefinition) => {
22
+ // For native mode, values are already in their correct JavaScript type
23
+ return value;
24
+ },
25
+ };
26
+
27
+ /**
28
+ * JSON string value transformer - converts between JSON strings and native values
29
+ */
30
+ export const jsonStringValueTransformer: ValueTransformer = {
31
+ parse: (value: string, fieldDef: UniversalFieldDefinition) => {
32
+ if (!value) {
33
+ return getDefaultValue(fieldDef);
34
+ }
35
+
36
+ try {
37
+ // For JSON string mode, parse the string to get the native value
38
+ const parsed = JSON.parse(value);
39
+
40
+ // Handle special cases for different field types
41
+ switch (fieldDef.type) {
42
+ case 'boolean':
43
+ return parsed === true || parsed === 'true';
44
+ case 'int':
45
+ case 'float':
46
+ return typeof parsed === 'number' ? parsed : parseFloat(parsed) || 0;
47
+ case 'datetime':
48
+ return parsed;
49
+ default:
50
+ return parsed;
51
+ }
52
+ } catch (error) {
53
+ // If parsing fails, try to handle as a plain string for certain types
54
+ switch (fieldDef.type) {
55
+ case 'boolean':
56
+ return value === 'true';
57
+ case 'int':
58
+ case 'float':
59
+ return parseFloat(value) || 0;
60
+ default:
61
+ return value;
62
+ }
63
+ }
64
+ },
65
+ serialize: (value: any, fieldDef: UniversalFieldDefinition) => {
66
+ if (value === null || value === undefined) {
67
+ return '';
68
+ }
69
+
70
+ // Handle special cases for different field types
71
+ switch (fieldDef.type) {
72
+ case 'boolean':
73
+ return (value === true || value === 'true').toString();
74
+ case 'int':
75
+ case 'float':
76
+ return typeof value === 'number' ? value.toString() : (parseFloat(value) || 0).toString();
77
+ case 'string':
78
+ return typeof value === 'string' ? value : JSON.stringify(value);
79
+ default:
80
+ // For complex values (arrays, objects), serialize as JSON
81
+ return typeof value === 'string' ? value : JSON.stringify(value);
82
+ }
83
+ },
84
+ };
85
+
86
+ /**
87
+ * Get the appropriate value transformer based on the value mode
88
+ */
89
+ export function getValueTransformer(valueMode: ValueMode): ValueTransformer {
90
+ switch (valueMode) {
91
+ case 'native':
92
+ return nativeValueTransformer;
93
+ case 'json-string':
94
+ return jsonStringValueTransformer;
95
+ default:
96
+ return nativeValueTransformer;
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Get default value for a field type
102
+ */
103
+ function getDefaultValue(fieldDef: UniversalFieldDefinition): any {
104
+ if (fieldDef.list) {
105
+ return [];
106
+ }
107
+
108
+ switch (fieldDef.type) {
109
+ case 'string':
110
+ case 'ID':
111
+ case 'localeString':
112
+ case 'localeText':
113
+ return '';
114
+ case 'int':
115
+ case 'float':
116
+ return 0;
117
+ case 'boolean':
118
+ return false;
119
+ case 'datetime':
120
+ return '';
121
+ case 'relation':
122
+ return fieldDef.list ? [] : null;
123
+ case 'struct':
124
+ return fieldDef.list ? [] : {};
125
+ default:
126
+ return '';
127
+ }
128
+ }
129
+
130
+ /**
131
+ * Utility to transform a value using the appropriate transformer
132
+ */
133
+ export function transformValue(
134
+ value: any,
135
+ fieldDef: UniversalFieldDefinition,
136
+ valueMode: ValueMode,
137
+ direction: 'parse' | 'serialize',
138
+ ): any {
139
+ const transformer = getValueTransformer(valueMode);
140
+ return direction === 'parse'
141
+ ? transformer.parse(value, fieldDef)
142
+ : transformer.serialize(value, fieldDef);
143
+ }
@@ -434,6 +434,144 @@ describe('form-schema-tools', () => {
434
434
  });
435
435
  });
436
436
 
437
+ describe('createFormSchemaFromFields - null constraint handling', () => {
438
+ it('should handle int custom fields with null min constraint from GraphQL API', () => {
439
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
440
+ // Simulate GraphQL API response where min/max are null instead of undefined
441
+ const customFields = [
442
+ createMockCustomField('quantity', 'int', {
443
+ intMin: null as any, // This comes as null from GraphQL API
444
+ intMax: 100,
445
+ }),
446
+ ];
447
+
448
+ // This test should fail if the bug exists - the schema creation should work
449
+ // but validation with null constraints should fail
450
+ expect(() => {
451
+ const _schema = createFormSchemaFromFields(fields, customFields, false);
452
+
453
+ // If the bug exists, this will try to validate against null min value
454
+ // which would always be false since no number can be >= null
455
+ const _validData = { customFields: { quantity: 50 } };
456
+ _schema.parse(_validData);
457
+ }).not.toThrow();
458
+
459
+ // Let's also test that we can create the schema without errors
460
+ const schema = createFormSchemaFromFields(fields, customFields, false);
461
+
462
+ // Should not apply min validation since intMin is null (if bug is fixed)
463
+ const validData = { customFields: { quantity: -50 } };
464
+ expect(() => schema.parse(validData)).not.toThrow();
465
+
466
+ // Should still apply max validation since intMax is 100
467
+ const aboveMaxData = { customFields: { quantity: 101 } };
468
+ expect(() => schema.parse(aboveMaxData)).toThrow();
469
+ });
470
+
471
+ it('should handle int custom fields with null max constraint from GraphQL API', () => {
472
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
473
+ // Simulate GraphQL API response where min/max are null instead of undefined
474
+ const customFields = [
475
+ createMockCustomField('quantity', 'int', {
476
+ intMin: 1,
477
+ intMax: null as any, // This comes as null from GraphQL API
478
+ }),
479
+ ];
480
+
481
+ const schema = createFormSchemaFromFields(fields, customFields, false);
482
+
483
+ // Should still apply min validation since intMin is 1
484
+ const belowMinData = { customFields: { quantity: 0 } };
485
+ expect(() => schema.parse(belowMinData)).toThrow();
486
+
487
+ // Should not apply max validation since intMax is null
488
+ const validData = { customFields: { quantity: 999999 } };
489
+ expect(() => schema.parse(validData)).not.toThrow();
490
+ });
491
+
492
+ it('should handle int custom fields with both null min and max constraints from GraphQL API', () => {
493
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
494
+ // Simulate GraphQL API response where min/max are both null
495
+ const customFields = [
496
+ createMockCustomField('quantity', 'int', {
497
+ intMin: null as any, // This comes as null from GraphQL API
498
+ intMax: null as any, // This comes as null from GraphQL API
499
+ }),
500
+ ];
501
+
502
+ const schema = createFormSchemaFromFields(fields, customFields, false);
503
+
504
+ // Should not apply any validation since both are null
505
+ const negativeData = { customFields: { quantity: -999 } };
506
+ expect(() => schema.parse(negativeData)).not.toThrow();
507
+
508
+ const largeData = { customFields: { quantity: 999999 } };
509
+ expect(() => schema.parse(largeData)).not.toThrow();
510
+ });
511
+
512
+ it('should handle float custom fields with null min constraint from GraphQL API', () => {
513
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
514
+ // Simulate GraphQL API response where min/max are null instead of undefined
515
+ const customFields = [
516
+ createMockCustomField('weight', 'float', {
517
+ floatMin: null as any, // This comes as null from GraphQL API
518
+ floatMax: 999.9,
519
+ }),
520
+ ];
521
+
522
+ const schema = createFormSchemaFromFields(fields, customFields, false);
523
+
524
+ // Should not apply min validation since floatMin is null
525
+ const validData = { customFields: { weight: -50.5 } };
526
+ expect(() => schema.parse(validData)).not.toThrow();
527
+
528
+ // Should still apply max validation since floatMax is 999.9
529
+ const aboveMaxData = { customFields: { weight: 1000.0 } };
530
+ expect(() => schema.parse(aboveMaxData)).toThrow();
531
+ });
532
+
533
+ it('should handle float custom fields with null max constraint from GraphQL API', () => {
534
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
535
+ // Simulate GraphQL API response where min/max are null instead of undefined
536
+ const customFields = [
537
+ createMockCustomField('weight', 'float', {
538
+ floatMin: 0.1,
539
+ floatMax: null as any, // This comes as null from GraphQL API
540
+ }),
541
+ ];
542
+
543
+ const schema = createFormSchemaFromFields(fields, customFields, false);
544
+
545
+ // Should still apply min validation since floatMin is 0.1
546
+ const belowMinData = { customFields: { weight: 0.05 } };
547
+ expect(() => schema.parse(belowMinData)).toThrow();
548
+
549
+ // Should not apply max validation since floatMax is null
550
+ const validData = { customFields: { weight: 999999.99 } };
551
+ expect(() => schema.parse(validData)).not.toThrow();
552
+ });
553
+
554
+ it('should handle float custom fields with both null min and max constraints from GraphQL API', () => {
555
+ const fields = [createMockField('customFields', 'Object', false, false, [])];
556
+ // Simulate GraphQL API response where min/max are both null
557
+ const customFields = [
558
+ createMockCustomField('weight', 'float', {
559
+ floatMin: null as any, // This comes as null from GraphQL API
560
+ floatMax: null as any, // This comes as null from GraphQL API
561
+ }),
562
+ ];
563
+
564
+ const schema = createFormSchemaFromFields(fields, customFields, false);
565
+
566
+ // Should not apply any validation since both are null
567
+ const negativeData = { customFields: { weight: -999.99 } };
568
+ expect(() => schema.parse(negativeData)).not.toThrow();
569
+
570
+ const largeData = { customFields: { weight: 999999.99 } };
571
+ expect(() => schema.parse(largeData)).not.toThrow();
572
+ });
573
+ });
574
+
437
575
  describe('createFormSchemaFromFields - edge cases', () => {
438
576
  it('should handle empty custom field config', () => {
439
577
  const fields = [
@@ -136,12 +136,12 @@ function createStringValidationSchema(pattern?: string): ZodType {
136
136
  */
137
137
  function createIntValidationSchema(min?: number, max?: number): ZodType {
138
138
  let schema = z.number();
139
- if (min !== undefined) {
139
+ if (min != null) {
140
140
  schema = schema.min(min, {
141
141
  message: `Value must be at least ${min}`,
142
142
  });
143
143
  }
144
- if (max !== undefined) {
144
+ if (max != null) {
145
145
  schema = schema.max(max, {
146
146
  message: `Value must be at most ${max}`,
147
147
  });
@@ -159,12 +159,12 @@ function createIntValidationSchema(min?: number, max?: number): ZodType {
159
159
  */
160
160
  function createFloatValidationSchema(min?: number, max?: number): ZodType {
161
161
  let schema = z.number();
162
- if (min !== undefined) {
162
+ if (min != null) {
163
163
  schema = schema.min(min, {
164
164
  message: `Value must be at least ${min}`,
165
165
  });
166
166
  }
167
- if (max !== undefined) {
167
+ if (max != null) {
168
168
  schema = schema.max(max, {
169
169
  message: `Value must be at most ${max}`,
170
170
  });