@conform-ed/contracts 0.0.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 (187) hide show
  1. package/.turbo/turbo-build.log +2 -0
  2. package/.turbo/turbo-format.log +6 -0
  3. package/.turbo/turbo-lint.log +4 -0
  4. package/.turbo/turbo-test.log +196 -0
  5. package/.turbo/turbo-typecheck.log +2 -0
  6. package/README.md +52 -0
  7. package/caliper-v1_2-zod-templates.md +123 -0
  8. package/case-v1_1-zod-templates.md +290 -0
  9. package/cat-v1_0-zod-templates.md +73 -0
  10. package/cc-v1_3-zod-templates.md +531 -0
  11. package/cc-v1_4-zod-templates.md +247 -0
  12. package/clr-v2_0-zod-templates.md +117 -0
  13. package/cmi5-v1_0-zod-templates.md +9 -0
  14. package/lti-zod-templates.md +11 -0
  15. package/oneroster-v1_2-zod-templates.md +76 -0
  16. package/open-badges-v3_0-zod-templates.md +66 -0
  17. package/package.json +42 -0
  18. package/qti-v2_1-zod-templates.md +45 -0
  19. package/qti-v2_2-zod-templates.md +32 -0
  20. package/qti-v3_0_1-zod-templates.md +421 -0
  21. package/src/adapter.ts +83 -0
  22. package/src/caliper/v1_2/caliper_v1p2_bootcamp_schema.ts +781 -0
  23. package/src/caliper/v1_2/index.ts +37 -0
  24. package/src/caliper/v1_2/shared.ts +122 -0
  25. package/src/caliper/v1_2/textual_requirements.ts +219 -0
  26. package/src/case/v1_1/case_v1p1_cfassociation_jsonschema1.ts +1 -0
  27. package/src/case/v1_1/case_v1p1_cfassociationgrouping_jsonschema1.ts +1 -0
  28. package/src/case/v1_1/case_v1p1_cfassociationset_jsonschema1.ts +1 -0
  29. package/src/case/v1_1/case_v1p1_cfconceptset_jsonschema1.ts +1 -0
  30. package/src/case/v1_1/case_v1p1_cfdocument_jsonschema1.ts +1 -0
  31. package/src/case/v1_1/case_v1p1_cfdocumentset_jsonschema1.ts +1 -0
  32. package/src/case/v1_1/case_v1p1_cfitem_jsonschema1.ts +1 -0
  33. package/src/case/v1_1/case_v1p1_cfitemtypeset_jsonschema1.ts +1 -0
  34. package/src/case/v1_1/case_v1p1_cflicense_jsonschema1.ts +1 -0
  35. package/src/case/v1_1/case_v1p1_cfpackage_jsonschema1.ts +1 -0
  36. package/src/case/v1_1/case_v1p1_cfrubric_jsonschema1.ts +1 -0
  37. package/src/case/v1_1/case_v1p1_cfsubjectset_jsonschema1.ts +1 -0
  38. package/src/case/v1_1/case_v1p1_imsx_statusinfo_jsonschema1.ts +1 -0
  39. package/src/case/v1_1/case_v1p1_openapi3_restbinding_schema.ts +113 -0
  40. package/src/case/v1_1/index.ts +95 -0
  41. package/src/case/v1_1/shared.ts +384 -0
  42. package/src/cat/v1_0/cat_v1p0_restbinding_operations_schema.ts +75 -0
  43. package/src/cat/v1_0/index.ts +124 -0
  44. package/src/cat/v1_0/shared.ts +345 -0
  45. package/src/clr/v2_0/clr_v2p0_achievementcredential_schema.ts +16 -0
  46. package/src/clr/v2_0/clr_v2p0_clrcredential_schema.ts +9 -0
  47. package/src/clr/v2_0/clr_v2p0_endorsementcredential_schema.ts +11 -0
  48. package/src/clr/v2_0/clr_v2p0_getclrcredentialsresponse_schema.ts +1 -0
  49. package/src/clr/v2_0/clr_v2p0_imsx_statusinfo_schema.ts +8 -0
  50. package/src/clr/v2_0/clr_v2p0_profile_schema.ts +9 -0
  51. package/src/clr/v2_0/index.ts +22 -0
  52. package/src/clr/v2_0/shared.ts +206 -0
  53. package/src/cmi5/index.ts +3 -0
  54. package/src/cmi5/v1_0/index.ts +134 -0
  55. package/src/common-cartridge/v1_3/ccv1p3_imsccauth_v1p3.ts +26 -0
  56. package/src/common-cartridge/v1_3/ccv1p3_imscp_v1p2_v1p0.ts +352 -0
  57. package/src/common-cartridge/v1_3/ccv1p3_imscsmd_v1p0.ts +35 -0
  58. package/src/common-cartridge/v1_3/ccv1p3_imsdt_v1p3.ts +33 -0
  59. package/src/common-cartridge/v1_3/ccv1p3_imswl_v1p3.ts +23 -0
  60. package/src/common-cartridge/v1_3/ccv1p3_lomccltilink_v1p0.ts +14 -0
  61. package/src/common-cartridge/v1_3/ccv1p3_lommanifest_v1p0.ts +14 -0
  62. package/src/common-cartridge/v1_3/ccv1p3_lomresource_v1p0.ts +14 -0
  63. package/src/common-cartridge/v1_3/ccv1p3_qtiasiv1p2p1_v1p0.ts +964 -0
  64. package/src/common-cartridge/v1_3/index.ts +41 -0
  65. package/src/common-cartridge/v1_3/lom-internal.ts +396 -0
  66. package/src/common-cartridge/v1_3/shared.ts +68 -0
  67. package/src/common-cartridge/v1_4/core/ccv1p4_imscp_v1p2_v1p0.ts +360 -0
  68. package/src/common-cartridge/v1_4/core/ccv1p4_lommanifest_v1p0.ts +14 -0
  69. package/src/common-cartridge/v1_4/core/ccv1p4_lomresource_v1p0.ts +14 -0
  70. package/src/common-cartridge/v1_4/extension/cc_extresource_assignmentv1p0_v1p0.ts +61 -0
  71. package/src/common-cartridge/v1_4/extension/ccv1p4_cpextensionv1p2_v1p0.ts +20 -0
  72. package/src/common-cartridge/v1_4/extension/ims_openvideov1p0_v1p0.ts +325 -0
  73. package/src/common-cartridge/v1_4/index.ts +104 -0
  74. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0.ts +19 -0
  75. package/src/common-cartridge/v1_4/k12/ccv1p4_imscp_v1p2_v1p0_thin.ts +17 -0
  76. package/src/common-cartridge/v1_4/k12/ccv1p4_lommanifest_v1p0.ts +14 -0
  77. package/src/common-cartridge/v1_4/k12/ccv1p4_lomresource_v1p0.ts +14 -0
  78. package/src/common-cartridge/v1_4/lom-internal.ts +476 -0
  79. package/src/common-cartridge/v1_4/shared/ccv1p4_imsccauth_v1p4.ts +26 -0
  80. package/src/common-cartridge/v1_4/shared/ccv1p4_imscsmd_v1p1.ts +36 -0
  81. package/src/common-cartridge/v1_4/shared/ccv1p4_imsdt_v1p4.ts +33 -0
  82. package/src/common-cartridge/v1_4/shared/ccv1p4_imslticc_v1p4.ts +45 -0
  83. package/src/common-cartridge/v1_4/shared/ccv1p4_imswl_v1p4.ts +23 -0
  84. package/src/common-cartridge/v1_4/shared/ccv1p4_lomccltilink_v1p0.ts +14 -0
  85. package/src/common-cartridge/v1_4/shared/ccv1p4_qtiasiv1p2p1_v1p0.ts +964 -0
  86. package/src/common-cartridge/v1_4/shared/imsbasiclti_v1p0p1.ts +24 -0
  87. package/src/common-cartridge/v1_4/shared/imslticm_v1p0.ts +23 -0
  88. package/src/common-cartridge/v1_4/shared/imslticp_v1p0.ts +57 -0
  89. package/src/common-cartridge/v1_4/shared/lineitem_v1p0.ts +35 -0
  90. package/src/common-cartridge/v1_4/shared/resourcea11ymetadata-20210915.ts +110 -0
  91. package/src/common-cartridge/v1_4/shared.ts +68 -0
  92. package/src/common-cartridge/v1_4/thin/ccv1p4_imscp_v1p2_v1p0.ts +243 -0
  93. package/src/common-cartridge/v1_4/thin/ccv1p4_lommanifest_v1p0.ts +14 -0
  94. package/src/common-cartridge/v1_4/thin/ccv1p4_lomresource_v1p0.ts +14 -0
  95. package/src/common-cartridge/v1_4/vdex/imsmd_loose_v1p3p2.ts +13 -0
  96. package/src/common-cartridge/v1_4/vdex/imsvdex_v1p0.ts +124 -0
  97. package/src/config.ts +121 -0
  98. package/src/index.ts +32 -0
  99. package/src/lti/ags/v2_0/index.ts +97 -0
  100. package/src/lti/deep-linking/v2_0/index.ts +100 -0
  101. package/src/lti/index.ts +26 -0
  102. package/src/lti/nrps/v2_0/index.ts +69 -0
  103. package/src/lti/proctoring/v1_0/index.ts +131 -0
  104. package/src/lti/shared.ts +66 -0
  105. package/src/lti/v1_3/index.ts +84 -0
  106. package/src/oneroster/v1_2/index.ts +156 -0
  107. package/src/oneroster/v1_2/or_v1p2_csv_binding_schema.ts +427 -0
  108. package/src/oneroster/v1_2/or_v1p2_gradebook_restbinding_schema.ts +120 -0
  109. package/src/oneroster/v1_2/or_v1p2_gradebook_service_schema.ts +243 -0
  110. package/src/oneroster/v1_2/or_v1p2_resource_restbinding_schema.ts +24 -0
  111. package/src/oneroster/v1_2/or_v1p2_resource_service_schema.ts +47 -0
  112. package/src/oneroster/v1_2/or_v1p2_rostering_restbinding_schema.ts +84 -0
  113. package/src/oneroster/v1_2/or_v1p2_rostering_service_schema.ts +288 -0
  114. package/src/oneroster/v1_2/shared.ts +90 -0
  115. package/src/open-badges/v3_0/index.ts +20 -0
  116. package/src/open-badges/v3_0/ob_v3p0_achievementcredential_schema.ts +17 -0
  117. package/src/open-badges/v3_0/ob_v3p0_endorsementcredential_schema.ts +11 -0
  118. package/src/open-badges/v3_0/ob_v3p0_getopenbadgecredentialsresponse_schema.ts +1 -0
  119. package/src/open-badges/v3_0/ob_v3p0_imsx_statusinfo_schema.ts +8 -0
  120. package/src/open-badges/v3_0/ob_v3p0_profile_schema.ts +9 -0
  121. package/src/open-badges/v3_0/shared.ts +458 -0
  122. package/src/qti/v2-internal.ts +1683 -0
  123. package/src/qti/v2_1/apipv1p0_qtiextv2p1_v1p0.ts +3 -0
  124. package/src/qti/v2_1/imsqti_metadata_v2p1.ts +3 -0
  125. package/src/qti/v2_1/imsqti_result_v2p1.ts +3 -0
  126. package/src/qti/v2_1/imsqti_usagedata_v2p1.ts +3 -0
  127. package/src/qti/v2_1/imsqti_v2p1p2.ts +71 -0
  128. package/src/qti/v2_1/index.ts +53 -0
  129. package/src/qti/v2_1/qtiv2p1_imscpv1p2_v1p0.ts +3 -0
  130. package/src/qti/v2_1/schemas.ts +15 -0
  131. package/src/qti/v2_1/shared.ts +3 -0
  132. package/src/qti/v2_2/apipv1p0_qtiextv2p2_v1p0p1.ts +3 -0
  133. package/src/qti/v2_2/imsqti_metadata_v2p2.ts +3 -0
  134. package/src/qti/v2_2/imsqti_result_v2p2.ts +3 -0
  135. package/src/qti/v2_2/imsqti_usagedata_v2p2.ts +3 -0
  136. package/src/qti/v2_2/imsqti_v2p2p4.ts +76 -0
  137. package/src/qti/v2_2/imsqtiv2p2p4_html5_v1p0.ts +8 -0
  138. package/src/qti/v2_2/index.ts +59 -0
  139. package/src/qti/v2_2/qtiv2p2_csm_v2p2.ts +4 -0
  140. package/src/qti/v2_2/qtiv2p2_imscpv1p2_v1p0.ts +3 -0
  141. package/src/qti/v2_2/schemas.ts +19 -0
  142. package/src/qti/v2_2/shared.ts +3 -0
  143. package/src/qti/v3_0_1/assessment-internal.ts +1509 -0
  144. package/src/qti/v3_0_1/imsqti_asiv3p0p1_v1p0.ts +57 -0
  145. package/src/qti/v3_0_1/imsqti_itemv3p0p1_v1p0.ts +60 -0
  146. package/src/qti/v3_0_1/imsqti_metadatav3p0_v1p0.ts +73 -0
  147. package/src/qti/v3_0_1/imsqti_outcomev3p0p1_v1p0.ts +11 -0
  148. package/src/qti/v3_0_1/imsqti_responseprocessingv3p0p1_v1p0.ts +17 -0
  149. package/src/qti/v3_0_1/imsqti_resultv3p0_v1p0.ts +222 -0
  150. package/src/qti/v3_0_1/imsqti_sectionv3p0p1_v1p0.ts +15 -0
  151. package/src/qti/v3_0_1/imsqti_stimulusv3p0p1_v1p0.ts +15 -0
  152. package/src/qti/v3_0_1/imsqti_testv3p0p1_v1p0.ts +49 -0
  153. package/src/qti/v3_0_1/imsqti_usagedatav3p0_v1p0.ts +77 -0
  154. package/src/qti/v3_0_1/imsqtiv3p0_afa3p0pnp_v1p0.ts +269 -0
  155. package/src/qti/v3_0_1/index.ts +50 -0
  156. package/src/qti/v3_0_1/processing-internal.ts +667 -0
  157. package/src/qti/v3_0_1/shared.ts +146 -0
  158. package/src/qti/v3_0_1/variables-internal.ts +274 -0
  159. package/src/shared/imsx-status.ts +49 -0
  160. package/src/summary.ts +70 -0
  161. package/src/vc-data-model/v2_0/index.ts +27 -0
  162. package/src/vc-data-model/v2_0/shared.ts +206 -0
  163. package/src/xapi/index.ts +12 -0
  164. package/src/xapi/shared.ts +444 -0
  165. package/src/xapi/v1_0_3/index.ts +169 -0
  166. package/src/xapi/v2_0/index.ts +256 -0
  167. package/test/caliper-v1_2.test.ts +270 -0
  168. package/test/case-v1_1.test.ts +147 -0
  169. package/test/cat-v1_0.test.ts +338 -0
  170. package/test/cc-v1_4.test.ts +239 -0
  171. package/test/clr-v2_0.test.ts +225 -0
  172. package/test/cmi5-v1_0.test.ts +196 -0
  173. package/test/contracts.test.ts +125 -0
  174. package/test/fixtures/cmi5/extended-cmi5.xml +72 -0
  175. package/test/fixtures/cmi5/simple-cmi5.xml +26 -0
  176. package/test/lti.test.ts +146 -0
  177. package/test/oneroster-v1_2.test.ts +234 -0
  178. package/test/open-badges-v3_0.test.ts +144 -0
  179. package/test/qti-v2_1.test.ts +146 -0
  180. package/test/qti-v2_2.test.ts +154 -0
  181. package/test/qti-v3_0_1.test.ts +576 -0
  182. package/test/schema-examples.test.ts +101 -0
  183. package/test/vc-data-model-v2_0.test.ts +63 -0
  184. package/test/xapi.test.ts +384 -0
  185. package/tsconfig.json +7 -0
  186. package/vc-data-model-v2_0-zod-templates.md +43 -0
  187. package/xapi-zod-templates.md +95 -0
@@ -0,0 +1,144 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ import { OpenBadgesV3_0 } from "../src";
4
+
5
+ test("AchievementCredentialSchema parses a realistic Open Badge credential", () => {
6
+ const parsed = OpenBadgesV3_0.AchievementCredentialSchema.safeParse({
7
+ "@context": [
8
+ "https://www.w3.org/ns/credentials/v2",
9
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
10
+ "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",
11
+ ],
12
+ id: "http://example.edu/credentials/3732",
13
+ type: ["VerifiableCredential", "OpenBadgeCredential"],
14
+ issuer: {
15
+ id: "https://example.edu/issuers/565049",
16
+ type: ["Profile"],
17
+ name: "Example University",
18
+ },
19
+ validFrom: "2010-01-01T00:00:00Z",
20
+ name: "Example University Degree",
21
+ credentialSubject: {
22
+ id: "did:example:ebfeb1f712ebc6f1c276e12ec21",
23
+ type: ["AchievementSubject"],
24
+ achievement: {
25
+ id: "https://example.com/achievements/21st-century-skills/teamwork",
26
+ type: ["Achievement"],
27
+ criteria: {
28
+ narrative: "Team members are nominated for this badge by peers and recognized upon management review.",
29
+ },
30
+ description: "This badge recognizes collaboration capacity in group environments.",
31
+ name: "Teamwork",
32
+ },
33
+ },
34
+ credentialSchema: [
35
+ {
36
+ id: "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_achievementcredential_schema.json",
37
+ type: "1EdTechJsonSchemaValidator2019",
38
+ },
39
+ ],
40
+ proof: {
41
+ type: "DataIntegrityProof",
42
+ created: "2010-01-01T19:23:24Z",
43
+ verificationMethod: "https://example.edu/issuers/565049#z6MkjZRsdasZv3aez3r18pB1RBFJR1kwUVJ5jHt92JmQwXbd5hwi",
44
+ cryptosuite: "eddsa-rdfc-2022",
45
+ proofPurpose: "assertionMethod",
46
+ proofValue: "z4HwAsa7GvwL7so7CoQ8v3ShzykRPCq8pfkAFKuAPrJx28S69pXphpqL8ApjoxEcMaqbgkaCUyKuEohhGXBR4Fh3L",
47
+ },
48
+ });
49
+
50
+ expect(parsed.success).toBe(true);
51
+ });
52
+
53
+ test("EndorsementCredentialSchema parses a valid endorsement credential", () => {
54
+ const parsed = OpenBadgesV3_0.EndorsementCredentialSchema.safeParse({
55
+ "@context": [
56
+ "https://www.w3.org/ns/credentials/v2",
57
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
58
+ "https://purl.imsglobal.org/spec/ob/v3p0/extensions.json",
59
+ ],
60
+ id: "http://example.edu/endorsementcredential/3732",
61
+ type: ["VerifiableCredential", "EndorsementCredential"],
62
+ issuer: {
63
+ id: "https://state.gov/issuers/565049",
64
+ type: ["Profile"],
65
+ name: "State Department of Education",
66
+ },
67
+ validFrom: "2010-01-01T00:00:00Z",
68
+ validUntil: "2030-01-01T00:00:00Z",
69
+ name: "Example endorsement",
70
+ credentialSubject: {
71
+ id: "https://example.edu/issuers/565049",
72
+ type: ["EndorsementSubject"],
73
+ endorsementComment: "Example University is in good standing",
74
+ },
75
+ credentialSchema: [
76
+ {
77
+ id: "https://purl.imsglobal.org/spec/ob/v3p0/schema/json/ob_v3p0_endorsementcredential_schema.json",
78
+ type: "1EdTechJsonSchemaValidator2019",
79
+ },
80
+ ],
81
+ });
82
+
83
+ expect(parsed.success).toBe(true);
84
+ });
85
+
86
+ test("GetOpenBadgeCredentialsResponseSchema parses credential and JWS collections", () => {
87
+ const parsed = OpenBadgesV3_0.GetOpenBadgeCredentialsResponseSchema.safeParse({
88
+ credential: {
89
+ "@context": [
90
+ "https://www.w3.org/ns/credentials/v2",
91
+ "https://purl.imsglobal.org/spec/ob/v3p0/context-3.0.3.json",
92
+ ],
93
+ id: "https://example.test/credentials/1",
94
+ type: ["VerifiableCredential", "OpenBadgeCredential"],
95
+ issuer: "https://example.test/issuers/1",
96
+ validFrom: "2025-01-01T00:00:00Z",
97
+ credentialSubject: {
98
+ type: ["AchievementSubject"],
99
+ achievement: {
100
+ id: "https://example.test/achievements/1",
101
+ type: ["Achievement"],
102
+ name: "Example Achievement",
103
+ description: "Awarded for completing the example pathway.",
104
+ criteria: { narrative: "Complete every required task." },
105
+ },
106
+ },
107
+ },
108
+ compactJwsString: "header.payload.signature",
109
+ });
110
+
111
+ expect(parsed.success).toBe(true);
112
+ });
113
+
114
+ test("AchievementCredentialSchema rejects missing OB context", () => {
115
+ const parsed = OpenBadgesV3_0.AchievementCredentialSchema.safeParse({
116
+ "@context": ["https://www.w3.org/ns/credentials/v2"],
117
+ id: "https://example.test/credentials/1",
118
+ type: ["VerifiableCredential", "OpenBadgeCredential"],
119
+ issuer: "https://example.test/issuers/1",
120
+ validFrom: "2025-01-01T00:00:00Z",
121
+ credentialSubject: {
122
+ type: ["AchievementSubject"],
123
+ achievement: {
124
+ id: "https://example.test/achievements/1",
125
+ type: ["Achievement"],
126
+ name: "Example Achievement",
127
+ description: "Awarded for completing the example pathway.",
128
+ criteria: { narrative: "Complete every required task." },
129
+ },
130
+ },
131
+ });
132
+
133
+ expect(parsed.success).toBe(false);
134
+ });
135
+
136
+ test("OpenBadges30DerivedZodTemplates exposes expected entry points", () => {
137
+ expect(OpenBadgesV3_0.OpenBadges30DerivedZodTemplates.achievementCredential).toBe(
138
+ OpenBadgesV3_0.AchievementCredentialSchema,
139
+ );
140
+ expect(OpenBadgesV3_0.OpenBadges30DerivedZodTemplates.openBadgeCredential).toBe(
141
+ OpenBadgesV3_0.OpenBadgeCredentialSchema,
142
+ );
143
+ expect(OpenBadgesV3_0.OpenBadges30DerivedZodTemplates.imsxStatusInfo).toBe(OpenBadgesV3_0.ImsxStatusInfoSchema);
144
+ });
@@ -0,0 +1,146 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ import { QtiV2_1 } from "../src";
4
+
5
+ test("QtiV2_1 metadata document parses a minimal metadata payload", () => {
6
+ const parsed = QtiV2_1.QtiMetadataDocumentSchema.safeParse({
7
+ qtiMetadata: {
8
+ itemTemplate: false,
9
+ interactionType: ["choiceInteraction"],
10
+ feedbackType: "nonadaptive",
11
+ solutionAvailable: true,
12
+ },
13
+ });
14
+
15
+ expect(parsed.success).toBe(true);
16
+ });
17
+
18
+ test("QtiV2_1 assessment item document parses a minimal choice item", () => {
19
+ const parsed = QtiV2_1.QtiAssessmentItemDocumentSchema.safeParse({
20
+ assessmentItem: {
21
+ identifier: "ITEM1",
22
+ responseDeclarations: [
23
+ {
24
+ identifier: "RESPONSE",
25
+ cardinality: "single",
26
+ baseType: "identifier",
27
+ correctResponse: {
28
+ values: [{ value: "A" }],
29
+ },
30
+ },
31
+ ],
32
+ outcomeDeclarations: [
33
+ {
34
+ identifier: "SCORE",
35
+ cardinality: "single",
36
+ baseType: "float",
37
+ defaultValue: {
38
+ values: [{ value: "0" }],
39
+ },
40
+ },
41
+ ],
42
+ itemBody: {
43
+ children: [
44
+ {
45
+ kind: "choiceInteraction",
46
+ responseIdentifier: "RESPONSE",
47
+ simpleChoices: [
48
+ {
49
+ kind: "simpleChoice",
50
+ identifier: "A",
51
+ children: [{ kind: "text", value: "Option A" }],
52
+ },
53
+ {
54
+ kind: "simpleChoice",
55
+ identifier: "B",
56
+ children: [{ kind: "text", value: "Option B" }],
57
+ },
58
+ ],
59
+ },
60
+ ],
61
+ },
62
+ responseProcessing: {
63
+ rules: [
64
+ {
65
+ kind: "setOutcomeValue",
66
+ identifier: "SCORE",
67
+ expression: {
68
+ kind: "baseValue",
69
+ baseType: "float",
70
+ value: "1.0",
71
+ },
72
+ },
73
+ ],
74
+ },
75
+ },
76
+ });
77
+
78
+ expect(parsed.success).toBe(true);
79
+ });
80
+
81
+ test("QtiV2_1 result document rejects record responses without field identifiers", () => {
82
+ const parsed = QtiV2_1.QtiAssessmentResultDocumentSchema.safeParse({
83
+ assessmentResult: {
84
+ context: {},
85
+ itemResults: [
86
+ {
87
+ identifier: "ITEM1",
88
+ datestamp: "2026-05-27T12:00:00Z",
89
+ sessionStatus: "final",
90
+ responseVariables: [
91
+ {
92
+ identifier: "RESPONSE",
93
+ cardinality: "record",
94
+ candidateResponse: {
95
+ values: [{ value: "A" }],
96
+ },
97
+ },
98
+ ],
99
+ },
100
+ ],
101
+ },
102
+ });
103
+
104
+ expect(parsed.success).toBe(false);
105
+ });
106
+
107
+ test("QtiV2_1 manifest document parses a minimal package", () => {
108
+ const parsed = QtiV2_1.QtiManifestDocumentSchema.safeParse({
109
+ manifest: {
110
+ identifier: "manifest-1",
111
+ metadata: {
112
+ schema: "QTIv2.1 Package",
113
+ schemaVersion: "1.0.0",
114
+ },
115
+ organizations: {},
116
+ resources: [
117
+ {
118
+ identifier: "item-resource",
119
+ type: "imsqti_item_xmlv2p1",
120
+ href: "items/item1.xml",
121
+ files: [{ href: "items/item1.xml" }],
122
+ },
123
+ ],
124
+ },
125
+ });
126
+
127
+ expect(parsed.success).toBe(true);
128
+ });
129
+
130
+ test("QtiV2_1 APIP accessibility document parses a minimal payload", () => {
131
+ const parsed = QtiV2_1.QtiApipAccessibilityDocumentSchema.safeParse({
132
+ apipAccessibility: {
133
+ accessibilityInfo: {
134
+ accessElements: [
135
+ {
136
+ identifier: "ae1",
137
+ contentLinkInfos: [{ qtiLinkIdentifier: "choice-A" }],
138
+ relatedElementInfo: {},
139
+ },
140
+ ],
141
+ },
142
+ },
143
+ });
144
+
145
+ expect(parsed.success).toBe(true);
146
+ });
@@ -0,0 +1,154 @@
1
+ import { expect, test } from "bun:test";
2
+
3
+ import { QtiV2_2 } from "../src";
4
+
5
+ test("QtiV2_2 metadata document parses a minimal metadata payload with scoring modes", () => {
6
+ const parsed = QtiV2_2.QtiMetadataDocumentSchema.safeParse({
7
+ qtiMetadata: {
8
+ itemTemplate: false,
9
+ interactionType: ["choiceInteraction", "mediaInteraction"],
10
+ scoringMode: ["responseprocessing"],
11
+ toolName: "conform-ed",
12
+ },
13
+ });
14
+
15
+ expect(parsed.success).toBe(true);
16
+ });
17
+
18
+ test("QtiV2_2 assessment stimulus and assessment item documents parse together", () => {
19
+ const stimulusParsed = QtiV2_2.QtiAssessmentStimulusDocumentSchema.safeParse({
20
+ assessmentStimulus: {
21
+ identifier: "STIM1",
22
+ stimulusBody: {
23
+ children: [
24
+ {
25
+ kind: "article",
26
+ children: [{ kind: "text", value: "Shared stimulus" }],
27
+ },
28
+ ],
29
+ },
30
+ },
31
+ });
32
+
33
+ const itemParsed = QtiV2_2.QtiAssessmentItemDocumentSchema.safeParse({
34
+ assessmentItem: {
35
+ identifier: "ITEM1",
36
+ assessmentStimulusRefs: [{ href: "stimulus.xml", identifier: "STIM1" }],
37
+ responseDeclarations: [
38
+ {
39
+ identifier: "RESPONSE",
40
+ cardinality: "single",
41
+ baseType: "identifier",
42
+ },
43
+ ],
44
+ itemBody: {
45
+ children: [
46
+ {
47
+ kind: "choiceInteraction",
48
+ responseIdentifier: "RESPONSE",
49
+ simpleChoices: [
50
+ { kind: "simpleChoice", identifier: "A", children: [{ kind: "text", value: "A" }] },
51
+ { kind: "simpleChoice", identifier: "B", children: [{ kind: "text", value: "B" }] },
52
+ ],
53
+ },
54
+ ],
55
+ },
56
+ },
57
+ });
58
+
59
+ expect(stimulusParsed.success).toBe(true);
60
+ expect(itemParsed.success).toBe(true);
61
+ });
62
+
63
+ test("QtiV2_2 processing rejects invalid numeric operator parameters", () => {
64
+ const parsed = QtiV2_2.QtiResponseProcessingDocumentSchema.safeParse({
65
+ responseProcessing: {
66
+ rules: [
67
+ {
68
+ kind: "setOutcomeValue",
69
+ identifier: "RANDOM",
70
+ expression: {
71
+ kind: "randomInteger",
72
+ min: 10,
73
+ max: 1,
74
+ },
75
+ },
76
+ ],
77
+ },
78
+ });
79
+
80
+ expect(parsed.success).toBe(false);
81
+ });
82
+
83
+ test("QtiV2_2 curriculum standards metadata and manifest documents parse", () => {
84
+ const csmParsed = QtiV2_2.QtiCurriculumStandardsMetadataSetDocumentSchema.safeParse({
85
+ curriculumStandardsMetadataSet: {
86
+ resourceLabel: "Item",
87
+ curriculumStandardsMetadata: [
88
+ {
89
+ providerId: "provider-1",
90
+ setOfGuids: [
91
+ {
92
+ region: "AU",
93
+ labelledGuids: [{ guid: "GUID-1", label: "Outcome 1" }],
94
+ },
95
+ ],
96
+ },
97
+ ],
98
+ },
99
+ });
100
+
101
+ const manifestParsed = QtiV2_2.QtiManifestDocumentSchema.safeParse({
102
+ manifest: {
103
+ identifier: "manifest-2",
104
+ metadata: {
105
+ schema: "QTIv2.2 Package",
106
+ schemaVersion: "1.0.0",
107
+ curriculumStandardsMetadataSet: {
108
+ curriculumStandardsMetadata: [
109
+ {
110
+ setOfGuids: [
111
+ {
112
+ labelledGuids: [{ guid: "GUID-1" }],
113
+ },
114
+ ],
115
+ },
116
+ ],
117
+ },
118
+ },
119
+ organizations: {},
120
+ resources: [
121
+ {
122
+ identifier: "stimulus-resource",
123
+ type: "imsqti_stimulus_xmlv2p2",
124
+ href: "stimulus.xml",
125
+ files: [{ href: "stimulus.xml" }],
126
+ },
127
+ ],
128
+ },
129
+ });
130
+
131
+ expect(csmParsed.success).toBe(true);
132
+ expect(manifestParsed.success).toBe(true);
133
+ });
134
+
135
+ test("QtiV2_2 APIP accessibility document parses external supplemental accessibility", () => {
136
+ const parsed = QtiV2_2.QtiApipAccessibilityDocumentSchema.safeParse({
137
+ apipAccessibility: {
138
+ externalSupplementalAccessibility: {
139
+ fileHrefs: [{ href: "supplement.xml" }],
140
+ },
141
+ accessibilityInfo: {
142
+ accessElements: [
143
+ {
144
+ identifier: "ae1",
145
+ contentLinkInfos: [{ qtiLinkIdentifier: "choice-A" }],
146
+ relatedElementInfo: {},
147
+ },
148
+ ],
149
+ },
150
+ },
151
+ });
152
+
153
+ expect(parsed.success).toBe(true);
154
+ });