@longsightgroup/qti3-core 0.10.1 → 0.10.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (119) hide show
  1. package/README.md +21 -2
  2. package/dist/attempt-state.d.ts.map +1 -1
  3. package/dist/attempt-state.js +18 -0
  4. package/dist/attempt-state.js.map +1 -1
  5. package/dist/content-text.d.ts +4 -0
  6. package/dist/content-text.d.ts.map +1 -1
  7. package/dist/content-text.js +112 -9
  8. package/dist/content-text.js.map +1 -1
  9. package/dist/index.d.ts +4 -0
  10. package/dist/index.d.ts.map +1 -1
  11. package/dist/index.js +4 -0
  12. package/dist/index.js.map +1 -1
  13. package/dist/media-definition.d.ts +55 -0
  14. package/dist/media-definition.d.ts.map +1 -0
  15. package/dist/media-definition.js +97 -0
  16. package/dist/media-definition.js.map +1 -0
  17. package/dist/parser-declarations.d.ts +4 -4
  18. package/dist/parser-declarations.d.ts.map +1 -1
  19. package/dist/parser-declarations.js +87 -46
  20. package/dist/parser-declarations.js.map +1 -1
  21. package/dist/parser-item-metadata.d.ts.map +1 -1
  22. package/dist/parser-item-metadata.js +4 -3
  23. package/dist/parser-item-metadata.js.map +1 -1
  24. package/dist/parser-processing.js +10 -9
  25. package/dist/parser-processing.js.map +1 -1
  26. package/dist/parser-values.d.ts +2 -0
  27. package/dist/parser-values.d.ts.map +1 -1
  28. package/dist/parser-values.js +7 -0
  29. package/dist/parser-values.js.map +1 -1
  30. package/dist/parser.js +16 -13
  31. package/dist/parser.js.map +1 -1
  32. package/dist/processing-evaluator-collection.d.ts.map +1 -1
  33. package/dist/processing-evaluator-collection.js +8 -2
  34. package/dist/processing-evaluator-collection.js.map +1 -1
  35. package/dist/processing-evaluator-comparison.d.ts.map +1 -1
  36. package/dist/processing-evaluator-comparison.js +6 -0
  37. package/dist/processing-evaluator-comparison.js.map +1 -1
  38. package/dist/processing-evaluator-numeric.d.ts.map +1 -1
  39. package/dist/processing-evaluator-numeric.js +8 -1
  40. package/dist/processing-evaluator-numeric.js.map +1 -1
  41. package/dist/processing-expression-children.d.ts.map +1 -1
  42. package/dist/processing-expression-children.js +2 -1
  43. package/dist/processing-expression-children.js.map +1 -1
  44. package/dist/processing-limits.d.ts +3 -0
  45. package/dist/processing-limits.d.ts.map +1 -0
  46. package/dist/processing-limits.js +3 -0
  47. package/dist/processing-limits.js.map +1 -0
  48. package/dist/processing-mapping.d.ts.map +1 -1
  49. package/dist/processing-mapping.js +13 -2
  50. package/dist/processing-mapping.js.map +1 -1
  51. package/dist/processing-operators.d.ts +2 -0
  52. package/dist/processing-operators.d.ts.map +1 -1
  53. package/dist/processing-operators.js +34 -5
  54. package/dist/processing-operators.js.map +1 -1
  55. package/dist/response-validation-policy.d.ts.map +1 -1
  56. package/dist/response-validation-policy.js +5 -6
  57. package/dist/response-validation-policy.js.map +1 -1
  58. package/dist/serializer-processing-expressions.js +1 -1
  59. package/dist/serializer-processing-expressions.js.map +1 -1
  60. package/dist/slider-definition.d.ts +59 -0
  61. package/dist/slider-definition.d.ts.map +1 -0
  62. package/dist/slider-definition.js +198 -0
  63. package/dist/slider-definition.js.map +1 -0
  64. package/dist/support-evidence.d.ts +6 -2
  65. package/dist/support-evidence.d.ts.map +1 -1
  66. package/dist/support-evidence.js +15 -11
  67. package/dist/support-evidence.js.map +1 -1
  68. package/dist/support.d.ts.map +1 -1
  69. package/dist/support.js +96 -100
  70. package/dist/support.js.map +1 -1
  71. package/dist/types.d.ts +3 -0
  72. package/dist/types.d.ts.map +1 -1
  73. package/dist/validation-declaration-entries.d.ts +13 -0
  74. package/dist/validation-declaration-entries.d.ts.map +1 -0
  75. package/dist/validation-declaration-entries.js +136 -0
  76. package/dist/validation-declaration-entries.js.map +1 -0
  77. package/dist/validation-interactions.d.ts.map +1 -1
  78. package/dist/validation-interactions.js +14 -46
  79. package/dist/validation-interactions.js.map +1 -1
  80. package/dist/validation-primitives.js +2 -2
  81. package/dist/validation-primitives.js.map +1 -1
  82. package/dist/validation-processing.d.ts.map +1 -1
  83. package/dist/validation-processing.js +125 -1
  84. package/dist/validation-processing.js.map +1 -1
  85. package/dist/validation.d.ts.map +1 -1
  86. package/dist/validation.js +8 -134
  87. package/dist/validation.js.map +1 -1
  88. package/dist/xml.d.ts.map +1 -1
  89. package/dist/xml.js +3 -2
  90. package/dist/xml.js.map +1 -1
  91. package/package.json +1 -1
  92. package/src/attempt-state.ts +20 -0
  93. package/src/content-text.ts +135 -11
  94. package/src/index.ts +25 -0
  95. package/src/media-definition.ts +174 -0
  96. package/src/parser-declarations.ts +134 -40
  97. package/src/parser-item-metadata.ts +4 -3
  98. package/src/parser-processing.ts +10 -9
  99. package/src/parser-values.ts +7 -0
  100. package/src/parser.ts +16 -13
  101. package/src/processing-evaluator-collection.ts +7 -2
  102. package/src/processing-evaluator-comparison.ts +6 -0
  103. package/src/processing-evaluator-numeric.ts +5 -1
  104. package/src/processing-expression-children.ts +2 -1
  105. package/src/processing-limits.ts +2 -0
  106. package/src/processing-mapping.ts +10 -2
  107. package/src/processing-operators.ts +38 -5
  108. package/src/response-validation-policy.ts +5 -5
  109. package/src/serializer-processing-expressions.ts +1 -1
  110. package/src/slider-definition.ts +363 -0
  111. package/src/support-evidence.ts +15 -17
  112. package/src/support.ts +106 -102
  113. package/src/types.ts +8 -1
  114. package/src/validation-declaration-entries.ts +162 -0
  115. package/src/validation-interactions.ts +14 -64
  116. package/src/validation-primitives.ts +2 -2
  117. package/src/validation-processing.ts +145 -1
  118. package/src/validation.ts +11 -151
  119. package/src/xml.ts +3 -4
package/src/support.ts CHANGED
@@ -11,9 +11,14 @@ import type {
11
11
  import {
12
12
  browserFeedbackTests,
13
13
  browserHarnessTests,
14
+ coreIntegrationTest,
14
15
  interactionSupportFixtures,
15
16
  interactionSupportTests,
16
17
  processingBrowserEvidence,
18
+ processingMappingTest,
19
+ processingOperatorsTest,
20
+ processingResponseTest,
21
+ processingTemplateTest,
17
22
  } from "./support-evidence.js";
18
23
 
19
24
  export const interactionSupport: QtiInteractionElementSupport[] = [
@@ -52,15 +57,19 @@ export const deprecatedInteractionSupport: QtiInteractionElementSupport[] = [
52
57
  render: false,
53
58
  process: false,
54
59
  fixtures: [],
55
- tests: ["packages/core/src/core.test.ts"],
60
+ tests: [coreIntegrationTest],
56
61
  notes: "Deprecated in favor of qti-portable-custom-interaction.",
57
62
  },
58
63
  ];
59
64
 
65
+ const templateProcessingEntry = processingEntryFor(processingTemplateTest);
66
+ const responseProcessingEntry = processingEntryFor(processingResponseTest);
67
+ const mappingProcessingEntry = processingEntryFor(processingMappingTest);
68
+ const operatorProcessingEntry = processingEntryFor(processingOperatorsTest);
69
+
60
70
  export const processingSupport: QtiProcessingElementSupport[] = [
61
- processingEntry(
71
+ templateProcessingEntry(
62
72
  "qti-template-processing",
63
- "packages/core/src/core.test.ts",
64
73
  [
65
74
  "packages/fixtures/xml/template-processing-reference.xml",
66
75
  "packages/fixtures/xml/random-integer-template-reference.xml",
@@ -68,9 +77,8 @@ export const processingSupport: QtiProcessingElementSupport[] = [
68
77
  ],
69
78
  [...processingBrowserEvidence["qti-template-processing"]],
70
79
  ),
71
- processingEntry(
80
+ responseProcessingEntry(
72
81
  "qti-response-processing",
73
- "packages/core/src/core.test.ts",
74
82
  [
75
83
  "packages/fixtures/xml/mapping-processing-reference.xml",
76
84
  "packages/fixtures/xml/generic-match-processing-reference.xml",
@@ -79,64 +87,64 @@ export const processingSupport: QtiProcessingElementSupport[] = [
79
87
  ],
80
88
  [...processingBrowserEvidence["qti-response-processing"]],
81
89
  ),
82
- processingEntry("qti-set-template-value", "packages/core/src/core.test.ts", [
90
+ templateProcessingEntry("qti-set-template-value", [
83
91
  "packages/fixtures/xml/template-processing-reference.xml",
84
92
  "packages/fixtures/xml/random-integer-template-reference.xml",
85
93
  "packages/fixtures/xml/template-content-reference.xml",
86
94
  ]),
87
- processingEntry("qti-set-default-value", "packages/core/src/core.test.ts"),
88
- processingEntry("qti-set-correct-response", "packages/core/src/core.test.ts", [
95
+ templateProcessingEntry("qti-set-default-value"),
96
+ templateProcessingEntry("qti-set-correct-response", [
89
97
  "packages/fixtures/xml/template-processing-reference.xml",
90
98
  "packages/fixtures/xml/random-integer-template-reference.xml",
91
99
  ]),
92
- processingEntry("qti-template-condition", "packages/core/src/core.test.ts"),
93
- processingEntry("qti-template-if", "packages/core/src/core.test.ts"),
94
- processingEntry("qti-template-else-if", "packages/core/src/core.test.ts"),
95
- processingEntry("qti-template-else", "packages/core/src/core.test.ts"),
96
- processingEntry("qti-template-constraint", "packages/core/src/core.test.ts"),
97
- processingEntry("qti-response-condition", "packages/core/src/core.test.ts", [
100
+ templateProcessingEntry("qti-template-condition"),
101
+ templateProcessingEntry("qti-template-if"),
102
+ templateProcessingEntry("qti-template-else-if"),
103
+ templateProcessingEntry("qti-template-else"),
104
+ templateProcessingEntry("qti-template-constraint"),
105
+ responseProcessingEntry("qti-response-condition", [
98
106
  "packages/fixtures/xml/generic-match-processing-reference.xml",
99
107
  "packages/fixtures/xml/advanced-processing-reference.xml",
100
108
  "packages/fixtures/xml/adaptive-feedback-reference.xml",
101
109
  ]),
102
- processingEntry("qti-response-if", "packages/core/src/core.test.ts", [
110
+ responseProcessingEntry("qti-response-if", [
103
111
  "packages/fixtures/xml/generic-match-processing-reference.xml",
104
112
  "packages/fixtures/xml/advanced-processing-reference.xml",
105
113
  "packages/fixtures/xml/adaptive-feedback-reference.xml",
106
114
  ]),
107
- processingEntry("qti-response-else-if", "packages/core/src/core.test.ts"),
108
- processingEntry("qti-response-else", "packages/core/src/core.test.ts", [
115
+ responseProcessingEntry("qti-response-else-if"),
116
+ responseProcessingEntry("qti-response-else", [
109
117
  "packages/fixtures/xml/generic-match-processing-reference.xml",
110
118
  ]),
111
- processingEntry("qti-set-outcome-value", "packages/core/src/core.test.ts", [
119
+ responseProcessingEntry("qti-set-outcome-value", [
112
120
  "packages/fixtures/xml/generic-match-processing-reference.xml",
113
121
  "packages/fixtures/xml/advanced-processing-reference.xml",
114
122
  "packages/fixtures/xml/adaptive-feedback-reference.xml",
115
123
  ]),
116
- processingEntry("qti-lookup-outcome-value", "packages/core/src/core.test.ts"),
117
- processingEntry("qti-exit-response", "packages/core/src/core.test.ts"),
118
- processingEntry("qti-exit-template", "packages/core/src/core.test.ts"),
119
- processingEntry("qti-response-processing-fragment", "packages/core/src/core.test.ts"),
120
- processingEntry("qti-base-value", "packages/core/src/core.test.ts", [
124
+ mappingProcessingEntry("qti-lookup-outcome-value"),
125
+ responseProcessingEntry("qti-exit-response"),
126
+ templateProcessingEntry("qti-exit-template"),
127
+ responseProcessingEntry("qti-response-processing-fragment"),
128
+ operatorProcessingEntry("qti-base-value", [
121
129
  "packages/fixtures/xml/generic-match-processing-reference.xml",
122
130
  "packages/fixtures/xml/template-processing-reference.xml",
123
131
  "packages/fixtures/xml/advanced-processing-reference.xml",
124
132
  ]),
125
- processingEntry("qti-null", "packages/core/src/core.test.ts"),
126
- processingEntry("qti-match-table", "packages/core/src/core.test.ts"),
127
- processingEntry("qti-match-table-entry", "packages/core/src/core.test.ts"),
128
- processingEntry("qti-interpolation-table", "packages/core/src/core.test.ts"),
129
- processingEntry("qti-interpolation-table-entry", "packages/core/src/core.test.ts"),
130
- processingEntry("qti-is-null", "packages/core/src/core.test.ts"),
131
- processingEntry("qti-match", "packages/core/src/core.test.ts", [
133
+ operatorProcessingEntry("qti-null"),
134
+ mappingProcessingEntry("qti-match-table"),
135
+ mappingProcessingEntry("qti-match-table-entry"),
136
+ mappingProcessingEntry("qti-interpolation-table"),
137
+ mappingProcessingEntry("qti-interpolation-table-entry"),
138
+ operatorProcessingEntry("qti-is-null"),
139
+ mappingProcessingEntry("qti-match", [
132
140
  "packages/fixtures/xml/generic-match-processing-reference.xml",
133
141
  "packages/fixtures/xml/advanced-processing-reference.xml",
134
142
  ]),
135
- processingEntry("qti-correct", "packages/core/src/core.test.ts"),
136
- processingEntry("qti-default", "packages/core/src/core.test.ts"),
137
- processingEntry("qti-map-response", "packages/core/src/core.test.ts"),
138
- processingEntry("qti-map-response-point", "packages/core/src/core.test.ts"),
139
- processingEntry("qti-variable", "packages/core/src/core.test.ts", [
143
+ mappingProcessingEntry("qti-correct"),
144
+ mappingProcessingEntry("qti-default"),
145
+ mappingProcessingEntry("qti-map-response"),
146
+ mappingProcessingEntry("qti-map-response-point"),
147
+ responseProcessingEntry("qti-variable", [
140
148
  "packages/fixtures/xml/generic-match-processing-reference.xml",
141
149
  "packages/fixtures/xml/template-processing-reference.xml",
142
150
  "packages/fixtures/xml/random-integer-template-reference.xml",
@@ -144,81 +152,74 @@ export const processingSupport: QtiProcessingElementSupport[] = [
144
152
  "packages/fixtures/xml/advanced-processing-reference.xml",
145
153
  "packages/fixtures/xml/adaptive-feedback-reference.xml",
146
154
  ]),
147
- processingEntry(
155
+ templateProcessingEntry(
148
156
  "qti-random-integer",
149
- "packages/core/src/core.test.ts",
150
157
  ["packages/fixtures/xml/random-integer-template-reference.xml"],
151
158
  [...processingBrowserEvidence["qti-random-integer"]],
152
159
  ),
153
- processingEntry("qti-random-float", "packages/core/src/core.test.ts"),
154
- processingEntry("qti-random", "packages/core/src/core.test.ts"),
155
- processingEntry("qti-multiple", "packages/core/src/core.test.ts", [
160
+ operatorProcessingEntry("qti-random-float"),
161
+ operatorProcessingEntry("qti-random"),
162
+ operatorProcessingEntry("qti-multiple", [
156
163
  "packages/fixtures/xml/advanced-processing-reference.xml",
157
164
  ]),
158
- processingEntry("qti-ordered", "packages/core/src/core.test.ts", [
165
+ operatorProcessingEntry("qti-ordered", [
159
166
  "packages/fixtures/xml/advanced-processing-reference.xml",
160
167
  ]),
161
- processingEntry("qti-index", "packages/core/src/core.test.ts"),
162
- processingEntry("qti-contains", "packages/core/src/core.test.ts"),
163
- processingEntry("qti-container-size", "packages/core/src/core.test.ts", [
168
+ operatorProcessingEntry("qti-index"),
169
+ operatorProcessingEntry("qti-contains"),
170
+ operatorProcessingEntry("qti-container-size", [
164
171
  "packages/fixtures/xml/advanced-processing-reference.xml",
165
172
  ]),
166
- processingEntry("qti-sum", "packages/core/src/core.test.ts", [
173
+ operatorProcessingEntry("qti-sum", [
167
174
  "packages/fixtures/xml/template-processing-reference.xml",
168
175
  "packages/fixtures/xml/random-integer-template-reference.xml",
169
176
  ]),
170
- processingEntry("qti-product", "packages/core/src/core.test.ts", [
177
+ operatorProcessingEntry("qti-product", [
171
178
  "packages/fixtures/xml/random-integer-template-reference.xml",
172
179
  ]),
173
- processingEntry("qti-min", "packages/core/src/core.test.ts"),
174
- processingEntry("qti-max", "packages/core/src/core.test.ts"),
175
- processingEntry("qti-subtract", "packages/core/src/core.test.ts"),
176
- processingEntry("qti-divide", "packages/core/src/core.test.ts"),
177
- processingEntry("qti-power", "packages/core/src/core.test.ts"),
178
- processingEntry("qti-integer-divide", "packages/core/src/core.test.ts"),
179
- processingEntry("qti-integer-modulus", "packages/core/src/core.test.ts"),
180
- processingEntry("qti-round", "packages/core/src/core.test.ts"),
181
- processingEntry("qti-round-to", "packages/core/src/core.test.ts"),
182
- processingEntry("qti-truncate", "packages/core/src/core.test.ts"),
183
- processingEntry("qti-integer-to-float", "packages/core/src/core.test.ts"),
184
- processingEntry("qti-and", "packages/core/src/core.test.ts"),
185
- processingEntry("qti-any-n", "packages/core/src/core.test.ts"),
186
- processingEntry("qti-or", "packages/core/src/core.test.ts"),
187
- processingEntry("qti-not", "packages/core/src/core.test.ts"),
188
- processingEntry("qti-equal", "packages/core/src/core.test.ts"),
189
- processingEntry("qti-equal-rounded", "packages/core/src/core.test.ts", [
190
- "packages/fixtures/xml/advanced-processing-reference.xml",
191
- ]),
192
- processingEntry("qti-field-value", "packages/core/src/core.test.ts"),
193
- processingEntry("qti-lt", "packages/core/src/core.test.ts"),
194
- processingEntry("qti-lte", "packages/core/src/core.test.ts"),
195
- processingEntry("qti-gt", "packages/core/src/core.test.ts"),
196
- processingEntry("qti-gte", "packages/core/src/core.test.ts"),
197
- processingEntry("qti-string-match", "packages/core/src/core.test.ts"),
198
- processingEntry("qti-substring", "packages/core/src/core.test.ts"),
199
- processingEntry("qti-pattern-match", "packages/core/src/core.test.ts"),
200
- processingEntry("qti-member", "packages/core/src/core.test.ts"),
201
- processingEntry("qti-delete", "packages/core/src/core.test.ts"),
202
- processingEntry("qti-duration-gte", "packages/core/src/core.test.ts"),
203
- processingEntry("qti-duration-lt", "packages/core/src/core.test.ts"),
204
- processingEntry("qti-gcd", "packages/core/src/core.test.ts", [
205
- "packages/fixtures/xml/advanced-processing-reference.xml",
206
- ]),
207
- processingEntry("qti-inside", "packages/core/src/core.test.ts", [
180
+ operatorProcessingEntry("qti-min"),
181
+ operatorProcessingEntry("qti-max"),
182
+ operatorProcessingEntry("qti-subtract"),
183
+ operatorProcessingEntry("qti-divide"),
184
+ operatorProcessingEntry("qti-power"),
185
+ operatorProcessingEntry("qti-integer-divide"),
186
+ operatorProcessingEntry("qti-integer-modulus"),
187
+ operatorProcessingEntry("qti-round"),
188
+ operatorProcessingEntry("qti-round-to"),
189
+ operatorProcessingEntry("qti-truncate"),
190
+ operatorProcessingEntry("qti-integer-to-float"),
191
+ operatorProcessingEntry("qti-and"),
192
+ operatorProcessingEntry("qti-any-n"),
193
+ operatorProcessingEntry("qti-or"),
194
+ operatorProcessingEntry("qti-not"),
195
+ operatorProcessingEntry("qti-equal"),
196
+ operatorProcessingEntry("qti-equal-rounded", [
208
197
  "packages/fixtures/xml/advanced-processing-reference.xml",
209
198
  ]),
210
- processingEntry("qti-lcm", "packages/core/src/core.test.ts", [
199
+ mappingProcessingEntry("qti-field-value"),
200
+ operatorProcessingEntry("qti-lt"),
201
+ operatorProcessingEntry("qti-lte"),
202
+ operatorProcessingEntry("qti-gt"),
203
+ operatorProcessingEntry("qti-gte"),
204
+ operatorProcessingEntry("qti-string-match"),
205
+ operatorProcessingEntry("qti-substring"),
206
+ operatorProcessingEntry("qti-pattern-match"),
207
+ operatorProcessingEntry("qti-member"),
208
+ operatorProcessingEntry("qti-delete"),
209
+ operatorProcessingEntry("qti-duration-gte"),
210
+ operatorProcessingEntry("qti-duration-lt"),
211
+ operatorProcessingEntry("qti-gcd", ["packages/fixtures/xml/advanced-processing-reference.xml"]),
212
+ mappingProcessingEntry("qti-inside", ["packages/fixtures/xml/advanced-processing-reference.xml"]),
213
+ operatorProcessingEntry("qti-lcm", ["packages/fixtures/xml/advanced-processing-reference.xml"]),
214
+ operatorProcessingEntry("qti-math-constant"),
215
+ operatorProcessingEntry("qti-math-operator"),
216
+ operatorProcessingEntry("qti-repeat", [
211
217
  "packages/fixtures/xml/advanced-processing-reference.xml",
212
218
  ]),
213
- processingEntry("qti-math-constant", "packages/core/src/core.test.ts"),
214
- processingEntry("qti-math-operator", "packages/core/src/core.test.ts"),
215
- processingEntry("qti-repeat", "packages/core/src/core.test.ts", [
219
+ operatorProcessingEntry("qti-stats-operator", [
216
220
  "packages/fixtures/xml/advanced-processing-reference.xml",
217
221
  ]),
218
- processingEntry("qti-stats-operator", "packages/core/src/core.test.ts", [
219
- "packages/fixtures/xml/advanced-processing-reference.xml",
220
- ]),
221
- processingEntry("qti-custom-operator", "packages/core/src/core.test.ts"),
222
+ operatorProcessingEntry("qti-custom-operator"),
222
223
  ];
223
224
 
224
225
  export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
@@ -234,11 +235,7 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
234
235
  fixtures: [
235
236
  "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
236
237
  ],
237
- tests: [
238
- "packages/core/src/core.test.ts",
239
- "packages/core/src/parser-item-metadata.test.ts",
240
- ...browserHarnessTests,
241
- ],
238
+ tests: ["packages/core/src/parser-item-metadata.test.ts", ...browserHarnessTests],
242
239
  notes:
243
240
  "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.",
244
241
  },
@@ -255,7 +252,6 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
255
252
  "packages/fixtures/packages/basic-item-player/valid-item-only/items/tolerance-extra-features.xml",
256
253
  ],
257
254
  tests: [
258
- "packages/core/src/core.test.ts",
259
255
  "packages/core/src/parser-item-metadata.test.ts",
260
256
  "tests/browser/player-package.spec.ts",
261
257
  ...browserHarnessTests,
@@ -266,20 +262,20 @@ export const itemMetadataSupport: QtiItemMetadataElementSupport[] = [
266
262
  {
267
263
  qtiName: "qti-modal-feedback",
268
264
  category: "itemMetadata",
269
- support: "parsed",
265
+ support: "rendered",
270
266
  specReference: "QTI 3.0.1 ASI",
271
267
  parse: true,
272
268
  validate: true,
273
- render: false,
274
- process: false,
269
+ render: true,
270
+ process: true,
275
271
  fixtures: ["packages/fixtures/src/index.ts"],
276
272
  tests: [
277
- "packages/core/src/core.test.ts",
273
+ processingResponseTest,
278
274
  "packages/core/src/parser-item-metadata.test.ts",
279
275
  ...browserFeedbackTests,
280
276
  ],
281
277
  notes:
282
- "Parsed by parser-item-metadata.ts and validated by validateModalFeedback. Player renders outcome-gated modal feedback in player-feedback.spec.ts.",
278
+ "Parsed by parser-item-metadata.ts and validated by validateModalFeedback. Response processing supplies the outcome value used for visibility, and the player renders the matching feedback in player-feedback.spec.ts.",
283
279
  },
284
280
  {
285
281
  qtiName: "qti-companion-materials-info",
@@ -430,9 +426,17 @@ function pciEntry(): QtiInteractionElementSupport {
430
426
  };
431
427
  }
432
428
 
429
+ function processingEntryFor(coreTest: string) {
430
+ return (
431
+ qtiName: string,
432
+ fixtures: string[] = [],
433
+ extraTests: string[] = [],
434
+ ): QtiProcessingElementSupport => processingEntry(coreTest, qtiName, fixtures, extraTests);
435
+ }
436
+
433
437
  function processingEntry(
438
+ coreTest: string,
434
439
  qtiName: string,
435
- test: string,
436
440
  fixtures: string[] = [],
437
441
  extraTests: string[] = [],
438
442
  ): QtiProcessingElementSupport {
@@ -446,6 +450,6 @@ function processingEntry(
446
450
  render: false,
447
451
  process: true,
448
452
  fixtures,
449
- tests: [test, ...extraTests],
453
+ tests: [coreTest, ...extraTests],
450
454
  };
451
455
  }
package/src/types.ts CHANGED
@@ -687,7 +687,14 @@ export type QtiProcessingExpression = (
687
687
  | { type: "mathConstant"; name: string }
688
688
  | { type: "mathOperator"; name: string; expressions: QtiProcessingExpression[] }
689
689
  | { type: "repeat"; numberRepeats: string; expressions: QtiProcessingExpression[] }
690
- | { type: "statsOperator"; name: string; expression: QtiProcessingExpression }
690
+ | {
691
+ type: "statsOperator";
692
+ name: string;
693
+ /** First child retained for compatibility with manually constructed expressions. */
694
+ expression: QtiProcessingExpression;
695
+ /** Authored children, including invalid zero- or multi-child forms, when parsed from XML. */
696
+ expressions?: QtiProcessingExpression[] | undefined;
697
+ }
691
698
  | {
692
699
  type: "customOperator";
693
700
  definition?: string | undefined;
@@ -0,0 +1,162 @@
1
+ import type { QtiDiagnostic, QtiLookupTable } from "./types.js";
2
+ import {
3
+ hasValidShapeCoordinateCount,
4
+ isAreaShape,
5
+ isNumericCsv,
6
+ numericCsv,
7
+ } from "./validation-geometry.js";
8
+ import { isFiniteNumber, isInteger } from "./validation-primitives.js";
9
+
10
+ interface DeclarationEntryAttributes {
11
+ attributes: Record<string, string>;
12
+ source?: QtiDiagnostic["source"] | undefined;
13
+ }
14
+
15
+ /** Append diagnostics for raw qti-map-entry attributes. */
16
+ export function validateMapEntryAttributes(
17
+ declarationIdentifier: string,
18
+ entry: DeclarationEntryAttributes,
19
+ diagnostics: QtiDiagnostic[],
20
+ ): void {
21
+ if (!entry.attributes["map-key"]) {
22
+ diagnostics.push({
23
+ code: "mapEntry.mapKey.required",
24
+ severity: "error",
25
+ message: `Response declaration ${declarationIdentifier} map entry requires map-key.`,
26
+ path: entry.source?.path,
27
+ source: entry.source,
28
+ });
29
+ }
30
+
31
+ const mappedValue = entry.attributes["mapped-value"];
32
+ if (mappedValue === undefined) {
33
+ diagnostics.push({
34
+ code: "mapEntry.mappedValue.required",
35
+ severity: "error",
36
+ message: `Response declaration ${declarationIdentifier} map entry requires mapped-value.`,
37
+ path: entry.source?.path,
38
+ source: entry.source,
39
+ });
40
+ } else if (!isFiniteNumber(mappedValue)) {
41
+ diagnostics.push({
42
+ code: "mapEntry.mappedValue",
43
+ severity: "error",
44
+ message: `Response declaration ${declarationIdentifier} map entry requires numeric mapped-value.`,
45
+ path: entry.source?.path,
46
+ source: entry.source,
47
+ });
48
+ }
49
+ }
50
+
51
+ /** Append diagnostics for raw qti-area-map-entry attributes. */
52
+ export function validateAreaMapEntryAttributes(
53
+ declarationIdentifier: string,
54
+ entry: DeclarationEntryAttributes,
55
+ diagnostics: QtiDiagnostic[],
56
+ ): void {
57
+ const shape = entry.attributes.shape;
58
+ const coords = entry.attributes.coords;
59
+ const mappedValue = entry.attributes["mapped-value"];
60
+
61
+ if (!shape) {
62
+ diagnostics.push({
63
+ code: "areaMapEntry.shape.required",
64
+ severity: "error",
65
+ message: `Response declaration ${declarationIdentifier} area map entry requires shape.`,
66
+ path: entry.source?.path,
67
+ source: entry.source,
68
+ });
69
+ } else if (!isAreaShape(shape)) {
70
+ diagnostics.push({
71
+ code: "areaMapEntry.shape",
72
+ severity: "error",
73
+ message: `Response declaration ${declarationIdentifier} area map entry has unsupported shape ${shape}.`,
74
+ path: entry.source?.path,
75
+ source: entry.source,
76
+ });
77
+ }
78
+
79
+ if (!coords) {
80
+ diagnostics.push({
81
+ code: "areaMapEntry.coords.required",
82
+ severity: "error",
83
+ message: `Response declaration ${declarationIdentifier} area map entry requires coords.`,
84
+ path: entry.source?.path,
85
+ source: entry.source,
86
+ });
87
+ } else if (!isNumericCsv(coords)) {
88
+ diagnostics.push({
89
+ code: "areaMapEntry.coords",
90
+ severity: "error",
91
+ message: `Response declaration ${declarationIdentifier} area map entry requires comma-separated numeric coords.`,
92
+ path: entry.source?.path,
93
+ source: entry.source,
94
+ });
95
+ } else if (
96
+ shape &&
97
+ isAreaShape(shape) &&
98
+ !hasValidShapeCoordinateCount(shape, numericCsv(coords))
99
+ ) {
100
+ diagnostics.push({
101
+ code: "areaMapEntry.coords.shape",
102
+ severity: "error",
103
+ message: `Response declaration ${declarationIdentifier} area map entry shape ${shape} has invalid coords arity.`,
104
+ path: entry.source?.path,
105
+ source: entry.source,
106
+ });
107
+ }
108
+
109
+ if (mappedValue === undefined) {
110
+ diagnostics.push({
111
+ code: "areaMapEntry.mappedValue.required",
112
+ severity: "error",
113
+ message: `Response declaration ${declarationIdentifier} area map entry requires mapped-value.`,
114
+ path: entry.source?.path,
115
+ source: entry.source,
116
+ });
117
+ } else if (!isFiniteNumber(mappedValue)) {
118
+ diagnostics.push({
119
+ code: "areaMapEntry.mappedValue",
120
+ severity: "error",
121
+ message: `Response declaration ${declarationIdentifier} area map entry requires numeric mapped-value.`,
122
+ path: entry.source?.path,
123
+ source: entry.source,
124
+ });
125
+ }
126
+ }
127
+
128
+ /** Append diagnostics for raw lookup-table entry attributes. */
129
+ export function validateLookupTableEntryAttributes(
130
+ tableType: QtiLookupTable["type"],
131
+ entry: DeclarationEntryAttributes,
132
+ diagnostics: QtiDiagnostic[],
133
+ ): void {
134
+ const sourceValue = entry.attributes["source-value"];
135
+ if (sourceValue === undefined || !isFiniteNumber(sourceValue)) {
136
+ diagnostics.push({
137
+ code: "lookupTable.entry.sourceValue",
138
+ severity: "error",
139
+ message: "Lookup table entry requires numeric source-value.",
140
+ path: entry.source?.path,
141
+ source: entry.source,
142
+ });
143
+ }
144
+ if (entry.attributes["target-value"] === undefined) {
145
+ diagnostics.push({
146
+ code: "lookupTable.entry.targetValue",
147
+ severity: "error",
148
+ message: "Lookup table entry requires target-value.",
149
+ path: entry.source?.path,
150
+ source: entry.source,
151
+ });
152
+ }
153
+ if (tableType === "match" && sourceValue !== undefined && !isInteger(sourceValue)) {
154
+ diagnostics.push({
155
+ code: "lookupTable.match.sourceValue",
156
+ severity: "error",
157
+ message: "qti-match-table-entry source-value must be an integer.",
158
+ path: entry.source?.path,
159
+ source: entry.source,
160
+ });
161
+ }
162
+ }
@@ -9,6 +9,8 @@ import type {
9
9
  } from "./types.js";
10
10
  import { assertNever } from "./assert-never.js";
11
11
  import { isValidQtiPatternMask } from "./pattern-mask.js";
12
+ import { parseQtiMediaDefinition } from "./media-definition.js";
13
+ import { parseQtiSliderDefinition } from "./slider-definition.js";
12
14
  import { validateInteractionSharedVocabulary } from "./shared-vocabulary-interaction-validation.js";
13
15
  import { qtiValueToStringList } from "./value-format.js";
14
16
  import {
@@ -17,7 +19,6 @@ import {
17
19
  } from "./validation-geometry.js";
18
20
  import {
19
21
  isBooleanAttribute,
20
- isFiniteNumber,
21
22
  isNonNegativeInteger,
22
23
  requireIdentifier,
23
24
  } from "./validation-primitives.js";
@@ -104,7 +105,11 @@ function validateInteractionResponseShape(
104
105
  });
105
106
  }
106
107
 
107
- if (interaction.responseBaseType && !expected.baseTypes.includes(interaction.responseBaseType)) {
108
+ if (
109
+ interaction.type !== "slider" &&
110
+ interaction.responseBaseType &&
111
+ !expected.baseTypes.includes(interaction.responseBaseType)
112
+ ) {
108
113
  diagnostics.push({
109
114
  code: "interaction.baseType",
110
115
  severity: "error",
@@ -305,46 +310,13 @@ function validateInteractionRequiredAttributes(
305
310
  }
306
311
 
307
312
  if (interaction.type === "slider") {
308
- const lower = interaction.attributes["lower-bound"];
309
- const upper = interaction.attributes["upper-bound"];
310
- requireInteractionAttribute(
311
- interaction,
312
- "lower-bound",
313
- "interaction.slider.lowerBound",
314
- diagnostics,
315
- );
316
- requireInteractionAttribute(
317
- interaction,
318
- "upper-bound",
319
- "interaction.slider.upperBound",
320
- diagnostics,
321
- );
322
- if (lower !== undefined && !isFiniteNumber(lower)) {
323
- invalidNumber(interaction, "lower-bound", lower, diagnostics);
324
- }
325
- if (upper !== undefined && !isFiniteNumber(upper)) {
326
- invalidNumber(interaction, "upper-bound", upper, diagnostics);
327
- }
328
- if (
329
- lower !== undefined &&
330
- upper !== undefined &&
331
- isFiniteNumber(lower) &&
332
- isFiniteNumber(upper)
333
- ) {
334
- if (Number(lower) >= Number(upper)) {
335
- diagnostics.push({
336
- code: "interaction.slider.bounds",
337
- severity: "error",
338
- message: `${interaction.qtiName} requires lower-bound to be less than upper-bound.`,
339
- path: interaction.source?.path,
340
- source: interaction.source,
341
- });
342
- }
343
- }
344
- const step = interaction.attributes.step;
345
- if (step !== undefined && (!isFiniteNumber(step) || Number(step) <= 0)) {
346
- invalidNumber(interaction, "step", step, diagnostics);
347
- }
313
+ const definition = parseQtiSliderDefinition(interaction);
314
+ if (!definition.ok) diagnostics.push(...definition.diagnostics);
315
+ }
316
+
317
+ if (interaction.type === "media") {
318
+ const definition = parseQtiMediaDefinition(interaction);
319
+ if (!definition.ok) diagnostics.push(...definition.diagnostics);
348
320
  }
349
321
  }
350
322
 
@@ -497,21 +469,6 @@ function warnExternalPortableCustomUrl(
497
469
  });
498
470
  }
499
471
 
500
- function invalidNumber(
501
- interaction: QtiInteraction,
502
- attribute: string,
503
- value: string,
504
- diagnostics: QtiDiagnostic[],
505
- ): void {
506
- diagnostics.push({
507
- code: "interaction.numericAttribute",
508
- severity: "error",
509
- message: `${interaction.qtiName} requires numeric ${attribute}, got ${value}.`,
510
- path: interaction.source?.path,
511
- source: interaction.source,
512
- });
513
- }
514
-
515
472
  function validatePatternMaskAttribute(
516
473
  interaction: QtiInteraction,
517
474
  diagnostics: QtiDiagnostic[],
@@ -542,13 +499,6 @@ function validateInteractionLimitAttributes(
542
499
  validateMinMaxPair(interaction, "min-choices", "max-choices", diagnostics);
543
500
  validateMinMaxPair(interaction, "min-associations", "max-associations", diagnostics);
544
501
  validateBooleanAttribute(interaction, "required", diagnostics);
545
- if (interaction.type === "media") {
546
- validateNonNegativeIntegerAttribute(interaction, "max-plays", diagnostics);
547
- validateNonNegativeIntegerAttribute(interaction, "min-plays", diagnostics);
548
- validateBooleanAttribute(interaction, "autostart", diagnostics);
549
- validateBooleanAttribute(interaction, "loop", diagnostics);
550
- validateMinMaxPair(interaction, "min-plays", "max-plays", diagnostics);
551
- }
552
502
  }
553
503
 
554
504
  function validateChoiceLimitAttributes(choice: QtiChoice, diagnostics: QtiDiagnostic[]): void {
@@ -1,5 +1,5 @@
1
1
  import type { QtiBaseType, QtiCardinality, QtiDiagnostic } from "./types.js";
2
- import { parseXmlBoolean } from "./parser-values.js";
2
+ import { parseFiniteNumber, parseXmlBoolean } from "./parser-values.js";
3
3
 
4
4
  export function requireIdentifier(
5
5
  qtiName: string,
@@ -38,7 +38,7 @@ export function isBaseType(value: string): value is QtiBaseType {
38
38
  }
39
39
 
40
40
  export function isFiniteNumber(value: string): boolean {
41
- return Number.isFinite(Number(value));
41
+ return parseFiniteNumber(value) !== undefined;
42
42
  }
43
43
 
44
44
  export function isInteger(value: string): boolean {