@longsightgroup/qti3-core 0.9.2 → 0.9.4
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/README.md +108 -11
- package/dist/adaptive-turn-materializer.d.ts +2 -0
- package/dist/adaptive-turn-materializer.d.ts.map +1 -1
- package/dist/adaptive-turn-materializer.js +25 -19
- package/dist/adaptive-turn-materializer.js.map +1 -1
- package/dist/adaptive-turn.d.ts +8 -1
- package/dist/adaptive-turn.d.ts.map +1 -1
- package/dist/adaptive-turn.js +15 -5
- package/dist/adaptive-turn.js.map +1 -1
- package/dist/assert-never.d.ts +3 -0
- package/dist/assert-never.d.ts.map +1 -0
- package/dist/assert-never.js +5 -0
- package/dist/assert-never.js.map +1 -0
- package/dist/attempt-state-constants.d.ts +6 -0
- package/dist/attempt-state-constants.d.ts.map +1 -0
- package/dist/attempt-state-constants.js +6 -0
- package/dist/attempt-state-constants.js.map +1 -0
- package/dist/attempt-state.d.ts +5 -0
- package/dist/attempt-state.d.ts.map +1 -0
- package/dist/attempt-state.js +190 -0
- package/dist/attempt-state.js.map +1 -0
- package/dist/catalog.js +1 -1
- package/dist/catalog.js.map +1 -1
- package/dist/content-text.d.ts.map +1 -1
- package/dist/content-text.js +3 -0
- package/dist/content-text.js.map +1 -1
- package/dist/custom-operators.d.ts +13 -0
- package/dist/custom-operators.d.ts.map +1 -0
- package/dist/custom-operators.js +2 -0
- package/dist/custom-operators.js.map +1 -0
- package/dist/delivery-preparation.d.ts +20 -0
- package/dist/delivery-preparation.d.ts.map +1 -0
- package/dist/delivery-preparation.js +71 -0
- package/dist/delivery-preparation.js.map +1 -0
- package/dist/delivery-redaction.d.ts +5 -0
- package/dist/delivery-redaction.d.ts.map +1 -1
- package/dist/delivery-redaction.js +27 -6
- package/dist/delivery-redaction.js.map +1 -1
- package/dist/delivery-security.js +2 -2
- package/dist/delivery-security.js.map +1 -1
- package/dist/index.d.ts +8 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +8 -0
- package/dist/index.js.map +1 -1
- package/dist/item-scoring-expectation.d.ts +4 -0
- package/dist/item-scoring-expectation.d.ts.map +1 -0
- package/dist/item-scoring-expectation.js +6 -0
- package/dist/item-scoring-expectation.js.map +1 -0
- package/dist/item-submission-materialization.d.ts +25 -0
- package/dist/item-submission-materialization.d.ts.map +1 -0
- package/dist/item-submission-materialization.js +68 -0
- package/dist/item-submission-materialization.js.map +1 -0
- package/dist/parser-custom-interactions.js +1 -1
- package/dist/parser-custom-interactions.js.map +1 -1
- package/dist/parser-declarations.d.ts.map +1 -1
- package/dist/parser-declarations.js +4 -4
- package/dist/parser-declarations.js.map +1 -1
- package/dist/parser-item-metadata.d.ts.map +1 -1
- package/dist/parser-item-metadata.js +8 -7
- package/dist/parser-item-metadata.js.map +1 -1
- package/dist/parser-processing.js +2 -2
- package/dist/parser-processing.js.map +1 -1
- package/dist/parser-values.d.ts +5 -1
- package/dist/parser-values.d.ts.map +1 -1
- package/dist/parser-values.js +33 -0
- package/dist/parser-values.js.map +1 -1
- package/dist/processing-evaluator-boolean.d.ts +8 -0
- package/dist/processing-evaluator-boolean.d.ts.map +1 -0
- package/dist/processing-evaluator-boolean.js +47 -0
- package/dist/processing-evaluator-boolean.js.map +1 -0
- package/dist/processing-evaluator-collection.d.ts +12 -0
- package/dist/processing-evaluator-collection.d.ts.map +1 -0
- package/dist/processing-evaluator-collection.js +35 -0
- package/dist/processing-evaluator-collection.js.map +1 -0
- package/dist/processing-evaluator-comparison.d.ts +8 -0
- package/dist/processing-evaluator-comparison.d.ts.map +1 -0
- package/dist/processing-evaluator-comparison.js +88 -0
- package/dist/processing-evaluator-comparison.js.map +1 -0
- package/dist/processing-evaluator-custom.d.ts +8 -0
- package/dist/processing-evaluator-custom.d.ts.map +1 -0
- package/dist/processing-evaluator-custom.js +14 -0
- package/dist/processing-evaluator-custom.js.map +1 -0
- package/dist/processing-evaluator-geometry.d.ts +8 -0
- package/dist/processing-evaluator-geometry.d.ts.map +1 -0
- package/dist/processing-evaluator-geometry.js +21 -0
- package/dist/processing-evaluator-geometry.js.map +1 -0
- package/dist/processing-evaluator-numeric.d.ts +8 -0
- package/dist/processing-evaluator-numeric.d.ts.map +1 -0
- package/dist/processing-evaluator-numeric.js +112 -0
- package/dist/processing-evaluator-numeric.js.map +1 -0
- package/dist/processing-evaluator-string.d.ts +8 -0
- package/dist/processing-evaluator-string.d.ts.map +1 -0
- package/dist/processing-evaluator-string.js +33 -0
- package/dist/processing-evaluator-string.js.map +1 -0
- package/dist/processing-evaluator-variable.d.ts +8 -0
- package/dist/processing-evaluator-variable.d.ts.map +1 -0
- package/dist/processing-evaluator-variable.js +39 -0
- package/dist/processing-evaluator-variable.js.map +1 -0
- package/dist/processing-evaluator.d.ts +22 -0
- package/dist/processing-evaluator.d.ts.map +1 -0
- package/dist/processing-evaluator.js +137 -0
- package/dist/processing-evaluator.js.map +1 -0
- package/dist/processing-expression-children.d.ts +3 -0
- package/dist/processing-expression-children.d.ts.map +1 -0
- package/dist/processing-expression-children.js +68 -0
- package/dist/processing-expression-children.js.map +1 -0
- package/dist/processing-mapping.d.ts +13 -0
- package/dist/processing-mapping.d.ts.map +1 -0
- package/dist/processing-mapping.js +120 -0
- package/dist/processing-mapping.js.map +1 -0
- package/dist/processing-operators.d.ts +11 -0
- package/dist/processing-operators.d.ts.map +1 -0
- package/dist/processing-operators.js +178 -0
- package/dist/processing-operators.js.map +1 -0
- package/dist/processing-random.d.ts +2 -0
- package/dist/processing-random.d.ts.map +1 -0
- package/dist/processing-random.js +16 -0
- package/dist/processing-random.js.map +1 -0
- package/dist/processing-state.d.ts +8 -0
- package/dist/processing-state.d.ts.map +1 -0
- package/dist/processing-state.js +44 -0
- package/dist/processing-state.js.map +1 -0
- package/dist/processing-templates.d.ts +3 -0
- package/dist/processing-templates.d.ts.map +1 -0
- package/dist/processing-templates.js +12 -0
- package/dist/processing-templates.js.map +1 -0
- package/dist/processing-values.d.ts +15 -0
- package/dist/processing-values.d.ts.map +1 -0
- package/dist/processing-values.js +150 -0
- package/dist/processing-values.js.map +1 -0
- package/dist/processing-variables.d.ts +7 -0
- package/dist/processing-variables.d.ts.map +1 -0
- package/dist/processing-variables.js +40 -0
- package/dist/processing-variables.js.map +1 -0
- package/dist/qti-package-assets.d.ts +8 -0
- package/dist/qti-package-assets.d.ts.map +1 -0
- package/dist/qti-package-assets.js +173 -0
- package/dist/qti-package-assets.js.map +1 -0
- package/dist/qti-package-items.d.ts +14 -0
- package/dist/qti-package-items.d.ts.map +1 -0
- package/dist/qti-package-items.js +138 -0
- package/dist/qti-package-items.js.map +1 -0
- package/dist/qti-package-manifest.d.ts +13 -0
- package/dist/qti-package-manifest.d.ts.map +1 -0
- package/dist/qti-package-manifest.js +109 -0
- package/dist/qti-package-manifest.js.map +1 -0
- package/dist/qti-package-metadata.d.ts +8 -0
- package/dist/qti-package-metadata.d.ts.map +1 -0
- package/dist/qti-package-metadata.js +115 -0
- package/dist/qti-package-metadata.js.map +1 -0
- package/dist/qti-package-paths.d.ts +6 -0
- package/dist/qti-package-paths.d.ts.map +1 -0
- package/dist/qti-package-paths.js +32 -0
- package/dist/qti-package-paths.js.map +1 -0
- package/dist/qti-package-types.d.ts +98 -0
- package/dist/qti-package-types.d.ts.map +1 -0
- package/dist/qti-package-types.js +2 -0
- package/dist/qti-package-types.js.map +1 -0
- package/dist/qti-package-xml.d.ts +21 -0
- package/dist/qti-package-xml.d.ts.map +1 -0
- package/dist/qti-package-xml.js +68 -0
- package/dist/qti-package-xml.js.map +1 -0
- package/dist/qti-package-zip.d.ts +24 -0
- package/dist/qti-package-zip.d.ts.map +1 -0
- package/dist/qti-package-zip.js +185 -0
- package/dist/qti-package-zip.js.map +1 -0
- package/dist/qti-package.d.ts +7 -0
- package/dist/qti-package.d.ts.map +1 -0
- package/dist/qti-package.js +76 -0
- package/dist/qti-package.js.map +1 -0
- package/dist/response-input.d.ts +9 -0
- package/dist/response-input.d.ts.map +1 -0
- package/dist/response-input.js +9 -0
- package/dist/response-input.js.map +1 -0
- package/dist/response-validation-policy.d.ts +31 -0
- package/dist/response-validation-policy.d.ts.map +1 -0
- package/dist/response-validation-policy.js +158 -0
- package/dist/response-validation-policy.js.map +1 -0
- package/dist/response-validation.d.ts +23 -0
- package/dist/response-validation.d.ts.map +1 -0
- package/dist/response-validation.js +157 -0
- package/dist/response-validation.js.map +1 -0
- package/dist/scoring-disposition-policy.d.ts +8 -0
- package/dist/scoring-disposition-policy.d.ts.map +1 -0
- package/dist/scoring-disposition-policy.js +24 -0
- package/dist/scoring-disposition-policy.js.map +1 -0
- package/dist/serializer-processing-expressions.d.ts.map +1 -1
- package/dist/serializer-processing-expressions.js +2 -8
- package/dist/serializer-processing-expressions.js.map +1 -1
- package/dist/serializer-processing-xml.js +2 -2
- package/dist/serializer-processing-xml.js.map +1 -1
- package/dist/serializer-processing.d.ts.map +1 -1
- package/dist/serializer-processing.js +3 -0
- package/dist/serializer-processing.js.map +1 -1
- package/dist/session.d.ts +5 -14
- package/dist/session.d.ts.map +1 -1
- package/dist/session.js +76 -1158
- package/dist/session.js.map +1 -1
- package/dist/shared-vocabulary-coverage-policy.js +1 -1
- package/dist/shared-vocabulary-coverage-policy.js.map +1 -1
- package/dist/shared-vocabulary-generated-families.js +1 -1
- package/dist/shared-vocabulary-generated-families.js.map +1 -1
- package/dist/shared-vocabulary-support.js +3 -3
- package/dist/shared-vocabulary-support.js.map +1 -1
- package/dist/shared-vocabulary.d.ts.map +1 -1
- package/dist/shared-vocabulary.js +4 -3
- package/dist/shared-vocabulary.js.map +1 -1
- package/dist/support-evidence.d.ts +22 -0
- package/dist/support-evidence.d.ts.map +1 -0
- package/dist/support-evidence.js +93 -0
- package/dist/support-evidence.js.map +1 -0
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +30 -43
- package/dist/support.js.map +1 -1
- package/dist/template-presentation-materializer.d.ts +14 -0
- package/dist/template-presentation-materializer.d.ts.map +1 -0
- package/dist/template-presentation-materializer.js +143 -0
- package/dist/template-presentation-materializer.js.map +1 -0
- package/dist/trusted-item-session.d.ts +25 -8
- package/dist/trusted-item-session.d.ts.map +1 -1
- package/dist/trusted-item-session.js +57 -51
- package/dist/trusted-item-session.js.map +1 -1
- package/dist/trusted-session-materialization.d.ts +33 -0
- package/dist/trusted-session-materialization.d.ts.map +1 -0
- package/dist/trusted-session-materialization.js +26 -0
- package/dist/trusted-session-materialization.js.map +1 -0
- package/dist/tts.d.ts.map +1 -1
- package/dist/tts.js +16 -2
- package/dist/tts.js.map +1 -1
- package/dist/validation-geometry.d.ts +8 -0
- package/dist/validation-geometry.d.ts.map +1 -0
- package/dist/validation-geometry.js +187 -0
- package/dist/validation-geometry.js.map +1 -0
- package/dist/validation-interactions.d.ts +3 -0
- package/dist/validation-interactions.d.ts.map +1 -0
- package/dist/validation-interactions.js +644 -0
- package/dist/validation-interactions.js.map +1 -0
- package/dist/validation-primitives.d.ts +12 -0
- package/dist/validation-primitives.d.ts.map +1 -0
- package/dist/validation-primitives.js +53 -0
- package/dist/validation-primitives.js.map +1 -0
- package/dist/validation-processing.d.ts +4 -0
- package/dist/validation-processing.d.ts.map +1 -0
- package/dist/validation-processing.js +569 -0
- package/dist/validation-processing.js.map +1 -0
- package/dist/validation-shared-vocabulary-layout.d.ts +10 -0
- package/dist/validation-shared-vocabulary-layout.d.ts.map +1 -0
- package/dist/validation-shared-vocabulary-layout.js +102 -0
- package/dist/validation-shared-vocabulary-layout.js.map +1 -0
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +27 -1641
- package/dist/validation.js.map +1 -1
- package/dist/variable-snapshot.d.ts +9 -0
- package/dist/variable-snapshot.d.ts.map +1 -0
- package/dist/variable-snapshot.js +9 -0
- package/dist/variable-snapshot.js.map +1 -0
- package/dist/xml.js +1 -1
- package/dist/xml.js.map +1 -1
- package/package.json +1 -1
- package/src/adaptive-turn-materializer.ts +29 -16
- package/src/adaptive-turn.ts +29 -6
- package/src/assert-never.ts +4 -0
- package/src/attempt-state-constants.ts +5 -0
- package/src/attempt-state.ts +245 -0
- package/src/catalog.ts +1 -1
- package/src/content-text.ts +3 -0
- package/src/custom-operators.ts +12 -0
- package/src/delivery-preparation.ts +121 -0
- package/src/delivery-redaction.ts +37 -6
- package/src/delivery-security.ts +2 -2
- package/src/index.ts +67 -0
- package/src/item-scoring-expectation.ts +9 -0
- package/src/item-submission-materialization.ts +120 -0
- package/src/parser-custom-interactions.ts +1 -1
- package/src/parser-declarations.ts +4 -3
- package/src/parser-item-metadata.ts +8 -7
- package/src/parser-processing.ts +2 -2
- package/src/parser-values.ts +36 -0
- package/src/processing-evaluator-boolean.ts +48 -0
- package/src/processing-evaluator-collection.ts +50 -0
- package/src/processing-evaluator-comparison.ts +106 -0
- package/src/processing-evaluator-custom.ts +20 -0
- package/src/processing-evaluator-geometry.ts +26 -0
- package/src/processing-evaluator-numeric.ts +155 -0
- package/src/processing-evaluator-string.ts +60 -0
- package/src/processing-evaluator-variable.ts +81 -0
- package/src/processing-evaluator.ts +173 -0
- package/src/processing-expression-children.ts +69 -0
- package/src/processing-mapping.ts +153 -0
- package/src/processing-operators.ts +184 -0
- package/src/processing-random.ts +16 -0
- package/src/processing-state.ts +69 -0
- package/src/processing-templates.ts +12 -0
- package/src/processing-values.ts +162 -0
- package/src/processing-variables.ts +83 -0
- package/src/qti-package-assets.ts +256 -0
- package/src/qti-package-items.ts +240 -0
- package/src/qti-package-manifest.ts +210 -0
- package/src/qti-package-metadata.ts +143 -0
- package/src/qti-package-paths.ts +56 -0
- package/src/qti-package-types.ts +117 -0
- package/src/qti-package-xml.ts +97 -0
- package/src/qti-package-zip.ts +325 -0
- package/src/qti-package.ts +176 -0
- package/src/response-input.ts +19 -0
- package/src/response-validation-policy.ts +218 -0
- package/src/response-validation.ts +282 -0
- package/src/scoring-disposition-policy.ts +37 -0
- package/src/serializer-processing-expressions.ts +2 -12
- package/src/serializer-processing-xml.ts +2 -2
- package/src/serializer-processing.ts +3 -0
- package/src/session.ts +128 -2006
- package/src/shared-vocabulary-coverage-policy.ts +1 -1
- package/src/shared-vocabulary-generated-families.ts +1 -1
- package/src/shared-vocabulary-support.ts +3 -3
- package/src/shared-vocabulary.ts +3 -2
- package/src/support-evidence.ts +107 -0
- package/src/support.ts +59 -50
- package/src/template-presentation-materializer.ts +219 -0
- package/src/trusted-item-session.ts +86 -78
- package/src/trusted-session-materialization.ts +67 -0
- package/src/tts.ts +29 -15
- package/src/validation-geometry.ts +204 -0
- package/src/validation-interactions.ts +831 -0
- package/src/validation-primitives.ts +68 -0
- package/src/validation-processing.ts +785 -0
- package/src/validation-shared-vocabulary-layout.ts +117 -0
- package/src/validation.ts +44 -2077
- package/src/variable-snapshot.ts +20 -0
- package/src/xml.ts +1 -1
|
@@ -48,7 +48,7 @@ export function findSharedVocabularyCoverageViolations(input: {
|
|
|
48
48
|
supportByClass.set(entry.className, existing);
|
|
49
49
|
}
|
|
50
50
|
|
|
51
|
-
const gatedSupportClasses = [...supportByClass.keys()].
|
|
51
|
+
const gatedSupportClasses = [...supportByClass.keys()].toSorted();
|
|
52
52
|
if (gatedSupportClasses.length === 0) {
|
|
53
53
|
violations.push({ message: "expected at least one enforced shared vocabulary class" });
|
|
54
54
|
}
|
|
@@ -122,7 +122,7 @@ function escapeRegExp(value: string): string {
|
|
|
122
122
|
|
|
123
123
|
function alternationPattern(values: readonly string[]): string {
|
|
124
124
|
return [...values]
|
|
125
|
-
.
|
|
125
|
+
.toSorted((left, right) => right.length - left.length)
|
|
126
126
|
.map(escapeRegExp)
|
|
127
127
|
.join("|");
|
|
128
128
|
}
|
|
@@ -52,7 +52,7 @@ const graphicBrowserTests = [
|
|
|
52
52
|
"tests/browser/player-graphic.spec.ts",
|
|
53
53
|
"tests/browser/player-graphic-gap-match.spec.ts",
|
|
54
54
|
];
|
|
55
|
-
const mediaBrowserTests = ["tests/browser/player.spec.ts", ...sharedVocabularyMatrixTests];
|
|
55
|
+
const mediaBrowserTests = ["tests/browser/player-media.spec.ts", ...sharedVocabularyMatrixTests];
|
|
56
56
|
const mediaPlayerFixture =
|
|
57
57
|
"packages/fixtures/packages/sv-matrix/items/media-controls-and-pause.xml";
|
|
58
58
|
const interactionInputWidthFixtures = [
|
|
@@ -204,14 +204,14 @@ function registryAttributeSupportProfile(fieldId: string): {
|
|
|
204
204
|
fixtures: [mediaPlayerFixture],
|
|
205
205
|
tests: mediaBrowserTests,
|
|
206
206
|
notes:
|
|
207
|
-
"Reflects authored pause-delay values on rendered media assets. Pause timer behavior is covered in tests/browser/player.spec.ts.",
|
|
207
|
+
"Reflects authored pause-delay values on rendered media assets. Pause timer behavior is covered in tests/browser/player-media.spec.ts.",
|
|
208
208
|
};
|
|
209
209
|
case "media-player-pause-duration":
|
|
210
210
|
return {
|
|
211
211
|
fixtures: [mediaPlayerFixture],
|
|
212
212
|
tests: mediaBrowserTests,
|
|
213
213
|
notes:
|
|
214
|
-
"Reflects authored pause-duration values on rendered media assets. Pause timer behavior is covered in tests/browser/player.spec.ts.",
|
|
214
|
+
"Reflects authored pause-duration values on rendered media assets. Pause timer behavior is covered in tests/browser/player-media.spec.ts.",
|
|
215
215
|
};
|
|
216
216
|
default:
|
|
217
217
|
return {
|
package/src/shared-vocabulary.ts
CHANGED
|
@@ -73,9 +73,10 @@ export function firstMatchingSharedVocabularyClass<T extends string>(
|
|
|
73
73
|
classNames: string[],
|
|
74
74
|
tokens: readonly T[],
|
|
75
75
|
): T | undefined {
|
|
76
|
-
const tokenSet = new Set<string>(tokens);
|
|
77
76
|
for (const className of classNames) {
|
|
78
|
-
|
|
77
|
+
for (const token of tokens) {
|
|
78
|
+
if (token === className) return token;
|
|
79
|
+
}
|
|
79
80
|
}
|
|
80
81
|
return undefined;
|
|
81
82
|
}
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
import type { QtiInteractionType } from "./types.js";
|
|
2
|
+
|
|
3
|
+
const coreProcessingTest = "packages/core/src/core.test.ts";
|
|
4
|
+
|
|
5
|
+
export const browserProcessingTests = ["tests/browser/player-processing.spec.ts"];
|
|
6
|
+
export const browserFeedbackTests = ["tests/browser/player-feedback.spec.ts"];
|
|
7
|
+
export const browserAdaptiveTests = ["tests/browser/player-adaptive.spec.ts"];
|
|
8
|
+
export const browserBodyContentTests = ["tests/browser/player-body-content.spec.ts"];
|
|
9
|
+
export const browserHarnessTests = ["tests/browser/player-harness.spec.ts"];
|
|
10
|
+
export const browserMathmlTests = ["tests/browser/player-mathml.spec.ts"];
|
|
11
|
+
export const browserKeyboardA11yTests = ["tests/browser/player-keyboard-a11y.spec.ts"];
|
|
12
|
+
export const browserLifecycleTests = ["tests/browser/player-lifecycle.spec.ts"];
|
|
13
|
+
|
|
14
|
+
const templateProcessingBrowserEvidence = [
|
|
15
|
+
...browserProcessingTests,
|
|
16
|
+
...browserBodyContentTests,
|
|
17
|
+
...browserMathmlTests,
|
|
18
|
+
];
|
|
19
|
+
const responseProcessingBrowserEvidence = [
|
|
20
|
+
...browserProcessingTests,
|
|
21
|
+
...browserFeedbackTests,
|
|
22
|
+
...browserAdaptiveTests,
|
|
23
|
+
];
|
|
24
|
+
const randomIntegerBrowserEvidence = browserProcessingTests;
|
|
25
|
+
|
|
26
|
+
/** Browser evidence bundles keyed by parent processing constructs only. */
|
|
27
|
+
export const processingBrowserEvidence = {
|
|
28
|
+
"qti-template-processing": templateProcessingBrowserEvidence,
|
|
29
|
+
"qti-response-processing": responseProcessingBrowserEvidence,
|
|
30
|
+
"qti-random-integer": randomIntegerBrowserEvidence,
|
|
31
|
+
} as const satisfies Record<string, readonly string[]>;
|
|
32
|
+
|
|
33
|
+
export type ProcessingBrowserEvidenceName = keyof typeof processingBrowserEvidence;
|
|
34
|
+
|
|
35
|
+
export const interactionExtraFixtures: Partial<Record<QtiInteractionType, readonly string[]>> = {
|
|
36
|
+
extendedText: [
|
|
37
|
+
"packages/fixtures/packages/sv-matrix/items/extended-text-pattern-mask.xml",
|
|
38
|
+
"packages/fixtures/packages/sv-matrix/items/extended-text-xhtml.xml",
|
|
39
|
+
],
|
|
40
|
+
textEntry: ["packages/fixtures/packages/sv-matrix/items/text-entry-pattern-mask-inline.xml"],
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export function browserTestsFor(interactionType: QtiInteractionType): string[] {
|
|
44
|
+
const base = [
|
|
45
|
+
"packages/fixtures/src/fixtures.test.ts",
|
|
46
|
+
"packages/conformance/src/conformance.test.ts",
|
|
47
|
+
"packages/a11y/src/a11y.test.ts",
|
|
48
|
+
"tests/browser/player-interaction-sweep.spec.ts",
|
|
49
|
+
];
|
|
50
|
+
const extras: Partial<Record<QtiInteractionType, string[]>> = {
|
|
51
|
+
associate: browserKeyboardA11yTests,
|
|
52
|
+
choice: ["tests/browser/player-choice.spec.ts", "tests/browser/player-dom-behavior.spec.ts"],
|
|
53
|
+
drawing: ["tests/browser/player-graphic.spec.ts"],
|
|
54
|
+
endAttempt: ["tests/browser/player-dom-behavior.spec.ts", ...browserKeyboardA11yTests],
|
|
55
|
+
extendedText: [
|
|
56
|
+
"tests/browser/player-dom-behavior.spec.ts",
|
|
57
|
+
"tests/browser/player-extended-text-xhtml.spec.ts",
|
|
58
|
+
],
|
|
59
|
+
gapMatch: ["tests/browser/player-gap-match.spec.ts"],
|
|
60
|
+
graphicAssociate: ["tests/browser/player-graphic.spec.ts"],
|
|
61
|
+
graphicGapMatch: [
|
|
62
|
+
"tests/browser/player-graphic-gap-match.spec.ts",
|
|
63
|
+
"tests/browser/player-graphic.spec.ts",
|
|
64
|
+
],
|
|
65
|
+
graphicOrder: ["tests/browser/player-graphic.spec.ts"],
|
|
66
|
+
hotspot: ["tests/browser/player-graphic.spec.ts"],
|
|
67
|
+
hottext: ["tests/browser/player-hottext.spec.ts", "tests/browser/player-dom-behavior.spec.ts"],
|
|
68
|
+
inlineChoice: ["tests/browser/player-inline-choice.spec.ts"],
|
|
69
|
+
match: ["tests/browser/player-match.spec.ts", "tests/browser/player-dom-behavior.spec.ts"],
|
|
70
|
+
media: ["tests/browser/player-media.spec.ts"],
|
|
71
|
+
order: [...browserKeyboardA11yTests, ...browserLifecycleTests],
|
|
72
|
+
portableCustom: ["tests/browser/player-portable-custom.spec.ts"],
|
|
73
|
+
positionObject: ["tests/browser/player-graphic.spec.ts", ...browserKeyboardA11yTests],
|
|
74
|
+
selectPoint: ["tests/browser/player-graphic.spec.ts"],
|
|
75
|
+
slider: browserKeyboardA11yTests,
|
|
76
|
+
textEntry: ["tests/browser/player-dom-behavior.spec.ts"],
|
|
77
|
+
upload: ["tests/browser/player-dom-behavior.spec.ts"],
|
|
78
|
+
};
|
|
79
|
+
return [...base, ...(extras[interactionType] ?? [])];
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
export function interactionSupportFixtures(interactionType: QtiInteractionType): string[] {
|
|
83
|
+
return [
|
|
84
|
+
`packages/fixtures/xml/${interactionType}-reference.xml`,
|
|
85
|
+
...(interactionExtraFixtures[interactionType] ?? []),
|
|
86
|
+
];
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export function interactionSupportTests(interactionType: QtiInteractionType): string[] {
|
|
90
|
+
if (interactionType === "extendedText" || interactionType === "textEntry") {
|
|
91
|
+
return [...browserTestsFor(interactionType), "packages/core/src/pattern-mask.test.ts"];
|
|
92
|
+
}
|
|
93
|
+
return browserTestsFor(interactionType);
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function isProcessingBrowserEvidenceName(
|
|
97
|
+
qtiName: string,
|
|
98
|
+
): qtiName is ProcessingBrowserEvidenceName {
|
|
99
|
+
return Object.hasOwn(processingBrowserEvidence, qtiName);
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function processingSupportTests(qtiName: string): string[] {
|
|
103
|
+
if (!isProcessingBrowserEvidenceName(qtiName)) {
|
|
104
|
+
return [coreProcessingTest];
|
|
105
|
+
}
|
|
106
|
+
return [coreProcessingTest, ...processingBrowserEvidence[qtiName]];
|
|
107
|
+
}
|
package/src/support.ts
CHANGED
|
@@ -8,6 +8,13 @@ import type {
|
|
|
8
8
|
QtiProcessingElementSupport,
|
|
9
9
|
QtiSourceLocation,
|
|
10
10
|
} from "./types.js";
|
|
11
|
+
import {
|
|
12
|
+
browserFeedbackTests,
|
|
13
|
+
browserHarnessTests,
|
|
14
|
+
interactionSupportFixtures,
|
|
15
|
+
interactionSupportTests,
|
|
16
|
+
processingBrowserEvidence,
|
|
17
|
+
} from "./support-evidence.js";
|
|
11
18
|
|
|
12
19
|
export const interactionSupport: QtiInteractionElementSupport[] = [
|
|
13
20
|
entry("qti-associate-interaction", "associate"),
|
|
@@ -51,23 +58,36 @@ export const deprecatedInteractionSupport: QtiInteractionElementSupport[] = [
|
|
|
51
58
|
];
|
|
52
59
|
|
|
53
60
|
export const processingSupport: QtiProcessingElementSupport[] = [
|
|
54
|
-
processingEntry(
|
|
55
|
-
"
|
|
56
|
-
"packages/
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
"
|
|
63
|
-
|
|
61
|
+
processingEntry(
|
|
62
|
+
"qti-template-processing",
|
|
63
|
+
"packages/core/src/core.test.ts",
|
|
64
|
+
[
|
|
65
|
+
"packages/fixtures/xml/template-processing-reference.xml",
|
|
66
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
67
|
+
"packages/fixtures/xml/template-content-reference.xml",
|
|
68
|
+
],
|
|
69
|
+
[...processingBrowserEvidence["qti-template-processing"]],
|
|
70
|
+
),
|
|
71
|
+
processingEntry(
|
|
72
|
+
"qti-response-processing",
|
|
73
|
+
"packages/core/src/core.test.ts",
|
|
74
|
+
[
|
|
75
|
+
"packages/fixtures/xml/mapping-processing-reference.xml",
|
|
76
|
+
"packages/fixtures/xml/generic-match-processing-reference.xml",
|
|
77
|
+
"packages/fixtures/xml/advanced-processing-reference.xml",
|
|
78
|
+
"packages/fixtures/xml/adaptive-feedback-reference.xml",
|
|
79
|
+
],
|
|
80
|
+
[...processingBrowserEvidence["qti-response-processing"]],
|
|
81
|
+
),
|
|
64
82
|
processingEntry("qti-set-template-value", "packages/core/src/core.test.ts", [
|
|
65
83
|
"packages/fixtures/xml/template-processing-reference.xml",
|
|
84
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
66
85
|
"packages/fixtures/xml/template-content-reference.xml",
|
|
67
86
|
]),
|
|
68
87
|
processingEntry("qti-set-default-value", "packages/core/src/core.test.ts"),
|
|
69
88
|
processingEntry("qti-set-correct-response", "packages/core/src/core.test.ts", [
|
|
70
89
|
"packages/fixtures/xml/template-processing-reference.xml",
|
|
90
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
71
91
|
]),
|
|
72
92
|
processingEntry("qti-template-condition", "packages/core/src/core.test.ts"),
|
|
73
93
|
processingEntry("qti-template-if", "packages/core/src/core.test.ts"),
|
|
@@ -119,11 +139,17 @@ export const processingSupport: QtiProcessingElementSupport[] = [
|
|
|
119
139
|
processingEntry("qti-variable", "packages/core/src/core.test.ts", [
|
|
120
140
|
"packages/fixtures/xml/generic-match-processing-reference.xml",
|
|
121
141
|
"packages/fixtures/xml/template-processing-reference.xml",
|
|
142
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
122
143
|
"packages/fixtures/xml/template-content-reference.xml",
|
|
123
144
|
"packages/fixtures/xml/advanced-processing-reference.xml",
|
|
124
145
|
"packages/fixtures/xml/adaptive-feedback-reference.xml",
|
|
125
146
|
]),
|
|
126
|
-
processingEntry(
|
|
147
|
+
processingEntry(
|
|
148
|
+
"qti-random-integer",
|
|
149
|
+
"packages/core/src/core.test.ts",
|
|
150
|
+
["packages/fixtures/xml/random-integer-template-reference.xml"],
|
|
151
|
+
[...processingBrowserEvidence["qti-random-integer"]],
|
|
152
|
+
),
|
|
127
153
|
processingEntry("qti-random-float", "packages/core/src/core.test.ts"),
|
|
128
154
|
processingEntry("qti-random", "packages/core/src/core.test.ts"),
|
|
129
155
|
processingEntry("qti-multiple", "packages/core/src/core.test.ts", [
|
|
@@ -139,8 +165,11 @@ export const processingSupport: QtiProcessingElementSupport[] = [
|
|
|
139
165
|
]),
|
|
140
166
|
processingEntry("qti-sum", "packages/core/src/core.test.ts", [
|
|
141
167
|
"packages/fixtures/xml/template-processing-reference.xml",
|
|
168
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
169
|
+
]),
|
|
170
|
+
processingEntry("qti-product", "packages/core/src/core.test.ts", [
|
|
171
|
+
"packages/fixtures/xml/random-integer-template-reference.xml",
|
|
142
172
|
]),
|
|
143
|
-
processingEntry("qti-product", "packages/core/src/core.test.ts"),
|
|
144
173
|
processingEntry("qti-min", "packages/core/src/core.test.ts"),
|
|
145
174
|
processingEntry("qti-max", "packages/core/src/core.test.ts"),
|
|
146
175
|
processingEntry("qti-subtract", "packages/core/src/core.test.ts"),
|
|
@@ -205,9 +234,13 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
|
|
|
205
234
|
fixtures: [
|
|
206
235
|
"packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
|
|
207
236
|
],
|
|
208
|
-
tests: [
|
|
237
|
+
tests: [
|
|
238
|
+
"packages/core/src/core.test.ts",
|
|
239
|
+
"packages/core/src/parser-item-metadata.test.ts",
|
|
240
|
+
...browserHarnessTests,
|
|
241
|
+
],
|
|
209
242
|
notes:
|
|
210
|
-
"Parsed by parser-item-metadata.ts and validated by validateCatalogInfo. Duplicate containers emit item.child.duplicate.",
|
|
243
|
+
"Parsed by parser-item-metadata.ts and validated by validateCatalogInfo. Duplicate containers emit item.child.duplicate. Manual harness debugger coverage is in player-harness.spec.ts.",
|
|
211
244
|
},
|
|
212
245
|
{
|
|
213
246
|
qtiName: "qti-stylesheet",
|
|
@@ -225,6 +258,7 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
|
|
|
225
258
|
"packages/core/src/core.test.ts",
|
|
226
259
|
"packages/core/src/parser-item-metadata.test.ts",
|
|
227
260
|
"tests/browser/player-package.spec.ts",
|
|
261
|
+
...browserHarnessTests,
|
|
228
262
|
],
|
|
229
263
|
notes:
|
|
230
264
|
"Parsed by parser-item-metadata.ts, validated by validateStylesheets, and delivered by the player only when a host resolveStylesheet hook returns a safe resolved stylesheet URL.",
|
|
@@ -239,9 +273,13 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
|
|
|
239
273
|
render: false,
|
|
240
274
|
process: false,
|
|
241
275
|
fixtures: ["packages/fixtures/src/index.ts"],
|
|
242
|
-
tests: [
|
|
276
|
+
tests: [
|
|
277
|
+
"packages/core/src/core.test.ts",
|
|
278
|
+
"packages/core/src/parser-item-metadata.test.ts",
|
|
279
|
+
...browserFeedbackTests,
|
|
280
|
+
],
|
|
243
281
|
notes:
|
|
244
|
-
"Parsed by parser-item-metadata.ts and validated by validateModalFeedback. Player
|
|
282
|
+
"Parsed by parser-item-metadata.ts and validated by validateModalFeedback. Player renders outcome-gated modal feedback in player-feedback.spec.ts.",
|
|
245
283
|
},
|
|
246
284
|
{
|
|
247
285
|
qtiName: "qti-companion-materials-info",
|
|
@@ -258,6 +296,7 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
|
|
|
258
296
|
tests: [
|
|
259
297
|
"packages/core/src/parser-companion-materials.test.ts",
|
|
260
298
|
"packages/core/src/parser-item-metadata.test.ts",
|
|
299
|
+
...browserHarnessTests,
|
|
261
300
|
],
|
|
262
301
|
notes:
|
|
263
302
|
"Parses qti-physical-material text and qti-digital-material file references. Digital materials require non-empty qti-file-href text and may include label, mime-type, and qti-resource-icon metadata. Hosts read resolved materials through createCompanionMaterialsResolution() or player.getCompanionMaterialsResolution().",
|
|
@@ -364,33 +403,14 @@ function entry(qtiName: string, interactionType: QtiInteractionType): QtiInterac
|
|
|
364
403
|
validate: true,
|
|
365
404
|
render: true,
|
|
366
405
|
process: true,
|
|
367
|
-
fixtures:
|
|
368
|
-
tests:
|
|
369
|
-
"packages/fixtures/src/fixtures.test.ts",
|
|
370
|
-
"packages/conformance/src/conformance.test.ts",
|
|
371
|
-
"packages/a11y/src/a11y.test.ts",
|
|
372
|
-
"tests/browser/player.spec.ts",
|
|
373
|
-
],
|
|
406
|
+
fixtures: interactionSupportFixtures(interactionType),
|
|
407
|
+
tests: interactionSupportTests(interactionType),
|
|
374
408
|
};
|
|
375
409
|
}
|
|
376
410
|
|
|
377
411
|
function extendedTextInteractionEntry(): QtiInteractionElementSupport {
|
|
378
412
|
return {
|
|
379
413
|
...entry("qti-extended-text-interaction", "extendedText"),
|
|
380
|
-
fixtures: [
|
|
381
|
-
"packages/fixtures/xml/extendedText-reference.xml",
|
|
382
|
-
"packages/fixtures/packages/sv-matrix/items/extended-text-pattern-mask.xml",
|
|
383
|
-
"packages/fixtures/packages/sv-matrix/items/extended-text-xhtml.xml",
|
|
384
|
-
],
|
|
385
|
-
tests: [
|
|
386
|
-
"packages/fixtures/src/fixtures.test.ts",
|
|
387
|
-
"packages/conformance/src/conformance.test.ts",
|
|
388
|
-
"packages/a11y/src/a11y.test.ts",
|
|
389
|
-
"packages/core/src/pattern-mask.test.ts",
|
|
390
|
-
"tests/browser/player.spec.ts",
|
|
391
|
-
"tests/browser/player-dom-behavior.spec.ts",
|
|
392
|
-
"tests/browser/player-extended-text-xhtml.spec.ts",
|
|
393
|
-
],
|
|
394
414
|
notes: "Supports plain and format=xhtml extended text.",
|
|
395
415
|
};
|
|
396
416
|
}
|
|
@@ -398,18 +418,6 @@ function extendedTextInteractionEntry(): QtiInteractionElementSupport {
|
|
|
398
418
|
function textEntryInteractionEntry(): QtiInteractionElementSupport {
|
|
399
419
|
return {
|
|
400
420
|
...entry("qti-text-entry-interaction", "textEntry"),
|
|
401
|
-
fixtures: [
|
|
402
|
-
"packages/fixtures/xml/textEntry-reference.xml",
|
|
403
|
-
"packages/fixtures/packages/sv-matrix/items/text-entry-pattern-mask-inline.xml",
|
|
404
|
-
],
|
|
405
|
-
tests: [
|
|
406
|
-
"packages/fixtures/src/fixtures.test.ts",
|
|
407
|
-
"packages/conformance/src/conformance.test.ts",
|
|
408
|
-
"packages/a11y/src/a11y.test.ts",
|
|
409
|
-
"packages/core/src/pattern-mask.test.ts",
|
|
410
|
-
"tests/browser/player.spec.ts",
|
|
411
|
-
"tests/browser/player-dom-behavior.spec.ts",
|
|
412
|
-
],
|
|
413
421
|
notes: "Supports placeholder-text and pattern-mask on text-entry controls.",
|
|
414
422
|
};
|
|
415
423
|
}
|
|
@@ -426,6 +434,7 @@ function processingEntry(
|
|
|
426
434
|
qtiName: string,
|
|
427
435
|
test: string,
|
|
428
436
|
fixtures: string[] = [],
|
|
437
|
+
extraTests: string[] = [],
|
|
429
438
|
): QtiProcessingElementSupport {
|
|
430
439
|
return {
|
|
431
440
|
qtiName,
|
|
@@ -437,6 +446,6 @@ function processingEntry(
|
|
|
437
446
|
render: false,
|
|
438
447
|
process: true,
|
|
439
448
|
fixtures,
|
|
440
|
-
tests: [test],
|
|
449
|
+
tests: [test, ...extraTests],
|
|
441
450
|
};
|
|
442
451
|
}
|
|
@@ -0,0 +1,219 @@
|
|
|
1
|
+
import {
|
|
2
|
+
applySourceRangeEdits,
|
|
3
|
+
parseDeliveryXml,
|
|
4
|
+
sourceRangeFor,
|
|
5
|
+
type SourceRangeEdit,
|
|
6
|
+
} from "./delivery-redaction.js";
|
|
7
|
+
import type { QtiDiagnostic, QtiValue } from "./types.js";
|
|
8
|
+
import { qtiValueToString } from "./value-format.js";
|
|
9
|
+
import { descendants, escapeXmlText, type XmlNode } from "./xml.js";
|
|
10
|
+
|
|
11
|
+
interface TemplateBlockBinding {
|
|
12
|
+
readonly templateIdentifier: string;
|
|
13
|
+
readonly identifier: string;
|
|
14
|
+
readonly value: QtiValue;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export interface QtiTemplatePresentationVariables {
|
|
18
|
+
readonly outcomes: Record<string, QtiValue>;
|
|
19
|
+
readonly templateValues: Record<string, QtiValue>;
|
|
20
|
+
readonly responses?: Record<string, QtiValue> | undefined;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function materializeTemplatePresentation(
|
|
24
|
+
xml: string,
|
|
25
|
+
variables: QtiTemplatePresentationVariables,
|
|
26
|
+
): { ok: true; xml: string } | { ok: false; diagnostics: QtiDiagnostic[] } {
|
|
27
|
+
const parsed = parseDeliveryXml(xml);
|
|
28
|
+
if (!parsed.root) return { ok: true, xml };
|
|
29
|
+
|
|
30
|
+
const diagnostics: QtiDiagnostic[] = [];
|
|
31
|
+
const edits: SourceRangeEdit[] = [];
|
|
32
|
+
const nodes = [parsed.root, ...descendants(parsed.root, () => true)];
|
|
33
|
+
|
|
34
|
+
for (const node of nodes) {
|
|
35
|
+
if (isInsideTemplateContentNode(node)) continue;
|
|
36
|
+
|
|
37
|
+
if (node.localName === "qti-printed-variable") {
|
|
38
|
+
const edit = printedVariableEdit(node, variables, diagnostics);
|
|
39
|
+
if (edit) edits.push(edit);
|
|
40
|
+
continue;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
if (node.localName === "qti-template-block" || node.localName === "qti-template-inline") {
|
|
44
|
+
const edit = templateContentEdit(xml, node, variables, diagnostics);
|
|
45
|
+
if (edit) edits.push(edit);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
if (diagnostics.some((diagnostic) => diagnostic.severity === "error")) {
|
|
50
|
+
return { ok: false, diagnostics };
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
return { ok: true, xml: applySourceRangeEdits(xml, edits) };
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function printedVariableEdit(
|
|
57
|
+
node: XmlNode,
|
|
58
|
+
variables: QtiTemplatePresentationVariables,
|
|
59
|
+
diagnostics: QtiDiagnostic[],
|
|
60
|
+
): SourceRangeEdit | undefined {
|
|
61
|
+
const text = resolvePrintedVariableText(node, variables, diagnostics);
|
|
62
|
+
if (text === undefined) return undefined;
|
|
63
|
+
|
|
64
|
+
const range = sourceRangeFor(node)[0];
|
|
65
|
+
if (!range) {
|
|
66
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
67
|
+
return undefined;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
return { ...range, text };
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function templateContentEdit(
|
|
74
|
+
xml: string,
|
|
75
|
+
node: XmlNode,
|
|
76
|
+
variables: QtiTemplatePresentationVariables,
|
|
77
|
+
diagnostics: QtiDiagnostic[],
|
|
78
|
+
): SourceRangeEdit | undefined {
|
|
79
|
+
const binding = resolveTemplateBlockBinding(node, variables.templateValues, diagnostics);
|
|
80
|
+
if (!binding) return undefined;
|
|
81
|
+
|
|
82
|
+
const range = sourceRangeFor(node)[0];
|
|
83
|
+
if (!range) {
|
|
84
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
85
|
+
return undefined;
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
return {
|
|
89
|
+
...range,
|
|
90
|
+
text: isTemplateContentVisible(node, binding.value)
|
|
91
|
+
? materializedContentXml(xml, node, variables, diagnostics)
|
|
92
|
+
: "",
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function resolvePrintedVariableText(
|
|
97
|
+
node: XmlNode,
|
|
98
|
+
variables: QtiTemplatePresentationVariables,
|
|
99
|
+
diagnostics: QtiDiagnostic[],
|
|
100
|
+
): string | undefined {
|
|
101
|
+
const identifier = node.attributes.identifier;
|
|
102
|
+
if (!identifier) {
|
|
103
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
104
|
+
return undefined;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
const value = resolvePresentationVariable(identifier, variables);
|
|
108
|
+
if (value === undefined) {
|
|
109
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
110
|
+
return undefined;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
return escapeXmlText(qtiValueToString(value));
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function resolvePresentationVariable(
|
|
117
|
+
identifier: string,
|
|
118
|
+
variables: QtiTemplatePresentationVariables,
|
|
119
|
+
): QtiValue | undefined {
|
|
120
|
+
return (
|
|
121
|
+
variables.outcomes[identifier] ??
|
|
122
|
+
variables.templateValues[identifier] ??
|
|
123
|
+
variables.responses?.[identifier]
|
|
124
|
+
);
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
function resolveTemplateBlockBinding(
|
|
128
|
+
node: XmlNode,
|
|
129
|
+
templateValues: Record<string, QtiValue>,
|
|
130
|
+
diagnostics: QtiDiagnostic[],
|
|
131
|
+
): TemplateBlockBinding | undefined {
|
|
132
|
+
const templateIdentifier = node.attributes["template-identifier"];
|
|
133
|
+
const identifier = node.attributes.identifier;
|
|
134
|
+
if (!templateIdentifier || !identifier || templateValues[templateIdentifier] === undefined) {
|
|
135
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
136
|
+
return undefined;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return { templateIdentifier, identifier, value: templateValues[templateIdentifier] };
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
function materializedContentXml(
|
|
143
|
+
xml: string,
|
|
144
|
+
node: XmlNode,
|
|
145
|
+
variables: QtiTemplatePresentationVariables,
|
|
146
|
+
diagnostics: QtiDiagnostic[],
|
|
147
|
+
): string {
|
|
148
|
+
return node.content
|
|
149
|
+
.map((child) => {
|
|
150
|
+
if (typeof child === "string") return escapeXmlText(child);
|
|
151
|
+
if (child.localName === "qti-printed-variable") {
|
|
152
|
+
return resolvePrintedVariableText(child, variables, diagnostics) ?? "";
|
|
153
|
+
}
|
|
154
|
+
if (child.localName === "qti-template-block" || child.localName === "qti-template-inline") {
|
|
155
|
+
return materializedNestedTemplateContentXml(xml, child, variables, diagnostics);
|
|
156
|
+
}
|
|
157
|
+
return materializedElementXml(xml, child, variables, diagnostics);
|
|
158
|
+
})
|
|
159
|
+
.join("");
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function materializedNestedTemplateContentXml(
|
|
163
|
+
xml: string,
|
|
164
|
+
node: XmlNode,
|
|
165
|
+
variables: QtiTemplatePresentationVariables,
|
|
166
|
+
diagnostics: QtiDiagnostic[],
|
|
167
|
+
): string {
|
|
168
|
+
const binding = resolveTemplateBlockBinding(node, variables.templateValues, diagnostics);
|
|
169
|
+
if (!binding) return "";
|
|
170
|
+
|
|
171
|
+
return isTemplateContentVisible(node, binding.value)
|
|
172
|
+
? materializedContentXml(xml, node, variables, diagnostics)
|
|
173
|
+
: "";
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
function isTemplateContentVisible(node: XmlNode, value: QtiValue): boolean {
|
|
177
|
+
const identifier = node.attributes.identifier;
|
|
178
|
+
const hasIdentifier = Array.isArray(value)
|
|
179
|
+
? value.map(String).includes(identifier ?? "")
|
|
180
|
+
: qtiValueToString(value) === identifier;
|
|
181
|
+
return node.attributes["show-hide"] === "hide" ? !hasIdentifier : hasIdentifier;
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
function materializedElementXml(
|
|
185
|
+
xml: string,
|
|
186
|
+
node: XmlNode,
|
|
187
|
+
variables: QtiTemplatePresentationVariables,
|
|
188
|
+
diagnostics: QtiDiagnostic[],
|
|
189
|
+
): string {
|
|
190
|
+
if (node.sourceRange.endOffset === undefined || node.endSource === undefined) {
|
|
191
|
+
diagnostics.push(unsupportedTemplateMaterializationDiagnostic(node));
|
|
192
|
+
return "";
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
const startTag = xml.slice(node.sourceRange.startOffset, node.sourceRange.startTagEndOffset + 1);
|
|
196
|
+
const endTag = xml.slice(node.endSource.offset, node.sourceRange.endOffset);
|
|
197
|
+
return `${startTag}${materializedContentXml(xml, node, variables, diagnostics)}${endTag}`;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
function isInsideTemplateContentNode(node: XmlNode): boolean {
|
|
201
|
+
let parent = node.parent;
|
|
202
|
+
while (parent) {
|
|
203
|
+
if (parent.localName === "qti-template-block" || parent.localName === "qti-template-inline") {
|
|
204
|
+
return true;
|
|
205
|
+
}
|
|
206
|
+
parent = parent.parent;
|
|
207
|
+
}
|
|
208
|
+
return false;
|
|
209
|
+
}
|
|
210
|
+
|
|
211
|
+
function unsupportedTemplateMaterializationDiagnostic(node: XmlNode): QtiDiagnostic {
|
|
212
|
+
return {
|
|
213
|
+
code: "adaptiveTurn.materialization.unsupported",
|
|
214
|
+
severity: "error",
|
|
215
|
+
message: `${node.name} could not be materialized from authoritative template state.`,
|
|
216
|
+
path: node.source.path,
|
|
217
|
+
source: node.source,
|
|
218
|
+
};
|
|
219
|
+
}
|