@conform-ed/contracts 0.0.12 → 0.0.14
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/.turbo/turbo-build.log +1 -2
- package/.turbo/turbo-format.log +5 -5
- package/.turbo/turbo-lint.log +3 -3
- package/.turbo/turbo-test.log +228 -223
- package/.turbo/turbo-typecheck.log +1 -1
- package/package.json +4 -4
- package/src/adapter.ts +1 -1
- package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +6 -6
- package/src/caliper/v1_2/shared.ts +2 -2
- package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +1 -0
- package/src/case/v1_1/index.ts +41 -41
- package/src/cat/v1_0/index.ts +39 -39
- package/src/cat/v1_0/shared.ts +1 -1
- package/src/clr/v2_0/shared.ts +19 -17
- package/src/cmi5/v1_0/index.ts +19 -19
- package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +3 -3
- package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +2 -1
- package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +2 -1
- package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +2 -1
- package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +16 -13
- package/src/common-cartridge/v1_3/index.ts +3 -3
- package/src/common-cartridge/v1_3/lom-internal.ts +19 -19
- package/src/common-cartridge/v1_3/shared.ts +1 -1
- package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +6 -6
- package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +1 -0
- package/src/common-cartridge/v1_4/index.ts +5 -5
- package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/lom-internal.ts +30 -27
- package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +16 -13
- package/src/common-cartridge/v1_4/shared.ts +1 -1
- package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +5 -5
- package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +2 -1
- package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +2 -1
- package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +1 -1
- package/src/config.ts +3 -3
- package/src/h5p/v1/content.ts +1 -0
- package/src/h5p/v1/h5p-json.ts +4 -3
- package/src/h5p/v1/index.ts +21 -21
- package/src/h5p/v1/library-json.ts +2 -1
- package/src/lti/ags/v2_0/index.ts +12 -12
- package/src/lti/deep-linking/v2_0/index.ts +10 -10
- package/src/lti/index.ts +2 -2
- package/src/lti/nrps/v2_0/index.ts +10 -10
- package/src/lti/proctoring/v1_0/index.ts +11 -11
- package/src/lti/v1_3/index.ts +17 -17
- package/src/oneroster/v1_2/index.ts +45 -45
- package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +3 -3
- package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +1 -1
- package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +1 -1
- package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +1 -1
- package/src/open-badges/v3_0/shared.ts +31 -29
- package/src/qti/v2-internal.ts +12 -11
- package/src/qti/v3_0_1/assessment-internal.ts +239 -197
- package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +4 -4
- package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +1 -1
- package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +1 -1
- package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +1 -1
- package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +4 -3
- package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +1 -1
- package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +1 -1
- package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +2 -2
- package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +1 -1
- package/src/qti/v3_0_1/processing-internal.ts +44 -33
- package/src/qti/v3_0_1/shared.ts +2 -2
- package/src/vc-data-model/v2_0/shared.ts +4 -4
- package/src/xapi/shared.ts +11 -14
- package/src/xapi/v1_0_3/index.ts +54 -54
- package/src/xapi/v2_0/index.ts +56 -56
- package/test/caliper-v1_2.test.ts +1 -1
- package/test/cat-v1_0.test.ts +1 -0
- package/test/cmi5-v1_0.test.ts +33 -31
- package/test/contracts.test.ts +1 -0
- package/test/h5p-v1.test.ts +1 -0
- package/test/lti.test.ts +1 -0
- package/test/qti-v3_0_1.test.ts +109 -0
- package/test/schema-examples.test.ts +1 -0
- package/test/xapi.test.ts +1 -0
|
@@ -1,5 +1,14 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
|
|
3
|
+
import {
|
|
4
|
+
QtiBranchRuleSchema,
|
|
5
|
+
QtiIncludeSchema,
|
|
6
|
+
QtiOutcomeProcessingSchema,
|
|
7
|
+
QtiPreConditionSchema,
|
|
8
|
+
QtiResponseProcessingSchema,
|
|
9
|
+
QtiTemplateDefaultSchema,
|
|
10
|
+
QtiTemplateProcessingSchema,
|
|
11
|
+
} from "./processing-internal";
|
|
3
12
|
import {
|
|
4
13
|
QtiDirectionSchema,
|
|
5
14
|
QtiIdentifierListSchema,
|
|
@@ -31,15 +40,6 @@ import {
|
|
|
31
40
|
QtiVariableMappingSchema,
|
|
32
41
|
QtiWeightSchema,
|
|
33
42
|
} from "./variables-internal";
|
|
34
|
-
import {
|
|
35
|
-
QtiBranchRuleSchema,
|
|
36
|
-
QtiIncludeSchema,
|
|
37
|
-
QtiOutcomeProcessingSchema,
|
|
38
|
-
QtiPreConditionSchema,
|
|
39
|
-
QtiResponseProcessingSchema,
|
|
40
|
-
QtiTemplateDefaultSchema,
|
|
41
|
-
QtiTemplateProcessingSchema,
|
|
42
|
-
} from "./processing-internal";
|
|
43
43
|
|
|
44
44
|
const QtiCommonNodeShape = {
|
|
45
45
|
id: QtiIdentifierSchema.optional(),
|
|
@@ -84,9 +84,9 @@ const interactionKinds = new Set<string>([
|
|
|
84
84
|
"endAttemptInteraction",
|
|
85
85
|
]);
|
|
86
86
|
|
|
87
|
-
export const QtiXmlContentNodeSchema: z.
|
|
87
|
+
export const QtiXmlContentNodeSchema: z.ZodType = z.lazy(() => createXmlNodeSchema(QtiContentFragmentSchema));
|
|
88
88
|
|
|
89
|
-
export const QtiPromptSchema: z.
|
|
89
|
+
export const QtiPromptSchema: z.ZodType = z.lazy(() =>
|
|
90
90
|
strictObject({
|
|
91
91
|
kind: z.literal("prompt"),
|
|
92
92
|
...QtiCommonNodeShape,
|
|
@@ -94,7 +94,7 @@ export const QtiPromptSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
94
94
|
}),
|
|
95
95
|
);
|
|
96
96
|
|
|
97
|
-
export const QtiLabelElementSchema: z.
|
|
97
|
+
export const QtiLabelElementSchema: z.ZodType = z.lazy(() =>
|
|
98
98
|
strictObject({
|
|
99
99
|
kind: z.literal("labelElement"),
|
|
100
100
|
...QtiCommonNodeShape,
|
|
@@ -133,14 +133,14 @@ export const QtiInteractionModulesSchema = strictObject({
|
|
|
133
133
|
modules: z.array(QtiInteractionModuleSchema).min(1),
|
|
134
134
|
});
|
|
135
135
|
|
|
136
|
-
export const QtiInteractionMarkupSchema: z.
|
|
136
|
+
export const QtiInteractionMarkupSchema: z.ZodType = z.lazy(() =>
|
|
137
137
|
strictObject({
|
|
138
138
|
kind: z.literal("interactionMarkup"),
|
|
139
139
|
content: z.array(QtiContentFragmentSchema).optional(),
|
|
140
140
|
}),
|
|
141
141
|
);
|
|
142
142
|
|
|
143
|
-
export const QtiFeedbackInlineSchema: z.
|
|
143
|
+
export const QtiFeedbackInlineSchema: z.ZodType = z.lazy(() =>
|
|
144
144
|
strictObject({
|
|
145
145
|
kind: z.literal("feedbackInline"),
|
|
146
146
|
...QtiCommonNodeShape,
|
|
@@ -151,7 +151,7 @@ export const QtiFeedbackInlineSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
151
151
|
}),
|
|
152
152
|
);
|
|
153
153
|
|
|
154
|
-
export const QtiFeedbackBlockSchema: z.
|
|
154
|
+
export const QtiFeedbackBlockSchema: z.ZodType = z.lazy(() =>
|
|
155
155
|
strictObject({
|
|
156
156
|
kind: z.literal("feedbackBlock"),
|
|
157
157
|
...QtiCommonNodeShape,
|
|
@@ -164,7 +164,7 @@ export const QtiFeedbackBlockSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
164
164
|
}),
|
|
165
165
|
);
|
|
166
166
|
|
|
167
|
-
export const QtiTemplateInlineSchema: z.
|
|
167
|
+
export const QtiTemplateInlineSchema: z.ZodType = z.lazy(() =>
|
|
168
168
|
strictObject({
|
|
169
169
|
kind: z.literal("templateInline"),
|
|
170
170
|
...QtiCommonNodeShape,
|
|
@@ -175,7 +175,7 @@ export const QtiTemplateInlineSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
175
175
|
}),
|
|
176
176
|
);
|
|
177
177
|
|
|
178
|
-
export const QtiTemplateBlockFeedbackBlockSchema: z.
|
|
178
|
+
export const QtiTemplateBlockFeedbackBlockSchema: z.ZodType = z.lazy(() =>
|
|
179
179
|
strictObject({
|
|
180
180
|
kind: z.literal("templateBlockFeedbackBlock"),
|
|
181
181
|
...QtiCommonNodeShape,
|
|
@@ -188,7 +188,7 @@ export const QtiTemplateBlockFeedbackBlockSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
188
188
|
}),
|
|
189
189
|
);
|
|
190
190
|
|
|
191
|
-
export const QtiTemplateBlockSchema: z.
|
|
191
|
+
export const QtiTemplateBlockSchema: z.ZodType = z.lazy(() =>
|
|
192
192
|
strictObject({
|
|
193
193
|
kind: z.literal("templateBlock"),
|
|
194
194
|
...QtiCommonNodeShape,
|
|
@@ -201,7 +201,7 @@ export const QtiTemplateBlockSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
201
201
|
}),
|
|
202
202
|
);
|
|
203
203
|
|
|
204
|
-
export const QtiRubricBlockSchema: z.
|
|
204
|
+
export const QtiRubricBlockSchema: z.ZodType = z.lazy(() =>
|
|
205
205
|
strictObject({
|
|
206
206
|
kind: z.literal("rubricBlock"),
|
|
207
207
|
...QtiCommonNodeShape,
|
|
@@ -213,7 +213,7 @@ export const QtiRubricBlockSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
213
213
|
}),
|
|
214
214
|
);
|
|
215
215
|
|
|
216
|
-
export const QtiTestRubricBlockSchema: z.
|
|
216
|
+
export const QtiTestRubricBlockSchema: z.ZodType = z.lazy(() =>
|
|
217
217
|
strictObject({
|
|
218
218
|
kind: z.literal("testRubricBlock"),
|
|
219
219
|
...QtiCommonNodeShape,
|
|
@@ -226,7 +226,7 @@ export const QtiTestRubricBlockSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
226
226
|
}),
|
|
227
227
|
);
|
|
228
228
|
|
|
229
|
-
export const QtiModalFeedbackSchema: z.
|
|
229
|
+
export const QtiModalFeedbackSchema: z.ZodType = z.lazy(() =>
|
|
230
230
|
strictObject({
|
|
231
231
|
kind: z.literal("modalFeedback"),
|
|
232
232
|
outcomeIdentifier: QtiIdentifierSchema,
|
|
@@ -240,7 +240,7 @@ export const QtiModalFeedbackSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
240
240
|
}),
|
|
241
241
|
);
|
|
242
242
|
|
|
243
|
-
export const QtiTestFeedbackSchema: z.
|
|
243
|
+
export const QtiTestFeedbackSchema: z.ZodType = z.lazy(() =>
|
|
244
244
|
strictObject({
|
|
245
245
|
kind: z.literal("testFeedback"),
|
|
246
246
|
access: z.enum(["atEnd", "during"]),
|
|
@@ -265,7 +265,7 @@ export const QtiGapSchema = strictObject({
|
|
|
265
265
|
required: z.boolean().optional(),
|
|
266
266
|
});
|
|
267
267
|
|
|
268
|
-
export const QtiGapTextSchema: z.
|
|
268
|
+
export const QtiGapTextSchema: z.ZodType = z.lazy(() =>
|
|
269
269
|
strictObject({
|
|
270
270
|
kind: z.literal("gapText"),
|
|
271
271
|
...QtiCommonNodeShape,
|
|
@@ -279,7 +279,7 @@ export const QtiGapTextSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
279
279
|
}),
|
|
280
280
|
);
|
|
281
281
|
|
|
282
|
-
export const QtiGapImgSchema: z.
|
|
282
|
+
export const QtiGapImgSchema: z.ZodType = z.lazy(() =>
|
|
283
283
|
strictObject({
|
|
284
284
|
kind: z.literal("gapImg"),
|
|
285
285
|
...QtiCommonNodeShape,
|
|
@@ -296,7 +296,7 @@ export const QtiGapImgSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
296
296
|
}),
|
|
297
297
|
);
|
|
298
298
|
|
|
299
|
-
export const QtiHotTextSchema: z.
|
|
299
|
+
export const QtiHotTextSchema: z.ZodType = z.lazy(() =>
|
|
300
300
|
strictObject({
|
|
301
301
|
kind: z.literal("hotText"),
|
|
302
302
|
...QtiCommonNodeShape,
|
|
@@ -331,7 +331,7 @@ export const QtiAssociableHotspotSchema = strictObject({
|
|
|
331
331
|
hotspotLabel: z.string().optional(),
|
|
332
332
|
});
|
|
333
333
|
|
|
334
|
-
export const QtiSimpleChoiceSchema: z.
|
|
334
|
+
export const QtiSimpleChoiceSchema: z.ZodType = z.lazy(() =>
|
|
335
335
|
strictObject({
|
|
336
336
|
kind: z.literal("simpleChoice"),
|
|
337
337
|
...QtiCommonNodeShape,
|
|
@@ -343,7 +343,7 @@ export const QtiSimpleChoiceSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
343
343
|
}),
|
|
344
344
|
);
|
|
345
345
|
|
|
346
|
-
export const QtiSimpleAssociableChoiceSchema: z.
|
|
346
|
+
export const QtiSimpleAssociableChoiceSchema: z.ZodType = z.lazy(() =>
|
|
347
347
|
strictObject({
|
|
348
348
|
kind: z.literal("simpleAssociableChoice"),
|
|
349
349
|
...QtiCommonNodeShape,
|
|
@@ -358,7 +358,7 @@ export const QtiSimpleAssociableChoiceSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
358
358
|
}),
|
|
359
359
|
);
|
|
360
360
|
|
|
361
|
-
export const QtiInlineChoiceSchema: z.
|
|
361
|
+
export const QtiInlineChoiceSchema: z.ZodType = z.lazy(() =>
|
|
362
362
|
strictObject({
|
|
363
363
|
kind: z.literal("inlineChoice"),
|
|
364
364
|
...QtiCommonNodeShape,
|
|
@@ -372,7 +372,7 @@ export const QtiInlineChoiceSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
372
372
|
|
|
373
373
|
const QtiRenderableNodeSchema = z.union([QtiXmlContentNodeSchema, QtiIncludeSchema]);
|
|
374
374
|
|
|
375
|
-
export const QtiChoiceInteractionSchema: z.
|
|
375
|
+
export const QtiChoiceInteractionSchema: z.ZodType = z.lazy(() =>
|
|
376
376
|
strictObject({
|
|
377
377
|
kind: z.literal("choiceInteraction"),
|
|
378
378
|
...QtiCommonNodeShape,
|
|
@@ -388,7 +388,7 @@ export const QtiChoiceInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
388
388
|
}),
|
|
389
389
|
);
|
|
390
390
|
|
|
391
|
-
export const QtiInlineChoiceInteractionSchema: z.
|
|
391
|
+
export const QtiInlineChoiceInteractionSchema: z.ZodType = z.lazy(() =>
|
|
392
392
|
strictObject({
|
|
393
393
|
kind: z.literal("inlineChoiceInteraction"),
|
|
394
394
|
...QtiCommonNodeShape,
|
|
@@ -416,7 +416,7 @@ export const QtiTextEntryInteractionSchema = strictObject({
|
|
|
416
416
|
dataPatternmaskMessage: z.string().optional(),
|
|
417
417
|
});
|
|
418
418
|
|
|
419
|
-
export const QtiExtendedTextInteractionSchema: z.
|
|
419
|
+
export const QtiExtendedTextInteractionSchema: z.ZodType = z.lazy(() =>
|
|
420
420
|
strictObject({
|
|
421
421
|
kind: z.literal("extendedTextInteraction"),
|
|
422
422
|
...QtiCommonNodeShape,
|
|
@@ -435,7 +435,7 @@ export const QtiExtendedTextInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
435
435
|
}),
|
|
436
436
|
);
|
|
437
437
|
|
|
438
|
-
export const QtiSimpleMatchSetSchema: z.
|
|
438
|
+
export const QtiSimpleMatchSetSchema: z.ZodType = z.lazy(() =>
|
|
439
439
|
strictObject({
|
|
440
440
|
kind: z.literal("simpleMatchSet"),
|
|
441
441
|
id: QtiIdentifierSchema.optional(),
|
|
@@ -444,7 +444,7 @@ export const QtiSimpleMatchSetSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
444
444
|
}),
|
|
445
445
|
);
|
|
446
446
|
|
|
447
|
-
export const QtiMatchInteractionSchema: z.
|
|
447
|
+
export const QtiMatchInteractionSchema: z.ZodType = z.lazy(() =>
|
|
448
448
|
strictObject({
|
|
449
449
|
kind: z.literal("matchInteraction"),
|
|
450
450
|
...QtiCommonNodeShape,
|
|
@@ -462,7 +462,7 @@ export const QtiMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
462
462
|
|
|
463
463
|
export const QtiGapChoiceSchema = z.union([QtiGapTextSchema, QtiGapImgSchema]);
|
|
464
464
|
|
|
465
|
-
export const QtiGapMatchInteractionSchema: z.
|
|
465
|
+
export const QtiGapMatchInteractionSchema: z.ZodType = z.lazy(() =>
|
|
466
466
|
strictObject({
|
|
467
467
|
kind: z.literal("gapMatchInteraction"),
|
|
468
468
|
...QtiCommonNodeShape,
|
|
@@ -479,7 +479,7 @@ export const QtiGapMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
479
479
|
}),
|
|
480
480
|
);
|
|
481
481
|
|
|
482
|
-
export const QtiMediaInteractionSchema: z.
|
|
482
|
+
export const QtiMediaInteractionSchema: z.ZodType = z.lazy(() =>
|
|
483
483
|
strictObject({
|
|
484
484
|
kind: z.literal("mediaInteraction"),
|
|
485
485
|
...QtiCommonNodeShape,
|
|
@@ -494,7 +494,7 @@ export const QtiMediaInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
494
494
|
}),
|
|
495
495
|
);
|
|
496
496
|
|
|
497
|
-
export const QtiUploadInteractionSchema: z.
|
|
497
|
+
export const QtiUploadInteractionSchema: z.ZodType = z.lazy(() =>
|
|
498
498
|
strictObject({
|
|
499
499
|
kind: z.literal("uploadInteraction"),
|
|
500
500
|
...QtiCommonNodeShape,
|
|
@@ -504,7 +504,7 @@ export const QtiUploadInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
504
504
|
}),
|
|
505
505
|
);
|
|
506
506
|
|
|
507
|
-
export const QtiOrderInteractionSchema: z.
|
|
507
|
+
export const QtiOrderInteractionSchema: z.ZodType = z.lazy(() =>
|
|
508
508
|
strictObject({
|
|
509
509
|
kind: z.literal("orderInteraction"),
|
|
510
510
|
...QtiCommonNodeShape,
|
|
@@ -521,7 +521,7 @@ export const QtiOrderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
521
521
|
}),
|
|
522
522
|
);
|
|
523
523
|
|
|
524
|
-
export const QtiHotTextInteractionSchema: z.
|
|
524
|
+
export const QtiHotTextInteractionSchema: z.ZodType = z.lazy(() =>
|
|
525
525
|
strictObject({
|
|
526
526
|
kind: z.literal("hotTextInteraction"),
|
|
527
527
|
...QtiCommonNodeShape,
|
|
@@ -535,7 +535,7 @@ export const QtiHotTextInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
535
535
|
}),
|
|
536
536
|
);
|
|
537
537
|
|
|
538
|
-
export const QtiHotspotInteractionSchema: z.
|
|
538
|
+
export const QtiHotspotInteractionSchema: z.ZodType = z.lazy(() =>
|
|
539
539
|
strictObject({
|
|
540
540
|
kind: z.literal("hotspotInteraction"),
|
|
541
541
|
...QtiCommonNodeShape,
|
|
@@ -550,7 +550,7 @@ export const QtiHotspotInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
550
550
|
}),
|
|
551
551
|
);
|
|
552
552
|
|
|
553
|
-
export const QtiAssociateInteractionSchema: z.
|
|
553
|
+
export const QtiAssociateInteractionSchema: z.ZodType = z.lazy(() =>
|
|
554
554
|
strictObject({
|
|
555
555
|
kind: z.literal("associateInteraction"),
|
|
556
556
|
...QtiCommonNodeShape,
|
|
@@ -563,7 +563,7 @@ export const QtiAssociateInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
563
563
|
}),
|
|
564
564
|
);
|
|
565
565
|
|
|
566
|
-
export const QtiGraphicAssociateInteractionSchema: z.
|
|
566
|
+
export const QtiGraphicAssociateInteractionSchema: z.ZodType = z.lazy(() =>
|
|
567
567
|
strictObject({
|
|
568
568
|
kind: z.literal("graphicAssociateInteraction"),
|
|
569
569
|
...QtiCommonNodeShape,
|
|
@@ -576,7 +576,7 @@ export const QtiGraphicAssociateInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
576
576
|
}),
|
|
577
577
|
);
|
|
578
578
|
|
|
579
|
-
export const QtiGraphicGapMatchInteractionSchema: z.
|
|
579
|
+
export const QtiGraphicGapMatchInteractionSchema: z.ZodType = z.lazy(() =>
|
|
580
580
|
strictObject({
|
|
581
581
|
kind: z.literal("graphicGapMatchInteraction"),
|
|
582
582
|
...QtiCommonNodeShape,
|
|
@@ -593,7 +593,7 @@ export const QtiGraphicGapMatchInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
593
593
|
}),
|
|
594
594
|
);
|
|
595
595
|
|
|
596
|
-
export const QtiGraphicOrderInteractionSchema: z.
|
|
596
|
+
export const QtiGraphicOrderInteractionSchema: z.ZodType = z.lazy(() =>
|
|
597
597
|
strictObject({
|
|
598
598
|
kind: z.literal("graphicOrderInteraction"),
|
|
599
599
|
...QtiCommonNodeShape,
|
|
@@ -606,7 +606,7 @@ export const QtiGraphicOrderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
606
606
|
}),
|
|
607
607
|
);
|
|
608
608
|
|
|
609
|
-
export const QtiSelectPointInteractionSchema: z.
|
|
609
|
+
export const QtiSelectPointInteractionSchema: z.ZodType = z.lazy(() =>
|
|
610
610
|
strictObject({
|
|
611
611
|
kind: z.literal("selectPointInteraction"),
|
|
612
612
|
...QtiCommonNodeShape,
|
|
@@ -618,7 +618,7 @@ export const QtiSelectPointInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
618
618
|
}),
|
|
619
619
|
);
|
|
620
620
|
|
|
621
|
-
export const QtiSliderInteractionSchema: z.
|
|
621
|
+
export const QtiSliderInteractionSchema: z.ZodType = z.lazy(() =>
|
|
622
622
|
strictObject({
|
|
623
623
|
kind: z.literal("sliderInteraction"),
|
|
624
624
|
...QtiCommonNodeShape,
|
|
@@ -633,7 +633,7 @@ export const QtiSliderInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
633
633
|
}),
|
|
634
634
|
);
|
|
635
635
|
|
|
636
|
-
export const QtiPositionObjectInteractionSchema: z.
|
|
636
|
+
export const QtiPositionObjectInteractionSchema: z.ZodType = z.lazy(() =>
|
|
637
637
|
strictObject({
|
|
638
638
|
kind: z.literal("positionObjectInteraction"),
|
|
639
639
|
...QtiCommonNodeShape,
|
|
@@ -645,7 +645,7 @@ export const QtiPositionObjectInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
645
645
|
}),
|
|
646
646
|
);
|
|
647
647
|
|
|
648
|
-
export const QtiPortableCustomInteractionSchema: z.
|
|
648
|
+
export const QtiPortableCustomInteractionSchema: z.ZodType = z.lazy(() =>
|
|
649
649
|
strictObject({
|
|
650
650
|
kind: z.literal("portableCustomInteraction"),
|
|
651
651
|
...QtiCommonNodeShape,
|
|
@@ -659,10 +659,12 @@ export const QtiPortableCustomInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
659
659
|
catalogInfo: QtiCatalogInfoSchema.optional(),
|
|
660
660
|
customInteractionTypeIdentifier: z.string(),
|
|
661
661
|
module: z.string().optional(),
|
|
662
|
+
/** PCI configuration properties: the element's data-* attributes, prefix stripped. */
|
|
663
|
+
properties: z.record(z.string(), z.string()).optional(),
|
|
662
664
|
}),
|
|
663
665
|
);
|
|
664
666
|
|
|
665
|
-
export const QtiCustomInteractionSchema: z.
|
|
667
|
+
export const QtiCustomInteractionSchema: z.ZodType = z.lazy(() =>
|
|
666
668
|
strictObject({
|
|
667
669
|
kind: z.literal("customInteraction"),
|
|
668
670
|
...QtiCommonNodeShape,
|
|
@@ -672,7 +674,7 @@ export const QtiCustomInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
672
674
|
}),
|
|
673
675
|
);
|
|
674
676
|
|
|
675
|
-
export const QtiDrawingInteractionSchema: z.
|
|
677
|
+
export const QtiDrawingInteractionSchema: z.ZodType = z.lazy(() =>
|
|
676
678
|
strictObject({
|
|
677
679
|
kind: z.literal("drawingInteraction"),
|
|
678
680
|
...QtiCommonNodeShape,
|
|
@@ -683,17 +685,19 @@ export const QtiDrawingInteractionSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
683
685
|
}),
|
|
684
686
|
);
|
|
685
687
|
|
|
686
|
-
export const QtiEndAttemptInteractionSchema: z.
|
|
688
|
+
export const QtiEndAttemptInteractionSchema: z.ZodType = z.lazy(() =>
|
|
687
689
|
strictObject({
|
|
688
690
|
kind: z.literal("endAttemptInteraction"),
|
|
689
691
|
...QtiCommonNodeShape,
|
|
690
692
|
responseIdentifier: QtiIdentifierSchema.optional(),
|
|
693
|
+
// The XSD-required button label (`title` on qti-end-attempt-interaction).
|
|
694
|
+
title: z.string().optional(),
|
|
691
695
|
content: z.array(QtiContentFragmentSchema).optional(),
|
|
692
696
|
attributes: XmlForeignAttributesSchema.optional(),
|
|
693
697
|
}),
|
|
694
698
|
);
|
|
695
699
|
|
|
696
|
-
export const QtiPositionObjectStageSchema: z.
|
|
700
|
+
export const QtiPositionObjectStageSchema: z.ZodType = z.lazy(() =>
|
|
697
701
|
strictObject({
|
|
698
702
|
kind: z.literal("positionObjectStage"),
|
|
699
703
|
id: QtiIdentifierSchema.optional(),
|
|
@@ -703,7 +707,7 @@ export const QtiPositionObjectStageSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
703
707
|
}),
|
|
704
708
|
);
|
|
705
709
|
|
|
706
|
-
export const QtiContentFragmentSchema: z.
|
|
710
|
+
export const QtiContentFragmentSchema: z.ZodType = z.lazy(() =>
|
|
707
711
|
z.union([
|
|
708
712
|
z.string(),
|
|
709
713
|
QtiXmlContentNodeSchema,
|
|
@@ -749,13 +753,13 @@ export const QtiContentFragmentSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
749
753
|
]),
|
|
750
754
|
);
|
|
751
755
|
|
|
752
|
-
export const QtiItemBodySchema: z.
|
|
756
|
+
export const QtiItemBodySchema: z.ZodType = z.lazy(() =>
|
|
753
757
|
strictObject({
|
|
754
758
|
content: z.array(QtiContentFragmentSchema).min(1),
|
|
755
759
|
}),
|
|
756
760
|
);
|
|
757
761
|
|
|
758
|
-
export const QtiStimulusBodySchema: z.
|
|
762
|
+
export const QtiStimulusBodySchema: z.ZodType = z.lazy(() =>
|
|
759
763
|
strictObject({
|
|
760
764
|
...QtiCommonNodeShape,
|
|
761
765
|
content: z.array(QtiContentFragmentSchema).min(1),
|
|
@@ -827,7 +831,35 @@ export const QtiAssessmentStimulusRefSchema = strictObject({
|
|
|
827
831
|
foreignAttributes: XmlForeignAttributesSchema.optional(),
|
|
828
832
|
});
|
|
829
833
|
|
|
830
|
-
|
|
834
|
+
/**
|
|
835
|
+
* The ASI node fields the cross-validators read. Nodes arrive pre-validation
|
|
836
|
+
* (these refinements run during parsing), so every field stays `unknown` until
|
|
837
|
+
* narrowed; the declared names are the QTI vocabulary the checks are defined
|
|
838
|
+
* over -- anything else on a node flows through the index signature.
|
|
839
|
+
*/
|
|
840
|
+
type AsiNodeView = {
|
|
841
|
+
kind?: unknown;
|
|
842
|
+
identifier?: unknown;
|
|
843
|
+
responseIdentifier?: unknown;
|
|
844
|
+
content?: unknown;
|
|
845
|
+
simpleChoices?: unknown;
|
|
846
|
+
inlineChoices?: unknown;
|
|
847
|
+
gapChoices?: unknown;
|
|
848
|
+
hotspotChoices?: unknown;
|
|
849
|
+
minChoices?: unknown;
|
|
850
|
+
maxChoices?: unknown;
|
|
851
|
+
minStrings?: unknown;
|
|
852
|
+
maxStrings?: unknown;
|
|
853
|
+
minAssociations?: unknown;
|
|
854
|
+
maxAssociations?: unknown;
|
|
855
|
+
minPlays?: unknown;
|
|
856
|
+
maxPlays?: unknown;
|
|
857
|
+
lowerBound?: unknown;
|
|
858
|
+
upperBound?: unknown;
|
|
859
|
+
[key: string]: unknown;
|
|
860
|
+
};
|
|
861
|
+
|
|
862
|
+
function walkUnknown(value: unknown, visit: (node: AsiNodeView) => void) {
|
|
831
863
|
if (Array.isArray(value)) {
|
|
832
864
|
for (const entry of value) {
|
|
833
865
|
walkUnknown(entry, visit);
|
|
@@ -839,7 +871,7 @@ function walkUnknown(value: unknown, visit: (node: Record<string, unknown>) => v
|
|
|
839
871
|
return;
|
|
840
872
|
}
|
|
841
873
|
|
|
842
|
-
const node = value as
|
|
874
|
+
const node = value as AsiNodeView;
|
|
843
875
|
visit(node);
|
|
844
876
|
|
|
845
877
|
for (const [key, child] of Object.entries(node)) {
|
|
@@ -863,8 +895,8 @@ function containsInteraction(value: unknown): boolean {
|
|
|
863
895
|
return found;
|
|
864
896
|
}
|
|
865
897
|
|
|
866
|
-
function collectInteractionNodes(value: unknown):
|
|
867
|
-
const interactions:
|
|
898
|
+
function collectInteractionNodes(value: unknown): AsiNodeView[] {
|
|
899
|
+
const interactions: AsiNodeView[] = [];
|
|
868
900
|
|
|
869
901
|
walkUnknown(value, (node) => {
|
|
870
902
|
const kind = typeof node.kind === "string" ? node.kind : null;
|
|
@@ -876,8 +908,8 @@ function collectInteractionNodes(value: unknown): Array<Record<string, unknown>>
|
|
|
876
908
|
return interactions;
|
|
877
909
|
}
|
|
878
910
|
|
|
879
|
-
function collectNodesByKind(value: unknown, kinds: readonly string[]):
|
|
880
|
-
const matches:
|
|
911
|
+
function collectNodesByKind(value: unknown, kinds: readonly string[]): AsiNodeView[] {
|
|
912
|
+
const matches: AsiNodeView[] = [];
|
|
881
913
|
const kindSet = new Set(kinds);
|
|
882
914
|
|
|
883
915
|
walkUnknown(value, (node) => {
|
|
@@ -936,14 +968,16 @@ function validateOutcomeDeclarationConventions(
|
|
|
936
968
|
path: Array<string | number>,
|
|
937
969
|
) {
|
|
938
970
|
for (const [index, declaration] of declarations.entries()) {
|
|
971
|
+
// The information model recommends float, but the XSD does not enforce it and the
|
|
972
|
+
// official corpus ships integer SCOREs — require single numeric, nothing stricter.
|
|
939
973
|
if (
|
|
940
974
|
["SCORE", "MAXSCORE"].includes(declaration.identifier) &&
|
|
941
|
-
(declaration.baseType
|
|
975
|
+
(!["float", "integer"].includes(declaration.baseType ?? "") || declaration.cardinality !== "single")
|
|
942
976
|
) {
|
|
943
977
|
addIssue(
|
|
944
978
|
context,
|
|
945
979
|
[...path, index],
|
|
946
|
-
`${declaration.identifier} should
|
|
980
|
+
`${declaration.identifier} should be a single numeric outcome (baseType 'float' or 'integer').`,
|
|
947
981
|
);
|
|
948
982
|
}
|
|
949
983
|
|
|
@@ -971,7 +1005,7 @@ function addDuplicateSummaryIssue(
|
|
|
971
1005
|
}
|
|
972
1006
|
|
|
973
1007
|
function validateResponseBinding(
|
|
974
|
-
interaction:
|
|
1008
|
+
interaction: AsiNodeView,
|
|
975
1009
|
responsesById: Map<string, z.infer<typeof QtiResponseDeclarationSchema>>,
|
|
976
1010
|
context: z.RefinementCtx,
|
|
977
1011
|
path: Array<string | number>,
|
|
@@ -1018,8 +1052,8 @@ function validateResponseBinding(
|
|
|
1018
1052
|
typeof choice.identifier === "string" ? [choice.identifier] : [],
|
|
1019
1053
|
),
|
|
1020
1054
|
);
|
|
1021
|
-
const minChoices =
|
|
1022
|
-
const maxChoices =
|
|
1055
|
+
const minChoices = minOf(interaction.minChoices);
|
|
1056
|
+
const maxChoices = boundedMax(interaction.maxChoices);
|
|
1023
1057
|
if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
|
|
1024
1058
|
addIssue(context, path, "choiceInteraction minChoices must not exceed maxChoices.");
|
|
1025
1059
|
}
|
|
@@ -1052,8 +1086,8 @@ function validateResponseBinding(
|
|
|
1052
1086
|
break;
|
|
1053
1087
|
}
|
|
1054
1088
|
requireBaseAndCardinality(["string", "integer", "float"], ["single", "multiple", "ordered"]);
|
|
1055
|
-
const minStrings =
|
|
1056
|
-
const maxStrings =
|
|
1089
|
+
const minStrings = minOf(interaction.minStrings);
|
|
1090
|
+
const maxStrings = boundedMax(interaction.maxStrings);
|
|
1057
1091
|
if (minStrings !== undefined && maxStrings !== undefined && minStrings > maxStrings) {
|
|
1058
1092
|
addIssue(context, path, "extendedTextInteraction minStrings must not exceed maxStrings.");
|
|
1059
1093
|
}
|
|
@@ -1062,8 +1096,8 @@ function validateResponseBinding(
|
|
|
1062
1096
|
|
|
1063
1097
|
case "matchInteraction": {
|
|
1064
1098
|
requireBaseAndCardinality(["directedPair"], ["single", "multiple"]);
|
|
1065
|
-
const minAssociations =
|
|
1066
|
-
const maxAssociations =
|
|
1099
|
+
const minAssociations = minOf(interaction.minAssociations);
|
|
1100
|
+
const maxAssociations = boundedMax(interaction.maxAssociations);
|
|
1067
1101
|
if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
|
|
1068
1102
|
addIssue(context, path, "matchInteraction minAssociations must not exceed maxAssociations.");
|
|
1069
1103
|
}
|
|
@@ -1080,8 +1114,8 @@ function validateResponseBinding(
|
|
|
1080
1114
|
typeof choice.identifier === "string" ? [choice.identifier] : [],
|
|
1081
1115
|
),
|
|
1082
1116
|
);
|
|
1083
|
-
const minAssociations =
|
|
1084
|
-
const maxAssociations =
|
|
1117
|
+
const minAssociations = minOf(interaction.minAssociations);
|
|
1118
|
+
const maxAssociations = boundedMax(interaction.maxAssociations);
|
|
1085
1119
|
if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
|
|
1086
1120
|
addIssue(context, path, "gapMatchInteraction minAssociations must not exceed maxAssociations.");
|
|
1087
1121
|
}
|
|
@@ -1090,8 +1124,8 @@ function validateResponseBinding(
|
|
|
1090
1124
|
|
|
1091
1125
|
case "mediaInteraction": {
|
|
1092
1126
|
requireBaseAndCardinality(["integer"], ["single"]);
|
|
1093
|
-
const minPlays =
|
|
1094
|
-
const maxPlays =
|
|
1127
|
+
const minPlays = minOf(interaction.minPlays);
|
|
1128
|
+
const maxPlays = boundedMax(interaction.maxPlays);
|
|
1095
1129
|
if (minPlays !== undefined && maxPlays !== undefined && minPlays > maxPlays) {
|
|
1096
1130
|
addIssue(context, path, "mediaInteraction minPlays must not exceed maxPlays.");
|
|
1097
1131
|
}
|
|
@@ -1139,8 +1173,8 @@ function validateResponseBinding(
|
|
|
1139
1173
|
typeof choice.identifier === "string" ? [choice.identifier] : [],
|
|
1140
1174
|
),
|
|
1141
1175
|
);
|
|
1142
|
-
const minChoices =
|
|
1143
|
-
const maxChoices =
|
|
1176
|
+
const minChoices = minOf(interaction.minChoices);
|
|
1177
|
+
const maxChoices = boundedMax(interaction.maxChoices);
|
|
1144
1178
|
if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
|
|
1145
1179
|
addIssue(context, path, "hotspotInteraction minChoices must not exceed maxChoices.");
|
|
1146
1180
|
}
|
|
@@ -1150,8 +1184,8 @@ function validateResponseBinding(
|
|
|
1150
1184
|
case "associateInteraction":
|
|
1151
1185
|
case "graphicAssociateInteraction": {
|
|
1152
1186
|
requireBaseAndCardinality(["pair"], ["single", "multiple"]);
|
|
1153
|
-
const minAssociations =
|
|
1154
|
-
const maxAssociations =
|
|
1187
|
+
const minAssociations = minOf(interaction.minAssociations);
|
|
1188
|
+
const maxAssociations = boundedMax(interaction.maxAssociations);
|
|
1155
1189
|
if (minAssociations !== undefined && maxAssociations !== undefined && minAssociations > maxAssociations) {
|
|
1156
1190
|
addIssue(context, path, `${kind} minAssociations must not exceed maxAssociations.`);
|
|
1157
1191
|
}
|
|
@@ -1171,8 +1205,8 @@ function validateResponseBinding(
|
|
|
1171
1205
|
case "selectPointInteraction":
|
|
1172
1206
|
case "positionObjectInteraction": {
|
|
1173
1207
|
requireBaseAndCardinality(["point"], ["single", "multiple"]);
|
|
1174
|
-
const minChoices =
|
|
1175
|
-
const maxChoices =
|
|
1208
|
+
const minChoices = minOf(interaction.minChoices);
|
|
1209
|
+
const maxChoices = boundedMax(interaction.maxChoices);
|
|
1176
1210
|
if (minChoices !== undefined && maxChoices !== undefined && minChoices > maxChoices) {
|
|
1177
1211
|
addIssue(context, path, `${kind} minChoices must not exceed maxChoices.`);
|
|
1178
1212
|
}
|
|
@@ -1191,6 +1225,13 @@ function validateResponseBinding(
|
|
|
1191
1225
|
}
|
|
1192
1226
|
}
|
|
1193
1227
|
|
|
1228
|
+
/**
|
|
1229
|
+
* Built-in session outcomes every QTI item declares implicitly. `completion_status`
|
|
1230
|
+
* is the snake_case authoring of the same built-in that the official corpus ships
|
|
1231
|
+
* (the runtime treats it as an alias when reading adaptive completion).
|
|
1232
|
+
*/
|
|
1233
|
+
const builtInOutcomeIdentifiers = new Set(["completionStatus", "completion_status"]);
|
|
1234
|
+
|
|
1194
1235
|
function validateOutcomeReferences(
|
|
1195
1236
|
value: unknown,
|
|
1196
1237
|
declaredOutcomes: Map<string, unknown>,
|
|
@@ -1199,12 +1240,21 @@ function validateOutcomeReferences(
|
|
|
1199
1240
|
) {
|
|
1200
1241
|
for (const rule of collectNodesByKind(value, ["setOutcomeValue", "lookupOutcomeValue"])) {
|
|
1201
1242
|
const identifier = typeof rule.identifier === "string" ? rule.identifier : undefined;
|
|
1202
|
-
if (identifier && !declaredOutcomes.has(identifier)) {
|
|
1243
|
+
if (identifier && !declaredOutcomes.has(identifier) && !builtInOutcomeIdentifiers.has(identifier)) {
|
|
1203
1244
|
addIssue(context, path, `Processing rule references undeclared outcome identifier '${identifier}'.`);
|
|
1204
1245
|
}
|
|
1205
1246
|
}
|
|
1206
1247
|
}
|
|
1207
1248
|
|
|
1249
|
+
/** QTI max-* attributes use 0 to mean "unbounded"; bound checks must ignore it. */
|
|
1250
|
+
function boundedMax(value: unknown): number | undefined {
|
|
1251
|
+
return typeof value === "number" && value > 0 ? value : undefined;
|
|
1252
|
+
}
|
|
1253
|
+
|
|
1254
|
+
function minOf(value: unknown): number | undefined {
|
|
1255
|
+
return typeof value === "number" ? value : undefined;
|
|
1256
|
+
}
|
|
1257
|
+
|
|
1208
1258
|
function validateTemplateAndResponseReferences(
|
|
1209
1259
|
value: unknown,
|
|
1210
1260
|
declaredTemplates: Map<string, unknown>,
|
|
@@ -1227,7 +1277,7 @@ function validateTemplateAndResponseReferences(
|
|
|
1227
1277
|
}
|
|
1228
1278
|
}
|
|
1229
1279
|
|
|
1230
|
-
export const QtiAssessmentSectionRawSchema: z.
|
|
1280
|
+
export const QtiAssessmentSectionRawSchema: z.ZodType = z.lazy(() =>
|
|
1231
1281
|
strictObject({
|
|
1232
1282
|
identifier: QtiIdentifierSchema,
|
|
1233
1283
|
required: z.boolean().optional(),
|
|
@@ -1258,7 +1308,7 @@ export const QtiAssessmentSectionRawSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
1258
1308
|
}),
|
|
1259
1309
|
);
|
|
1260
1310
|
|
|
1261
|
-
export const QtiAssessmentSectionSchema: z.
|
|
1311
|
+
export const QtiAssessmentSectionSchema: z.ZodType = QtiAssessmentSectionRawSchema.superRefine(
|
|
1262
1312
|
(value: unknown, context) => {
|
|
1263
1313
|
const section = value as QtiSectionValidationValue;
|
|
1264
1314
|
|
|
@@ -1287,7 +1337,7 @@ export const QtiAssessmentSectionSchema: z.ZodTypeAny = QtiAssessmentSectionRawS
|
|
|
1287
1337
|
},
|
|
1288
1338
|
);
|
|
1289
1339
|
|
|
1290
|
-
export const QtiTestPartSchema: z.
|
|
1340
|
+
export const QtiTestPartSchema: z.ZodType = z.lazy(() =>
|
|
1291
1341
|
strictObject({
|
|
1292
1342
|
identifier: QtiIdentifierSchema,
|
|
1293
1343
|
title: z.string().optional(),
|
|
@@ -1305,7 +1355,7 @@ export const QtiTestPartSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
1305
1355
|
}),
|
|
1306
1356
|
);
|
|
1307
1357
|
|
|
1308
|
-
export const QtiAssessmentStimulusSchema: z.
|
|
1358
|
+
export const QtiAssessmentStimulusSchema: z.ZodType = z.lazy(() =>
|
|
1309
1359
|
strictObject({
|
|
1310
1360
|
identifier: QtiIdentifierSchema,
|
|
1311
1361
|
title: z.string(),
|
|
@@ -1319,7 +1369,7 @@ export const QtiAssessmentStimulusSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
1319
1369
|
}),
|
|
1320
1370
|
);
|
|
1321
1371
|
|
|
1322
|
-
export const QtiAssessmentItemRawSchema: z.
|
|
1372
|
+
export const QtiAssessmentItemRawSchema: z.ZodType = z.lazy(() =>
|
|
1323
1373
|
strictObject({
|
|
1324
1374
|
identifier: QtiIdentifierSchema,
|
|
1325
1375
|
title: z.string(),
|
|
@@ -1345,92 +1395,86 @@ export const QtiAssessmentItemRawSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
1345
1395
|
}),
|
|
1346
1396
|
);
|
|
1347
1397
|
|
|
1348
|
-
export const QtiAssessmentItemSchema: z.
|
|
1349
|
-
|
|
1350
|
-
|
|
1351
|
-
|
|
1352
|
-
|
|
1353
|
-
|
|
1354
|
-
|
|
1355
|
-
|
|
1356
|
-
|
|
1357
|
-
|
|
1358
|
-
|
|
1359
|
-
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1363
|
-
|
|
1364
|
-
|
|
1365
|
-
|
|
1366
|
-
|
|
1367
|
-
|
|
1368
|
-
|
|
1369
|
-
|
|
1370
|
-
|
|
1371
|
-
|
|
1372
|
-
|
|
1373
|
-
|
|
1374
|
-
|
|
1375
|
-
|
|
1376
|
-
|
|
1377
|
-
|
|
1378
|
-
|
|
1379
|
-
|
|
1380
|
-
|
|
1381
|
-
|
|
1382
|
-
|
|
1383
|
-
|
|
1384
|
-
|
|
1398
|
+
export const QtiAssessmentItemSchema: z.ZodType = QtiAssessmentItemRawSchema.superRefine((value: unknown, context) => {
|
|
1399
|
+
const item = value as QtiItemValidationValue;
|
|
1400
|
+
const contextDeclarations = asArray(item.contextDeclarations);
|
|
1401
|
+
const responseDeclarationsList = asArray(item.responseDeclarations);
|
|
1402
|
+
const outcomeDeclarationsList = asArray(item.outcomeDeclarations);
|
|
1403
|
+
const templateDeclarationsList = asArray(item.templateDeclarations);
|
|
1404
|
+
const modalFeedbacks = asArray(item.modalFeedbacks);
|
|
1405
|
+
|
|
1406
|
+
const variableDeclarations = [
|
|
1407
|
+
...contextDeclarations,
|
|
1408
|
+
...responseDeclarationsList,
|
|
1409
|
+
...outcomeDeclarationsList,
|
|
1410
|
+
...templateDeclarationsList,
|
|
1411
|
+
];
|
|
1412
|
+
|
|
1413
|
+
addDuplicateSummaryIssue(
|
|
1414
|
+
context,
|
|
1415
|
+
[],
|
|
1416
|
+
"assessment item variable declaration identifiers",
|
|
1417
|
+
variableDeclarations.map((declaration) => declaration.identifier),
|
|
1418
|
+
);
|
|
1419
|
+
|
|
1420
|
+
validateOutcomeDeclarationConventions(outcomeDeclarationsList, context, ["outcomeDeclarations"]);
|
|
1421
|
+
|
|
1422
|
+
const responseDeclarations = new Map(
|
|
1423
|
+
responseDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
|
|
1424
|
+
);
|
|
1425
|
+
const outcomeDeclarations = new Map(
|
|
1426
|
+
outcomeDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
|
|
1427
|
+
);
|
|
1428
|
+
const templateDeclarations = new Map(
|
|
1429
|
+
templateDeclarationsList.map((declaration) => [declaration.identifier, declaration]),
|
|
1430
|
+
);
|
|
1431
|
+
|
|
1432
|
+
const interactions = collectInteractionNodes(item.itemBody?.content);
|
|
1433
|
+
const responseBindings = new Map<string, string[]>();
|
|
1434
|
+
|
|
1435
|
+
for (const interaction of interactions) {
|
|
1436
|
+
const responseIdentifier =
|
|
1437
|
+
typeof interaction.responseIdentifier === "string" ? interaction.responseIdentifier : undefined;
|
|
1438
|
+
if (responseIdentifier) {
|
|
1439
|
+
const boundKinds = responseBindings.get(responseIdentifier) ?? [];
|
|
1440
|
+
boundKinds.push(String(interaction.kind));
|
|
1441
|
+
responseBindings.set(responseIdentifier, boundKinds);
|
|
1442
|
+
}
|
|
1385
1443
|
|
|
1386
|
-
|
|
1387
|
-
|
|
1388
|
-
typeof interaction.responseIdentifier === "string" ? interaction.responseIdentifier : undefined;
|
|
1389
|
-
if (responseIdentifier) {
|
|
1390
|
-
const boundKinds = responseBindings.get(responseIdentifier) ?? [];
|
|
1391
|
-
boundKinds.push(String(interaction.kind));
|
|
1392
|
-
responseBindings.set(responseIdentifier, boundKinds);
|
|
1393
|
-
}
|
|
1444
|
+
validateResponseBinding(interaction, responseDeclarations, context, ["itemBody"]);
|
|
1445
|
+
}
|
|
1394
1446
|
|
|
1395
|
-
|
|
1447
|
+
for (const [identifier, kinds] of responseBindings) {
|
|
1448
|
+
if (kinds.length > 1) {
|
|
1449
|
+
addIssue(
|
|
1450
|
+
context,
|
|
1451
|
+
["itemBody"],
|
|
1452
|
+
`Response identifier '${identifier}' is bound by multiple interactions: ${kinds.join(", ")}.`,
|
|
1453
|
+
);
|
|
1396
1454
|
}
|
|
1455
|
+
}
|
|
1397
1456
|
|
|
1398
|
-
|
|
1399
|
-
|
|
1400
|
-
|
|
1401
|
-
|
|
1402
|
-
|
|
1403
|
-
|
|
1404
|
-
|
|
1405
|
-
}
|
|
1457
|
+
for (const [index, feedback] of modalFeedbacks.entries()) {
|
|
1458
|
+
if (!outcomeDeclarations.has(feedback.outcomeIdentifier)) {
|
|
1459
|
+
addIssue(
|
|
1460
|
+
context,
|
|
1461
|
+
["modalFeedbacks", index, "outcomeIdentifier"],
|
|
1462
|
+
`modalFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
|
|
1463
|
+
);
|
|
1406
1464
|
}
|
|
1407
1465
|
|
|
1408
|
-
|
|
1409
|
-
|
|
1410
|
-
addIssue(
|
|
1411
|
-
context,
|
|
1412
|
-
["modalFeedbacks", index, "outcomeIdentifier"],
|
|
1413
|
-
`modalFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
|
|
1414
|
-
);
|
|
1415
|
-
}
|
|
1416
|
-
|
|
1417
|
-
if (containsInteraction(feedback.content)) {
|
|
1418
|
-
addIssue(context, ["modalFeedbacks", index, "content"], "modalFeedback content must not contain interactions.");
|
|
1419
|
-
}
|
|
1466
|
+
if (containsInteraction(feedback.content)) {
|
|
1467
|
+
addIssue(context, ["modalFeedbacks", index, "content"], "modalFeedback content must not contain interactions.");
|
|
1420
1468
|
}
|
|
1469
|
+
}
|
|
1421
1470
|
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
context,
|
|
1428
|
-
["templateProcessing"],
|
|
1429
|
-
);
|
|
1430
|
-
},
|
|
1431
|
-
);
|
|
1471
|
+
validateOutcomeReferences(item.responseProcessing, outcomeDeclarations, context, ["responseProcessing"]);
|
|
1472
|
+
validateTemplateAndResponseReferences(item.templateProcessing, templateDeclarations, responseDeclarations, context, [
|
|
1473
|
+
"templateProcessing",
|
|
1474
|
+
]);
|
|
1475
|
+
});
|
|
1432
1476
|
|
|
1433
|
-
export const QtiAssessmentTestRawSchema: z.
|
|
1477
|
+
export const QtiAssessmentTestRawSchema: z.ZodType = z.lazy(() =>
|
|
1434
1478
|
strictObject({
|
|
1435
1479
|
identifier: QtiIdentifierSchema,
|
|
1436
1480
|
title: z.string(),
|
|
@@ -1449,47 +1493,45 @@ export const QtiAssessmentTestRawSchema: z.ZodTypeAny = z.lazy(() =>
|
|
|
1449
1493
|
}),
|
|
1450
1494
|
);
|
|
1451
1495
|
|
|
1452
|
-
export const QtiAssessmentTestSchema: z.
|
|
1453
|
-
|
|
1454
|
-
|
|
1455
|
-
|
|
1456
|
-
|
|
1457
|
-
|
|
1458
|
-
const testFeedbacks = asArray(testValue.testFeedbacks);
|
|
1459
|
-
|
|
1460
|
-
addDuplicateSummaryIssue(context, [], "assessment test variable declaration identifiers", [
|
|
1461
|
-
...contextDeclarations.map((declaration) => declaration.identifier),
|
|
1462
|
-
...outcomeDeclarationsList.map((declaration) => declaration.identifier),
|
|
1463
|
-
]);
|
|
1464
|
-
|
|
1465
|
-
addDuplicateSummaryIssue(
|
|
1466
|
-
context,
|
|
1467
|
-
["testParts"],
|
|
1468
|
-
"test part identifiers",
|
|
1469
|
-
testParts.map((testPart) => testPart.identifier),
|
|
1470
|
-
);
|
|
1496
|
+
export const QtiAssessmentTestSchema: z.ZodType = QtiAssessmentTestRawSchema.superRefine((value: unknown, context) => {
|
|
1497
|
+
const testValue = value as QtiTestValidationValue;
|
|
1498
|
+
const contextDeclarations = asArray(testValue.contextDeclarations);
|
|
1499
|
+
const outcomeDeclarationsList = asArray(testValue.outcomeDeclarations);
|
|
1500
|
+
const testParts = asArray(testValue.testParts);
|
|
1501
|
+
const testFeedbacks = asArray(testValue.testFeedbacks);
|
|
1471
1502
|
|
|
1472
|
-
|
|
1503
|
+
addDuplicateSummaryIssue(context, [], "assessment test variable declaration identifiers", [
|
|
1504
|
+
...contextDeclarations.map((declaration) => declaration.identifier),
|
|
1505
|
+
...outcomeDeclarationsList.map((declaration) => declaration.identifier),
|
|
1506
|
+
]);
|
|
1473
1507
|
|
|
1474
|
-
|
|
1508
|
+
addDuplicateSummaryIssue(
|
|
1509
|
+
context,
|
|
1510
|
+
["testParts"],
|
|
1511
|
+
"test part identifiers",
|
|
1512
|
+
testParts.map((testPart) => testPart.identifier),
|
|
1513
|
+
);
|
|
1475
1514
|
|
|
1476
|
-
|
|
1477
|
-
if (!outcomes.has(feedback.outcomeIdentifier)) {
|
|
1478
|
-
addIssue(
|
|
1479
|
-
context,
|
|
1480
|
-
["testFeedbacks", index, "outcomeIdentifier"],
|
|
1481
|
-
`testFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
|
|
1482
|
-
);
|
|
1483
|
-
}
|
|
1515
|
+
validateOutcomeDeclarationConventions(outcomeDeclarationsList, context, ["outcomeDeclarations"]);
|
|
1484
1516
|
|
|
1485
|
-
|
|
1486
|
-
|
|
1487
|
-
|
|
1517
|
+
const outcomes = new Map(outcomeDeclarationsList.map((declaration) => [declaration.identifier, declaration]));
|
|
1518
|
+
|
|
1519
|
+
for (const [index, feedback] of testFeedbacks.entries()) {
|
|
1520
|
+
if (!outcomes.has(feedback.outcomeIdentifier)) {
|
|
1521
|
+
addIssue(
|
|
1522
|
+
context,
|
|
1523
|
+
["testFeedbacks", index, "outcomeIdentifier"],
|
|
1524
|
+
`testFeedback references undeclared outcome identifier '${feedback.outcomeIdentifier}'.`,
|
|
1525
|
+
);
|
|
1488
1526
|
}
|
|
1489
1527
|
|
|
1490
|
-
|
|
1491
|
-
|
|
1492
|
-
|
|
1528
|
+
if (containsInteraction(feedback.content)) {
|
|
1529
|
+
addIssue(context, ["testFeedbacks", index, "content"], "testFeedback content must not contain interactions.");
|
|
1530
|
+
}
|
|
1531
|
+
}
|
|
1532
|
+
|
|
1533
|
+
validateOutcomeReferences(testValue.outcomeProcessing, outcomes, context, ["outcomeProcessing"]);
|
|
1534
|
+
});
|
|
1493
1535
|
// Inferred types from exported Zod validators.
|
|
1494
1536
|
export type QtiPrintedVariable = z.infer<typeof QtiPrintedVariableSchema>;
|
|
1495
1537
|
export type QtiInteractionModule = z.infer<typeof QtiInteractionModuleSchema>;
|