@longsightgroup/qti3-core 0.7.2 → 0.8.0
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 +41 -10
- package/dist/content-text.d.ts +14 -0
- package/dist/content-text.d.ts.map +1 -0
- package/dist/content-text.js +58 -0
- package/dist/content-text.js.map +1 -0
- package/dist/index.d.ts +7 -3
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +6 -2
- package/dist/index.js.map +1 -1
- package/dist/package-xml.d.ts +12 -0
- package/dist/package-xml.d.ts.map +1 -0
- package/dist/package-xml.js +17 -0
- package/dist/package-xml.js.map +1 -0
- package/dist/parser-custom-interactions.d.ts +8 -0
- package/dist/parser-custom-interactions.d.ts.map +1 -0
- package/dist/parser-custom-interactions.js +178 -0
- package/dist/parser-custom-interactions.js.map +1 -0
- package/dist/parser-declarations.d.ts +6 -0
- package/dist/parser-declarations.d.ts.map +1 -0
- package/dist/parser-declarations.js +136 -0
- package/dist/parser-declarations.js.map +1 -0
- package/dist/parser-item-metadata.d.ts +9 -0
- package/dist/parser-item-metadata.d.ts.map +1 -0
- package/dist/parser-item-metadata.js +158 -0
- package/dist/parser-item-metadata.js.map +1 -0
- package/dist/parser-processing.d.ts +5 -0
- package/dist/parser-processing.d.ts.map +1 -0
- package/dist/parser-processing.js +642 -0
- package/dist/parser-processing.js.map +1 -0
- package/dist/parser-values.d.ts +8 -0
- package/dist/parser-values.d.ts.map +1 -0
- package/dist/parser-values.js +49 -0
- package/dist/parser-values.js.map +1 -0
- package/dist/parser.d.ts.map +1 -1
- package/dist/parser.js +32 -1086
- package/dist/parser.js.map +1 -1
- package/dist/processing-expression-tags.d.ts +56 -0
- package/dist/processing-expression-tags.d.ts.map +1 -0
- package/dist/processing-expression-tags.js +52 -0
- package/dist/processing-expression-tags.js.map +1 -0
- package/dist/processing-rules.d.ts +3 -0
- package/dist/processing-rules.d.ts.map +1 -0
- package/dist/processing-rules.js +10 -0
- package/dist/processing-rules.js.map +1 -0
- package/dist/serializer-processing-expressions.d.ts +8 -0
- package/dist/serializer-processing-expressions.d.ts.map +1 -0
- package/dist/serializer-processing-expressions.js +198 -0
- package/dist/serializer-processing-expressions.js.map +1 -0
- package/dist/serializer-processing-xml.d.ts +15 -0
- package/dist/serializer-processing-xml.d.ts.map +1 -0
- package/dist/serializer-processing-xml.js +46 -0
- package/dist/serializer-processing-xml.js.map +1 -0
- package/dist/serializer-processing.d.ts +8 -0
- package/dist/serializer-processing.d.ts.map +1 -0
- package/dist/serializer-processing.js +106 -0
- package/dist/serializer-processing.js.map +1 -0
- package/dist/shared-vocabulary-authoring.d.ts +50 -0
- package/dist/shared-vocabulary-authoring.d.ts.map +1 -0
- package/dist/shared-vocabulary-authoring.js +302 -0
- package/dist/shared-vocabulary-authoring.js.map +1 -0
- package/dist/shared-vocabulary-interaction-sets.d.ts +6 -0
- package/dist/shared-vocabulary-interaction-sets.d.ts.map +1 -0
- package/dist/shared-vocabulary-interaction-sets.js +23 -0
- package/dist/shared-vocabulary-interaction-sets.js.map +1 -0
- package/dist/shared-vocabulary-interaction-validation.d.ts +3 -0
- package/dist/shared-vocabulary-interaction-validation.d.ts.map +1 -0
- package/dist/shared-vocabulary-interaction-validation.js +99 -0
- package/dist/shared-vocabulary-interaction-validation.js.map +1 -0
- package/dist/shared-vocabulary-registry-validation.d.ts +5 -0
- package/dist/shared-vocabulary-registry-validation.d.ts.map +1 -0
- package/dist/shared-vocabulary-registry-validation.js +147 -0
- package/dist/shared-vocabulary-registry-validation.js.map +1 -0
- package/dist/shared-vocabulary-support.d.ts.map +1 -1
- package/dist/shared-vocabulary-support.js +96 -56
- package/dist/shared-vocabulary-support.js.map +1 -1
- package/dist/shared-vocabulary.d.ts +6 -0
- package/dist/shared-vocabulary.d.ts.map +1 -1
- package/dist/shared-vocabulary.js +12 -0
- package/dist/shared-vocabulary.js.map +1 -1
- package/dist/support.d.ts +4 -1
- package/dist/support.d.ts.map +1 -1
- package/dist/support.js +159 -10
- package/dist/support.js.map +1 -1
- package/dist/types.d.ts +38 -7
- package/dist/types.d.ts.map +1 -1
- package/dist/validation.d.ts.map +1 -1
- package/dist/validation.js +29 -250
- package/dist/validation.js.map +1 -1
- package/dist/xml.d.ts +10 -0
- package/dist/xml.d.ts.map +1 -1
- package/dist/xml.js +321 -249
- package/dist/xml.js.map +1 -1
- package/package.json +1 -4
- package/src/content-text.ts +80 -0
- package/src/index.ts +67 -0
- package/src/interaction-test-fixtures.ts +44 -0
- package/src/package-xml.ts +30 -0
- package/src/parser-custom-interactions.ts +309 -0
- package/src/parser-declarations.ts +174 -0
- package/src/parser-item-metadata.ts +194 -0
- package/src/parser-processing.ts +701 -0
- package/src/parser-values.ts +61 -0
- package/src/parser.ts +59 -1230
- package/src/processing-expression-tags.ts +65 -0
- package/src/processing-rules.ts +11 -0
- package/src/serializer-processing-expressions.ts +456 -0
- package/src/serializer-processing-xml.ts +67 -0
- package/src/serializer-processing.fixtures.ts +469 -0
- package/src/serializer-processing.ts +196 -0
- package/src/shared-vocabulary-authoring.ts +409 -0
- package/src/shared-vocabulary-interaction-sets.ts +28 -0
- package/src/shared-vocabulary-interaction-validation.ts +143 -0
- package/src/shared-vocabulary-registry-validation.ts +207 -0
- package/src/shared-vocabulary-support.ts +127 -101
- package/src/shared-vocabulary.ts +18 -0
- package/src/support.ts +174 -14
- package/src/types.ts +48 -7
- package/src/validation.ts +33 -341
- package/src/xml.ts +378 -255
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import {
|
|
2
|
+
describeSharedVocabularyPrecedence,
|
|
3
|
+
formatSharedVocabularyClassValueRange,
|
|
4
|
+
matchedSharedVocabularyClassNames,
|
|
5
|
+
parseClassValue,
|
|
6
|
+
sharedVocabularyFieldById,
|
|
7
|
+
sharedVocabularyFieldsForInteraction,
|
|
8
|
+
sharedVocabularyFixedClassName,
|
|
9
|
+
type QtiSharedVocabularyField,
|
|
10
|
+
} from "./shared-vocabulary-authoring.js";
|
|
11
|
+
import type { QtiDiagnostic, QtiInteraction } from "./types.js";
|
|
12
|
+
|
|
13
|
+
const LEGACY_CLASS_VALUE_CONFLICT_CODES: Record<string, string> = {
|
|
14
|
+
"labels-style": "interaction.sharedVocabulary.labelsConflict",
|
|
15
|
+
"labels-suffix": "interaction.sharedVocabulary.labelSuffixConflict",
|
|
16
|
+
orientation: "interaction.sharedVocabulary.orientationConflict",
|
|
17
|
+
"choices-stacking": "interaction.sharedVocabulary.stackingConflict",
|
|
18
|
+
"choices-position": "interaction.sharedVocabulary.orderChoicesPositionConflict",
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
const LEGACY_CLASS_VALUE_INVALID_CODES: Record<string, string> = {
|
|
22
|
+
"choices-stacking": "interaction.sharedVocabulary.stackingInvalid",
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
export function validateRegistrySharedVocabularyClasses(
|
|
26
|
+
interaction: QtiInteraction,
|
|
27
|
+
classNames: string[],
|
|
28
|
+
diagnostics: QtiDiagnostic[],
|
|
29
|
+
): void {
|
|
30
|
+
for (const field of sharedVocabularyFieldsForInteraction(interaction.type)) {
|
|
31
|
+
if (field.kind !== "class-value") continue;
|
|
32
|
+
validateClassValueFieldConflicts(interaction, field, classNames, diagnostics);
|
|
33
|
+
validateUnsupportedClassValueClasses(interaction, field, classNames, diagnostics);
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function validateClassValueFieldConflicts(
|
|
38
|
+
interaction: QtiInteraction,
|
|
39
|
+
field: Extract<QtiSharedVocabularyField, { kind: "class-value" }>,
|
|
40
|
+
classNames: string[],
|
|
41
|
+
diagnostics: QtiDiagnostic[],
|
|
42
|
+
): void {
|
|
43
|
+
const matched = matchedSharedVocabularyClassNames(field, classNames);
|
|
44
|
+
if (new Set(matched).size <= 1) return;
|
|
45
|
+
|
|
46
|
+
diagnostics.push({
|
|
47
|
+
code: classValueConflictCode(field.id),
|
|
48
|
+
severity: "warning",
|
|
49
|
+
message: conflictMessage(interaction, field, matched),
|
|
50
|
+
path: interaction.source?.path,
|
|
51
|
+
source: interaction.source,
|
|
52
|
+
});
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
function classValueConflictCode(fieldId: string): string {
|
|
56
|
+
return (
|
|
57
|
+
LEGACY_CLASS_VALUE_CONFLICT_CODES[fieldId] ??
|
|
58
|
+
`interaction.sharedVocabulary.${fieldIdToDiagnosticSuffix(fieldId)}Conflict`
|
|
59
|
+
);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function classValueInvalidCode(fieldId: string): string {
|
|
63
|
+
return (
|
|
64
|
+
LEGACY_CLASS_VALUE_INVALID_CODES[fieldId] ??
|
|
65
|
+
`interaction.sharedVocabulary.${fieldIdToDiagnosticSuffix(fieldId)}Invalid`
|
|
66
|
+
);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
function fieldIdToDiagnosticSuffix(fieldId: string): string {
|
|
70
|
+
return fieldId.replace(/-([a-z])/g, (_, letter: string) => letter.toUpperCase());
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
function conflictMessage(
|
|
74
|
+
interaction: QtiInteraction,
|
|
75
|
+
field: Extract<QtiSharedVocabularyField, { kind: "class-value" }>,
|
|
76
|
+
matchedClassNames: string[],
|
|
77
|
+
): string {
|
|
78
|
+
const uniqueMatched = [...new Set(matchedClassNames)];
|
|
79
|
+
const precedence = describeSharedVocabularyPrecedence(field, uniqueMatched);
|
|
80
|
+
|
|
81
|
+
switch (field.id) {
|
|
82
|
+
case "labels-style":
|
|
83
|
+
return `${interaction.qtiName} should not include multiple qti-labels-* style classes: ${uniqueMatched.join(", ")}. ${precedence} at runtime.`;
|
|
84
|
+
case "labels-suffix":
|
|
85
|
+
return `${interaction.qtiName} should not include multiple qti-labels-suffix-* classes: ${uniqueMatched.join(", ")}. ${precedence} at runtime.`;
|
|
86
|
+
case "orientation":
|
|
87
|
+
return `${interaction.qtiName} should not include both qti-orientation-horizontal and qti-orientation-vertical; ${precedence} at runtime.`;
|
|
88
|
+
case "choices-stacking":
|
|
89
|
+
return `qti-choice-interaction should not include multiple qti-choices-stacking-* classes: ${uniqueMatched.join(", ")}. The first valid stacking class in class attribute order takes precedence at runtime.`;
|
|
90
|
+
case "choices-position":
|
|
91
|
+
return `${interaction.qtiName} should not include multiple qti-choices-* position classes: ${uniqueMatched.join(", ")}. The first position class in class attribute order takes precedence at runtime.`;
|
|
92
|
+
default:
|
|
93
|
+
return `${interaction.qtiName} should not include multiple ${field.classPrefix}* classes: ${uniqueMatched.join(", ")}. ${precedence} at runtime.`;
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
function validateUnsupportedClassValueClasses(
|
|
98
|
+
interaction: QtiInteraction,
|
|
99
|
+
field: Extract<QtiSharedVocabularyField, { kind: "class-value" }>,
|
|
100
|
+
classNames: string[],
|
|
101
|
+
diagnostics: QtiDiagnostic[],
|
|
102
|
+
): void {
|
|
103
|
+
for (const className of classNames) {
|
|
104
|
+
if (!className.startsWith(field.classPrefix)) continue;
|
|
105
|
+
if (parseClassValue(field, className) !== undefined) continue;
|
|
106
|
+
diagnostics.push({
|
|
107
|
+
code: classValueInvalidCode(field.id),
|
|
108
|
+
severity: "warning",
|
|
109
|
+
message: unsupportedClassValueMessage(interaction, field, className),
|
|
110
|
+
path: interaction.source?.path,
|
|
111
|
+
source: interaction.source,
|
|
112
|
+
});
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
function unsupportedClassValueMessage(
|
|
117
|
+
interaction: QtiInteraction,
|
|
118
|
+
field: Extract<QtiSharedVocabularyField, { kind: "class-value" }>,
|
|
119
|
+
className: string,
|
|
120
|
+
): string {
|
|
121
|
+
if (field.id === "choices-stacking") {
|
|
122
|
+
return `qti-choice-interaction shared vocabulary class ${className} is not supported; expected ${formatSharedVocabularyClassValueRange(field)}.`;
|
|
123
|
+
}
|
|
124
|
+
return `${interaction.qtiName} shared vocabulary class ${className} is not supported; expected ${formatSharedVocabularyClassValueRange(field)}.`;
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
export function validateChoicesContainerWidthSharedVocabulary(
|
|
128
|
+
interaction: QtiInteraction,
|
|
129
|
+
diagnostics: QtiDiagnostic[],
|
|
130
|
+
): void {
|
|
131
|
+
const width = interaction.attributes["data-choices-container-width"];
|
|
132
|
+
if (width === undefined) return;
|
|
133
|
+
const parsed = Number(width);
|
|
134
|
+
if (!Number.isFinite(parsed) || parsed <= 0) {
|
|
135
|
+
diagnostics.push({
|
|
136
|
+
code: "interaction.sharedVocabulary.orderChoicesContainerWidth",
|
|
137
|
+
severity: "warning",
|
|
138
|
+
message: `${interaction.qtiName} data-choices-container-width must be a positive pixel value; the invalid value is ignored at runtime.`,
|
|
139
|
+
path: interaction.source?.path,
|
|
140
|
+
source: interaction.source,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
export function validateMatchInteractionSharedVocabulary(
|
|
146
|
+
interaction: QtiInteraction,
|
|
147
|
+
classNames: string[],
|
|
148
|
+
diagnostics: QtiDiagnostic[],
|
|
149
|
+
): void {
|
|
150
|
+
const matchTabularClass = sharedVocabularyFixedClassName("match-tabular");
|
|
151
|
+
const headerHiddenClass = sharedVocabularyFixedClassName("header-hidden");
|
|
152
|
+
const hasTabular =
|
|
153
|
+
matchTabularClass === undefined ? false : classNames.includes(matchTabularClass);
|
|
154
|
+
const hasHeaderHidden =
|
|
155
|
+
headerHiddenClass === undefined ? false : classNames.includes(headerHiddenClass);
|
|
156
|
+
const firstColumnHeader = interaction.attributes["data-first-column-header"];
|
|
157
|
+
if (!hasTabular && (hasHeaderHidden || firstColumnHeader !== undefined)) {
|
|
158
|
+
diagnostics.push({
|
|
159
|
+
code: "interaction.sharedVocabulary.matchTabularContext",
|
|
160
|
+
severity: "warning",
|
|
161
|
+
message:
|
|
162
|
+
"qti-match-interaction shared vocabulary class qti-header-hidden and data-first-column-header are only relevant when qti-match-tabular is specified; they are ignored at runtime.",
|
|
163
|
+
path: interaction.source?.path,
|
|
164
|
+
source: interaction.source,
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
if (hasTabular && hasHeaderHidden && firstColumnHeader !== undefined) {
|
|
168
|
+
diagnostics.push({
|
|
169
|
+
code: "interaction.sharedVocabulary.matchTabularHeaderHidden",
|
|
170
|
+
severity: "warning",
|
|
171
|
+
message:
|
|
172
|
+
"qti-match-interaction data-first-column-header is ignored when qti-header-hidden suppresses the tabular header row.",
|
|
173
|
+
path: interaction.source?.path,
|
|
174
|
+
source: interaction.source,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (!hasTabular) return;
|
|
178
|
+
|
|
179
|
+
const choicesPositionField = sharedVocabularyFieldById("choices-position");
|
|
180
|
+
const choicesPositionClasses =
|
|
181
|
+
choicesPositionField?.kind === "class-value"
|
|
182
|
+
? matchedSharedVocabularyClassNames(choicesPositionField, classNames)
|
|
183
|
+
: [];
|
|
184
|
+
if (
|
|
185
|
+
choicesPositionClasses.length > 0 ||
|
|
186
|
+
interaction.attributes["data-choices-container-width"] !== undefined
|
|
187
|
+
) {
|
|
188
|
+
diagnostics.push({
|
|
189
|
+
code: "interaction.sharedVocabulary.matchTabularChoicesConflict",
|
|
190
|
+
severity: "warning",
|
|
191
|
+
message:
|
|
192
|
+
"qti-match-interaction qti-match-tabular uses a matrix layout; qti-choices-* position classes and data-choices-container-width are ignored at runtime.",
|
|
193
|
+
path: interaction.source?.path,
|
|
194
|
+
source: interaction.source,
|
|
195
|
+
});
|
|
196
|
+
}
|
|
197
|
+
if (!hasHeaderHidden && (firstColumnHeader === undefined || firstColumnHeader === "")) {
|
|
198
|
+
diagnostics.push({
|
|
199
|
+
code: "interaction.sharedVocabulary.matchTabularFirstColumnHeader",
|
|
200
|
+
severity: "warning",
|
|
201
|
+
message:
|
|
202
|
+
"qti-match-interaction with qti-match-tabular should specify data-first-column-header for the top-left table header when the tabular header row is shown.",
|
|
203
|
+
path: interaction.source?.path,
|
|
204
|
+
source: interaction.source,
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import {
|
|
2
|
-
SHARED_VOCABULARY_CHOICE_WRITING_ORIENTATIONS,
|
|
3
2
|
SHARED_VOCABULARY_CONTENT_ALIGNMENTS,
|
|
4
3
|
SHARED_VOCABULARY_CONTENT_FLOAT_SUFFIXES,
|
|
5
4
|
SHARED_VOCABULARY_CONTENT_LIST_STYLE_TYPES,
|
|
@@ -9,11 +8,17 @@ import {
|
|
|
9
8
|
SHARED_VOCABULARY_LAYOUT_COLUMN_SPAN_COUNT,
|
|
10
9
|
SHARED_VOCABULARY_LAYOUT_OFFSET_COUNT,
|
|
11
10
|
} from "./shared-vocabulary-generated-families.js";
|
|
11
|
+
import {
|
|
12
|
+
sharedVocabularyFixedClassName,
|
|
13
|
+
sharedVocabularyInteractionFields,
|
|
14
|
+
type QtiSharedVocabularyField,
|
|
15
|
+
} from "./shared-vocabulary-authoring.js";
|
|
12
16
|
import {
|
|
13
17
|
SHARED_VOCABULARY_EXTENDED_TEXT_COUNTER_CLASSES,
|
|
14
18
|
SHARED_VOCABULARY_EXTENDED_TEXT_HEIGHT_LINES,
|
|
15
19
|
SHARED_VOCABULARY_INPUT_WIDTHS,
|
|
16
20
|
} from "./shared-vocabulary.js";
|
|
21
|
+
import { SHARED_VOCABULARY_CHOICE_AND_ORDER_INTERACTIONS } from "./shared-vocabulary-interaction-sets.js";
|
|
17
22
|
import type { QtiInteractionType, SharedVocabularyClassSupport } from "./types.js";
|
|
18
23
|
|
|
19
24
|
const sharedVocabularyFixture = [
|
|
@@ -34,7 +39,10 @@ const sharedVocabularyUnitTests = [
|
|
|
34
39
|
const sharedVocabularyValidationTests = [
|
|
35
40
|
"packages/core/src/core.test.ts",
|
|
36
41
|
"packages/core/src/shared-vocabulary-validation.test.ts",
|
|
42
|
+
"packages/core/src/shared-vocabulary-registry-validation.test.ts",
|
|
43
|
+
"packages/core/src/shared-vocabulary-interaction-validation.test.ts",
|
|
37
44
|
];
|
|
45
|
+
const choiceLayoutTests = ["packages/player/src/interactions/choice-layout.test.ts"];
|
|
38
46
|
const browserBehaviorTests = [
|
|
39
47
|
"tests/browser/player-dom-behavior.spec.ts",
|
|
40
48
|
...sharedVocabularyMatrixTests,
|
|
@@ -53,19 +61,107 @@ const interactionInputWidthFixtures = [
|
|
|
53
61
|
const orderMinMaxMessagesFixture =
|
|
54
62
|
"packages/fixtures/packages/sv-matrix/items/order-min-max-messages.xml";
|
|
55
63
|
|
|
56
|
-
const choiceAndOrder
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
"
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
64
|
+
const choiceAndOrder = SHARED_VOCABULARY_CHOICE_AND_ORDER_INTERACTIONS;
|
|
65
|
+
|
|
66
|
+
function registryClassValueSupportProfile(fieldId: string): {
|
|
67
|
+
level: "full" | "stylesheet";
|
|
68
|
+
tests: string[];
|
|
69
|
+
} {
|
|
70
|
+
switch (fieldId) {
|
|
71
|
+
case "labels-style":
|
|
72
|
+
case "labels-suffix":
|
|
73
|
+
return { level: "full", tests: [...sharedVocabularyUnitTests, ...browserBehaviorTests] };
|
|
74
|
+
case "orientation":
|
|
75
|
+
return {
|
|
76
|
+
level: "full",
|
|
77
|
+
tests: [...choiceLayoutTests, ...sharedVocabularyUnitTests, ...browserBehaviorTests],
|
|
78
|
+
};
|
|
79
|
+
case "choices-stacking":
|
|
80
|
+
return {
|
|
81
|
+
level: "full",
|
|
82
|
+
tests: [...choiceLayoutTests, "packages/core/src/core.test.ts", ...browserBehaviorTests],
|
|
83
|
+
};
|
|
84
|
+
case "choices-position":
|
|
85
|
+
return {
|
|
86
|
+
level: "full",
|
|
87
|
+
tests: [...sharedVocabularyUnitTests, ...browserBehaviorTests, ...graphicBrowserTests],
|
|
88
|
+
};
|
|
89
|
+
case "selections-tone":
|
|
90
|
+
return { level: "stylesheet", tests: [...browserBehaviorTests, ...graphicBrowserTests] };
|
|
91
|
+
case "writing-orientation":
|
|
92
|
+
return { level: "stylesheet", tests: [...browserBehaviorTests] };
|
|
93
|
+
default:
|
|
94
|
+
return { level: "full", tests: [...sharedVocabularyUnitTests, ...browserBehaviorTests] };
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function registryClassValueSupportEntries(): SharedVocabularyClassSupport[] {
|
|
99
|
+
return sharedVocabularyInteractionFields.flatMap((field) => {
|
|
100
|
+
if (field.kind !== "class-value") return [];
|
|
101
|
+
const profile = registryClassValueSupportProfile(field.id);
|
|
102
|
+
const createEntry =
|
|
103
|
+
profile.level === "stylesheet" ? interactionStylesheetEntry : interactionFullEntry;
|
|
104
|
+
return field.values.map((value) =>
|
|
105
|
+
createEntry(`${field.classPrefix}${String(value)}`, field.interactions, profile.tests),
|
|
106
|
+
);
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
function registryClassFixedSupportProfile(fieldId: string): {
|
|
111
|
+
level: "full" | "stylesheet";
|
|
112
|
+
tests: string[];
|
|
113
|
+
} {
|
|
114
|
+
switch (fieldId) {
|
|
115
|
+
case "input-control-hidden":
|
|
116
|
+
return {
|
|
117
|
+
level: "stylesheet",
|
|
118
|
+
tests: [...browserBehaviorTests, "tests/browser/player-keyboard-a11y.spec.ts"],
|
|
119
|
+
};
|
|
120
|
+
case "unselected-hidden":
|
|
121
|
+
return { level: "stylesheet", tests: [...browserBehaviorTests, ...graphicBrowserTests] };
|
|
122
|
+
case "match-tabular":
|
|
123
|
+
return {
|
|
124
|
+
level: "full",
|
|
125
|
+
tests: [
|
|
126
|
+
"packages/core/src/core.test.ts",
|
|
127
|
+
...browserBehaviorTests,
|
|
128
|
+
"tests/browser/player-keyboard-a11y.spec.ts",
|
|
129
|
+
],
|
|
130
|
+
};
|
|
131
|
+
case "gap-placement":
|
|
132
|
+
return {
|
|
133
|
+
level: "full",
|
|
134
|
+
tests: [
|
|
135
|
+
...sharedVocabularyUnitTests,
|
|
136
|
+
"packages/core/src/core.test.ts",
|
|
137
|
+
...browserBehaviorTests,
|
|
138
|
+
],
|
|
139
|
+
};
|
|
140
|
+
default:
|
|
141
|
+
return { level: "full", tests: [...browserBehaviorTests] };
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
const registryClassFixedSupportIds = [
|
|
146
|
+
"input-control-hidden",
|
|
147
|
+
"unselected-hidden",
|
|
148
|
+
"match-tabular",
|
|
149
|
+
"gap-placement",
|
|
150
|
+
] as const;
|
|
151
|
+
|
|
152
|
+
function registryClassFixedSupportEntries(): SharedVocabularyClassSupport[] {
|
|
153
|
+
return registryClassFixedSupportIds.flatMap((fieldId) => {
|
|
154
|
+
const field = sharedVocabularyInteractionFields.find(
|
|
155
|
+
(candidate): candidate is Extract<QtiSharedVocabularyField, { kind: "class-fixed" }> =>
|
|
156
|
+
candidate.kind === "class-fixed" && candidate.id === fieldId,
|
|
157
|
+
);
|
|
158
|
+
if (field === undefined) return [];
|
|
159
|
+
const profile = registryClassFixedSupportProfile(field.id);
|
|
160
|
+
const createEntry =
|
|
161
|
+
profile.level === "stylesheet" ? interactionStylesheetEntry : interactionFullEntry;
|
|
162
|
+
return [createEntry(field.className, field.interactions, profile.tests)];
|
|
163
|
+
});
|
|
164
|
+
}
|
|
69
165
|
|
|
70
166
|
function svEntry(
|
|
71
167
|
className: string,
|
|
@@ -91,12 +187,12 @@ function contentStylesheetEntry(className: string, notes?: string): SharedVocabu
|
|
|
91
187
|
|
|
92
188
|
function interactionFullEntry(
|
|
93
189
|
className: string,
|
|
94
|
-
interactions: QtiInteractionType[],
|
|
190
|
+
interactions: readonly QtiInteractionType[],
|
|
95
191
|
tests: string[],
|
|
96
192
|
notes?: string,
|
|
97
193
|
): SharedVocabularyClassSupport {
|
|
98
194
|
return svEntry(className, "interaction", "full", {
|
|
99
|
-
interactions,
|
|
195
|
+
interactions: [...interactions],
|
|
100
196
|
fixtures: sharedVocabularyFixture,
|
|
101
197
|
tests,
|
|
102
198
|
notes,
|
|
@@ -105,12 +201,12 @@ function interactionFullEntry(
|
|
|
105
201
|
|
|
106
202
|
function interactionStylesheetEntry(
|
|
107
203
|
className: string,
|
|
108
|
-
interactions: QtiInteractionType[],
|
|
204
|
+
interactions: readonly QtiInteractionType[],
|
|
109
205
|
tests: string[],
|
|
110
206
|
notes?: string,
|
|
111
207
|
): SharedVocabularyClassSupport {
|
|
112
208
|
return svEntry(className, "interaction", "stylesheet", {
|
|
113
|
-
interactions,
|
|
209
|
+
interactions: [...interactions],
|
|
114
210
|
fixtures: sharedVocabularyFixture,
|
|
115
211
|
tests,
|
|
116
212
|
notes,
|
|
@@ -187,69 +283,8 @@ export const sharedVocabularyClassSupport: SharedVocabularyClassSupport[] = [
|
|
|
187
283
|
contentStylesheetEntry("qti-italic"),
|
|
188
284
|
contentStylesheetEntry("qti-display-inline-block"),
|
|
189
285
|
|
|
190
|
-
...
|
|
191
|
-
|
|
192
|
-
...sharedVocabularyUnitTests,
|
|
193
|
-
...browserBehaviorTests,
|
|
194
|
-
]),
|
|
195
|
-
),
|
|
196
|
-
...["none", "period", "parenthesis"].map((suffix) =>
|
|
197
|
-
interactionFullEntry(`qti-labels-suffix-${suffix}`, choiceAndOrder, [
|
|
198
|
-
...sharedVocabularyUnitTests,
|
|
199
|
-
...browserBehaviorTests,
|
|
200
|
-
]),
|
|
201
|
-
),
|
|
202
|
-
...["horizontal", "vertical"].map((orientation) =>
|
|
203
|
-
interactionFullEntry(
|
|
204
|
-
`qti-orientation-${orientation}`,
|
|
205
|
-
["choice", "order"],
|
|
206
|
-
[
|
|
207
|
-
"packages/player/src/interactions/choice-layout.test.ts",
|
|
208
|
-
...sharedVocabularyUnitTests,
|
|
209
|
-
...browserBehaviorTests,
|
|
210
|
-
],
|
|
211
|
-
),
|
|
212
|
-
),
|
|
213
|
-
...Array.from({ length: 5 }, (_, index) => index + 1).map((stacking) =>
|
|
214
|
-
interactionFullEntry(
|
|
215
|
-
`qti-choices-stacking-${stacking}`,
|
|
216
|
-
["choice"],
|
|
217
|
-
[
|
|
218
|
-
"packages/player/src/interactions/choice-layout.test.ts",
|
|
219
|
-
"packages/core/src/core.test.ts",
|
|
220
|
-
...browserBehaviorTests,
|
|
221
|
-
],
|
|
222
|
-
),
|
|
223
|
-
),
|
|
224
|
-
interactionStylesheetEntry(
|
|
225
|
-
"qti-input-control-hidden",
|
|
226
|
-
["choice", "hottext"],
|
|
227
|
-
[...browserBehaviorTests, "tests/browser/player-keyboard-a11y.spec.ts"],
|
|
228
|
-
),
|
|
229
|
-
...SHARED_VOCABULARY_CHOICE_WRITING_ORIENTATIONS.map((orientation) =>
|
|
230
|
-
interactionStylesheetEntry(
|
|
231
|
-
`qti-writing-orientation-${orientation}`,
|
|
232
|
-
["choice", "inlineChoice"],
|
|
233
|
-
[...browserBehaviorTests],
|
|
234
|
-
),
|
|
235
|
-
),
|
|
236
|
-
...["light", "dark"].map((tone) =>
|
|
237
|
-
interactionStylesheetEntry(`qti-selections-${tone}`, selectionPresentationInteractions, [
|
|
238
|
-
...browserBehaviorTests,
|
|
239
|
-
...graphicBrowserTests,
|
|
240
|
-
]),
|
|
241
|
-
),
|
|
242
|
-
interactionStylesheetEntry("qti-unselected-hidden", selectionPresentationInteractions, [
|
|
243
|
-
...browserBehaviorTests,
|
|
244
|
-
...graphicBrowserTests,
|
|
245
|
-
]),
|
|
246
|
-
...["top", "bottom", "left", "right"].map((position) =>
|
|
247
|
-
interactionFullEntry(`qti-choices-${position}`, choicesLayoutInteractions, [
|
|
248
|
-
...sharedVocabularyUnitTests,
|
|
249
|
-
...browserBehaviorTests,
|
|
250
|
-
...graphicBrowserTests,
|
|
251
|
-
]),
|
|
252
|
-
),
|
|
286
|
+
...registryClassValueSupportEntries(),
|
|
287
|
+
...registryClassFixedSupportEntries(),
|
|
253
288
|
svEntry("data-min-selections-message", "interaction", "full", {
|
|
254
289
|
interactions: ["order"],
|
|
255
290
|
fixtures: [orderMinMaxMessagesFixture],
|
|
@@ -257,30 +292,21 @@ export const sharedVocabularyClassSupport: SharedVocabularyClassSupport[] = [
|
|
|
257
292
|
notes: "Overrides minimum response validation text for order interactions.",
|
|
258
293
|
}),
|
|
259
294
|
svEntry("data-max-selections-message", "interaction", "full", {
|
|
260
|
-
interactions: [
|
|
295
|
+
interactions: [...choiceAndOrder],
|
|
261
296
|
fixtures: [orderMinMaxMessagesFixture],
|
|
262
297
|
tests: ["tests/browser/player-validation.spec.ts", ...sharedVocabularyMatrixTests],
|
|
263
298
|
notes: "Overrides maximum response validation text for choice and order interactions.",
|
|
264
299
|
}),
|
|
265
|
-
|
|
266
|
-
"qti-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
interactions: ["match"],
|
|
276
|
-
fixtures: sharedVocabularyFixture,
|
|
277
|
-
tests: ["packages/core/src/core.test.ts", ...browserBehaviorTests],
|
|
278
|
-
notes: "Applied when qti-match-tabular selects the tabular match renderer.",
|
|
279
|
-
}),
|
|
280
|
-
interactionFullEntry(
|
|
281
|
-
"qti-gap-placement",
|
|
282
|
-
["gapMatch"],
|
|
283
|
-
[...sharedVocabularyUnitTests, "packages/core/src/core.test.ts", ...browserBehaviorTests],
|
|
300
|
+
svEntry(
|
|
301
|
+
sharedVocabularyFixedClassName("header-hidden") ?? "qti-header-hidden",
|
|
302
|
+
"interaction",
|
|
303
|
+
"conditional",
|
|
304
|
+
{
|
|
305
|
+
interactions: ["match"],
|
|
306
|
+
fixtures: sharedVocabularyFixture,
|
|
307
|
+
tests: ["packages/core/src/core.test.ts", ...browserBehaviorTests],
|
|
308
|
+
notes: "Applied when qti-match-tabular selects the tabular match renderer.",
|
|
309
|
+
},
|
|
284
310
|
),
|
|
285
311
|
...SHARED_VOCABULARY_INPUT_WIDTHS.map((width) =>
|
|
286
312
|
svEntry(`qti-input-width-${width}`, "interaction", "full", {
|
package/src/shared-vocabulary.ts
CHANGED
|
@@ -7,6 +7,24 @@ export const SHARED_VOCABULARY_INPUT_WIDTHS = [
|
|
|
7
7
|
|
|
8
8
|
export const SHARED_VOCABULARY_GAP_INPUT_WIDTHS = SHARED_VOCABULARY_INPUT_WIDTHS;
|
|
9
9
|
|
|
10
|
+
export const SHARED_VOCABULARY_LABEL_STYLES = [
|
|
11
|
+
"none",
|
|
12
|
+
"decimal",
|
|
13
|
+
"cjk-ideographic",
|
|
14
|
+
"lower-alpha",
|
|
15
|
+
"upper-alpha",
|
|
16
|
+
] as const;
|
|
17
|
+
|
|
18
|
+
export const SHARED_VOCABULARY_LABEL_SUFFIXES = ["none", "period", "parenthesis"] as const;
|
|
19
|
+
|
|
20
|
+
export const SHARED_VOCABULARY_ORIENTATIONS = ["horizontal", "vertical"] as const;
|
|
21
|
+
|
|
22
|
+
export const SHARED_VOCABULARY_CHOICES_STACKING = [1, 2, 3, 4, 5] as const;
|
|
23
|
+
|
|
24
|
+
export const SHARED_VOCABULARY_CHOICES_POSITIONS = ["top", "bottom", "left", "right"] as const;
|
|
25
|
+
|
|
26
|
+
export const SHARED_VOCABULARY_SELECTION_TONES = ["light", "dark"] as const;
|
|
27
|
+
|
|
10
28
|
/** Normative QTI 3 shared vocabulary extended text height token values (1EdTech vocab). */
|
|
11
29
|
export const SHARED_VOCABULARY_EXTENDED_TEXT_HEIGHT_LINES = [3, 6, 15] as const;
|
|
12
30
|
|