@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.
Files changed (330) hide show
  1. package/README.md +108 -11
  2. package/dist/adaptive-turn-materializer.d.ts +2 -0
  3. package/dist/adaptive-turn-materializer.d.ts.map +1 -1
  4. package/dist/adaptive-turn-materializer.js +25 -19
  5. package/dist/adaptive-turn-materializer.js.map +1 -1
  6. package/dist/adaptive-turn.d.ts +8 -1
  7. package/dist/adaptive-turn.d.ts.map +1 -1
  8. package/dist/adaptive-turn.js +15 -5
  9. package/dist/adaptive-turn.js.map +1 -1
  10. package/dist/assert-never.d.ts +3 -0
  11. package/dist/assert-never.d.ts.map +1 -0
  12. package/dist/assert-never.js +5 -0
  13. package/dist/assert-never.js.map +1 -0
  14. package/dist/attempt-state-constants.d.ts +6 -0
  15. package/dist/attempt-state-constants.d.ts.map +1 -0
  16. package/dist/attempt-state-constants.js +6 -0
  17. package/dist/attempt-state-constants.js.map +1 -0
  18. package/dist/attempt-state.d.ts +5 -0
  19. package/dist/attempt-state.d.ts.map +1 -0
  20. package/dist/attempt-state.js +190 -0
  21. package/dist/attempt-state.js.map +1 -0
  22. package/dist/catalog.js +1 -1
  23. package/dist/catalog.js.map +1 -1
  24. package/dist/content-text.d.ts.map +1 -1
  25. package/dist/content-text.js +3 -0
  26. package/dist/content-text.js.map +1 -1
  27. package/dist/custom-operators.d.ts +13 -0
  28. package/dist/custom-operators.d.ts.map +1 -0
  29. package/dist/custom-operators.js +2 -0
  30. package/dist/custom-operators.js.map +1 -0
  31. package/dist/delivery-preparation.d.ts +20 -0
  32. package/dist/delivery-preparation.d.ts.map +1 -0
  33. package/dist/delivery-preparation.js +71 -0
  34. package/dist/delivery-preparation.js.map +1 -0
  35. package/dist/delivery-redaction.d.ts +5 -0
  36. package/dist/delivery-redaction.d.ts.map +1 -1
  37. package/dist/delivery-redaction.js +27 -6
  38. package/dist/delivery-redaction.js.map +1 -1
  39. package/dist/delivery-security.js +2 -2
  40. package/dist/delivery-security.js.map +1 -1
  41. package/dist/index.d.ts +8 -0
  42. package/dist/index.d.ts.map +1 -1
  43. package/dist/index.js +8 -0
  44. package/dist/index.js.map +1 -1
  45. package/dist/item-scoring-expectation.d.ts +4 -0
  46. package/dist/item-scoring-expectation.d.ts.map +1 -0
  47. package/dist/item-scoring-expectation.js +6 -0
  48. package/dist/item-scoring-expectation.js.map +1 -0
  49. package/dist/item-submission-materialization.d.ts +25 -0
  50. package/dist/item-submission-materialization.d.ts.map +1 -0
  51. package/dist/item-submission-materialization.js +68 -0
  52. package/dist/item-submission-materialization.js.map +1 -0
  53. package/dist/parser-custom-interactions.js +1 -1
  54. package/dist/parser-custom-interactions.js.map +1 -1
  55. package/dist/parser-declarations.d.ts.map +1 -1
  56. package/dist/parser-declarations.js +4 -4
  57. package/dist/parser-declarations.js.map +1 -1
  58. package/dist/parser-item-metadata.d.ts.map +1 -1
  59. package/dist/parser-item-metadata.js +8 -7
  60. package/dist/parser-item-metadata.js.map +1 -1
  61. package/dist/parser-processing.js +2 -2
  62. package/dist/parser-processing.js.map +1 -1
  63. package/dist/parser-values.d.ts +5 -1
  64. package/dist/parser-values.d.ts.map +1 -1
  65. package/dist/parser-values.js +33 -0
  66. package/dist/parser-values.js.map +1 -1
  67. package/dist/processing-evaluator-boolean.d.ts +8 -0
  68. package/dist/processing-evaluator-boolean.d.ts.map +1 -0
  69. package/dist/processing-evaluator-boolean.js +47 -0
  70. package/dist/processing-evaluator-boolean.js.map +1 -0
  71. package/dist/processing-evaluator-collection.d.ts +12 -0
  72. package/dist/processing-evaluator-collection.d.ts.map +1 -0
  73. package/dist/processing-evaluator-collection.js +35 -0
  74. package/dist/processing-evaluator-collection.js.map +1 -0
  75. package/dist/processing-evaluator-comparison.d.ts +8 -0
  76. package/dist/processing-evaluator-comparison.d.ts.map +1 -0
  77. package/dist/processing-evaluator-comparison.js +88 -0
  78. package/dist/processing-evaluator-comparison.js.map +1 -0
  79. package/dist/processing-evaluator-custom.d.ts +8 -0
  80. package/dist/processing-evaluator-custom.d.ts.map +1 -0
  81. package/dist/processing-evaluator-custom.js +14 -0
  82. package/dist/processing-evaluator-custom.js.map +1 -0
  83. package/dist/processing-evaluator-geometry.d.ts +8 -0
  84. package/dist/processing-evaluator-geometry.d.ts.map +1 -0
  85. package/dist/processing-evaluator-geometry.js +21 -0
  86. package/dist/processing-evaluator-geometry.js.map +1 -0
  87. package/dist/processing-evaluator-numeric.d.ts +8 -0
  88. package/dist/processing-evaluator-numeric.d.ts.map +1 -0
  89. package/dist/processing-evaluator-numeric.js +112 -0
  90. package/dist/processing-evaluator-numeric.js.map +1 -0
  91. package/dist/processing-evaluator-string.d.ts +8 -0
  92. package/dist/processing-evaluator-string.d.ts.map +1 -0
  93. package/dist/processing-evaluator-string.js +33 -0
  94. package/dist/processing-evaluator-string.js.map +1 -0
  95. package/dist/processing-evaluator-variable.d.ts +8 -0
  96. package/dist/processing-evaluator-variable.d.ts.map +1 -0
  97. package/dist/processing-evaluator-variable.js +39 -0
  98. package/dist/processing-evaluator-variable.js.map +1 -0
  99. package/dist/processing-evaluator.d.ts +22 -0
  100. package/dist/processing-evaluator.d.ts.map +1 -0
  101. package/dist/processing-evaluator.js +137 -0
  102. package/dist/processing-evaluator.js.map +1 -0
  103. package/dist/processing-expression-children.d.ts +3 -0
  104. package/dist/processing-expression-children.d.ts.map +1 -0
  105. package/dist/processing-expression-children.js +68 -0
  106. package/dist/processing-expression-children.js.map +1 -0
  107. package/dist/processing-mapping.d.ts +13 -0
  108. package/dist/processing-mapping.d.ts.map +1 -0
  109. package/dist/processing-mapping.js +120 -0
  110. package/dist/processing-mapping.js.map +1 -0
  111. package/dist/processing-operators.d.ts +11 -0
  112. package/dist/processing-operators.d.ts.map +1 -0
  113. package/dist/processing-operators.js +178 -0
  114. package/dist/processing-operators.js.map +1 -0
  115. package/dist/processing-random.d.ts +2 -0
  116. package/dist/processing-random.d.ts.map +1 -0
  117. package/dist/processing-random.js +16 -0
  118. package/dist/processing-random.js.map +1 -0
  119. package/dist/processing-state.d.ts +8 -0
  120. package/dist/processing-state.d.ts.map +1 -0
  121. package/dist/processing-state.js +44 -0
  122. package/dist/processing-state.js.map +1 -0
  123. package/dist/processing-templates.d.ts +3 -0
  124. package/dist/processing-templates.d.ts.map +1 -0
  125. package/dist/processing-templates.js +12 -0
  126. package/dist/processing-templates.js.map +1 -0
  127. package/dist/processing-values.d.ts +15 -0
  128. package/dist/processing-values.d.ts.map +1 -0
  129. package/dist/processing-values.js +150 -0
  130. package/dist/processing-values.js.map +1 -0
  131. package/dist/processing-variables.d.ts +7 -0
  132. package/dist/processing-variables.d.ts.map +1 -0
  133. package/dist/processing-variables.js +40 -0
  134. package/dist/processing-variables.js.map +1 -0
  135. package/dist/qti-package-assets.d.ts +8 -0
  136. package/dist/qti-package-assets.d.ts.map +1 -0
  137. package/dist/qti-package-assets.js +173 -0
  138. package/dist/qti-package-assets.js.map +1 -0
  139. package/dist/qti-package-items.d.ts +14 -0
  140. package/dist/qti-package-items.d.ts.map +1 -0
  141. package/dist/qti-package-items.js +138 -0
  142. package/dist/qti-package-items.js.map +1 -0
  143. package/dist/qti-package-manifest.d.ts +13 -0
  144. package/dist/qti-package-manifest.d.ts.map +1 -0
  145. package/dist/qti-package-manifest.js +109 -0
  146. package/dist/qti-package-manifest.js.map +1 -0
  147. package/dist/qti-package-metadata.d.ts +8 -0
  148. package/dist/qti-package-metadata.d.ts.map +1 -0
  149. package/dist/qti-package-metadata.js +115 -0
  150. package/dist/qti-package-metadata.js.map +1 -0
  151. package/dist/qti-package-paths.d.ts +6 -0
  152. package/dist/qti-package-paths.d.ts.map +1 -0
  153. package/dist/qti-package-paths.js +32 -0
  154. package/dist/qti-package-paths.js.map +1 -0
  155. package/dist/qti-package-types.d.ts +98 -0
  156. package/dist/qti-package-types.d.ts.map +1 -0
  157. package/dist/qti-package-types.js +2 -0
  158. package/dist/qti-package-types.js.map +1 -0
  159. package/dist/qti-package-xml.d.ts +21 -0
  160. package/dist/qti-package-xml.d.ts.map +1 -0
  161. package/dist/qti-package-xml.js +68 -0
  162. package/dist/qti-package-xml.js.map +1 -0
  163. package/dist/qti-package-zip.d.ts +24 -0
  164. package/dist/qti-package-zip.d.ts.map +1 -0
  165. package/dist/qti-package-zip.js +185 -0
  166. package/dist/qti-package-zip.js.map +1 -0
  167. package/dist/qti-package.d.ts +7 -0
  168. package/dist/qti-package.d.ts.map +1 -0
  169. package/dist/qti-package.js +76 -0
  170. package/dist/qti-package.js.map +1 -0
  171. package/dist/response-input.d.ts +9 -0
  172. package/dist/response-input.d.ts.map +1 -0
  173. package/dist/response-input.js +9 -0
  174. package/dist/response-input.js.map +1 -0
  175. package/dist/response-validation-policy.d.ts +31 -0
  176. package/dist/response-validation-policy.d.ts.map +1 -0
  177. package/dist/response-validation-policy.js +158 -0
  178. package/dist/response-validation-policy.js.map +1 -0
  179. package/dist/response-validation.d.ts +23 -0
  180. package/dist/response-validation.d.ts.map +1 -0
  181. package/dist/response-validation.js +157 -0
  182. package/dist/response-validation.js.map +1 -0
  183. package/dist/scoring-disposition-policy.d.ts +8 -0
  184. package/dist/scoring-disposition-policy.d.ts.map +1 -0
  185. package/dist/scoring-disposition-policy.js +24 -0
  186. package/dist/scoring-disposition-policy.js.map +1 -0
  187. package/dist/serializer-processing-expressions.d.ts.map +1 -1
  188. package/dist/serializer-processing-expressions.js +2 -8
  189. package/dist/serializer-processing-expressions.js.map +1 -1
  190. package/dist/serializer-processing-xml.js +2 -2
  191. package/dist/serializer-processing-xml.js.map +1 -1
  192. package/dist/serializer-processing.d.ts.map +1 -1
  193. package/dist/serializer-processing.js +3 -0
  194. package/dist/serializer-processing.js.map +1 -1
  195. package/dist/session.d.ts +5 -14
  196. package/dist/session.d.ts.map +1 -1
  197. package/dist/session.js +76 -1158
  198. package/dist/session.js.map +1 -1
  199. package/dist/shared-vocabulary-coverage-policy.js +1 -1
  200. package/dist/shared-vocabulary-coverage-policy.js.map +1 -1
  201. package/dist/shared-vocabulary-generated-families.js +1 -1
  202. package/dist/shared-vocabulary-generated-families.js.map +1 -1
  203. package/dist/shared-vocabulary-support.js +3 -3
  204. package/dist/shared-vocabulary-support.js.map +1 -1
  205. package/dist/shared-vocabulary.d.ts.map +1 -1
  206. package/dist/shared-vocabulary.js +4 -3
  207. package/dist/shared-vocabulary.js.map +1 -1
  208. package/dist/support-evidence.d.ts +22 -0
  209. package/dist/support-evidence.d.ts.map +1 -0
  210. package/dist/support-evidence.js +93 -0
  211. package/dist/support-evidence.js.map +1 -0
  212. package/dist/support.d.ts.map +1 -1
  213. package/dist/support.js +30 -43
  214. package/dist/support.js.map +1 -1
  215. package/dist/template-presentation-materializer.d.ts +14 -0
  216. package/dist/template-presentation-materializer.d.ts.map +1 -0
  217. package/dist/template-presentation-materializer.js +143 -0
  218. package/dist/template-presentation-materializer.js.map +1 -0
  219. package/dist/trusted-item-session.d.ts +25 -8
  220. package/dist/trusted-item-session.d.ts.map +1 -1
  221. package/dist/trusted-item-session.js +57 -51
  222. package/dist/trusted-item-session.js.map +1 -1
  223. package/dist/trusted-session-materialization.d.ts +33 -0
  224. package/dist/trusted-session-materialization.d.ts.map +1 -0
  225. package/dist/trusted-session-materialization.js +26 -0
  226. package/dist/trusted-session-materialization.js.map +1 -0
  227. package/dist/tts.d.ts.map +1 -1
  228. package/dist/tts.js +16 -2
  229. package/dist/tts.js.map +1 -1
  230. package/dist/validation-geometry.d.ts +8 -0
  231. package/dist/validation-geometry.d.ts.map +1 -0
  232. package/dist/validation-geometry.js +187 -0
  233. package/dist/validation-geometry.js.map +1 -0
  234. package/dist/validation-interactions.d.ts +3 -0
  235. package/dist/validation-interactions.d.ts.map +1 -0
  236. package/dist/validation-interactions.js +644 -0
  237. package/dist/validation-interactions.js.map +1 -0
  238. package/dist/validation-primitives.d.ts +12 -0
  239. package/dist/validation-primitives.d.ts.map +1 -0
  240. package/dist/validation-primitives.js +53 -0
  241. package/dist/validation-primitives.js.map +1 -0
  242. package/dist/validation-processing.d.ts +4 -0
  243. package/dist/validation-processing.d.ts.map +1 -0
  244. package/dist/validation-processing.js +569 -0
  245. package/dist/validation-processing.js.map +1 -0
  246. package/dist/validation-shared-vocabulary-layout.d.ts +10 -0
  247. package/dist/validation-shared-vocabulary-layout.d.ts.map +1 -0
  248. package/dist/validation-shared-vocabulary-layout.js +102 -0
  249. package/dist/validation-shared-vocabulary-layout.js.map +1 -0
  250. package/dist/validation.d.ts.map +1 -1
  251. package/dist/validation.js +27 -1641
  252. package/dist/validation.js.map +1 -1
  253. package/dist/variable-snapshot.d.ts +9 -0
  254. package/dist/variable-snapshot.d.ts.map +1 -0
  255. package/dist/variable-snapshot.js +9 -0
  256. package/dist/variable-snapshot.js.map +1 -0
  257. package/dist/xml.js +1 -1
  258. package/dist/xml.js.map +1 -1
  259. package/package.json +1 -1
  260. package/src/adaptive-turn-materializer.ts +29 -16
  261. package/src/adaptive-turn.ts +29 -6
  262. package/src/assert-never.ts +4 -0
  263. package/src/attempt-state-constants.ts +5 -0
  264. package/src/attempt-state.ts +245 -0
  265. package/src/catalog.ts +1 -1
  266. package/src/content-text.ts +3 -0
  267. package/src/custom-operators.ts +12 -0
  268. package/src/delivery-preparation.ts +121 -0
  269. package/src/delivery-redaction.ts +37 -6
  270. package/src/delivery-security.ts +2 -2
  271. package/src/index.ts +67 -0
  272. package/src/item-scoring-expectation.ts +9 -0
  273. package/src/item-submission-materialization.ts +120 -0
  274. package/src/parser-custom-interactions.ts +1 -1
  275. package/src/parser-declarations.ts +4 -3
  276. package/src/parser-item-metadata.ts +8 -7
  277. package/src/parser-processing.ts +2 -2
  278. package/src/parser-values.ts +36 -0
  279. package/src/processing-evaluator-boolean.ts +48 -0
  280. package/src/processing-evaluator-collection.ts +50 -0
  281. package/src/processing-evaluator-comparison.ts +106 -0
  282. package/src/processing-evaluator-custom.ts +20 -0
  283. package/src/processing-evaluator-geometry.ts +26 -0
  284. package/src/processing-evaluator-numeric.ts +155 -0
  285. package/src/processing-evaluator-string.ts +60 -0
  286. package/src/processing-evaluator-variable.ts +81 -0
  287. package/src/processing-evaluator.ts +173 -0
  288. package/src/processing-expression-children.ts +69 -0
  289. package/src/processing-mapping.ts +153 -0
  290. package/src/processing-operators.ts +184 -0
  291. package/src/processing-random.ts +16 -0
  292. package/src/processing-state.ts +69 -0
  293. package/src/processing-templates.ts +12 -0
  294. package/src/processing-values.ts +162 -0
  295. package/src/processing-variables.ts +83 -0
  296. package/src/qti-package-assets.ts +256 -0
  297. package/src/qti-package-items.ts +240 -0
  298. package/src/qti-package-manifest.ts +210 -0
  299. package/src/qti-package-metadata.ts +143 -0
  300. package/src/qti-package-paths.ts +56 -0
  301. package/src/qti-package-types.ts +117 -0
  302. package/src/qti-package-xml.ts +97 -0
  303. package/src/qti-package-zip.ts +325 -0
  304. package/src/qti-package.ts +176 -0
  305. package/src/response-input.ts +19 -0
  306. package/src/response-validation-policy.ts +218 -0
  307. package/src/response-validation.ts +282 -0
  308. package/src/scoring-disposition-policy.ts +37 -0
  309. package/src/serializer-processing-expressions.ts +2 -12
  310. package/src/serializer-processing-xml.ts +2 -2
  311. package/src/serializer-processing.ts +3 -0
  312. package/src/session.ts +128 -2006
  313. package/src/shared-vocabulary-coverage-policy.ts +1 -1
  314. package/src/shared-vocabulary-generated-families.ts +1 -1
  315. package/src/shared-vocabulary-support.ts +3 -3
  316. package/src/shared-vocabulary.ts +3 -2
  317. package/src/support-evidence.ts +107 -0
  318. package/src/support.ts +59 -50
  319. package/src/template-presentation-materializer.ts +219 -0
  320. package/src/trusted-item-session.ts +86 -78
  321. package/src/trusted-session-materialization.ts +67 -0
  322. package/src/tts.ts +29 -15
  323. package/src/validation-geometry.ts +204 -0
  324. package/src/validation-interactions.ts +831 -0
  325. package/src/validation-primitives.ts +68 -0
  326. package/src/validation-processing.ts +785 -0
  327. package/src/validation-shared-vocabulary-layout.ts +117 -0
  328. package/src/validation.ts +44 -2077
  329. package/src/variable-snapshot.ts +20 -0
  330. 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()].sort();
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
- .sort((left, right) => right.length - left.length)
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 {
@@ -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
- if (tokenSet.has(className)) return className as T;
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("qti-template-processing", "packages/core/src/core.test.ts", [
55
- "packages/fixtures/xml/template-processing-reference.xml",
56
- "packages/fixtures/xml/template-content-reference.xml",
57
- ]),
58
- processingEntry("qti-response-processing", "packages/core/src/core.test.ts", [
59
- "packages/fixtures/xml/mapping-processing-reference.xml",
60
- "packages/fixtures/xml/generic-match-processing-reference.xml",
61
- "packages/fixtures/xml/advanced-processing-reference.xml",
62
- "packages/fixtures/xml/adaptive-feedback-reference.xml",
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("qti-random-integer", "packages/core/src/core.test.ts"),
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: ["packages/core/src/core.test.ts", "packages/core/src/parser-item-metadata.test.ts"],
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: ["packages/core/src/core.test.ts", "packages/core/src/parser-item-metadata.test.ts"],
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 rendering remains out of scope for this metadata slice.",
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: [`packages/fixtures/xml/${interactionType}-reference.xml`],
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
+ }