cabloy 5.1.207 → 5.1.209
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.
- package/.cabloy-version +1 -1
- package/CHANGELOG.md +13 -0
- package/README.md +1 -0
- package/package.json +1 -1
- package/repo-agent-governance/managed-assets.json +9 -9
- package/repo-agent-governance/policies/repository.md +1 -1
- package/repo-agent-governance/skills/cabloy-workflow/SKILL.md +4 -2
- package/repo-agent-governance/skills/cabloy-workflow/evals/evals.json +13 -1
- package/repo-docs/backend/unit-testing.md +1 -1
- package/repo-docs/frontend/form-guide.md +17 -1
- package/repo-docs/frontend/model-architecture.md +4 -0
- package/repo-docs/frontend/model-state-guide.md +23 -2
- package/repo-docs/frontend/resource-entry-page-deep-dive.md +119 -0
- package/repo-docs/frontend/scripts.md +3 -3
- package/repo-docs/frontend/zova-form-under-the-hood.md +5 -0
- package/repo-docs/fullstack/parallel-worktree-environment.md +3 -3
- package/repo-docs/fullstack/quickstart.md +11 -1
- package/repo-docs/reference/repo-scripts.md +19 -5
- package/repo-e2e/specs/cabloy-basic.spec.ts +1577 -0
- package/vona/packages-vona/vona/package.json +1 -1
- package/vona/src/suite/a-home/modules/home-user/src/controller/passport.ts +1 -0
- package/vona/src/suite/a-home/modules/home-user/src/dto/passport.ts +4 -0
- package/vona/src/suite/a-home/modules/home-user/test/passport.test.ts +172 -0
- package/vona/src/suite/a-home/modules/home-user/test/role.test.ts +1 -0
- package/vona/src/suite-vendor/a-cabloy/modules/a-rbac/package.json +1 -1
- package/vona/src/suite-vendor/a-cabloy/package.json +2 -2
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/modules/a-openapi/src/types/rest.ts +1 -0
- package/vona/src/suite-vendor/a-vona/modules/a-permission/package.json +1 -1
- package/vona/src/suite-vendor/a-vona/package.json +1 -1
- package/zova/packages-cli/cli/package.json +2 -2
- package/zova/packages-cli/cli-set-front/cli/templates/rest/render.ts +2 -0
- package/zova/packages-cli/cli-set-front/cli/templates/rest/rest.ts +9 -0
- package/zova/packages-cli/cli-set-front/package.json +1 -1
- package/zova/packages-zova/zova/package.json +2 -2
- package/zova/src/suite/a-home/modules/home-api/src/.metadata/index.ts +17 -17
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/schemas.ts +68 -52
- package/zova/src/suite/a-home/modules/home-api/src/api/openapi/types.ts +524 -343
- package/zova/src/suite/a-home/modules/home-passport/src/model/passport.ts +45 -11
- package/zova/src/suite/a-home/modules/home-passport/test/lib/persistenceIdentity.test.ts +229 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/package.json +9 -1
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/.metadata/index.ts +81 -5
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/bean/bean.draftCoordinator.ts +158 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockForm/controller.tsx +11 -3
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/component/blockPageEntry/controller.tsx +438 -8
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/config.ts +11 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/en-us.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/config/locale/zh-cn.ts +9 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/lib/draftSchema.ts +649 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/model/draft.ts +59 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/monkey.ts +11 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/src/types/draft.ts +49 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftDate.test.ts +262 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLifecycle.test.ts +1073 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftLoader.mjs +13 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftModel.test.ts +572 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchema.test.ts +427 -0
- package/zova/src/suite/cabloy-basic/modules/basic-pageentry/test/lib/draftSchemaSafety.test.ts +200 -0
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/package.json +1 -1
- package/zova/src/suite-vendor/a-cabloy/modules/rest-resource/src/page/entry/controller.tsx +6 -1
- package/zova/src/suite-vendor/a-cabloy/package.json +2 -2
- package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/form/controller.tsx +86 -1
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +88 -13
- package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/schemaRefresh.ts +751 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefresh.test.ts +637 -0
- package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefreshLifecycle.test.ts +994 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.persister.ts +331 -87
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.query.ts +11 -6
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useQuery.ts +3 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/bean/bean.model/bean.model.useState.ts +17 -1
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/modelPersistence.ts +204 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/lib/persistenceQueue.ts +50 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/monkey.ts +5 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/service/storage.ts +47 -2
- package/zova/src/suite-vendor/a-zova/modules/a-model/src/types/query.ts +3 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelLoader.mjs +34 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/modelPersistence.test.ts +727 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceQueue.test.ts +137 -0
- package/zova/src/suite-vendor/a-zova/modules/a-model/test/lib/persistenceTransition.test.ts +129 -0
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/monkey.ts +7 -5
- package/zova/src/suite-vendor/a-zova/modules/a-openapi/src/types/rest.ts +2 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/bean/bean.router.ts +14 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/lib/routeDiscard.ts +26 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/service/routerGuards.ts +6 -3
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/index.ts +1 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/src/types/routeDiscard.ts +24 -0
- package/zova/src/suite-vendor/a-zova/modules/a-router/test/lib/routeDiscard.test.ts +105 -0
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/package.json +1 -1
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/src/model/tabs.ts +202 -30
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabs.test.ts +524 -0
- package/zova/src/suite-vendor/a-zova/modules/a-routertabs/test/lib/tabsLoader.mjs +35 -0
- package/zova/src/suite-vendor/a-zova/package.json +6 -6
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import type { IMonkeyAppInitialize } from 'zova';
|
|
2
|
+
|
|
3
|
+
import { BeanSimple } from 'zova';
|
|
4
|
+
|
|
5
|
+
export class Monkey extends BeanSimple implements IMonkeyAppInitialize {
|
|
6
|
+
async appInitialize() {
|
|
7
|
+
if (process.env.CLIENT) {
|
|
8
|
+
await this.bean._getBean('basic-pageentry.bean.draftCoordinator', true);
|
|
9
|
+
}
|
|
10
|
+
}
|
|
11
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SchemaObject } from 'openapi3-ts/oas31';
|
|
2
|
+
import type { ModelPersistenceToken } from 'zova-module-a-model';
|
|
3
|
+
import type { ISchemaObjectExtensionField, TypeFormSchemaScene } from 'zova-module-a-openapi';
|
|
4
|
+
|
|
5
|
+
import type { ModelDraft } from '../model/draft.js';
|
|
6
|
+
|
|
7
|
+
export interface IDraftIdentity {
|
|
8
|
+
context: ModelPersistenceToken;
|
|
9
|
+
resource: string;
|
|
10
|
+
mode: 'create' | 'edit';
|
|
11
|
+
id?: string | number;
|
|
12
|
+
path: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface IDraftEditor {
|
|
16
|
+
key: string;
|
|
17
|
+
generation: number;
|
|
18
|
+
owner: ModelDraft;
|
|
19
|
+
identity: IDraftIdentity;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface IDraftSchemaOptions {
|
|
23
|
+
scene: TypeFormSchemaScene;
|
|
24
|
+
loadProperties: (
|
|
25
|
+
schema: SchemaObject,
|
|
26
|
+
scene: TypeFormSchemaScene,
|
|
27
|
+
) => ISchemaObjectExtensionField[] | undefined;
|
|
28
|
+
resolveSchema: (reference: string) => SchemaObject | undefined;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
export interface IDraftRelationRow {
|
|
32
|
+
id?: string | number;
|
|
33
|
+
deleted?: boolean;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export interface IDraftProjection {
|
|
37
|
+
values: Record<string, unknown>;
|
|
38
|
+
/** JSON-encoded paths distinguish nested rows from literal dotted keys. */
|
|
39
|
+
relations: Record<string, IDraftRelationRow[]>;
|
|
40
|
+
undefinedPaths: string[];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export interface IDraftEnvelope {
|
|
44
|
+
version: 1;
|
|
45
|
+
savedAt: number;
|
|
46
|
+
schemaSignature: string;
|
|
47
|
+
baselineSignature?: string;
|
|
48
|
+
projection: IDraftProjection;
|
|
49
|
+
}
|
|
@@ -0,0 +1,262 @@
|
|
|
1
|
+
import type { SchemaObject } from 'openapi3-ts/oas31';
|
|
2
|
+
import type { ISchemaObjectExtensionField } from 'zova-module-a-openapi';
|
|
3
|
+
|
|
4
|
+
import assert from 'node:assert/strict';
|
|
5
|
+
import test from 'node:test';
|
|
6
|
+
|
|
7
|
+
import {
|
|
8
|
+
createDraftSchema,
|
|
9
|
+
draftBaselineSignature,
|
|
10
|
+
projectDraftValues,
|
|
11
|
+
restoreDraftValues,
|
|
12
|
+
} from '../../src/lib/draftSchema.js';
|
|
13
|
+
|
|
14
|
+
const dateTime = { type: 'string', format: 'date-time' } satisfies SchemaObject;
|
|
15
|
+
const iso = '2026-10-15T08:30:00.000Z';
|
|
16
|
+
const previous = '2026-10-14T08:30:00.000Z';
|
|
17
|
+
|
|
18
|
+
function compile(properties: Record<string, any>, refs: Record<string, SchemaObject> = {}) {
|
|
19
|
+
return createDraftSchema(
|
|
20
|
+
{ type: 'object', properties },
|
|
21
|
+
{
|
|
22
|
+
scene: 'form',
|
|
23
|
+
resolveSchema: reference => refs[reference],
|
|
24
|
+
loadProperties(schema) {
|
|
25
|
+
return Object.entries(schema.properties ?? {}).map(
|
|
26
|
+
([key, value]) => ({ ...value, key }) as ISchemaObjectExtensionField,
|
|
27
|
+
);
|
|
28
|
+
},
|
|
29
|
+
},
|
|
30
|
+
);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function roundTrip(value: unknown) {
|
|
34
|
+
return JSON.parse(JSON.stringify(value));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
test('draft date-time encodes native Dates and recovers ISO strings over the edit baseline', () => {
|
|
38
|
+
const compiled = compile({ trainingTime: dateTime });
|
|
39
|
+
const projected = projectDraftValues(compiled, { trainingTime: new Date(iso) });
|
|
40
|
+
assert.deepEqual(projected.values, { trainingTime: iso });
|
|
41
|
+
const restored = restoreDraftValues(compiled, roundTrip(projected), {
|
|
42
|
+
trainingTime: new Date(previous),
|
|
43
|
+
});
|
|
44
|
+
assert.deepEqual(restored, { trainingTime: iso });
|
|
45
|
+
assert.deepEqual(projectDraftValues(compiled, restored), projected);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test('draft date-time supports safe refs and unambiguous nullable scalar unions', () => {
|
|
49
|
+
for (const schema of [
|
|
50
|
+
dateTime,
|
|
51
|
+
{ ...dateTime, type: ['string', 'null'] },
|
|
52
|
+
{ $ref: '#Date' },
|
|
53
|
+
{ $ref: '#Date', type: ['string', 'null'] },
|
|
54
|
+
{ anyOf: [dateTime, { type: 'null' }] },
|
|
55
|
+
{ oneOf: [{ $ref: '#Date' }, { const: null }] },
|
|
56
|
+
{ anyOf: [dateTime, { ...dateTime, minLength: 1 }, { enum: [null] }] },
|
|
57
|
+
{ anyOf: [{ anyOf: [dateTime, { type: 'null' }] }, { type: 'null' }] },
|
|
58
|
+
]) {
|
|
59
|
+
const compiled = compile({ value: schema }, { '#Date': dateTime });
|
|
60
|
+
assert.deepEqual(projectDraftValues(compiled, { value: new Date(iso) }).values, { value: iso });
|
|
61
|
+
assert.deepEqual(
|
|
62
|
+
restoreDraftValues(compiled, roundTrip(projectDraftValues(compiled, { value: null })), {}),
|
|
63
|
+
{ value: null },
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
test('draft date-time does not infer Date eligibility from ambiguous alternatives or metadata', () => {
|
|
69
|
+
for (const schema of [
|
|
70
|
+
{ type: 'string' },
|
|
71
|
+
{ type: 'string', format: 'date' },
|
|
72
|
+
{ type: ['string', 'number'], format: 'date-time' },
|
|
73
|
+
{ enum: [iso], format: 'date-time' },
|
|
74
|
+
{ type: 'string', default: iso, rest: { render: 'basic-date:formFieldDate' } },
|
|
75
|
+
{ anyOf: [dateTime, { type: 'string' }] },
|
|
76
|
+
{ anyOf: [dateTime, { type: 'number' }, { type: 'null' }] },
|
|
77
|
+
{ anyOf: [dateTime, { type: 'string', format: 'date' }] },
|
|
78
|
+
{ $ref: '#Date', format: 'date' },
|
|
79
|
+
{ $ref: '#Date', type: 'number' },
|
|
80
|
+
{ type: 'number', anyOf: [dateTime, { type: 'null' }] },
|
|
81
|
+
{ format: 'date', anyOf: [dateTime, { type: 'null' }] },
|
|
82
|
+
{ type: 'object', properties: {} },
|
|
83
|
+
]) {
|
|
84
|
+
const compiled = compile({ value: schema }, { '#Date': dateTime });
|
|
85
|
+
assert.deepEqual(
|
|
86
|
+
projectDraftValues(compiled, { value: new Date(iso) }).values,
|
|
87
|
+
{},
|
|
88
|
+
JSON.stringify(schema),
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test('draft date-time keeps unfinished primitives and missing/undefined/null distinctions', () => {
|
|
94
|
+
const compiled = compile({ value: dateTime });
|
|
95
|
+
for (const value of ['', 'unfinished', '2026-10', iso, previous, false, 0, null, undefined]) {
|
|
96
|
+
const projected = projectDraftValues(compiled, { value });
|
|
97
|
+
assert.deepEqual(restoreDraftValues(compiled, roundTrip(projected), { value: previous }), {
|
|
98
|
+
value,
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
const baseline = new Date(previous);
|
|
102
|
+
const restored = restoreDraftValues(compiled, projectDraftValues(compiled, {}), {
|
|
103
|
+
value: baseline,
|
|
104
|
+
});
|
|
105
|
+
assert.deepEqual(restored, { value: baseline });
|
|
106
|
+
assert.notEqual(restored?.value, baseline);
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test('draft date-time uses native Date operations without running serialization hooks', () => {
|
|
110
|
+
const compiled = compile({ value: dateTime });
|
|
111
|
+
const fail = () => {
|
|
112
|
+
throw new Error('Must not call user serialization');
|
|
113
|
+
};
|
|
114
|
+
const date = new Date(iso);
|
|
115
|
+
date.toJSON = fail;
|
|
116
|
+
date.toISOString = fail;
|
|
117
|
+
date.getTime = fail;
|
|
118
|
+
assert.deepEqual(projectDraftValues(compiled, { value: date }).values, { value: iso });
|
|
119
|
+
for (const value of [
|
|
120
|
+
new Date(Number.NaN),
|
|
121
|
+
Object.create(Date.prototype),
|
|
122
|
+
{ toJSON: fail, toISOString: fail },
|
|
123
|
+
[iso],
|
|
124
|
+
new Map([['value', iso]]),
|
|
125
|
+
Number.NaN,
|
|
126
|
+
Infinity,
|
|
127
|
+
2n,
|
|
128
|
+
]) {
|
|
129
|
+
const projected = projectDraftValues(compiled, { value });
|
|
130
|
+
assert.deepEqual(projected.values, {});
|
|
131
|
+
assert.deepEqual(
|
|
132
|
+
restoreDraftValues(compiled, { ...projected, values: { value } }, { value: previous }),
|
|
133
|
+
{ value: previous },
|
|
134
|
+
);
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
test('draft date-time retains exclusions on fields and every union/ref branch', () => {
|
|
139
|
+
for (const metadata of [
|
|
140
|
+
{ readOnly: true },
|
|
141
|
+
{ writeOnly: true },
|
|
142
|
+
{ rest: { draft: false } },
|
|
143
|
+
{ rest: { readonly: true } },
|
|
144
|
+
{ rest: { render: 'basic-file:formFieldFile' } },
|
|
145
|
+
{ contentEncoding: 'base64' },
|
|
146
|
+
]) {
|
|
147
|
+
for (const schema of [
|
|
148
|
+
{ ...dateTime, ...metadata },
|
|
149
|
+
{ $ref: '#Date', ...metadata },
|
|
150
|
+
{ anyOf: [dateTime, { type: 'null', ...metadata }] },
|
|
151
|
+
{ oneOf: [dateTime, { $ref: '#Unsafe' }] },
|
|
152
|
+
]) {
|
|
153
|
+
const compiled = compile(
|
|
154
|
+
{ value: schema },
|
|
155
|
+
{ '#Date': dateTime, '#Unsafe': { ...dateTime, ...metadata } },
|
|
156
|
+
);
|
|
157
|
+
assert.deepEqual(projectDraftValues(compiled, { value: new Date(iso) }).values, {});
|
|
158
|
+
}
|
|
159
|
+
}
|
|
160
|
+
assert.deepEqual(
|
|
161
|
+
projectDraftValues(compile({ accessToken: dateTime }), { accessToken: new Date(iso) }).values,
|
|
162
|
+
{},
|
|
163
|
+
);
|
|
164
|
+
});
|
|
165
|
+
|
|
166
|
+
const record = {
|
|
167
|
+
type: 'object',
|
|
168
|
+
properties: {
|
|
169
|
+
id: { type: 'number', readOnly: true },
|
|
170
|
+
name: { type: 'string' },
|
|
171
|
+
trainingTime: dateTime,
|
|
172
|
+
serverTime: { ...dateTime, readOnly: true },
|
|
173
|
+
},
|
|
174
|
+
};
|
|
175
|
+
|
|
176
|
+
test('draft nested relation dates retain row identity, clearing and detached baseline dates', () => {
|
|
177
|
+
const compiled = compile({ trainingRecords: { type: 'array', items: record } });
|
|
178
|
+
const baseline = {
|
|
179
|
+
trainingRecords: [
|
|
180
|
+
{ id: 1, name: 'first', trainingTime: new Date(previous), serverTime: new Date(previous) },
|
|
181
|
+
{ id: 2, name: 'second', trainingTime: new Date(previous), serverTime: new Date(iso) },
|
|
182
|
+
],
|
|
183
|
+
};
|
|
184
|
+
const projected = projectDraftValues(
|
|
185
|
+
compiled,
|
|
186
|
+
{
|
|
187
|
+
trainingRecords: [
|
|
188
|
+
{ id: 2, name: 'edited', trainingTime: new Date(iso), serverTime: new Date(0) },
|
|
189
|
+
{ id: 1, name: 'cleared', trainingTime: undefined },
|
|
190
|
+
{ name: 'new', trainingTime: new Date(iso) },
|
|
191
|
+
],
|
|
192
|
+
},
|
|
193
|
+
baseline,
|
|
194
|
+
);
|
|
195
|
+
assert.deepEqual(projected.values, {
|
|
196
|
+
trainingRecords: [
|
|
197
|
+
{ name: 'edited', trainingTime: iso },
|
|
198
|
+
{ name: 'cleared' },
|
|
199
|
+
{ name: 'new', trainingTime: iso },
|
|
200
|
+
],
|
|
201
|
+
});
|
|
202
|
+
const restored = restoreDraftValues(compiled, roundTrip(projected), {
|
|
203
|
+
trainingRecords: baseline.trainingRecords.toReversed(),
|
|
204
|
+
});
|
|
205
|
+
assert.deepEqual(restored, {
|
|
206
|
+
trainingRecords: [
|
|
207
|
+
{ id: 2, name: 'edited', trainingTime: iso, serverTime: new Date(iso) },
|
|
208
|
+
{ id: 1, name: 'cleared', trainingTime: undefined, serverTime: new Date(previous) },
|
|
209
|
+
{ name: 'new', trainingTime: iso },
|
|
210
|
+
],
|
|
211
|
+
});
|
|
212
|
+
const serverTime = restored?.trainingRecords[0].serverTime;
|
|
213
|
+
assert.notEqual(serverTime, baseline.trainingRecords[1].serverTime);
|
|
214
|
+
serverTime?.setTime(0);
|
|
215
|
+
assert.equal(baseline.trainingRecords[1].serverTime.toISOString(), iso);
|
|
216
|
+
assert.equal(
|
|
217
|
+
restoreDraftValues(compiled, roundTrip(projected), {
|
|
218
|
+
trainingRecords: [baseline.trainingRecords[0]],
|
|
219
|
+
}),
|
|
220
|
+
undefined,
|
|
221
|
+
);
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
test('draft baseline signatures include dates and retain relation reorder equivalence', () => {
|
|
225
|
+
const compiled = compile({ trainingRecords: { type: 'array', items: record } });
|
|
226
|
+
const rows = [
|
|
227
|
+
{ id: 1, name: 'first', trainingTime: new Date(previous) },
|
|
228
|
+
{ id: 2, name: 'second', trainingTime: new Date(iso) },
|
|
229
|
+
];
|
|
230
|
+
const signature = draftBaselineSignature(compiled, { trainingRecords: rows });
|
|
231
|
+
assert.equal(signature, draftBaselineSignature(compiled, { trainingRecords: rows.toReversed() }));
|
|
232
|
+
assert.equal(signature, draftBaselineSignature(compiled, { trainingRecords: roundTrip(rows) }));
|
|
233
|
+
assert.equal(
|
|
234
|
+
signature,
|
|
235
|
+
draftBaselineSignature(compiled, {
|
|
236
|
+
trainingRecords: [rows[0], { ...rows[1], trainingTime: '2026-10-15T10:30:00+02:00' }],
|
|
237
|
+
}),
|
|
238
|
+
);
|
|
239
|
+
assert.notEqual(
|
|
240
|
+
signature,
|
|
241
|
+
draftBaselineSignature(compiled, {
|
|
242
|
+
trainingRecords: [rows[0], { ...rows[1], trainingTime: new Date(previous) }],
|
|
243
|
+
}),
|
|
244
|
+
);
|
|
245
|
+
assert.equal(
|
|
246
|
+
signature,
|
|
247
|
+
draftBaselineSignature(compiled, {
|
|
248
|
+
trainingRecords: rows.map(row => ({ ...row, serverTime: new Date(0) })),
|
|
249
|
+
}),
|
|
250
|
+
);
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test('draft date-time codec changes compatibility without making labels significant', () => {
|
|
254
|
+
const compiled = compile({ value: dateTime });
|
|
255
|
+
assert.equal(
|
|
256
|
+
compiled.signature,
|
|
257
|
+
compile({ value: { ...dateTime, title: '培训时间' } }).signature,
|
|
258
|
+
);
|
|
259
|
+
assert.notEqual(compiled.signature, compile({ value: { type: 'string' } }).signature);
|
|
260
|
+
// This same schema's descriptor before date-time projection had no codec marker.
|
|
261
|
+
assert.notEqual(compiled.signature, '329e741252b60096');
|
|
262
|
+
});
|