@react-typed-forms/schemas 14.0.3 → 14.0.5

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.
Files changed (43) hide show
  1. package/coverage/clover.xml +1426 -0
  2. package/coverage/coverage-final.json +17 -0
  3. package/coverage/lcov-report/base.css +224 -0
  4. package/coverage/lcov-report/block-navigation.js +87 -0
  5. package/coverage/lcov-report/favicon.png +0 -0
  6. package/coverage/lcov-report/index.html +131 -0
  7. package/coverage/lcov-report/prettify.css +1 -0
  8. package/coverage/lcov-report/prettify.js +2 -0
  9. package/coverage/lcov-report/sort-arrow-sprite.png +0 -0
  10. package/coverage/lcov-report/sorter.js +196 -0
  11. package/coverage/lcov-report/src/controlBuilder.ts.html +889 -0
  12. package/coverage/lcov-report/src/controlDefinition.ts.html +2362 -0
  13. package/coverage/lcov-report/src/controlRender.tsx.html +3532 -0
  14. package/coverage/lcov-report/src/createFormRenderer.tsx.html +721 -0
  15. package/coverage/lcov-report/src/defaultSchemaInterface.ts.html +658 -0
  16. package/coverage/lcov-report/src/dynamicHooks.ts.html +379 -0
  17. package/coverage/lcov-report/src/entityExpression.ts.html +199 -0
  18. package/coverage/lcov-report/src/hooks.tsx.html +1462 -0
  19. package/coverage/lcov-report/src/index.html +326 -0
  20. package/coverage/lcov-report/src/index.ts.html +127 -0
  21. package/coverage/lcov-report/src/renderers.tsx.html +679 -0
  22. package/coverage/lcov-report/src/schemaBuilder.ts.html +1039 -0
  23. package/coverage/lcov-report/src/schemaField.ts.html +1735 -0
  24. package/coverage/lcov-report/src/schemaValidator.ts.html +181 -0
  25. package/coverage/lcov-report/src/util.ts.html +2719 -0
  26. package/coverage/lcov-report/src/validators.ts.html +736 -0
  27. package/coverage/lcov-report/test/gen.ts.html +745 -0
  28. package/coverage/lcov-report/test/index.html +116 -0
  29. package/coverage/lcov.info +3266 -0
  30. package/jest.config.js +8 -0
  31. package/lib/controlBuilder.d.ts +4 -1
  32. package/lib/controlDefinition.d.ts +13 -0
  33. package/lib/index.cjs +1 -1
  34. package/lib/index.cjs.map +1 -1
  35. package/lib/index.js +1 -1
  36. package/lib/index.js.map +1 -1
  37. package/lib/schemaField.d.ts +4 -3
  38. package/lib/util.d.ts +2 -1
  39. package/package.json +8 -2
  40. package/test/diff.test.ts +146 -0
  41. package/test/gen.ts +220 -0
  42. package/test/play.ts +54 -0
  43. package/tsconfig.json +1 -1
@@ -212,7 +212,7 @@ export interface SchemaDataNode {
212
212
  id: string;
213
213
  schema: SchemaNode;
214
214
  elementIndex?: number;
215
- control?: Control<unknown>;
215
+ control: Control<any>;
216
216
  parent?: SchemaDataNode;
217
217
  getChild(schemaNode: SchemaNode): SchemaDataNode;
218
218
  getChildElement(index: number): SchemaDataNode;
@@ -221,7 +221,7 @@ export declare function findField(fields: SchemaField[], field: string): SchemaF
221
221
  export declare function isScalarField(sf: SchemaField): sf is SchemaField;
222
222
  export declare function isCompoundField(sf: SchemaField): sf is CompoundField;
223
223
  export declare function createSchemaLookup<A extends Record<string, SchemaField[]>>(schemaMap: A): SchemaTreeLookup<keyof A>;
224
- export declare function makeSchemaDataNode(schema: SchemaNode, control?: Control<unknown>, parent?: SchemaDataNode, elementIndex?: number): SchemaDataNode;
224
+ export declare function makeSchemaDataNode(schema: SchemaNode, control: Control<unknown>, parent?: SchemaDataNode, elementIndex?: number): SchemaDataNode;
225
225
  export declare function schemaDataForFieldRef(fieldRef: string | undefined, schema: SchemaDataNode): SchemaDataNode;
226
226
  export declare function schemaForFieldRef(fieldRef: string | undefined, schema: SchemaNode): SchemaNode;
227
227
  export declare function traverseSchemaPath<A>(fieldPath: string[], schema: SchemaNode, acc: A, next: (acc: A, node: SchemaNode) => A): A;
@@ -243,7 +243,8 @@ export declare enum SchemaTags {
243
243
  NoControl = "_NoControl",
244
244
  HtmlEditor = "_HtmlEditor",
245
245
  ControlGroup = "_ControlGroup:",
246
- ControlRef = "_ControlRef:"
246
+ ControlRef = "_ControlRef:",
247
+ IdField = "_IdField:"
247
248
  }
248
249
  export declare function getTagParam(field: SchemaField, tag: string): string | undefined;
249
250
  export declare function makeParamTag(tag: string, value: string): string;
package/lib/util.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  import { ControlActionHandler, ControlDefinition, DataControlDefinition, DisplayOnlyRenderOptions, GroupRenderOptions } from "./controlDefinition";
2
2
  import { MutableRefObject } from "react";
3
- import { CompoundField, FieldOption, SchemaField, SchemaNode } from "./schemaField";
3
+ import { CompoundField, FieldOption, SchemaDataNode, SchemaField, SchemaNode } from "./schemaField";
4
4
  /**
5
5
  * Interface representing the classes for a control.
6
6
  */
@@ -243,3 +243,4 @@ export declare function isControlDisplayOnly(def: ControlDefinition): boolean;
243
243
  * @returns {ControlActionHandler} - The combined action handler.
244
244
  */
245
245
  export declare function actionHandlers(...handlers: (ControlActionHandler | undefined)[]): ControlActionHandler;
246
+ export declare function getDiffObject(dataNode: SchemaDataNode, force?: boolean): any;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@react-typed-forms/schemas",
3
- "version": "14.0.3",
3
+ "version": "14.0.5",
4
4
  "description": "",
5
5
  "type": "module",
6
6
  "main": "lib/index.cjs",
@@ -42,6 +42,11 @@
42
42
  "devDependencies": {
43
43
  "react": "^18.2.0",
44
44
  "@react-typed-forms/transform": "^0.2.0",
45
+ "jest": "^29.7.0",
46
+ "tsx": "^4.19.1",
47
+ "fast-check": "^3.22.0",
48
+ "ts-jest": "^29.2.5",
49
+ "@jest/globals": "^29.7.0",
45
50
  "typedoc": "^0.27.2",
46
51
  "@types/uuid": "^10.0.0",
47
52
  "@types/react": "^18.2.28",
@@ -51,12 +56,13 @@
51
56
  "prettier": "^3.0.3",
52
57
  "rimraf": "^3.0.2",
53
58
  "typescript": "^5.6.2",
54
- "@react-typed-forms/core": "4.0.0"
59
+ "@react-typed-forms/core": "4.0.3"
55
60
  },
56
61
  "gitHead": "698e16cd3ab31b7dd0528fc76536f4d3205ce8c6",
57
62
  "scripts": {
58
63
  "build": "rimraf ./lib/ node_modules/.cache && microbundle -f modern,cjs --jsx React.createElement --jsxFragment React.Fragment",
59
64
  "watch": "microbundle -w -f modern,cjs --no-compress --jsx React.createElement --jsxFragment React.Fragment",
65
+ "test": "jest --coverage",
60
66
  "update-readme": "md-magic --path README.md",
61
67
  "gencode": "nswag swagger2tsclient /input:http://localhost:5216/swagger/v1/swagger.json /runtime:Net60 /output:src/types.ts /GenerateClientClasses:false /MarkOptionalProperties:false /Template:Fetch /TypeStyle:Interface /DateTimeType:string"
62
68
  }
@@ -0,0 +1,146 @@
1
+ import { describe, expect, it } from "@jest/globals";
2
+ import fc from "fast-check";
3
+ import { makeDataNode, newIndexes, valueAndSchema } from "./gen";
4
+ import { getDiffObject, getTagParam, SchemaDataNode, SchemaTags } from "../src";
5
+ import {
6
+ deepEquals,
7
+ newElement,
8
+ updateElements,
9
+ } from "@react-typed-forms/core";
10
+
11
+ describe("diff", () => {
12
+ it("unchanged value always returns undefined", () => {
13
+ fc.assert(
14
+ fc.property(valueAndSchema(), (fv) => {
15
+ const dataNode = makeDataNode(fv);
16
+ expect(getDiffObject(dataNode)).toBeUndefined();
17
+ }),
18
+ );
19
+ });
20
+
21
+ it("primitive value always returns new value", () => {
22
+ fc.assert(
23
+ fc.property(
24
+ valueAndSchema({ arrayChance: 0, compoundChance: 0 }),
25
+ (fv) => {
26
+ const dataNode = makeDataNode(fv);
27
+ const control = dataNode.control!;
28
+ control.setValue((x) => fv.newValue);
29
+ expect(getDiffObject(dataNode)).toBe(control.value);
30
+ },
31
+ ),
32
+ );
33
+ });
34
+
35
+ it("compound fields only return changed values", () => {
36
+ fc.assert(
37
+ fc.property(
38
+ valueAndSchema({
39
+ arrayChance: 0,
40
+ compoundChance: 0,
41
+ forceCompound: true,
42
+ }),
43
+ (fv) => {
44
+ const dataNode = makeDataNode(fv);
45
+ const control = dataNode.control!;
46
+ control.value = fv.newValue;
47
+ const changed = dataNode.schema
48
+ .getChildNodes()
49
+ .map((x) => x.field.field)
50
+ .filter((x) => fv.value?.[x] !== fv.newValue?.[x])
51
+ .map((x) => [x, fv.newValue?.[x]]);
52
+ expect(getDiffObject(dataNode)).toStrictEqual(
53
+ changed.length ? Object.fromEntries(changed) : undefined,
54
+ );
55
+ },
56
+ ),
57
+ );
58
+ });
59
+
60
+ it("array compound with id field always returns id field and changes", () => {
61
+ fc.assert(
62
+ fc.property(
63
+ valueAndSchema({
64
+ forceArray: true,
65
+ forceCompound: true,
66
+ arrayChance: 0,
67
+ compoundChance: 0,
68
+ idField: true,
69
+ }),
70
+ (fv) => {
71
+ const dataNode = makeDataNode(fv);
72
+ const arrayControl = dataNode.control!;
73
+ arrayControl.value = fv.newValue;
74
+ const idField = getTagParam(
75
+ dataNode.schema.field,
76
+ SchemaTags.IdField,
77
+ );
78
+ const origValues = (fv.value ?? []) as any[];
79
+ const newValues = (fv.newValue ?? []) as any[];
80
+ const expected = newValues.map((nv, i) => {
81
+ if (nv == null) return null;
82
+ return objectDiff(dataNode, origValues[i], nv, idField);
83
+ });
84
+ expect(getDiffObject(dataNode)).toStrictEqual(
85
+ arrayControl.dirty ? expected : undefined,
86
+ );
87
+ },
88
+ ),
89
+ );
90
+ });
91
+
92
+ it("array without id always returns array index edit format", () => {
93
+ fc.assert(
94
+ fc.property(
95
+ valueAndSchema({
96
+ forceArray: true,
97
+ forceCompound: true,
98
+ arrayChance: 0,
99
+ compoundChance: 0,
100
+ })
101
+ .filter((x) => !deepEquals(x.newValue, x.value) && x.value != null)
102
+ .chain((x) => newIndexes(x.value.length).map((i) => [x, i] as const)),
103
+ ([fv, indexes]) => {
104
+ const dataNode = makeDataNode(fv);
105
+ const arrayControl = dataNode.control!.as<any[]>();
106
+ const newValue = fv.newValue as any[];
107
+ const results = Array.from({ length: newValue.length });
108
+ updateElements(arrayControl, (x) => {
109
+ const sorted = indexes.map((ni, i) => {
110
+ const c = x[ni];
111
+ c.value = newValue[i];
112
+ results[i] = {
113
+ old: ni,
114
+ edit: objectDiff(dataNode, c.initialValue, c.value),
115
+ };
116
+ return c;
117
+ });
118
+ while (newValue.length > sorted.length) {
119
+ const ne = newElement(arrayControl, undefined);
120
+ ne.value = newValue[sorted.length];
121
+ results[sorted.length] = { old: undefined, edit: ne.value };
122
+ sorted.push(ne);
123
+ }
124
+ return sorted;
125
+ });
126
+ expect(getDiffObject(dataNode)).toStrictEqual(results);
127
+ },
128
+ ),
129
+ );
130
+ });
131
+ });
132
+
133
+ function objectDiff(
134
+ dataNode: SchemaDataNode,
135
+ oldValue: Record<string, any> | undefined | null,
136
+ newValue: Record<string, any> | undefined | null,
137
+ idField?: string,
138
+ ) {
139
+ if (newValue == null) return oldValue == null ? undefined : newValue;
140
+ const changed = dataNode.schema
141
+ .getChildNodes()
142
+ .map((x) => x.field.field)
143
+ .filter((x) => x == idField || oldValue?.[x] !== newValue[x])
144
+ .map((x) => [x, newValue[x]]);
145
+ return changed.length ? Object.fromEntries(changed) : undefined;
146
+ }
package/test/gen.ts ADDED
@@ -0,0 +1,220 @@
1
+ import fc, { Arbitrary } from "fast-check";
2
+ import {
3
+ CompoundField,
4
+ createSchemaLookup,
5
+ FieldType,
6
+ getTagParam,
7
+ isCompoundField,
8
+ makeSchemaDataNode,
9
+ SchemaDataNode,
10
+ SchemaField,
11
+ SchemaTags,
12
+ } from "../src";
13
+ import { newControl } from "@react-typed-forms/core";
14
+
15
+ export interface FieldAndValue {
16
+ field: SchemaField;
17
+ value: any;
18
+ }
19
+
20
+ export interface FieldAndValueChanged extends FieldAndValue {
21
+ newValue: any;
22
+ }
23
+
24
+ export function newIndexes(arr: number): Arbitrary<number[]> {
25
+ return fc.array(fc.integer(), { minLength: arr, maxLength: arr }).map((x) =>
26
+ x
27
+ .map((n, i) => ({ n, i }))
28
+ .sort((a, b) => a.n - b.n)
29
+ .map((x) => x.i),
30
+ );
31
+ }
32
+ export function valueAndSchema(
33
+ schemaOptions?: SchemaFieldGenOptions,
34
+ ): Arbitrary<FieldAndValueChanged> {
35
+ return randomSchemaField(schemaOptions)
36
+ .chain((schema) =>
37
+ randomValueForField(schema).map((value) => ({
38
+ field: schema,
39
+ value,
40
+ })),
41
+ )
42
+ .chain(changedValue);
43
+ }
44
+
45
+ export function changedValue(
46
+ val: FieldAndValue,
47
+ ): Arbitrary<FieldAndValueChanged> {
48
+ return changeValue(val.value, val.field, true).map((newValue) => ({
49
+ ...val,
50
+ newValue,
51
+ }));
52
+ }
53
+
54
+ export function makeDataNode(fv: FieldAndValue): SchemaDataNode {
55
+ return makeSchemaDataNode(
56
+ createSchemaLookup({ "": [fv.field] })
57
+ .getSchema("")!
58
+ .getChildNode(fv.field.field)!,
59
+ newControl(fv.value),
60
+ );
61
+ }
62
+
63
+ export interface SchemaFieldGenOptions {
64
+ arrayChance?: number;
65
+ forceCompound?: boolean;
66
+ forceArray?: boolean;
67
+ compoundChance?: number;
68
+ idField?: boolean;
69
+ }
70
+ function randomSchemaField(
71
+ options: SchemaFieldGenOptions = {},
72
+ ): Arbitrary<SchemaField> {
73
+ const {
74
+ arrayChance = 5,
75
+ compoundChance = 10,
76
+ forceCompound,
77
+ forceArray,
78
+ idField,
79
+ } = options;
80
+ const nextOptions = { arrayChance, compoundChance };
81
+ const field = fc.oneof(
82
+ {
83
+ weight: forceCompound ? 100 : compoundChance,
84
+ arbitrary: fc.constant(FieldType.Compound),
85
+ },
86
+ {
87
+ weight: forceCompound ? 0 : 100 - compoundChance,
88
+ arbitrary: fc.constantFrom(
89
+ FieldType.String,
90
+ FieldType.Int,
91
+ FieldType.Double,
92
+ FieldType.Bool,
93
+ FieldType.Date,
94
+ FieldType.DateTime,
95
+ FieldType.Time,
96
+ ),
97
+ },
98
+ );
99
+ const collection = fc.oneof(
100
+ {
101
+ weight: forceArray ? 0 : 100 - arrayChance,
102
+ arbitrary: fc.constant(false),
103
+ },
104
+ {
105
+ weight: forceArray ? 100 : arrayChance,
106
+ arbitrary: fc.constant(true),
107
+ },
108
+ );
109
+
110
+ const withoutId = field.chain((fieldType) =>
111
+ fc.record({
112
+ field: fc.string(),
113
+ type: fc.constant(fieldType),
114
+ collection,
115
+ notNullable: fc.boolean(),
116
+ children:
117
+ fieldType == FieldType.Compound
118
+ ? fc
119
+ .array(randomSchemaField(nextOptions), {
120
+ minLength: 1,
121
+ maxLength: 10,
122
+ })
123
+ .map((x) =>
124
+ Object.values(Object.fromEntries(x.map((y) => [y.field, y]))),
125
+ )
126
+ : fc.constant(null),
127
+ }),
128
+ );
129
+ return !idField
130
+ ? withoutId
131
+ : withoutId.chain((x) =>
132
+ fc.integer({ min: 0, max: (x.children?.length ?? 0) - 1 }).map((i) => ({
133
+ ...x,
134
+ tags: [SchemaTags.IdField + x.children![i].field],
135
+ })),
136
+ );
137
+ }
138
+ function randomValueForField(
139
+ f: SchemaField,
140
+ element?: boolean,
141
+ ): Arbitrary<any> {
142
+ return fc.integer({ min: 0, max: 100 }).chain(((nc) => {
143
+ if (nc <= 75 || f.notNullable) {
144
+ if (!element && f.collection) {
145
+ return fc.array(randomValueForField(f, true), {
146
+ minLength: 0,
147
+ maxLength: 10,
148
+ });
149
+ }
150
+ if (f.type === FieldType.String) return fc.string();
151
+ if (f.type === FieldType.Int) return fc.integer();
152
+ if (f.type === FieldType.Double) return fc.double({ noNaN: true });
153
+ if (f.type === FieldType.Bool) return fc.boolean();
154
+ if (f.type === FieldType.Date)
155
+ return fc.date().map((x) => x.toISOString().substring(0, 10));
156
+ if (f.type === FieldType.DateTime)
157
+ return fc.date().map((x) => x.toISOString());
158
+ if (f.type === FieldType.Time)
159
+ return fc.date().map((x) => x.toISOString().substring(11));
160
+ if (isCompoundField(f))
161
+ return fc.record(
162
+ Object.fromEntries(
163
+ f.children.map((x) => [x.field, randomValueForField(x)]),
164
+ ),
165
+ );
166
+ }
167
+ return fc.constantFrom(null);
168
+ }) as (x: number) => Arbitrary<any>);
169
+ }
170
+
171
+ export function changeValue(
172
+ value: any,
173
+ field: SchemaField,
174
+ forceChange: boolean | undefined,
175
+ element?: boolean,
176
+ ): Arbitrary<any> {
177
+ return fc.boolean().chain((shouldChange) => {
178
+ if (forceChange === false || (!shouldChange && !forceChange))
179
+ return fc.constant(value);
180
+ if (field.collection && !element) {
181
+ if (!value || !value.length)
182
+ return changeValue(undefined, field, true, true).map((x) => [x]);
183
+ return (value as any[]).reduce(
184
+ (acc, x) =>
185
+ acc.chain((nx: any[]) =>
186
+ changeValue(x, field, undefined, true).map((v) => [...nx, v]),
187
+ ),
188
+ fc.constant([] as any[]),
189
+ );
190
+ }
191
+ if (isCompoundField(field)) {
192
+ return fc.record(
193
+ Object.fromEntries(
194
+ field.children.map((x) => [
195
+ x.field,
196
+ changeValue(value?.[x.field], x, value == null ? true : undefined),
197
+ ]),
198
+ ),
199
+ );
200
+ }
201
+ return fc.constant(changePrim() as any);
202
+ function changePrim() {
203
+ switch (field.type) {
204
+ case FieldType.String:
205
+ case FieldType.Date:
206
+ case FieldType.DateTime:
207
+ case FieldType.Time:
208
+ return (value ?? "") + "x";
209
+ case FieldType.Int:
210
+ const v = value ?? 0;
211
+ return !v ? 1 : -v;
212
+ case FieldType.Double:
213
+ const dv = value ?? 0;
214
+ return !dv ? 1 : -dv;
215
+ case FieldType.Bool:
216
+ return !(value ?? false);
217
+ }
218
+ }
219
+ });
220
+ }
package/test/play.ts ADDED
@@ -0,0 +1,54 @@
1
+ import {
2
+ changeValue,
3
+ FieldAndValue,
4
+ FieldAndValueChanged,
5
+ makeDataNode,
6
+ } from "./gen";
7
+ import { getDiffObject } from "../src";
8
+ import { updateElements } from "@react-typed-forms/core";
9
+
10
+ const testData = [
11
+ {
12
+ field: {
13
+ field: ")W",
14
+ type: "Compound",
15
+ collection: true,
16
+ notNullable: true,
17
+ children: [
18
+ {
19
+ field: "4N`rCgnDXm",
20
+ type: "DateTime",
21
+ collection: false,
22
+ notNullable: false,
23
+ children: null,
24
+ },
25
+ ],
26
+ },
27
+ value: [
28
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Z" },
29
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Z" },
30
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Z" },
31
+ ],
32
+ newValue: [
33
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Zx" },
34
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Z" },
35
+ { "4N`rCgnDXm": "1970-01-01T00:00:00.000Zz" },
36
+ ],
37
+ } as FieldAndValueChanged,
38
+ [2, 0, 1],
39
+ ] as [FieldAndValueChanged, number[]];
40
+
41
+ const [fv, indexes] = testData;
42
+ const dataNode = makeDataNode(fv);
43
+ const arrayControl = dataNode.control!;
44
+
45
+ const newValue = fv.newValue as any[];
46
+ updateElements(arrayControl.as<any[]>(), (x) => {
47
+ const sorted = indexes.map((ni, i) => {
48
+ const c = x[ni];
49
+ c.value = newValue[i];
50
+ return c;
51
+ });
52
+ return sorted;
53
+ });
54
+ console.log(getDiffObject(dataNode));
package/tsconfig.json CHANGED
@@ -13,7 +13,7 @@
13
13
  "moduleResolution": "node",
14
14
  "resolveJsonModule": true,
15
15
  "isolatedModules": true,
16
- "jsx": "preserve",
16
+ "jsx": "react",
17
17
  "outDir": "lib"
18
18
  },
19
19
  "include": ["src/**/*.ts", "src/**/*.tsx"],