@cosmicdrift/kumiko-framework 0.257.0 → 0.258.1
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/package.json +3 -3
- package/src/__tests__/anonymous-access.integration.test.ts +3 -3
- package/src/__tests__/derived-fields.integration.test.ts +1 -1
- package/src/__tests__/entity-list-limits.integration.test.ts +1 -1
- package/src/__tests__/error-contract.integration.test.ts +1 -1
- package/src/__tests__/ownership-where-write-path.integration.test.ts +1 -1
- package/src/__tests__/ownership.integration.test.ts +7 -3
- package/src/__tests__/schema-cli-rebuild.integration.test.ts +2 -2
- package/src/__tests__/transition-guard.integration.test.ts +3 -3
- package/src/api/__tests__/api.test.ts +21 -3
- package/src/api/__tests__/batch.integration.test.ts +3 -3
- package/src/api/__tests__/body-limit.test.ts +7 -1
- package/src/api/__tests__/dispatcher-live.integration.test.ts +1 -1
- package/src/api/__tests__/http-route-rate-limit.integration.test.ts +7 -1
- package/src/api/__tests__/metrics-endpoint.test.ts +7 -1
- package/src/api/__tests__/nested-write.integration.test.ts +3 -3
- package/src/api/__tests__/server-boot-guards.test.ts +11 -4
- package/src/bun-db/__tests__/write-brand.test.ts +4 -1
- package/src/db/__tests__/apply-entity-event-tenant.integration.test.ts +1 -1
- package/src/db/__tests__/assert-exists-in.integration.test.ts +1 -1
- package/src/db/__tests__/assert-no-unreachable-live-rows.integration.test.ts +2 -2
- package/src/db/__tests__/collect-table-metas.test.ts +9 -5
- package/src/db/__tests__/compound-types.test.ts +1 -1
- package/src/db/__tests__/config-seed.integration.test.ts +3 -3
- package/src/db/__tests__/eagerload.integration.test.ts +7 -7
- package/src/db/__tests__/entity-table-from-registry.test.ts +8 -2
- package/src/db/__tests__/entity-table-meta-source.test.ts +7 -3
- package/src/db/__tests__/entity-table-types.test.ts +4 -4
- package/src/db/__tests__/event-store-executor-list.integration.test.ts +9 -2
- package/src/db/__tests__/event-store-executor-reference-search.integration.test.ts +4 -4
- package/src/db/__tests__/event-store-executor-reference-sort.integration.test.ts +3 -3
- package/src/db/__tests__/event-store-executor-write-verbs.integration.test.ts +4 -4
- package/src/db/__tests__/event-store-executor.integration.test.ts +11 -6
- package/src/db/__tests__/event-store-system-stream.integration.test.ts +2 -2
- package/src/db/__tests__/implicit-projection-equivalence.integration.test.ts +6 -4
- package/src/db/__tests__/located-timestamp.test.ts +1 -1
- package/src/db/__tests__/money.test.ts +1 -1
- package/src/db/__tests__/multi-row-insert.integration.test.ts +3 -3
- package/src/db/__tests__/parent-ref-clause.test.ts +213 -0
- package/src/db/__tests__/schema-migration.integration.test.ts +30 -14
- package/src/db/__tests__/source-shadow-create.integration.test.ts +1 -1
- package/src/db/__tests__/table-builder-indexes.test.ts +14 -12
- package/src/db/__tests__/table-builder-required.test.ts +2 -2
- package/src/db/__tests__/tenant-db-where-merge.integration.test.ts +1 -1
- package/src/db/__tests__/tenant-db-where-merge.test.ts +5 -2
- package/src/db/__tests__/tenant-db.integration.test.ts +2 -2
- package/src/db/__tests__/unique-violation-mapping.integration.test.ts +2 -2
- package/src/db/event-store-executor-read.ts +97 -19
- package/src/db/parent-ref-clause.ts +182 -0
- package/src/db/table-builder.ts +7 -0
- package/src/engine/__tests__/boot-validator-action-wiring.test.ts +50 -8
- package/src/engine/__tests__/boot-validator-detail-for.test.ts +28 -4
- package/src/engine/__tests__/boot-validator-entity-list.test.ts +20 -4
- package/src/engine/__tests__/boot-validator-i18n-keys.test.ts +9 -3
- package/src/engine/__tests__/boot-validator.test.ts +716 -91
- package/src/engine/__tests__/define-feature-entity-mapping.test.ts +2 -2
- package/src/engine/__tests__/engine.test.ts +128 -37
- package/src/engine/__tests__/entity-handlers.test.ts +3 -3
- package/src/engine/__tests__/entity-presave-wiring.integration.test.ts +4 -4
- package/src/engine/__tests__/entity-restore-tenant-isolation.integration.test.ts +1 -1
- package/src/engine/__tests__/entity-write-crosstenant.integration.test.ts +1 -1
- package/src/engine/__tests__/extend-entity-projection.test.ts +1 -1
- package/src/engine/__tests__/extends-registrar.test.ts +13 -3
- package/src/engine/__tests__/factories-long-text.test.ts +10 -6
- package/src/engine/__tests__/feature-crud-shorthand.test.ts +1 -1
- package/src/engine/__tests__/feature-crud-verb-access.integration.test.ts +1 -1
- package/src/engine/__tests__/multiselect-filter.integration.test.ts +1 -1
- package/src/engine/__tests__/nav.test.ts +1 -1
- package/src/engine/__tests__/pipeline-handler.integration.test.ts +1 -1
- package/src/engine/__tests__/pipeline-observability.integration.test.ts +1 -1
- package/src/engine/__tests__/pipeline-performance.integration.test.ts +1 -1
- package/src/engine/__tests__/projection-detail-actions.test.ts +6 -1
- package/src/engine/__tests__/projection-detail-singleton.test.ts +6 -1
- package/src/engine/__tests__/projection-detail-tabs.test.ts +6 -1
- package/src/engine/__tests__/projection-list-filter.integration.test.ts +7 -2
- package/src/engine/__tests__/projection.test.ts +1 -1
- package/src/engine/__tests__/registry.test.ts +13 -3
- package/src/engine/__tests__/schema-builder.test.ts +39 -11
- package/src/engine/__tests__/screen.test.ts +21 -15
- package/src/engine/__tests__/search-payload-extension.test.ts +15 -3
- package/src/engine/__tests__/store-table.test.ts +6 -3
- package/src/engine/__tests__/validate-projection-allowlist.test.ts +12 -4
- package/src/engine/__tests__/validation-hooks.test.ts +7 -1
- package/src/engine/boot-validator/__tests__/ownership.test.ts +2 -2
- package/src/engine/boot-validator/__tests__/parent-ref.test.ts +149 -0
- package/src/engine/boot-validator/action-wiring.ts +7 -7
- package/src/engine/boot-validator/index.ts +2 -0
- package/src/engine/boot-validator/parent-ref.ts +96 -0
- package/src/engine/boot-validator/projection-list-screens.ts +36 -23
- package/src/engine/boot-validator/query-output-columns.ts +2 -1
- package/src/engine/boot-validator/query-refs.ts +2 -1
- package/src/engine/boot-validator/screens.ts +322 -131
- package/src/engine/build-config-feature-schema.ts +2 -2
- package/src/engine/define-handler.ts +6 -0
- package/src/engine/entity-handlers.ts +6 -1
- package/src/engine/factories.ts +2 -0
- package/src/engine/feature-entity-handlers.ts +5 -0
- package/src/engine/index.ts +6 -0
- package/src/engine/ownership.ts +15 -0
- package/src/engine/pattern-library/mixed-schemas.ts +1 -0
- package/src/engine/screen-helpers.ts +25 -0
- package/src/engine/types/index.ts +5 -0
- package/src/es-ops/__tests__/runner.integration.test.ts +1 -1
- package/src/event-store/__tests__/upcaster.integration.test.ts +1 -1
- package/src/files/__tests__/file-field-pipeline.integration.test.ts +1 -1
- package/src/files/__tests__/files.integration.test.ts +1 -1
- package/src/files/file-ref-entity.ts +9 -9
- package/src/i18n/__tests__/i18n.test.ts +7 -1
- package/src/i18n/required-surface-keys.ts +48 -0
- package/src/migrations/__tests__/kumiko-drift.integration.test.ts +3 -1
- package/src/migrations/__tests__/pending-rebuilds.integration.test.ts +2 -2
- package/src/pipeline/__tests__/archive-stream.integration.test.ts +1 -1
- package/src/pipeline/__tests__/cascade-handler.integration.test.ts +23 -8
- package/src/pipeline/__tests__/cascade-handler.test.ts +18 -3
- package/src/pipeline/__tests__/causation-chain.integration.test.ts +1 -1
- package/src/pipeline/__tests__/ctx-bridge.integration.test.ts +5 -3
- package/src/pipeline/__tests__/dispatcher.test.ts +42 -6
- package/src/pipeline/__tests__/domain-events-projections.integration.test.ts +2 -2
- package/src/pipeline/__tests__/fetch-for-writing.integration.test.ts +1 -1
- package/src/pipeline/__tests__/lifecycle-pipeline.test.ts +42 -6
- package/src/pipeline/__tests__/load-aggregate-query.integration.test.ts +2 -2
- package/src/pipeline/__tests__/msp-multi-hop.integration.test.ts +1 -1
- package/src/pipeline/__tests__/msp-rebuild.integration.test.ts +6 -2
- package/src/pipeline/__tests__/multi-stream-projection.integration.test.ts +6 -2
- package/src/pipeline/__tests__/perf-rebuild.integration.test.ts +1 -1
- package/src/pipeline/__tests__/post-query-hook.integration.test.ts +3 -3
- package/src/pipeline/__tests__/projection-rebuild.integration.test.ts +3 -3
- package/src/pipeline/__tests__/query-projection.integration.test.ts +1 -1
- package/src/pipeline/__tests__/rebuild-poison-quarantine.integration.test.ts +2 -2
- package/src/pipeline/__tests__/search-consumer-named-events.integration.test.ts +13 -2
- package/src/pipeline/__tests__/try-append-event.integration.test.ts +1 -1
- package/src/search/__tests__/reindex-entity.integration.test.ts +6 -1
- package/src/search/__tests__/search-pii-derived-index.integration.test.ts +7 -1
- package/src/stack/__tests__/ownership-boot-guard.integration.test.ts +1 -1
- package/src/testing/__tests__/e2e-generator.test.ts +15 -5
- package/src/testing/e2e-generator.ts +2 -0
- package/src/testing/shared-entities.ts +4 -2
- package/src/ui-types/index.ts +4 -0
|
@@ -50,7 +50,13 @@ describe("boot-validator", () => {
|
|
|
50
50
|
test("passes for valid features with no issues", () => {
|
|
51
51
|
const features = [
|
|
52
52
|
defineFeature("a", (r) => {
|
|
53
|
-
r.entity(
|
|
53
|
+
r.entity(
|
|
54
|
+
"user",
|
|
55
|
+
createEntity({
|
|
56
|
+
table: "Users",
|
|
57
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
58
|
+
}),
|
|
59
|
+
);
|
|
54
60
|
}),
|
|
55
61
|
];
|
|
56
62
|
expect(() => validateBoot(features)).not.toThrow();
|
|
@@ -404,7 +410,7 @@ describe("boot-validator", () => {
|
|
|
404
410
|
"item",
|
|
405
411
|
createEntity({
|
|
406
412
|
table: "Items",
|
|
407
|
-
fields: { name: createTextField() },
|
|
413
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
408
414
|
}),
|
|
409
415
|
);
|
|
410
416
|
r.extendsRegistrar("custom", {
|
|
@@ -426,7 +432,7 @@ describe("boot-validator", () => {
|
|
|
426
432
|
"item",
|
|
427
433
|
createEntity({
|
|
428
434
|
table: "Items",
|
|
429
|
-
fields: { status: createTextField() },
|
|
435
|
+
fields: { status: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
430
436
|
derivedFields: {
|
|
431
437
|
status: createDerivedField({ valueType: "text", derive: () => "computed" }),
|
|
432
438
|
},
|
|
@@ -446,7 +452,7 @@ describe("boot-validator", () => {
|
|
|
446
452
|
"item",
|
|
447
453
|
createEntity({
|
|
448
454
|
table: "Items",
|
|
449
|
-
fields: { status: createTextField() },
|
|
455
|
+
fields: { status: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
450
456
|
derivedFields: {
|
|
451
457
|
statusLabel: createDerivedField({ valueType: "text", derive: () => "computed" }),
|
|
452
458
|
},
|
|
@@ -498,7 +504,7 @@ describe("boot-validator", () => {
|
|
|
498
504
|
"item",
|
|
499
505
|
createEntity({
|
|
500
506
|
table: "Items",
|
|
501
|
-
fields: { name: createTextField() },
|
|
507
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
502
508
|
}),
|
|
503
509
|
);
|
|
504
510
|
r.extendsRegistrar("custom", {
|
|
@@ -514,7 +520,13 @@ describe("boot-validator", () => {
|
|
|
514
520
|
test("accepts bare CRUD write handler when feature name matches entity", () => {
|
|
515
521
|
const features = [
|
|
516
522
|
defineFeature("credit", (r) => {
|
|
517
|
-
r.entity(
|
|
523
|
+
r.entity(
|
|
524
|
+
"credit",
|
|
525
|
+
createEntity({
|
|
526
|
+
table: "Credits",
|
|
527
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
528
|
+
}),
|
|
529
|
+
);
|
|
518
530
|
r.writeHandler(
|
|
519
531
|
"create",
|
|
520
532
|
z.object({ name: z.string() }),
|
|
@@ -540,7 +552,13 @@ describe("boot-validator", () => {
|
|
|
540
552
|
});
|
|
541
553
|
const consumer = defineFeature("money-horse", (r) => {
|
|
542
554
|
r.requires("cap-ext");
|
|
543
|
-
r.entity(
|
|
555
|
+
r.entity(
|
|
556
|
+
"credit",
|
|
557
|
+
createEntity({
|
|
558
|
+
table: "Credits",
|
|
559
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
560
|
+
}),
|
|
561
|
+
);
|
|
544
562
|
r.writeHandler(
|
|
545
563
|
"doSomething",
|
|
546
564
|
z.object({}),
|
|
@@ -583,7 +601,9 @@ describe("boot-validator", () => {
|
|
|
583
601
|
test("registerEntityCrud without access options fails boot (no implicit openToAll)", () => {
|
|
584
602
|
const widgetEntity = createEntity({
|
|
585
603
|
table: "phase0_widgets",
|
|
586
|
-
fields: {
|
|
604
|
+
fields: {
|
|
605
|
+
name: createTextField({ required: true, personal: false, reason: "test_fixture" }),
|
|
606
|
+
},
|
|
587
607
|
});
|
|
588
608
|
const features = [
|
|
589
609
|
defineFeature("phase0", (r) => {
|
|
@@ -948,7 +968,13 @@ describe("boot-validator", () => {
|
|
|
948
968
|
"order",
|
|
949
969
|
createEntity({
|
|
950
970
|
table: "orders",
|
|
951
|
-
fields: {
|
|
971
|
+
fields: {
|
|
972
|
+
teamId: createTextField({
|
|
973
|
+
required: true,
|
|
974
|
+
personal: false,
|
|
975
|
+
reason: "test_fixture",
|
|
976
|
+
}),
|
|
977
|
+
},
|
|
952
978
|
access: {
|
|
953
979
|
read: { Admin: "all", TeamMember: from("claim:teams:teamId") },
|
|
954
980
|
},
|
|
@@ -966,7 +992,13 @@ describe("boot-validator", () => {
|
|
|
966
992
|
"order",
|
|
967
993
|
createEntity({
|
|
968
994
|
table: "orders",
|
|
969
|
-
fields: {
|
|
995
|
+
fields: {
|
|
996
|
+
teamId: createTextField({
|
|
997
|
+
required: true,
|
|
998
|
+
personal: false,
|
|
999
|
+
reason: "test_fixture",
|
|
1000
|
+
}),
|
|
1001
|
+
},
|
|
970
1002
|
access: {
|
|
971
1003
|
// No "teams" feature registered — claim doesn't exist.
|
|
972
1004
|
read: { TeamMember: from("claim:teams:teamId") },
|
|
@@ -990,7 +1022,13 @@ describe("boot-validator", () => {
|
|
|
990
1022
|
"order",
|
|
991
1023
|
createEntity({
|
|
992
1024
|
table: "orders",
|
|
993
|
-
fields: {
|
|
1025
|
+
fields: {
|
|
1026
|
+
teamId: createTextField({
|
|
1027
|
+
required: true,
|
|
1028
|
+
personal: false,
|
|
1029
|
+
reason: "test_fixture",
|
|
1030
|
+
}),
|
|
1031
|
+
},
|
|
994
1032
|
access: {
|
|
995
1033
|
read: {
|
|
996
1034
|
// column "nonExistentColumn" not on entity
|
|
@@ -1017,12 +1055,18 @@ describe("boot-validator", () => {
|
|
|
1017
1055
|
createEntity({
|
|
1018
1056
|
table: "contracts",
|
|
1019
1057
|
fields: {
|
|
1020
|
-
teamId: createTextField({
|
|
1058
|
+
teamId: createTextField({
|
|
1059
|
+
required: true,
|
|
1060
|
+
personal: false,
|
|
1061
|
+
reason: "test_fixture",
|
|
1062
|
+
}),
|
|
1021
1063
|
propC: createTextField({
|
|
1022
1064
|
access: {
|
|
1023
1065
|
read: { Admin: "all", TeamMember: from("claim:teams:teamId") },
|
|
1024
1066
|
write: { Admin: "all", TeamMember: from("claim:teams:teamId") },
|
|
1025
1067
|
},
|
|
1068
|
+
personal: false,
|
|
1069
|
+
reason: "test_fixture",
|
|
1026
1070
|
}),
|
|
1027
1071
|
},
|
|
1028
1072
|
}),
|
|
@@ -1040,12 +1084,18 @@ describe("boot-validator", () => {
|
|
|
1040
1084
|
createEntity({
|
|
1041
1085
|
table: "contracts",
|
|
1042
1086
|
fields: {
|
|
1043
|
-
teamId: createTextField({
|
|
1087
|
+
teamId: createTextField({
|
|
1088
|
+
required: true,
|
|
1089
|
+
personal: false,
|
|
1090
|
+
reason: "test_fixture",
|
|
1091
|
+
}),
|
|
1044
1092
|
propC: createTextField({
|
|
1045
1093
|
access: {
|
|
1046
1094
|
// claim not declared anywhere
|
|
1047
1095
|
read: { TeamMember: from("claim:nowhere:teamId") },
|
|
1048
1096
|
},
|
|
1097
|
+
personal: false,
|
|
1098
|
+
reason: "test_fixture",
|
|
1049
1099
|
}),
|
|
1050
1100
|
},
|
|
1051
1101
|
}),
|
|
@@ -1064,7 +1114,7 @@ describe("boot-validator", () => {
|
|
|
1064
1114
|
"order",
|
|
1065
1115
|
createEntity({
|
|
1066
1116
|
table: "orders",
|
|
1067
|
-
fields: { assigneeId: createTextField() },
|
|
1117
|
+
fields: { assigneeId: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
1068
1118
|
access: {
|
|
1069
1119
|
read: { Driver: from("user:id", "assigneeId") },
|
|
1070
1120
|
},
|
|
@@ -1082,7 +1132,7 @@ describe("boot-validator", () => {
|
|
|
1082
1132
|
"order",
|
|
1083
1133
|
createEntity({
|
|
1084
1134
|
table: "orders",
|
|
1085
|
-
fields: { assigneeId: createTextField() },
|
|
1135
|
+
fields: { assigneeId: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
1086
1136
|
access: {
|
|
1087
1137
|
read: {
|
|
1088
1138
|
Admin: "all",
|
|
@@ -1106,7 +1156,13 @@ describe("boot-validator", () => {
|
|
|
1106
1156
|
"order",
|
|
1107
1157
|
createEntity({
|
|
1108
1158
|
table: "orders",
|
|
1109
|
-
fields: {
|
|
1159
|
+
fields: {
|
|
1160
|
+
entityId: createTextField({
|
|
1161
|
+
required: true,
|
|
1162
|
+
personal: false,
|
|
1163
|
+
reason: "test_fixture",
|
|
1164
|
+
}),
|
|
1165
|
+
},
|
|
1110
1166
|
access: {
|
|
1111
1167
|
read: {
|
|
1112
1168
|
Auditor: {
|
|
@@ -1132,7 +1188,13 @@ describe("boot-validator", () => {
|
|
|
1132
1188
|
"order",
|
|
1133
1189
|
createEntity({
|
|
1134
1190
|
table: "orders",
|
|
1135
|
-
fields: {
|
|
1191
|
+
fields: {
|
|
1192
|
+
entityId: createTextField({
|
|
1193
|
+
required: true,
|
|
1194
|
+
personal: false,
|
|
1195
|
+
reason: "test_fixture",
|
|
1196
|
+
}),
|
|
1197
|
+
},
|
|
1136
1198
|
access: {
|
|
1137
1199
|
read: {
|
|
1138
1200
|
Auditor: {
|
|
@@ -1196,7 +1258,13 @@ describe("boot-validator", () => {
|
|
|
1196
1258
|
"order",
|
|
1197
1259
|
createEntity({
|
|
1198
1260
|
table: "orders",
|
|
1199
|
-
fields: {
|
|
1261
|
+
fields: {
|
|
1262
|
+
teamId: createTextField({
|
|
1263
|
+
required: true,
|
|
1264
|
+
personal: false,
|
|
1265
|
+
reason: "test_fixture",
|
|
1266
|
+
}),
|
|
1267
|
+
},
|
|
1200
1268
|
access: {
|
|
1201
1269
|
read: { Admi: "all" },
|
|
1202
1270
|
},
|
|
@@ -1225,7 +1293,11 @@ describe("boot-validator", () => {
|
|
|
1225
1293
|
createEntity({
|
|
1226
1294
|
table: "orders",
|
|
1227
1295
|
fields: {
|
|
1228
|
-
secret: createTextField({
|
|
1296
|
+
secret: createTextField({
|
|
1297
|
+
access: { read: ["Admni"] },
|
|
1298
|
+
personal: false,
|
|
1299
|
+
reason: "test_fixture",
|
|
1300
|
+
}),
|
|
1229
1301
|
},
|
|
1230
1302
|
}),
|
|
1231
1303
|
);
|
|
@@ -1251,7 +1323,13 @@ describe("boot-validator", () => {
|
|
|
1251
1323
|
"order",
|
|
1252
1324
|
createEntity({
|
|
1253
1325
|
table: "orders",
|
|
1254
|
-
fields: {
|
|
1326
|
+
fields: {
|
|
1327
|
+
teamId: createTextField({
|
|
1328
|
+
required: true,
|
|
1329
|
+
personal: false,
|
|
1330
|
+
reason: "test_fixture",
|
|
1331
|
+
}),
|
|
1332
|
+
},
|
|
1255
1333
|
access: {
|
|
1256
1334
|
read: { Admin: "all", TeamMember: "all" },
|
|
1257
1335
|
},
|
|
@@ -1273,7 +1351,13 @@ describe("boot-validator", () => {
|
|
|
1273
1351
|
"order",
|
|
1274
1352
|
createEntity({
|
|
1275
1353
|
table: "orders",
|
|
1276
|
-
fields: {
|
|
1354
|
+
fields: {
|
|
1355
|
+
teamId: createTextField({
|
|
1356
|
+
required: true,
|
|
1357
|
+
personal: false,
|
|
1358
|
+
reason: "test_fixture",
|
|
1359
|
+
}),
|
|
1360
|
+
},
|
|
1277
1361
|
access: {
|
|
1278
1362
|
read: { AnyRole: "all" },
|
|
1279
1363
|
},
|
|
@@ -1807,7 +1891,12 @@ describe("boot-validator", () => {
|
|
|
1807
1891
|
describe("entityList column renderer form", () => {
|
|
1808
1892
|
function shopFeature(renderer: unknown) {
|
|
1809
1893
|
return defineFeature("shop", (r) => {
|
|
1810
|
-
r.entity(
|
|
1894
|
+
r.entity(
|
|
1895
|
+
"product",
|
|
1896
|
+
createEntity({
|
|
1897
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
1898
|
+
}),
|
|
1899
|
+
);
|
|
1811
1900
|
r.screen({
|
|
1812
1901
|
id: "product-list",
|
|
1813
1902
|
type: "entityList",
|
|
@@ -1970,7 +2059,12 @@ describe("boot-validator", () => {
|
|
|
1970
2059
|
describe("entityList virtual labeled column", () => {
|
|
1971
2060
|
function noteFeature(column: unknown) {
|
|
1972
2061
|
return defineFeature("notes", (r) => {
|
|
1973
|
-
r.entity(
|
|
2062
|
+
r.entity(
|
|
2063
|
+
"note",
|
|
2064
|
+
createEntity({
|
|
2065
|
+
fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
2066
|
+
}),
|
|
2067
|
+
);
|
|
1974
2068
|
r.screen({
|
|
1975
2069
|
id: "note-list",
|
|
1976
2070
|
type: "entityList",
|
|
@@ -2030,7 +2124,10 @@ describe("boot-validator", () => {
|
|
|
2030
2124
|
return defineFeature("notes", (r) => {
|
|
2031
2125
|
r.entity(
|
|
2032
2126
|
"note",
|
|
2033
|
-
createEntity({
|
|
2127
|
+
createEntity({
|
|
2128
|
+
fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
2129
|
+
softDelete: options?.softDelete,
|
|
2130
|
+
}),
|
|
2034
2131
|
);
|
|
2035
2132
|
r.screen({
|
|
2036
2133
|
id: "note-list",
|
|
@@ -2089,7 +2186,12 @@ describe("boot-validator", () => {
|
|
|
2089
2186
|
for (const typo of typoColumns) {
|
|
2090
2187
|
test(`"${typo}" fails validateScreens (via validateBoot)`, () => {
|
|
2091
2188
|
const feature = defineFeature("notes", (r) => {
|
|
2092
|
-
r.entity(
|
|
2189
|
+
r.entity(
|
|
2190
|
+
"note",
|
|
2191
|
+
createEntity({
|
|
2192
|
+
fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
2193
|
+
}),
|
|
2194
|
+
);
|
|
2093
2195
|
r.screen({
|
|
2094
2196
|
id: "note-list",
|
|
2095
2197
|
type: "entityList",
|
|
@@ -2104,7 +2206,12 @@ describe("boot-validator", () => {
|
|
|
2104
2206
|
|
|
2105
2207
|
test(`"${typo}" fails validateEntityListScreens directly ("unknown column field")`, () => {
|
|
2106
2208
|
const feature = defineFeature("notes", (r) => {
|
|
2107
|
-
r.entity(
|
|
2209
|
+
r.entity(
|
|
2210
|
+
"note",
|
|
2211
|
+
createEntity({
|
|
2212
|
+
fields: { title: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
2213
|
+
}),
|
|
2214
|
+
);
|
|
2108
2215
|
r.screen({
|
|
2109
2216
|
id: "note-list",
|
|
2110
2217
|
type: "entityList",
|
|
@@ -2137,11 +2244,11 @@ describe("boot-validator", () => {
|
|
|
2137
2244
|
"product",
|
|
2138
2245
|
createEntity({
|
|
2139
2246
|
fields: {
|
|
2140
|
-
name: createTextField({ sortable: true }),
|
|
2247
|
+
name: createTextField({ sortable: true, personal: false, reason: "test_fixture" }),
|
|
2141
2248
|
// Bewusst NICHT sortable: bestätigt dass Validator das
|
|
2142
2249
|
// unterscheidet und nur sortable-Felder als defaultSort
|
|
2143
2250
|
// akzeptiert.
|
|
2144
|
-
description: createTextField(),
|
|
2251
|
+
description: createTextField({ personal: false, reason: "test_fixture" }),
|
|
2145
2252
|
},
|
|
2146
2253
|
}),
|
|
2147
2254
|
);
|
|
@@ -2558,6 +2665,234 @@ describe("boot-validator", () => {
|
|
|
2558
2665
|
});
|
|
2559
2666
|
});
|
|
2560
2667
|
|
|
2668
|
+
// --- secretMint-Screen (fw#2548) ---
|
|
2669
|
+
// Mint-form → one-time reveal → confirm. Shares its six inline-form checks
|
|
2670
|
+
// with actionForm (validateInlineFormScreen), plus its own reveal.fields
|
|
2671
|
+
// checks: non-empty, unique `field`, non-empty `label`.
|
|
2672
|
+
describe("secretMint screen (fw#2548)", () => {
|
|
2673
|
+
type SecretMintOverride = {
|
|
2674
|
+
readonly handler?: string | undefined;
|
|
2675
|
+
readonly fields?: Record<string, unknown>;
|
|
2676
|
+
readonly sections?: ReadonlyArray<{
|
|
2677
|
+
readonly title: string;
|
|
2678
|
+
readonly fields: readonly string[];
|
|
2679
|
+
}>;
|
|
2680
|
+
readonly reveal?: {
|
|
2681
|
+
readonly fields: ReadonlyArray<{
|
|
2682
|
+
readonly field: string;
|
|
2683
|
+
readonly label: string;
|
|
2684
|
+
readonly display?: "code" | "list";
|
|
2685
|
+
}>;
|
|
2686
|
+
};
|
|
2687
|
+
readonly redirect?: string;
|
|
2688
|
+
readonly extraScreens?: readonly string[];
|
|
2689
|
+
readonly confirm?: {
|
|
2690
|
+
readonly handler?: string;
|
|
2691
|
+
readonly fields?: Record<string, unknown>;
|
|
2692
|
+
readonly sections?: ReadonlyArray<{
|
|
2693
|
+
readonly title: string;
|
|
2694
|
+
readonly fields: readonly string[];
|
|
2695
|
+
}>;
|
|
2696
|
+
readonly carry?: readonly string[];
|
|
2697
|
+
readonly mode?: "single" | "wizard" | "tabs";
|
|
2698
|
+
readonly draft?: boolean;
|
|
2699
|
+
};
|
|
2700
|
+
};
|
|
2701
|
+
|
|
2702
|
+
function makeFeature(override: SecretMintOverride = {}) {
|
|
2703
|
+
const handler = override.handler ?? "shop:write:token:mint";
|
|
2704
|
+
const fields = override.fields ?? { label: { type: "text" } };
|
|
2705
|
+
const sections = override.sections ?? [{ title: "Mint", fields: ["label"] }];
|
|
2706
|
+
const reveal = override.reveal ?? {
|
|
2707
|
+
fields: [{ field: "token", label: "shop:screen:mint-token.field:token" }],
|
|
2708
|
+
};
|
|
2709
|
+
const confirmOverride = override.confirm;
|
|
2710
|
+
return defineFeature("shop", (r) => {
|
|
2711
|
+
r.writeHandler({
|
|
2712
|
+
name: "token:mint",
|
|
2713
|
+
schema: { _type: "stub" } as never,
|
|
2714
|
+
handler: async () => ({ isSuccess: true, data: {} }) as never,
|
|
2715
|
+
access: { openToAll: true },
|
|
2716
|
+
});
|
|
2717
|
+
if (confirmOverride !== undefined) {
|
|
2718
|
+
r.writeHandler({
|
|
2719
|
+
name: "token:confirm",
|
|
2720
|
+
schema: { _type: "stub" } as never,
|
|
2721
|
+
handler: async () => ({ isSuccess: true, data: {} }) as never,
|
|
2722
|
+
access: { openToAll: true },
|
|
2723
|
+
});
|
|
2724
|
+
}
|
|
2725
|
+
r.screen({
|
|
2726
|
+
id: "mint-token",
|
|
2727
|
+
type: "secretMint",
|
|
2728
|
+
handler,
|
|
2729
|
+
fields: fields as never,
|
|
2730
|
+
layout: { sections: sections as never },
|
|
2731
|
+
reveal: reveal as never,
|
|
2732
|
+
...(override.redirect !== undefined && { redirect: override.redirect }),
|
|
2733
|
+
...(confirmOverride !== undefined && {
|
|
2734
|
+
confirm: {
|
|
2735
|
+
handler: confirmOverride.handler ?? "shop:write:token:confirm",
|
|
2736
|
+
fields: (confirmOverride.fields ?? { code: { type: "text" } }) as never,
|
|
2737
|
+
layout: {
|
|
2738
|
+
sections: (confirmOverride.sections ?? [
|
|
2739
|
+
{ title: "Confirm", fields: ["code"] },
|
|
2740
|
+
]) as never,
|
|
2741
|
+
...(confirmOverride.mode !== undefined && { mode: confirmOverride.mode }),
|
|
2742
|
+
...(confirmOverride.draft !== undefined && { draft: confirmOverride.draft }),
|
|
2743
|
+
},
|
|
2744
|
+
...(confirmOverride.carry !== undefined && { carry: confirmOverride.carry }),
|
|
2745
|
+
},
|
|
2746
|
+
}),
|
|
2747
|
+
});
|
|
2748
|
+
for (const extra of override.extraScreens ?? []) {
|
|
2749
|
+
r.screen({
|
|
2750
|
+
id: extra,
|
|
2751
|
+
type: "custom",
|
|
2752
|
+
renderer: { react: "stub" },
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
});
|
|
2756
|
+
}
|
|
2757
|
+
|
|
2758
|
+
test("happy path: handler + fields + layout + reveal konsistent → kein Throw", () => {
|
|
2759
|
+
expect(() => validateBoot([makeFeature()])).not.toThrow();
|
|
2760
|
+
});
|
|
2761
|
+
|
|
2762
|
+
test("handler nicht als write-handler registriert → Throw", () => {
|
|
2763
|
+
expect(() => validateBoot([makeFeature({ handler: "shop:query:token:list" })])).toThrow(
|
|
2764
|
+
/\(secretMint\) handler "shop:query:token:list" is not a registered write-handler/,
|
|
2765
|
+
);
|
|
2766
|
+
});
|
|
2767
|
+
|
|
2768
|
+
// secretMint allows an empty fields map (input-less mint, fw#2838) — but
|
|
2769
|
+
// the default `sections` still references "label", which no longer
|
|
2770
|
+
// exists once `fields` is empty, so this still throws (just via the
|
|
2771
|
+
// layout check instead of the fields-map check).
|
|
2772
|
+
test("fields empty-Map without also clearing sections → Throw", () => {
|
|
2773
|
+
expect(() => validateBoot([makeFeature({ fields: {} })])).toThrow(
|
|
2774
|
+
/\(secretMint\) layout references unknown field "label"/,
|
|
2775
|
+
);
|
|
2776
|
+
});
|
|
2777
|
+
|
|
2778
|
+
test("layout referenziert unknown field → Throw", () => {
|
|
2779
|
+
expect(() =>
|
|
2780
|
+
validateBoot([makeFeature({ sections: [{ title: "x", fields: ["ghost"] }] })]),
|
|
2781
|
+
).toThrow(/\(secretMint\) layout references unknown field "ghost"/);
|
|
2782
|
+
});
|
|
2783
|
+
|
|
2784
|
+
test("redirect → existing screen-id → kein Throw", () => {
|
|
2785
|
+
expect(() =>
|
|
2786
|
+
validateBoot([makeFeature({ redirect: "after-mint", extraScreens: ["after-mint"] })]),
|
|
2787
|
+
).not.toThrow();
|
|
2788
|
+
});
|
|
2789
|
+
|
|
2790
|
+
test("redirect → unknown screen-id → Throw", () => {
|
|
2791
|
+
expect(() => validateBoot([makeFeature({ redirect: "ghost-screen" })])).toThrow(
|
|
2792
|
+
/\(secretMint\) redirect "ghost-screen" does not resolve to a registered screen/,
|
|
2793
|
+
);
|
|
2794
|
+
});
|
|
2795
|
+
|
|
2796
|
+
test("reveal.fields leer → Throw", () => {
|
|
2797
|
+
expect(() => validateBoot([makeFeature({ reveal: { fields: [] } })])).toThrow(
|
|
2798
|
+
/has an empty reveal\.fields list/,
|
|
2799
|
+
);
|
|
2800
|
+
});
|
|
2801
|
+
|
|
2802
|
+
test("reveal.fields mit doppeltem field → Throw", () => {
|
|
2803
|
+
expect(() =>
|
|
2804
|
+
validateBoot([
|
|
2805
|
+
makeFeature({
|
|
2806
|
+
reveal: {
|
|
2807
|
+
fields: [
|
|
2808
|
+
{ field: "token", label: "a" },
|
|
2809
|
+
{ field: "token", label: "b" },
|
|
2810
|
+
],
|
|
2811
|
+
},
|
|
2812
|
+
}),
|
|
2813
|
+
]),
|
|
2814
|
+
).toThrow(/reveal\.fields has a duplicate field "token"/);
|
|
2815
|
+
});
|
|
2816
|
+
|
|
2817
|
+
test("reveal.fields entry mit leerem label → Throw", () => {
|
|
2818
|
+
expect(() =>
|
|
2819
|
+
validateBoot([makeFeature({ reveal: { fields: [{ field: "token", label: "" }] } })]),
|
|
2820
|
+
).toThrow(/reveal\.fields entry "token" has an empty or non-string "label"/);
|
|
2821
|
+
});
|
|
2822
|
+
|
|
2823
|
+
// --- input-less mint (fw#2838): fields: {} + layout.sections: [] is the
|
|
2824
|
+
// only valid empty shape — the secret is server-generated, the mint step
|
|
2825
|
+
// is just its submit button.
|
|
2826
|
+
test("empty fields + empty sections (input-less mint) → no throw", () => {
|
|
2827
|
+
expect(() => validateBoot([makeFeature({ fields: {}, sections: [] })])).not.toThrow();
|
|
2828
|
+
});
|
|
2829
|
+
|
|
2830
|
+
test("empty fields but non-empty sections list → Throw", () => {
|
|
2831
|
+
expect(() =>
|
|
2832
|
+
validateBoot([makeFeature({ fields: {}, sections: [{ title: "x", fields: ["ghost"] }] })]),
|
|
2833
|
+
).toThrow(/\(secretMint\) layout references unknown field "ghost"/);
|
|
2834
|
+
});
|
|
2835
|
+
|
|
2836
|
+
test("non-empty fields but empty sections list → Throw", () => {
|
|
2837
|
+
expect(() => validateBoot([makeFeature({ sections: [] })])).toThrow(
|
|
2838
|
+
/\(secretMint\) has an empty sections list/,
|
|
2839
|
+
);
|
|
2840
|
+
});
|
|
2841
|
+
|
|
2842
|
+
// --- confirm step (fw#2838) ---
|
|
2843
|
+
describe("confirm step", () => {
|
|
2844
|
+
test("happy path: confirm handler + fields + layout consistent → no throw", () => {
|
|
2845
|
+
expect(() => validateBoot([makeFeature({ confirm: {} })])).not.toThrow();
|
|
2846
|
+
});
|
|
2847
|
+
|
|
2848
|
+
test("confirm.handler not a registered write-handler → Throw", () => {
|
|
2849
|
+
expect(() =>
|
|
2850
|
+
validateBoot([makeFeature({ confirm: { handler: "shop:query:token:list" } })]),
|
|
2851
|
+
).toThrow(
|
|
2852
|
+
/\(secretMint confirm\) handler "shop:query:token:list" is not a registered write-handler/,
|
|
2853
|
+
);
|
|
2854
|
+
});
|
|
2855
|
+
|
|
2856
|
+
test("confirm.fields empty map → Throw", () => {
|
|
2857
|
+
expect(() => validateBoot([makeFeature({ confirm: { fields: {} } })])).toThrow(
|
|
2858
|
+
/\(secretMint confirm\) has empty fields map/,
|
|
2859
|
+
);
|
|
2860
|
+
});
|
|
2861
|
+
|
|
2862
|
+
test("confirm layout references unknown field → Throw", () => {
|
|
2863
|
+
expect(() =>
|
|
2864
|
+
validateBoot([
|
|
2865
|
+
makeFeature({ confirm: { sections: [{ title: "x", fields: ["ghost"] }] } }),
|
|
2866
|
+
]),
|
|
2867
|
+
).toThrow(/\(secretMint confirm\) layout references unknown field "ghost"/);
|
|
2868
|
+
});
|
|
2869
|
+
|
|
2870
|
+
test("confirm layout mode 'wizard' → Throw", () => {
|
|
2871
|
+
expect(() => validateBoot([makeFeature({ confirm: { mode: "wizard" } })])).toThrow(
|
|
2872
|
+
/\(secretMint confirm\) sets layout\.mode: "wizard"/,
|
|
2873
|
+
);
|
|
2874
|
+
});
|
|
2875
|
+
|
|
2876
|
+
test("confirm layout draft true → Throw", () => {
|
|
2877
|
+
expect(() => validateBoot([makeFeature({ confirm: { draft: true } })])).toThrow(
|
|
2878
|
+
/\(secretMint confirm\) sets layout\.draft: true/,
|
|
2879
|
+
);
|
|
2880
|
+
});
|
|
2881
|
+
|
|
2882
|
+
test("carry entry colliding with a confirm.fields name → Throw", () => {
|
|
2883
|
+
expect(() => validateBoot([makeFeature({ confirm: { carry: ["code"] } })])).toThrow(
|
|
2884
|
+
/\(secretMint confirm\) carry field "code" also names a confirm\.fields entry/,
|
|
2885
|
+
);
|
|
2886
|
+
});
|
|
2887
|
+
|
|
2888
|
+
test("carry entry that is not a confirm.fields name → no throw", () => {
|
|
2889
|
+
expect(() =>
|
|
2890
|
+
validateBoot([makeFeature({ confirm: { carry: ["setupToken"] } })]),
|
|
2891
|
+
).not.toThrow();
|
|
2892
|
+
});
|
|
2893
|
+
});
|
|
2894
|
+
});
|
|
2895
|
+
|
|
2561
2896
|
// --- configEdit-Screen ---
|
|
2562
2897
|
// Form gegen das bundled config-feature. Boot-Validator prüft:
|
|
2563
2898
|
// 1) fields non-empty + jeder mit type-Discriminator
|
|
@@ -2728,7 +3063,12 @@ describe("boot-validator", () => {
|
|
|
2728
3063
|
describe("entityEdit extension section", () => {
|
|
2729
3064
|
function makeFeature(component: unknown) {
|
|
2730
3065
|
return defineFeature("shop", (r) => {
|
|
2731
|
-
r.entity(
|
|
3066
|
+
r.entity(
|
|
3067
|
+
"product",
|
|
3068
|
+
createEntity({
|
|
3069
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3070
|
+
}),
|
|
3071
|
+
);
|
|
2732
3072
|
r.screen({
|
|
2733
3073
|
id: "product-edit",
|
|
2734
3074
|
type: "entityEdit",
|
|
@@ -2769,7 +3109,12 @@ describe("boot-validator", () => {
|
|
|
2769
3109
|
return defineFeature("shop", (r) => {
|
|
2770
3110
|
r.entity(
|
|
2771
3111
|
"product",
|
|
2772
|
-
createEntity({
|
|
3112
|
+
createEntity({
|
|
3113
|
+
fields: {
|
|
3114
|
+
name: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3115
|
+
sku: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3116
|
+
},
|
|
3117
|
+
}),
|
|
2773
3118
|
);
|
|
2774
3119
|
r.screen({
|
|
2775
3120
|
id: "product-edit",
|
|
@@ -2826,7 +3171,12 @@ describe("boot-validator", () => {
|
|
|
2826
3171
|
return defineFeature("shop", (r) => {
|
|
2827
3172
|
r.entity(
|
|
2828
3173
|
"product",
|
|
2829
|
-
createEntity({
|
|
3174
|
+
createEntity({
|
|
3175
|
+
fields: {
|
|
3176
|
+
name: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3177
|
+
sku: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3178
|
+
},
|
|
3179
|
+
}),
|
|
2830
3180
|
);
|
|
2831
3181
|
r.screen({
|
|
2832
3182
|
id: "product-edit",
|
|
@@ -2960,7 +3310,7 @@ describe("boot-validator", () => {
|
|
|
2960
3310
|
"order",
|
|
2961
3311
|
createEntity({
|
|
2962
3312
|
fields: {
|
|
2963
|
-
kind: createTextField(),
|
|
3313
|
+
kind: createTextField({ personal: false, reason: "test_fixture" }),
|
|
2964
3314
|
lines: createEmbeddedField({ note: { type: "text" } }, { required: true }),
|
|
2965
3315
|
},
|
|
2966
3316
|
}),
|
|
@@ -3064,7 +3414,12 @@ describe("boot-validator", () => {
|
|
|
3064
3414
|
test("reference auf bestehende Entity → kein Throw", () => {
|
|
3065
3415
|
const features = [
|
|
3066
3416
|
defineFeature("shop", (r) => {
|
|
3067
|
-
r.entity(
|
|
3417
|
+
r.entity(
|
|
3418
|
+
"customer",
|
|
3419
|
+
createEntity({
|
|
3420
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3421
|
+
}),
|
|
3422
|
+
);
|
|
3068
3423
|
stubListHandler(r, "customer");
|
|
3069
3424
|
r.entity(
|
|
3070
3425
|
"order",
|
|
@@ -3100,7 +3455,12 @@ describe("boot-validator", () => {
|
|
|
3100
3455
|
test("reference labelField auf unknown Field → Throw", () => {
|
|
3101
3456
|
const features = [
|
|
3102
3457
|
defineFeature("shop", (r) => {
|
|
3103
|
-
r.entity(
|
|
3458
|
+
r.entity(
|
|
3459
|
+
"customer",
|
|
3460
|
+
createEntity({
|
|
3461
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3462
|
+
}),
|
|
3463
|
+
);
|
|
3104
3464
|
r.entity(
|
|
3105
3465
|
"order",
|
|
3106
3466
|
createEntity({
|
|
@@ -3119,7 +3479,12 @@ describe("boot-validator", () => {
|
|
|
3119
3479
|
test("reference labelField=id ist immer ok (PK)", () => {
|
|
3120
3480
|
const features = [
|
|
3121
3481
|
defineFeature("shop", (r) => {
|
|
3122
|
-
r.entity(
|
|
3482
|
+
r.entity(
|
|
3483
|
+
"customer",
|
|
3484
|
+
createEntity({
|
|
3485
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3486
|
+
}),
|
|
3487
|
+
);
|
|
3123
3488
|
stubListHandler(r, "customer");
|
|
3124
3489
|
r.entity(
|
|
3125
3490
|
"order",
|
|
@@ -3141,7 +3506,7 @@ describe("boot-validator", () => {
|
|
|
3141
3506
|
"category",
|
|
3142
3507
|
createEntity({
|
|
3143
3508
|
fields: {
|
|
3144
|
-
name: createTextField(),
|
|
3509
|
+
name: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3145
3510
|
parentId: { type: "reference", entity: "category", labelField: "name" },
|
|
3146
3511
|
},
|
|
3147
3512
|
}),
|
|
@@ -3155,13 +3520,18 @@ describe("boot-validator", () => {
|
|
|
3155
3520
|
test("reference mit multiple: true → kein Throw (Tier 2.7e-Multi)", () => {
|
|
3156
3521
|
const features = [
|
|
3157
3522
|
defineFeature("shop", (r) => {
|
|
3158
|
-
r.entity(
|
|
3523
|
+
r.entity(
|
|
3524
|
+
"tag",
|
|
3525
|
+
createEntity({
|
|
3526
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3527
|
+
}),
|
|
3528
|
+
);
|
|
3159
3529
|
stubListHandler(r, "tag");
|
|
3160
3530
|
r.entity(
|
|
3161
3531
|
"post",
|
|
3162
3532
|
createEntity({
|
|
3163
3533
|
fields: {
|
|
3164
|
-
title: createTextField(),
|
|
3534
|
+
title: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3165
3535
|
tagIds: {
|
|
3166
3536
|
type: "reference",
|
|
3167
3537
|
entity: "tag",
|
|
@@ -3180,7 +3550,12 @@ describe("boot-validator", () => {
|
|
|
3180
3550
|
test("cross-feature reference (feature:entity) → kein Throw", () => {
|
|
3181
3551
|
const features = [
|
|
3182
3552
|
defineFeature("users", (r) => {
|
|
3183
|
-
r.entity(
|
|
3553
|
+
r.entity(
|
|
3554
|
+
"user",
|
|
3555
|
+
createEntity({
|
|
3556
|
+
fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3557
|
+
}),
|
|
3558
|
+
);
|
|
3184
3559
|
stubListHandler(r, "user");
|
|
3185
3560
|
}),
|
|
3186
3561
|
defineFeature("shop", (r) => {
|
|
@@ -3200,7 +3575,12 @@ describe("boot-validator", () => {
|
|
|
3200
3575
|
test("Audit-Fix #2: cross-feature reference ohne list-handler → Throw", () => {
|
|
3201
3576
|
const features = [
|
|
3202
3577
|
defineFeature("users", (r) => {
|
|
3203
|
-
r.entity(
|
|
3578
|
+
r.entity(
|
|
3579
|
+
"user",
|
|
3580
|
+
createEntity({
|
|
3581
|
+
fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3582
|
+
}),
|
|
3583
|
+
);
|
|
3204
3584
|
// KEINE stubListHandler — das ist der Punkt des Tests
|
|
3205
3585
|
}),
|
|
3206
3586
|
defineFeature("shop", (r) => {
|
|
@@ -3240,7 +3620,12 @@ describe("boot-validator", () => {
|
|
|
3240
3620
|
test("cross-feature reference auf unknown entity → Throw mit feature-context", () => {
|
|
3241
3621
|
const features = [
|
|
3242
3622
|
defineFeature("users", (r) => {
|
|
3243
|
-
r.entity(
|
|
3623
|
+
r.entity(
|
|
3624
|
+
"user",
|
|
3625
|
+
createEntity({
|
|
3626
|
+
fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3627
|
+
}),
|
|
3628
|
+
);
|
|
3244
3629
|
}),
|
|
3245
3630
|
defineFeature("shop", (r) => {
|
|
3246
3631
|
r.entity(
|
|
@@ -3261,7 +3646,12 @@ describe("boot-validator", () => {
|
|
|
3261
3646
|
test("cross-feature labelField auf unknown Field → Throw", () => {
|
|
3262
3647
|
const features = [
|
|
3263
3648
|
defineFeature("users", (r) => {
|
|
3264
|
-
r.entity(
|
|
3649
|
+
r.entity(
|
|
3650
|
+
"user",
|
|
3651
|
+
createEntity({
|
|
3652
|
+
fields: { email: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3653
|
+
}),
|
|
3654
|
+
);
|
|
3265
3655
|
}),
|
|
3266
3656
|
defineFeature("shop", (r) => {
|
|
3267
3657
|
r.entity(
|
|
@@ -3312,7 +3702,12 @@ describe("boot-validator", () => {
|
|
|
3312
3702
|
test("reference sub-field with a valid target + registered list-handler → kein Throw", () => {
|
|
3313
3703
|
const features = [
|
|
3314
3704
|
defineFeature("shop", (r) => {
|
|
3315
|
-
r.entity(
|
|
3705
|
+
r.entity(
|
|
3706
|
+
"product",
|
|
3707
|
+
createEntity({
|
|
3708
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3709
|
+
}),
|
|
3710
|
+
);
|
|
3316
3711
|
stubListHandler(r, "product");
|
|
3317
3712
|
r.entity(
|
|
3318
3713
|
"invoice",
|
|
@@ -3332,7 +3727,12 @@ describe("boot-validator", () => {
|
|
|
3332
3727
|
test("reference sub-field with a valid target but no registered list-handler → Throw", () => {
|
|
3333
3728
|
const features = [
|
|
3334
3729
|
defineFeature("shop", (r) => {
|
|
3335
|
-
r.entity(
|
|
3730
|
+
r.entity(
|
|
3731
|
+
"product",
|
|
3732
|
+
createEntity({
|
|
3733
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3734
|
+
}),
|
|
3735
|
+
);
|
|
3336
3736
|
// KEIN stubListHandler — das ist der Punkt des Tests
|
|
3337
3737
|
r.entity(
|
|
3338
3738
|
"invoice",
|
|
@@ -3354,7 +3754,12 @@ describe("boot-validator", () => {
|
|
|
3354
3754
|
test("reference sub-field labelField referencing an unknown field → Throw with the parent.child field path", () => {
|
|
3355
3755
|
const features = [
|
|
3356
3756
|
defineFeature("shop", (r) => {
|
|
3357
|
-
r.entity(
|
|
3757
|
+
r.entity(
|
|
3758
|
+
"product",
|
|
3759
|
+
createEntity({
|
|
3760
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3761
|
+
}),
|
|
3762
|
+
);
|
|
3358
3763
|
stubListHandler(r, "product");
|
|
3359
3764
|
r.entity(
|
|
3360
3765
|
"invoice",
|
|
@@ -3382,7 +3787,12 @@ describe("boot-validator", () => {
|
|
|
3382
3787
|
describe("entityList rowAction kind=navigate (Tier 2.7e-1)", () => {
|
|
3383
3788
|
function makeFeature(targetScreen: string, withTarget: boolean) {
|
|
3384
3789
|
return defineFeature("shop", (r) => {
|
|
3385
|
-
r.entity(
|
|
3790
|
+
r.entity(
|
|
3791
|
+
"product",
|
|
3792
|
+
createEntity({
|
|
3793
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3794
|
+
}),
|
|
3795
|
+
);
|
|
3386
3796
|
r.screen({
|
|
3387
3797
|
id: "product-list",
|
|
3388
3798
|
type: "entityList",
|
|
@@ -3422,7 +3832,12 @@ describe("boot-validator", () => {
|
|
|
3422
3832
|
// Custom-Editor der Consumer-App (anderes Feature). Runtime löst die bare
|
|
3423
3833
|
// id app-weit auf → der Validator muss das erlauben.
|
|
3424
3834
|
const list = defineFeature("shop", (r) => {
|
|
3425
|
-
r.entity(
|
|
3835
|
+
r.entity(
|
|
3836
|
+
"product",
|
|
3837
|
+
createEntity({
|
|
3838
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3839
|
+
}),
|
|
3840
|
+
);
|
|
3426
3841
|
r.screen({
|
|
3427
3842
|
id: "product-list",
|
|
3428
3843
|
type: "entityList",
|
|
@@ -3445,7 +3860,12 @@ describe("boot-validator", () => {
|
|
|
3445
3860
|
// open with no entity context at runtime. The validator must catch it
|
|
3446
3861
|
// at boot instead.
|
|
3447
3862
|
const list = defineFeature("shop", (r) => {
|
|
3448
|
-
r.entity(
|
|
3863
|
+
r.entity(
|
|
3864
|
+
"product",
|
|
3865
|
+
createEntity({
|
|
3866
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3867
|
+
}),
|
|
3868
|
+
);
|
|
3449
3869
|
r.screen({
|
|
3450
3870
|
id: "product-list",
|
|
3451
3871
|
type: "entityList",
|
|
@@ -3457,7 +3877,12 @@ describe("boot-validator", () => {
|
|
|
3457
3877
|
});
|
|
3458
3878
|
});
|
|
3459
3879
|
const consumer = defineFeature("billing", (r) => {
|
|
3460
|
-
r.entity(
|
|
3880
|
+
r.entity(
|
|
3881
|
+
"invoice",
|
|
3882
|
+
createEntity({
|
|
3883
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3884
|
+
}),
|
|
3885
|
+
);
|
|
3461
3886
|
r.screen({
|
|
3462
3887
|
id: "invoice-edit",
|
|
3463
3888
|
type: "entityEdit",
|
|
@@ -3474,7 +3899,12 @@ describe("boot-validator", () => {
|
|
|
3474
3899
|
const list = defineFeature("shop", (r) => {
|
|
3475
3900
|
r.entity(
|
|
3476
3901
|
"product",
|
|
3477
|
-
createEntity({
|
|
3902
|
+
createEntity({
|
|
3903
|
+
fields: {
|
|
3904
|
+
name: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3905
|
+
invoiceId: createTextField({ personal: false, reason: "test_fixture" }),
|
|
3906
|
+
},
|
|
3907
|
+
}),
|
|
3478
3908
|
);
|
|
3479
3909
|
r.screen({
|
|
3480
3910
|
id: "product-list",
|
|
@@ -3493,7 +3923,12 @@ describe("boot-validator", () => {
|
|
|
3493
3923
|
});
|
|
3494
3924
|
});
|
|
3495
3925
|
const consumer = defineFeature("billing", (r) => {
|
|
3496
|
-
r.entity(
|
|
3926
|
+
r.entity(
|
|
3927
|
+
"invoice",
|
|
3928
|
+
createEntity({
|
|
3929
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3930
|
+
}),
|
|
3931
|
+
);
|
|
3497
3932
|
r.screen({
|
|
3498
3933
|
id: "invoice-edit",
|
|
3499
3934
|
type: "entityEdit",
|
|
@@ -3506,7 +3941,12 @@ describe("boot-validator", () => {
|
|
|
3506
3941
|
|
|
3507
3942
|
test("same-feature navigate to entityEdit WITHOUT entityId → kein Throw (same-feature fallback applies)", () => {
|
|
3508
3943
|
const feature = defineFeature("shop", (r) => {
|
|
3509
|
-
r.entity(
|
|
3944
|
+
r.entity(
|
|
3945
|
+
"product",
|
|
3946
|
+
createEntity({
|
|
3947
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3948
|
+
}),
|
|
3949
|
+
);
|
|
3510
3950
|
r.screen({
|
|
3511
3951
|
id: "product-list",
|
|
3512
3952
|
type: "entityList",
|
|
@@ -3531,7 +3971,12 @@ describe("boot-validator", () => {
|
|
|
3531
3971
|
describe("entityList rowAction kind=navigate entity-target (fw#2228)", () => {
|
|
3532
3972
|
test("entity-target resolving via detailFor → kein Throw", () => {
|
|
3533
3973
|
const feature = defineFeature("shop", (r) => {
|
|
3534
|
-
r.entity(
|
|
3974
|
+
r.entity(
|
|
3975
|
+
"product",
|
|
3976
|
+
createEntity({
|
|
3977
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
3978
|
+
}),
|
|
3979
|
+
);
|
|
3535
3980
|
r.screen({
|
|
3536
3981
|
id: "product-list",
|
|
3537
3982
|
type: "entityList",
|
|
@@ -3551,7 +3996,12 @@ describe("boot-validator", () => {
|
|
|
3551
3996
|
|
|
3552
3997
|
test("entity-target with no detailFor screen anywhere → Throw mit klarer Message", () => {
|
|
3553
3998
|
const feature = defineFeature("shop", (r) => {
|
|
3554
|
-
r.entity(
|
|
3999
|
+
r.entity(
|
|
4000
|
+
"product",
|
|
4001
|
+
createEntity({
|
|
4002
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4003
|
+
}),
|
|
4004
|
+
);
|
|
3555
4005
|
r.screen({
|
|
3556
4006
|
id: "product-list",
|
|
3557
4007
|
type: "entityList",
|
|
@@ -3567,7 +4017,12 @@ describe("boot-validator", () => {
|
|
|
3567
4017
|
|
|
3568
4018
|
test("both screen and entity set → Throw", () => {
|
|
3569
4019
|
const feature = defineFeature("shop", (r) => {
|
|
3570
|
-
r.entity(
|
|
4020
|
+
r.entity(
|
|
4021
|
+
"product",
|
|
4022
|
+
createEntity({
|
|
4023
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4024
|
+
}),
|
|
4025
|
+
);
|
|
3571
4026
|
r.screen({
|
|
3572
4027
|
id: "product-list",
|
|
3573
4028
|
type: "entityList",
|
|
@@ -3597,7 +4052,12 @@ describe("boot-validator", () => {
|
|
|
3597
4052
|
|
|
3598
4053
|
test("neither screen nor entity set → Throw", () => {
|
|
3599
4054
|
const feature = defineFeature("shop", (r) => {
|
|
3600
|
-
r.entity(
|
|
4055
|
+
r.entity(
|
|
4056
|
+
"product",
|
|
4057
|
+
createEntity({
|
|
4058
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4059
|
+
}),
|
|
4060
|
+
);
|
|
3601
4061
|
r.screen({
|
|
3602
4062
|
id: "product-list",
|
|
3603
4063
|
type: "entityList",
|
|
@@ -3616,7 +4076,12 @@ describe("boot-validator", () => {
|
|
|
3616
4076
|
// detailFor resolution — the owning feature's list navigates to a
|
|
3617
4077
|
// detail screen a consumer app registers for the entity.
|
|
3618
4078
|
const list = defineFeature("shop", (r) => {
|
|
3619
|
-
r.entity(
|
|
4079
|
+
r.entity(
|
|
4080
|
+
"product",
|
|
4081
|
+
createEntity({
|
|
4082
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4083
|
+
}),
|
|
4084
|
+
);
|
|
3620
4085
|
r.screen({
|
|
3621
4086
|
id: "product-list",
|
|
3622
4087
|
type: "entityList",
|
|
@@ -3643,7 +4108,12 @@ describe("boot-validator", () => {
|
|
|
3643
4108
|
// row["id"]), so the same-feature-fallback gap that check guards
|
|
3644
4109
|
// against doesn't exist here.
|
|
3645
4110
|
const list = defineFeature("shop", (r) => {
|
|
3646
|
-
r.entity(
|
|
4111
|
+
r.entity(
|
|
4112
|
+
"product",
|
|
4113
|
+
createEntity({
|
|
4114
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4115
|
+
}),
|
|
4116
|
+
);
|
|
3647
4117
|
r.screen({
|
|
3648
4118
|
id: "product-list",
|
|
3649
4119
|
type: "entityList",
|
|
@@ -3653,7 +4123,12 @@ describe("boot-validator", () => {
|
|
|
3653
4123
|
});
|
|
3654
4124
|
});
|
|
3655
4125
|
const consumer = defineFeature("billing", (r) => {
|
|
3656
|
-
r.entity(
|
|
4126
|
+
r.entity(
|
|
4127
|
+
"invoice",
|
|
4128
|
+
createEntity({
|
|
4129
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4130
|
+
}),
|
|
4131
|
+
);
|
|
3657
4132
|
r.screen({
|
|
3658
4133
|
id: "invoice-edit",
|
|
3659
4134
|
type: "entityEdit",
|
|
@@ -3670,7 +4145,12 @@ describe("boot-validator", () => {
|
|
|
3670
4145
|
// guaranteed "id" field, unlike entityList rows — an entity-target
|
|
3671
4146
|
// there must name the field to read the id from.
|
|
3672
4147
|
const feature = defineFeature("shop", (r) => {
|
|
3673
|
-
r.entity(
|
|
4148
|
+
r.entity(
|
|
4149
|
+
"product",
|
|
4150
|
+
createEntity({
|
|
4151
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4152
|
+
}),
|
|
4153
|
+
);
|
|
3674
4154
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
3675
4155
|
access: { openToAll: true },
|
|
3676
4156
|
});
|
|
@@ -3695,7 +4175,12 @@ describe("boot-validator", () => {
|
|
|
3695
4175
|
|
|
3696
4176
|
test("projectionList rowAction entity-target with explicit entityId → kein Throw", () => {
|
|
3697
4177
|
const feature = defineFeature("shop", (r) => {
|
|
3698
|
-
r.entity(
|
|
4178
|
+
r.entity(
|
|
4179
|
+
"product",
|
|
4180
|
+
createEntity({
|
|
4181
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4182
|
+
}),
|
|
4183
|
+
);
|
|
3699
4184
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
3700
4185
|
access: { openToAll: true },
|
|
3701
4186
|
});
|
|
@@ -3726,7 +4211,12 @@ describe("boot-validator", () => {
|
|
|
3726
4211
|
|
|
3727
4212
|
test("projectionDetail action entity-target without explicit entityId → Throw", () => {
|
|
3728
4213
|
const feature = defineFeature("shop", (r) => {
|
|
3729
|
-
r.entity(
|
|
4214
|
+
r.entity(
|
|
4215
|
+
"product",
|
|
4216
|
+
createEntity({
|
|
4217
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4218
|
+
}),
|
|
4219
|
+
);
|
|
3730
4220
|
r.screen({
|
|
3731
4221
|
id: "order-detail",
|
|
3732
4222
|
type: "projectionDetail",
|
|
@@ -3776,7 +4266,12 @@ describe("boot-validator", () => {
|
|
|
3776
4266
|
|
|
3777
4267
|
test('entityEdit.actions drawer-target unknown → throw with actionLabel "action"', () => {
|
|
3778
4268
|
const feature = defineFeature("shop", (r) => {
|
|
3779
|
-
r.entity(
|
|
4269
|
+
r.entity(
|
|
4270
|
+
"product",
|
|
4271
|
+
createEntity({
|
|
4272
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4273
|
+
}),
|
|
4274
|
+
);
|
|
3780
4275
|
r.screen({
|
|
3781
4276
|
id: "product-edit",
|
|
3782
4277
|
type: "entityEdit",
|
|
@@ -3794,7 +4289,12 @@ describe("boot-validator", () => {
|
|
|
3794
4289
|
|
|
3795
4290
|
test("entityEdit.actions drawer-target → registered actionForm → no throw", () => {
|
|
3796
4291
|
const feature = defineFeature("shop", (r) => {
|
|
3797
|
-
r.entity(
|
|
4292
|
+
r.entity(
|
|
4293
|
+
"product",
|
|
4294
|
+
createEntity({
|
|
4295
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4296
|
+
}),
|
|
4297
|
+
);
|
|
3798
4298
|
r.writeHandler(
|
|
3799
4299
|
"add-note",
|
|
3800
4300
|
z.object({ note: z.string() }),
|
|
@@ -3959,7 +4459,12 @@ describe("boot-validator", () => {
|
|
|
3959
4459
|
describe("entityList rowAction rowClick", () => {
|
|
3960
4460
|
function makeFeature(rowClickCount: number) {
|
|
3961
4461
|
return defineFeature("shop", (r) => {
|
|
3962
|
-
r.entity(
|
|
4462
|
+
r.entity(
|
|
4463
|
+
"product",
|
|
4464
|
+
createEntity({
|
|
4465
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4466
|
+
}),
|
|
4467
|
+
);
|
|
3963
4468
|
r.screen({ id: "product-editor", type: "custom", renderer: { react: "stub" } });
|
|
3964
4469
|
r.screen({
|
|
3965
4470
|
id: "product-list",
|
|
@@ -3992,7 +4497,12 @@ describe("boot-validator", () => {
|
|
|
3992
4497
|
describe("entityList rowAction kind=writeHandler handler-QN", () => {
|
|
3993
4498
|
function makeFeature(handlerQn: string, register: boolean) {
|
|
3994
4499
|
return defineFeature("shop", (r) => {
|
|
3995
|
-
r.entity(
|
|
4500
|
+
r.entity(
|
|
4501
|
+
"product",
|
|
4502
|
+
createEntity({
|
|
4503
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4504
|
+
}),
|
|
4505
|
+
);
|
|
3996
4506
|
r.screen({
|
|
3997
4507
|
id: "product-list",
|
|
3998
4508
|
type: "entityList",
|
|
@@ -4033,7 +4543,12 @@ describe("boot-validator", () => {
|
|
|
4033
4543
|
describe("entityList rowAction payload pick (Tier 2.7e-3)", () => {
|
|
4034
4544
|
function makeFeature(pick: readonly string[]) {
|
|
4035
4545
|
return defineFeature("shop", (r) => {
|
|
4036
|
-
r.entity(
|
|
4546
|
+
r.entity(
|
|
4547
|
+
"product",
|
|
4548
|
+
createEntity({
|
|
4549
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4550
|
+
}),
|
|
4551
|
+
);
|
|
4037
4552
|
r.screen({
|
|
4038
4553
|
id: "product-list",
|
|
4039
4554
|
type: "entityList",
|
|
@@ -4090,7 +4605,7 @@ describe("boot-validator", () => {
|
|
|
4090
4605
|
r.entity(
|
|
4091
4606
|
"product",
|
|
4092
4607
|
createEntity({
|
|
4093
|
-
fields: { name: createTextField() },
|
|
4608
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4094
4609
|
softDelete: opts.softDelete ?? false,
|
|
4095
4610
|
}),
|
|
4096
4611
|
);
|
|
@@ -4166,7 +4681,12 @@ describe("boot-validator", () => {
|
|
|
4166
4681
|
describe("entityList toolbarAction navigate (Tier 2.7e-2)", () => {
|
|
4167
4682
|
function makeFeature(targetScreen: string, withTarget: boolean) {
|
|
4168
4683
|
return defineFeature("shop", (r) => {
|
|
4169
|
-
r.entity(
|
|
4684
|
+
r.entity(
|
|
4685
|
+
"product",
|
|
4686
|
+
createEntity({
|
|
4687
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4688
|
+
}),
|
|
4689
|
+
);
|
|
4170
4690
|
r.screen({
|
|
4171
4691
|
id: "product-list",
|
|
4172
4692
|
type: "entityList",
|
|
@@ -4194,7 +4714,12 @@ describe("boot-validator", () => {
|
|
|
4194
4714
|
|
|
4195
4715
|
test("navigate-target → screen in ANOTHER feature → kein Throw (cross-feature)", () => {
|
|
4196
4716
|
const list = defineFeature("shop", (r) => {
|
|
4197
|
-
r.entity(
|
|
4717
|
+
r.entity(
|
|
4718
|
+
"product",
|
|
4719
|
+
createEntity({
|
|
4720
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4721
|
+
}),
|
|
4722
|
+
);
|
|
4198
4723
|
r.screen({
|
|
4199
4724
|
id: "product-list",
|
|
4200
4725
|
type: "entityList",
|
|
@@ -4215,7 +4740,12 @@ describe("boot-validator", () => {
|
|
|
4215
4740
|
describe("entityList toolbarAction writeHandler handler-QN (Tier 2.7e-2)", () => {
|
|
4216
4741
|
function makeFeature(handlerQn: string, register: boolean) {
|
|
4217
4742
|
return defineFeature("shop", (r) => {
|
|
4218
|
-
r.entity(
|
|
4743
|
+
r.entity(
|
|
4744
|
+
"product",
|
|
4745
|
+
createEntity({
|
|
4746
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4747
|
+
}),
|
|
4748
|
+
);
|
|
4219
4749
|
r.screen({
|
|
4220
4750
|
id: "product-list",
|
|
4221
4751
|
type: "entityList",
|
|
@@ -4257,7 +4787,12 @@ describe("boot-validator", () => {
|
|
|
4257
4787
|
}) {
|
|
4258
4788
|
const targetId = opts.targetId ?? "restock-form";
|
|
4259
4789
|
return defineFeature("shop", (r) => {
|
|
4260
|
-
r.entity(
|
|
4790
|
+
r.entity(
|
|
4791
|
+
"product",
|
|
4792
|
+
createEntity({
|
|
4793
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4794
|
+
}),
|
|
4795
|
+
);
|
|
4261
4796
|
r.screen({
|
|
4262
4797
|
id: "product-list",
|
|
4263
4798
|
type: "entityList",
|
|
@@ -4314,7 +4849,12 @@ describe("boot-validator", () => {
|
|
|
4314
4849
|
|
|
4315
4850
|
test("drawer-target → actionForm in ANOTHER feature → throw (drawer resolves same-feature only)", () => {
|
|
4316
4851
|
const list = defineFeature("shop", (r) => {
|
|
4317
|
-
r.entity(
|
|
4852
|
+
r.entity(
|
|
4853
|
+
"product",
|
|
4854
|
+
createEntity({
|
|
4855
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4856
|
+
}),
|
|
4857
|
+
);
|
|
4318
4858
|
r.screen({
|
|
4319
4859
|
id: "product-list",
|
|
4320
4860
|
type: "entityList",
|
|
@@ -4354,7 +4894,12 @@ describe("boot-validator", () => {
|
|
|
4354
4894
|
}) {
|
|
4355
4895
|
const targetId = opts.targetId ?? "restock-form";
|
|
4356
4896
|
return defineFeature("shop", (r) => {
|
|
4357
|
-
r.entity(
|
|
4897
|
+
r.entity(
|
|
4898
|
+
"product",
|
|
4899
|
+
createEntity({
|
|
4900
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4901
|
+
}),
|
|
4902
|
+
);
|
|
4358
4903
|
r.screen({
|
|
4359
4904
|
id: "product-list",
|
|
4360
4905
|
type: "entityList",
|
|
@@ -4576,7 +5121,12 @@ describe("boot-validator", () => {
|
|
|
4576
5121
|
test('screen.id mit "." → defineFeature throws (kebab-case)', () => {
|
|
4577
5122
|
expect(() =>
|
|
4578
5123
|
defineFeature("shop", (r) => {
|
|
4579
|
-
r.entity(
|
|
5124
|
+
r.entity(
|
|
5125
|
+
"product",
|
|
5126
|
+
createEntity({
|
|
5127
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5128
|
+
}),
|
|
5129
|
+
);
|
|
4580
5130
|
r.screen({
|
|
4581
5131
|
id: "product.list",
|
|
4582
5132
|
type: "entityList",
|
|
@@ -4589,7 +5139,12 @@ describe("boot-validator", () => {
|
|
|
4589
5139
|
|
|
4590
5140
|
test("screen.id im kebab-case → kein Throw", () => {
|
|
4591
5141
|
const feature = defineFeature("shop", (r) => {
|
|
4592
|
-
r.entity(
|
|
5142
|
+
r.entity(
|
|
5143
|
+
"product",
|
|
5144
|
+
createEntity({
|
|
5145
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5146
|
+
}),
|
|
5147
|
+
);
|
|
4593
5148
|
r.screen({ id: "product-list", type: "entityList", entity: "product", columns: ["name"] });
|
|
4594
5149
|
});
|
|
4595
5150
|
expect(() => validateBoot([feature])).not.toThrow();
|
|
@@ -4621,7 +5176,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4621
5176
|
|
|
4622
5177
|
test("navigate with params targeting a dashboard screen without a filter → Throw (params would be a no-op)", () => {
|
|
4623
5178
|
const feature = defineFeature("shop", (r) => {
|
|
4624
|
-
r.entity(
|
|
5179
|
+
r.entity(
|
|
5180
|
+
"product",
|
|
5181
|
+
createEntity({
|
|
5182
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5183
|
+
}),
|
|
5184
|
+
);
|
|
4625
5185
|
r.screen({
|
|
4626
5186
|
id: "product-list",
|
|
4627
5187
|
type: "entityList",
|
|
@@ -4661,7 +5221,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4661
5221
|
|
|
4662
5222
|
test("navigate with params targeting a dashboard screen with a matching filter → no throw (useFilterParams reads it, fw#1708 follow-up)", () => {
|
|
4663
5223
|
const feature = defineFeature("shop", (r) => {
|
|
4664
|
-
r.entity(
|
|
5224
|
+
r.entity(
|
|
5225
|
+
"product",
|
|
5226
|
+
createEntity({
|
|
5227
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5228
|
+
}),
|
|
5229
|
+
);
|
|
4665
5230
|
r.screen({
|
|
4666
5231
|
id: "product-list",
|
|
4667
5232
|
type: "entityList",
|
|
@@ -4705,7 +5270,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4705
5270
|
|
|
4706
5271
|
test("navigate with params targeting a dashboard screen whose filter id doesn't match any extracted key → Throw", () => {
|
|
4707
5272
|
const feature = defineFeature("shop", (r) => {
|
|
4708
|
-
r.entity(
|
|
5273
|
+
r.entity(
|
|
5274
|
+
"product",
|
|
5275
|
+
createEntity({
|
|
5276
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5277
|
+
}),
|
|
5278
|
+
);
|
|
4709
5279
|
r.screen({
|
|
4710
5280
|
id: "product-list",
|
|
4711
5281
|
type: "entityList",
|
|
@@ -4751,8 +5321,18 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4751
5321
|
|
|
4752
5322
|
test("navigate with params targeting a cross-entity entityList screen → no throw (list screens read URL search params for filter-prefill, fw#1708)", () => {
|
|
4753
5323
|
const feature = defineFeature("housing", (r) => {
|
|
4754
|
-
r.entity(
|
|
4755
|
-
|
|
5324
|
+
r.entity(
|
|
5325
|
+
"unit",
|
|
5326
|
+
createEntity({
|
|
5327
|
+
fields: { id: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5328
|
+
}),
|
|
5329
|
+
);
|
|
5330
|
+
r.entity(
|
|
5331
|
+
"contract",
|
|
5332
|
+
createEntity({
|
|
5333
|
+
fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5334
|
+
}),
|
|
5335
|
+
);
|
|
4756
5336
|
r.screen({
|
|
4757
5337
|
id: "unit-list",
|
|
4758
5338
|
type: "entityList",
|
|
@@ -4780,7 +5360,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4780
5360
|
|
|
4781
5361
|
test("navigate with params targeting a custom screen → no throw (author owns the component, may read searchParams itself)", () => {
|
|
4782
5362
|
const feature = defineFeature("shop", (r) => {
|
|
4783
|
-
r.entity(
|
|
5363
|
+
r.entity(
|
|
5364
|
+
"product",
|
|
5365
|
+
createEntity({
|
|
5366
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5367
|
+
}),
|
|
5368
|
+
);
|
|
4784
5369
|
r.screen({
|
|
4785
5370
|
id: "product-list",
|
|
4786
5371
|
type: "entityList",
|
|
@@ -4810,7 +5395,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4810
5395
|
// runNavigate() auto-fills row["id"], landing in EntityEditUpdateBody,
|
|
4811
5396
|
// which never reads searchParams. Params would silently no-op.
|
|
4812
5397
|
const feature = defineFeature("shop", (r) => {
|
|
4813
|
-
r.entity(
|
|
5398
|
+
r.entity(
|
|
5399
|
+
"product",
|
|
5400
|
+
createEntity({
|
|
5401
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5402
|
+
}),
|
|
5403
|
+
);
|
|
4814
5404
|
r.screen({
|
|
4815
5405
|
id: "product-list",
|
|
4816
5406
|
type: "entityList",
|
|
@@ -4840,8 +5430,18 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4840
5430
|
|
|
4841
5431
|
test("navigate with params + explicit entityId targeting an entityEdit screen → Throw (forced update mode)", () => {
|
|
4842
5432
|
const feature = defineFeature("housing", (r) => {
|
|
4843
|
-
r.entity(
|
|
4844
|
-
|
|
5433
|
+
r.entity(
|
|
5434
|
+
"unit",
|
|
5435
|
+
createEntity({
|
|
5436
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5437
|
+
}),
|
|
5438
|
+
);
|
|
5439
|
+
r.entity(
|
|
5440
|
+
"contract",
|
|
5441
|
+
createEntity({
|
|
5442
|
+
fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5443
|
+
}),
|
|
5444
|
+
);
|
|
4845
5445
|
r.screen({
|
|
4846
5446
|
id: "unit-list",
|
|
4847
5447
|
type: "entityList",
|
|
@@ -4874,7 +5474,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4874
5474
|
// extended to projectionList rowActions (#1680 covered entityList only).
|
|
4875
5475
|
test("projectionList rowAction: navigate with params to an entityEdit target + explicit entityId → Throw", () => {
|
|
4876
5476
|
const feature = defineFeature("shop", (r) => {
|
|
4877
|
-
r.entity(
|
|
5477
|
+
r.entity(
|
|
5478
|
+
"product",
|
|
5479
|
+
createEntity({
|
|
5480
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5481
|
+
}),
|
|
5482
|
+
);
|
|
4878
5483
|
r.screen({
|
|
4879
5484
|
id: "product-projection",
|
|
4880
5485
|
type: "projectionList",
|
|
@@ -4905,7 +5510,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4905
5510
|
|
|
4906
5511
|
test("projectionList rowAction: navigate with params to an entityEdit-create target (no entityId) → no throw", () => {
|
|
4907
5512
|
const feature = defineFeature("shop", (r) => {
|
|
4908
|
-
r.entity(
|
|
5513
|
+
r.entity(
|
|
5514
|
+
"product",
|
|
5515
|
+
createEntity({
|
|
5516
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5517
|
+
}),
|
|
5518
|
+
);
|
|
4909
5519
|
r.queryHandler("products", z.object({}), async () => ({ rows: [], nextCursor: null }), {
|
|
4910
5520
|
access: { openToAll: true },
|
|
4911
5521
|
});
|
|
@@ -4940,8 +5550,18 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4940
5550
|
// renderer's same-entity row["id"] fallback does NOT fire, so this stays
|
|
4941
5551
|
// in create mode and EntityEditCreateBody reads the params.
|
|
4942
5552
|
const feature = defineFeature("housing", (r) => {
|
|
4943
|
-
r.entity(
|
|
4944
|
-
|
|
5553
|
+
r.entity(
|
|
5554
|
+
"unit",
|
|
5555
|
+
createEntity({
|
|
5556
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5557
|
+
}),
|
|
5558
|
+
);
|
|
5559
|
+
r.entity(
|
|
5560
|
+
"contract",
|
|
5561
|
+
createEntity({
|
|
5562
|
+
fields: { unitId: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5563
|
+
}),
|
|
5564
|
+
);
|
|
4945
5565
|
r.screen({
|
|
4946
5566
|
id: "unit-list",
|
|
4947
5567
|
type: "entityList",
|
|
@@ -4969,7 +5589,12 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4969
5589
|
|
|
4970
5590
|
test("navigate with params targeting an actionForm screen → no throw", () => {
|
|
4971
5591
|
const feature = defineFeature("shop", (r) => {
|
|
4972
|
-
r.entity(
|
|
5592
|
+
r.entity(
|
|
5593
|
+
"product",
|
|
5594
|
+
createEntity({
|
|
5595
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
5596
|
+
}),
|
|
5597
|
+
);
|
|
4973
5598
|
r.writeHandler(
|
|
4974
5599
|
"create",
|
|
4975
5600
|
z.object({ name: z.string() }),
|
|
@@ -4995,7 +5620,7 @@ describe("boot-validator — config key backing × scope", () => {
|
|
|
4995
5620
|
id: "product-form",
|
|
4996
5621
|
type: "actionForm",
|
|
4997
5622
|
handler: "shop:write:create",
|
|
4998
|
-
fields: { name: createTextField() },
|
|
5623
|
+
fields: { name: createTextField({ personal: false, reason: "test_fixture" }) },
|
|
4999
5624
|
layout: { sections: [{ columns: 1, fields: ["name"] }] },
|
|
5000
5625
|
});
|
|
5001
5626
|
});
|