cabloy 5.1.209 → 5.1.211

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 (45) hide show
  1. package/.cabloy-version +1 -1
  2. package/CHANGELOG.md +13 -0
  3. package/package.json +2 -1
  4. package/repo-agent-governance/managed-assets.json +6 -6
  5. package/repo-agent-governance/skills/cabloy-contract-loop/SKILL.md +14 -8
  6. package/repo-agent-governance/skills/cabloy-contract-loop/references/verification-checklist.md +4 -1
  7. package/repo-agent-governance/tests/governance.test.mjs +65 -0
  8. package/repo-docs/ai/playbook-contract-regeneration.md +2 -0
  9. package/repo-docs/frontend/openapi-sdk-guide.md +23 -4
  10. package/repo-docs/fullstack/contract-loop-playbook.md +8 -0
  11. package/repo-docs/fullstack/openapi-to-sdk.md +12 -6
  12. package/repo-docs/reference/repo-scripts.md +3 -0
  13. package/scripts/bootstrapAgentGovernance.mjs +18 -0
  14. package/scripts/generateOpenapiIsolated.test.ts +207 -0
  15. package/scripts/generateOpenapiIsolated.ts +91 -0
  16. package/scripts/runIsolatedTest.test.ts +90 -0
  17. package/scripts/runIsolatedTest.ts +157 -82
  18. package/scripts/upgrade.test.ts +113 -0
  19. package/scripts/upgrade.ts +32 -0
  20. package/vona/packages-cli/cli/package.json +1 -1
  21. package/vona/packages-cli/cli-set-api/package.json +1 -1
  22. package/vona/packages-vona/vona/package.json +1 -1
  23. package/vona/packages-vona/vona-core/package.json +1 -1
  24. package/vona/packages-vona/vona-core/src/lib/core/testClaim.test.ts +8 -1
  25. package/vona/packages-vona/vona-core/src/lib/core/testClaim.ts +2 -2
  26. package/vona/packages-vona/vona-mock/package.json +1 -1
  27. package/vona/src/suite-vendor/a-vona/modules/a-core/package.json +1 -1
  28. package/vona/src/suite-vendor/a-vona/package.json +1 -1
  29. package/zova/openapi.config.test.ts +29 -0
  30. package/zova/openapi.config.ts +13 -4
  31. package/zova/packages-cli/cli/package.json +2 -2
  32. package/zova/packages-cli/cli-set-front/cli/templates/openapi/config/boilerplate/project/openapi.config.ts +13 -4
  33. package/zova/packages-cli/cli-set-front/package.json +1 -1
  34. package/zova/packages-zova/zova/package.json +2 -2
  35. package/zova/src/suite/a-training/modules/training-student/src/.metadata/index.ts +7 -7
  36. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/schemas.ts +12 -0
  37. package/zova/src/suite/a-training/modules/training-student/src/api/openapi/types.ts +173 -72
  38. package/zova/src/suite/a-training/modules/training-student/src/api/trainingStudent.ts +8 -1
  39. package/zova/src/suite/cabloy-basic/modules/basic-input/src/component/formFieldInput/controller.tsx +2 -1
  40. package/zova/src/suite-vendor/a-zova/modules/a-form/package.json +1 -1
  41. package/zova/src/suite-vendor/a-zova/modules/a-form/src/component/formField/controller.tsx +4 -5
  42. package/zova/src/suite-vendor/a-zova/modules/a-form/src/lib/schemaRefresh.ts +21 -12
  43. package/zova/src/suite-vendor/a-zova/modules/a-form/src/types/formField.ts +2 -0
  44. package/zova/src/suite-vendor/a-zova/modules/a-form/test/lib/schemaRefreshLifecycle.test.ts +242 -26
  45. package/zova/src/suite-vendor/a-zova/package.json +2 -2
@@ -82,6 +82,7 @@ export type ApiApiTrainingStudentdeleteForceResponseBody =
82
82
 
83
83
  @Api()
84
84
  export class ApiTrainingStudent extends BeanApiBase {
85
+ /** List Students */
85
86
  select(
86
87
  options?: {
87
88
  query?: ApiApiTrainingStudentselectRequestQuery;
@@ -93,6 +94,7 @@ export class ApiTrainingStudent extends BeanApiBase {
93
94
  );
94
95
  }
95
96
 
97
+ /** Create Student */
96
98
  create(body: ApiApiTrainingStudentcreateRequestBody, options?: IApiActionOptions) {
97
99
  return this.$fetch.post<any, ApiApiTrainingStudentcreateResponseBody>(
98
100
  ApiApiTrainingStudentcreatePath,
@@ -101,6 +103,7 @@ export class ApiTrainingStudent extends BeanApiBase {
101
103
  );
102
104
  }
103
105
 
106
+ /** View Student */
104
107
  view(
105
108
  options: {
106
109
  params: ApiApiTrainingStudentviewRequestParams;
@@ -112,6 +115,7 @@ export class ApiTrainingStudent extends BeanApiBase {
112
115
  );
113
116
  }
114
117
 
118
+ /** Delete Student */
115
119
  delete(
116
120
  options: {
117
121
  params: ApiApiTrainingStudentdeleteRequestParams;
@@ -123,6 +127,7 @@ export class ApiTrainingStudent extends BeanApiBase {
123
127
  );
124
128
  }
125
129
 
130
+ /** Update Student */
126
131
  update(
127
132
  body: ApiApiTrainingStudentupdateRequestBody,
128
133
  options: {
@@ -136,6 +141,7 @@ export class ApiTrainingStudent extends BeanApiBase {
136
141
  );
137
142
  }
138
143
 
144
+ /** View Student Summary */
139
145
  summary(
140
146
  options: {
141
147
  params: ApiApiTrainingStudentsummaryRequestParams;
@@ -147,7 +153,7 @@ export class ApiTrainingStudent extends BeanApiBase {
147
153
  );
148
154
  }
149
155
 
150
- /** Bulk Delete */
156
+ /** Bulk Delete Students */
151
157
  deleteBulk(body: ApiApiTrainingStudentdeleteBulkRequestBody, options?: IApiActionOptions) {
152
158
  return this.$fetch.post<any, ApiApiTrainingStudentdeleteBulkResponseBody>(
153
159
  ApiApiTrainingStudentdeleteBulkPath,
@@ -156,6 +162,7 @@ export class ApiTrainingStudent extends BeanApiBase {
156
162
  );
157
163
  }
158
164
 
165
+ /** Permanently Delete Student */
159
166
  deleteForce(
160
167
  options: {
161
168
  params: ApiApiTrainingStudentdeleteForceRequestParams;
@@ -5,7 +5,7 @@ import type { IResourceFormFieldOptionsBase } from 'zova-module-a-openapi';
5
5
  import { classes } from 'typestyle';
6
6
  import { BeanControllerBase } from 'zova';
7
7
  import { Controller } from 'zova-module-a-bean';
8
- import { ZFormField } from 'zova-module-a-form';
8
+ import { constFieldBindingSlot, ZFormField } from 'zova-module-a-form';
9
9
 
10
10
  declare module 'zova-module-a-openapi' {
11
11
  export interface IResourceFormFieldRecord {
@@ -37,6 +37,7 @@ export class ControllerFormFieldInput extends BeanControllerBase {
37
37
  return (
38
38
  <ZFormField
39
39
  {...this.$props}
40
+ {...{ [constFieldBindingSlot]: true }}
40
41
  slotDefault={({ propsBucket, props }, $$formField) => {
41
42
  const className = !propsBucket.needHandleBorder
42
43
  ? props.class
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-module-a-form",
3
- "version": "5.1.52",
3
+ "version": "5.1.54",
4
4
  "gitHead": "09d901d17140a80ee0764211b441cda72fd94663",
5
5
  "description": "",
6
6
  "keywords": [
@@ -12,6 +12,7 @@ import type { ControllerForm } from '../form/controller.jsx';
12
12
  import { schemaRefreshFingerprint } from '../../lib/schemaRefresh.js';
13
13
  import { TypeFormField } from '../../types/form.js';
14
14
  import {
15
+ constFieldBindingSlot,
15
16
  constFieldProps,
16
17
  IFormFieldComponentOptions,
17
18
  IFormFieldOptions,
@@ -105,6 +106,7 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
105
106
  props.behaviors,
106
107
  !!props.onEffect,
107
108
  !!props.slotDefault,
109
+ props[constFieldBindingSlot] === true,
108
110
  ]);
109
111
  }
110
112
 
@@ -118,12 +120,9 @@ export class ControllerFormField<TParentData extends {} = {}> extends BeanContro
118
120
  return;
119
121
  }
120
122
  const props = this.$props;
121
- // Basic Input owns its binding slot; direct custom slots remain opaque.
123
+ // Only explicitly renderer-owned binding slots qualify; custom slots remain opaque.
122
124
  const inputBindingSlot =
123
- props[constFieldProps] === true &&
124
- (props.render
125
- ? this.$$form.getRenderProvider(props.render)
126
- : this.formProvider.components?.Input) === 'basic-input:formFieldInput';
125
+ props[constFieldProps] === true && props[constFieldBindingSlot] === true;
127
126
  const eligible =
128
127
  !props.sys &&
129
128
  !props.behaviors &&
@@ -238,18 +238,16 @@ function opaqueBehavior(value: unknown): boolean {
238
238
  );
239
239
  }
240
240
 
241
+ const pageEntryBlockOptions = ['blocks', 'resource', 'id', 'formScene', 'pageTitleKey', 'draft'];
241
242
  const blockOptions: Record<string, string[]> = {
242
- 'basic-pageentry:blockPageEntry': [
243
- 'blocks',
244
- 'resource',
245
- 'id',
246
- 'formScene',
247
- 'pageTitleKey',
248
- 'draft',
249
- ],
243
+ 'basic-pageentry:blockPageEntry': pageEntryBlockOptions,
244
+ 'start-pageentry:blockPageEntry': pageEntryBlockOptions,
250
245
  'basic-pageentry:blockForm': ['blocks'],
246
+ 'start-pageentry:blockForm': ['blocks'],
251
247
  'basic-form:blockFormLayout': ['formLayout'],
248
+ 'start-form:blockFormLayout': ['formLayout'],
252
249
  'basic-pageentry:blockToolbarRow': ['actions'],
250
+ 'start-pageentry:blockToolbarRow': ['actions'],
253
251
  };
254
252
 
255
253
  function safeLayoutNode(value: unknown): boolean {
@@ -317,7 +315,7 @@ function safeBlocks(value: unknown): boolean {
317
315
  );
318
316
  }
319
317
 
320
- // Only Basic's declared layout grammar owns these title strings. Rest data/options,
318
+ // Only Basic's and Start's declared layout grammar owns these title strings. Rest data/options,
321
319
  // action metadata, descriptions, field names, and unknown structures stay strict.
322
320
  function normalizeLayoutNode(value: unknown): unknown {
323
321
  if (!isRecord(value)) return value;
@@ -334,7 +332,12 @@ function normalizeBlocks(value: unknown): unknown {
334
332
  if (!isRecord(block) || !isRecord(block.options)) return block;
335
333
  const options = { ...block.options };
336
334
  if (Object.hasOwn(options, 'blocks')) options.blocks = normalizeBlocks(options.blocks);
337
- if (block.render === 'basic-form:blockFormLayout' && safeLayout(options.formLayout)) {
335
+ if (
336
+ ['basic-form:blockFormLayout', 'start-form:blockFormLayout'].includes(
337
+ block.render as string,
338
+ ) &&
339
+ safeLayout(options.formLayout)
340
+ ) {
338
341
  const layout = options.formLayout as Record<string, unknown>;
339
342
  options.formLayout = { children: (layout.children as unknown[]).map(normalizeLayoutNode) };
340
343
  }
@@ -425,7 +428,13 @@ export function createSchemaRefreshContract(
425
428
  const form = isRecord(rest.form) ? rest.form : undefined;
426
429
  const render = rest.render ?? form?.render;
427
430
  const renderType = isRecord(render) ? render.type : render;
428
- if (renderType !== 'basic-details:formFieldDetails') continue;
431
+ if (
432
+ !['basic-details:formFieldDetails', 'start-details:formFieldDetails'].includes(
433
+ String(renderType),
434
+ )
435
+ ) {
436
+ continue;
437
+ }
429
438
  for (const name of [
430
439
  property.key,
431
440
  property.schemaKey,
@@ -434,7 +443,7 @@ export function createSchemaRefreshContract(
434
443
  if (typeof name === 'string') detailNames.add(name);
435
444
  }
436
445
  }
437
- // Basic Details resolves its row schema through the exact hidden `_name` auxiliary.
446
+ // Basic and Start Details resolve row schemas through the exact hidden `_name` auxiliary.
438
447
  // It belongs to that nested form, not to an unrelated parent field's behavior scope.
439
448
  const auxiliaryDetails = new Set<string>();
440
449
  for (const property of effective) {
@@ -28,6 +28,8 @@ export type HTMLInputElementType =
28
28
  | 'tel'
29
29
  | 'email';
30
30
  export const constFieldProps = '$$FieldProps';
31
+ // Internal: the renderer supplies its own form-value binding slot.
32
+ export const constFieldBindingSlot = '$$FieldBindingSlot';
31
33
 
32
34
  export interface IFormFieldScope<TParentData extends {} = {}> {
33
35
  name: string;
@@ -22,7 +22,12 @@ function deferred<T = void>() {
22
22
 
23
23
  // Like basic-pageentry's draftModel harness, execute the current source rather than a
24
24
  // copied refresh algorithm. TSX requires transpileModule instead of native stripping.
25
- function evaluateClass(path: string, name: string, bindings: Record<string, unknown>) {
25
+ function evaluateClass(
26
+ path: string,
27
+ name: string,
28
+ bindings: Record<string, unknown>,
29
+ jsx = ts.JsxEmit.Preserve,
30
+ ) {
26
31
  const source = readFileSync(new URL(path, import.meta.url), 'utf8')
27
32
  .replace(/^import[\s\S]*?;\n/gm, '')
28
33
  .replace(/^\s*@(Controller|UseScope|Use)\([^\n]*\)\s*\n/gm, '');
@@ -32,7 +37,8 @@ function evaluateClass(path: string, name: string, bindings: Record<string, unkn
32
37
  compilerOptions: {
33
38
  target: ts.ScriptTarget.ES2022,
34
39
  module: ts.ModuleKind.ESNext,
35
- jsx: ts.JsxEmit.Preserve,
40
+ jsx,
41
+ jsxFactory: 'createElement',
36
42
  },
37
43
  });
38
44
  assert.deepEqual(
@@ -85,7 +91,14 @@ class ControllerBase {
85
91
  }
86
92
  }
87
93
 
88
- const constFieldProps = Symbol('constFieldProps');
94
+ const fieldTypes = ts.transpileModule(
95
+ readFileSync(new URL('../../src/types/formField.ts', import.meta.url), 'utf8'),
96
+ { compilerOptions: { module: ts.ModuleKind.CommonJS } },
97
+ ).outputText;
98
+ const { constFieldProps, constFieldBindingSlot } = new Function(
99
+ 'exports',
100
+ `${fieldTypes}\nreturn exports;`,
101
+ )({});
89
102
  const commonBindings = {
90
103
  markRaw: (value: unknown) => value,
91
104
  useSelector: (store: any, selector: (state: any) => any) => ({
@@ -95,8 +108,32 @@ const commonBindings = {
95
108
  isNil: (value: unknown) => value === undefined || value === null,
96
109
  schemaRefreshFingerprint,
97
110
  constFieldProps,
111
+ constFieldBindingSlot,
98
112
  };
99
113
 
114
+ const ZFormField = {};
115
+ const ControllerFormFieldInput = evaluateClass(
116
+ '../../../../../../suite/cabloy-basic/modules/basic-input/src/component/formFieldInput/controller.tsx',
117
+ 'ControllerFormFieldInput',
118
+ {
119
+ BeanControllerBase: ControllerBase,
120
+ constFieldBindingSlot,
121
+ ZFormField,
122
+ classes: (...values: unknown[]) => values.filter(Boolean).join(' '),
123
+ createElement: (type: unknown, props: any) => ({ type, props }),
124
+ },
125
+ ts.JsxEmit.React,
126
+ );
127
+
128
+ function inputBindingProps(props: any = {}) {
129
+ const input = new ControllerFormFieldInput();
130
+ input.$props = props;
131
+ const rendered = input.render();
132
+ assert.equal(rendered.type, ZFormField);
133
+ assert.equal(rendered.props[constFieldBindingSlot], true);
134
+ return rendered.props;
135
+ }
136
+
100
137
  const ControllerForm = evaluateClass('../../src/component/form/controller.tsx', 'ControllerForm', {
101
138
  ...commonBindings,
102
139
  BeanControllerFormBase: ControllerBase,
@@ -387,6 +424,118 @@ test(
387
424
  },
388
425
  );
389
426
 
427
+ test(
428
+ 'Start edit layout locale refresh preserves recovered input while localized custom options reset',
429
+ { concurrency: false },
430
+ async () => {
431
+ const nestedEffect = () => {};
432
+ const studentSchema = (locale: string) => {
433
+ const title = locale === 'en-us' ? 'Training Records' : '培训记录';
434
+ const result: any = schema(
435
+ locale,
436
+ {},
437
+ {
438
+ rest: {
439
+ blocks: [
440
+ {
441
+ render: 'start-pageentry:blockPageEntry',
442
+ options: {
443
+ blocks: [
444
+ {
445
+ render: 'start-pageentry:blockForm',
446
+ options: {
447
+ blocks: [
448
+ {
449
+ render: 'start-form:blockFormLayout',
450
+ options: {
451
+ formLayout: {
452
+ children: [
453
+ {
454
+ type: 'tabs',
455
+ children: [
456
+ {
457
+ type: 'tab',
458
+ title,
459
+ children: [{ type: 'field', name: 'name' }],
460
+ },
461
+ ],
462
+ },
463
+ ],
464
+ },
465
+ },
466
+ },
467
+ ],
468
+ },
469
+ },
470
+ {
471
+ render: 'start-pageentry:blockToolbarRow',
472
+ options: {
473
+ actions: [
474
+ {
475
+ render: 'start-form:actionSubmit',
476
+ options: { permission: { actionInherit: 'update' } },
477
+ },
478
+ ],
479
+ },
480
+ },
481
+ ],
482
+ },
483
+ },
484
+ ],
485
+ },
486
+ },
487
+ );
488
+ result.properties.level = {
489
+ type: 'number',
490
+ rest: {
491
+ form: {
492
+ render: 'training-student:formFieldLevel',
493
+ options: { items: [{ value: 1, title }] },
494
+ },
495
+ },
496
+ };
497
+ result.properties.trainingRecords = {
498
+ type: 'array',
499
+ rest: { form: { render: 'start-details:formFieldDetails' } },
500
+ items: {
501
+ type: 'object',
502
+ properties: {
503
+ subject: { type: 'string', rest: { onEffect: nestedEffect } },
504
+ },
505
+ },
506
+ };
507
+ return result;
508
+ };
509
+ await withRuntime(
510
+ { data: { name: 'baseline', level: 1 }, schema: studentSchema('en-us') },
511
+ async runtime => {
512
+ const name = await initializedField(runtime, inputBindingProps());
513
+ const level = await initializedField(runtime, {
514
+ name: 'level',
515
+ slotDefault: () => 'custom level options',
516
+ });
517
+ runtime.form.reset(
518
+ { name: 'recovered unsaved name', level: 2 },
519
+ { keepDefaultValues: true },
520
+ );
521
+ // Establish the post-recovery epoch before switching locales, like mounted fields do.
522
+ name.refresh();
523
+ level.refresh();
524
+ for (const locale of ['zh-cn', 'en-us']) {
525
+ runtime.form.form.setFieldValue('level', 2, { dontValidate: true });
526
+ runtime.events.length = 0;
527
+ runtime.select(locale, studentSchema(locale));
528
+ name.refresh();
529
+ level.refresh();
530
+ assert.equal(runtime.form.form.getFieldValue('name'), 'recovered unsaved name');
531
+ assert.equal(runtime.form.form.getFieldValue('level'), 1);
532
+ assert.deepEqual(runtime.changed, []);
533
+ }
534
+ },
535
+ );
536
+ },
537
+ );
538
+
390
539
  test(
391
540
  'locale replay preserves exact raw empty and falsy values',
392
541
  { concurrency: false },
@@ -732,6 +881,7 @@ test(
732
881
  const before: unknown[] = [];
733
882
  const after: unknown[] = [];
734
883
  const owner = await initializedField(runtime, {
884
+ [constFieldBindingSlot]: true,
735
885
  slotDefault: () => 'initial slot',
736
886
  onFieldStateReady: (field: any) => before.push(field.form.getFieldValue(field.name)),
737
887
  });
@@ -755,31 +905,30 @@ test(
755
905
  );
756
906
 
757
907
  test(
758
- 'canonical Basic input binding slots preserve live names across locale refresh',
908
+ 'explicit renderer-owned binding slots preserve live names independently of component selectors',
759
909
  { concurrency: false },
760
910
  async () => {
761
- for (const render of [
762
- undefined,
763
- 'Input',
764
- 'basic-input:formFieldInput',
765
- { $$typeof: 'zova-jsx:component', type: 'basic-input:formFieldInput' },
766
- ]) {
767
- await withRuntime({}, async runtime => {
768
- runtime.form.$$scopeOpenapi.config.formProvider = {
769
- components: { Input: 'basic-input:formFieldInput' },
770
- };
771
- const owner = await initializedField(runtime, {
772
- render,
773
- slotDefault: () => 'binding slot',
911
+ for (const selector of ['basic-input:formFieldInput', 'start-input:formFieldInput']) {
912
+ for (const render of [
913
+ undefined,
914
+ 'Input',
915
+ selector,
916
+ { $$typeof: 'zova-jsx:component', type: selector },
917
+ ]) {
918
+ await withRuntime({}, async runtime => {
919
+ runtime.form.$$scopeOpenapi.config.formProvider = {
920
+ components: { Input: selector },
921
+ };
922
+ const owner = await initializedField(runtime, inputBindingProps({ render }));
923
+ dirty(runtime, 'typed name');
924
+ runtime.select('zh-cn');
925
+ owner.refresh();
926
+ assert.deepEqual(lifecycle(runtime.events), ['update', 'reset', 'validate', 'ready']);
927
+ assert.equal(lastEvent(runtime.events).value, 'typed name');
928
+ assertResetMeta(lastEvent(runtime.events).meta);
929
+ assert.deepEqual(runtime.changed, []);
774
930
  });
775
- dirty(runtime, 'typed name');
776
- runtime.select('zh-cn');
777
- owner.refresh();
778
- assert.deepEqual(lifecycle(runtime.events), ['update', 'reset', 'validate', 'ready']);
779
- assert.equal(lastEvent(runtime.events).value, 'typed name');
780
- assertResetMeta(lastEvent(runtime.events).meta);
781
- assert.deepEqual(runtime.changed, []);
782
- });
931
+ }
783
932
  }
784
933
  },
785
934
  );
@@ -789,9 +938,13 @@ test(
789
938
  { concurrency: false },
790
939
  async () => {
791
940
  for (const props of [
792
- { [constFieldProps]: false, render: 'basic-input:formFieldInput' },
941
+ inputBindingProps({ [constFieldProps]: false, render: 'basic-input:formFieldInput' }),
942
+ { render: 'basic-input:formFieldInput' },
793
943
  { render: 'custom:formFieldInput' },
794
944
  { render: 'Input' },
945
+ {},
946
+ { [constFieldBindingSlot]: false },
947
+ { [constFieldBindingSlot]: 'true' },
795
948
  ]) {
796
949
  await withRuntime({}, async runtime => {
797
950
  runtime.form.$$scopeOpenapi.config.formProvider = {
@@ -812,6 +965,69 @@ test(
812
965
  },
813
966
  );
814
967
 
968
+ test('the input wrapper owns its marker and value/blur binding slot', () => {
969
+ const externalSlot = () => 'external slot';
970
+ const props = inputBindingProps({
971
+ name: 'name',
972
+ [constFieldBindingSlot]: false,
973
+ slotDefault: externalSlot,
974
+ });
975
+ assert.equal(props.name, 'name');
976
+ assert.notEqual(props.slotDefault, externalSlot);
977
+ const writes: unknown[] = [];
978
+ let blurred = false;
979
+ const input = props.slotDefault(
980
+ { propsBucket: { value: 'current', disableNotifyChanged: true }, props: {} },
981
+ {
982
+ setValue: (...args: unknown[]) => writes.push(args),
983
+ handleBlur: () => {
984
+ blurred = true;
985
+ },
986
+ },
987
+ );
988
+ assert.equal(input.type, 'input');
989
+ assert.equal(input.props.value, 'current');
990
+ input.props.onInput({ target: { value: 'edited' } });
991
+ input.props.onBlur();
992
+ assert.deepEqual(writes, [['edited', true]]);
993
+ assert.equal(blurred, true);
994
+ });
995
+
996
+ test(
997
+ 'a missing Input provider does not authorize an unmarked slot',
998
+ { concurrency: false },
999
+ async () => {
1000
+ await withRuntime({}, async runtime => {
1001
+ const owner = await initializedField(runtime, { slotDefault: () => 'custom slot' });
1002
+ dirty(runtime, 'typed name');
1003
+ runtime.select('zh-cn');
1004
+ owner.refresh();
1005
+ assert.deepEqual(lifecycle(runtime.events), ['update', 'reset', 'ready']);
1006
+ assert.equal(lastEvent(runtime.events).value, 'baseline');
1007
+ });
1008
+ },
1009
+ );
1010
+
1011
+ test(
1012
+ 'binding-slot ownership changes invalidate the prior refresh configuration',
1013
+ { concurrency: false },
1014
+ async () => {
1015
+ for (const initiallyOwned of [false, true]) {
1016
+ await withRuntime({}, async runtime => {
1017
+ const owner = await initializedField(runtime, {
1018
+ [constFieldBindingSlot]: initiallyOwned,
1019
+ slotDefault: () => 'slot',
1020
+ });
1021
+ dirty(runtime, 'typed name');
1022
+ owner.field.$props[constFieldBindingSlot] = !initiallyOwned;
1023
+ owner.refresh();
1024
+ assert.deepEqual(lifecycle(runtime.events), ['update', 'reset', 'ready']);
1025
+ assert.equal(lastEvent(runtime.events).value, 'baseline');
1026
+ });
1027
+ }
1028
+ },
1029
+ );
1030
+
815
1031
  test(
816
1032
  'disposal during awaited behavior initialization suppresses refresh and ready callbacks',
817
1033
  { concurrency: false },
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zova-suite-a-zova",
3
- "version": "5.1.167",
3
+ "version": "5.1.169",
4
4
  "gitHead": "2c5c19284bab738e492856189acb6fad74b8a7b7",
5
5
  "description": "zova",
6
6
  "license": "MIT",
@@ -15,7 +15,7 @@
15
15
  "zova-module-a-behaviors": "^5.1.22",
16
16
  "zova-module-a-command": "^5.1.35",
17
17
  "zova-module-a-fetch": "^5.1.25",
18
- "zova-module-a-form": "^5.1.52",
18
+ "zova-module-a-form": "^5.1.54",
19
19
  "zova-module-a-icon": "^5.1.27",
20
20
  "zova-module-a-interceptor": "^5.1.31",
21
21
  "zova-module-a-logger": "^5.1.28",