@conform-ed/qti-react 0.0.15 → 0.0.17
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +6 -4
- package/dist/index.js +2486 -405
- package/dist/normalized-item.d.ts +7 -5
- package/dist/pnp.d.ts +115 -0
- package/dist/response-validity.d.ts +28 -0
- package/dist/rp/evaluate.d.ts +6 -1
- package/dist/rp/index.d.ts +2 -2
- package/dist/rp/interpreter.d.ts +7 -1
- package/dist/rp/lookup-table.d.ts +17 -0
- package/dist/rp/template-processing.d.ts +5 -0
- package/dist/rp/types.d.ts +71 -7
- package/dist/runtime.d.ts +95 -0
- package/dist/store.d.ts +47 -0
- package/dist/test/controller.d.ts +22 -0
- package/dist/test/index.d.ts +2 -1
- package/dist/test/results.d.ts +102 -0
- package/dist/test/session-store.d.ts +32 -0
- package/dist/test/types.d.ts +173 -5
- package/dist/types.d.ts +5 -0
- package/package.json +7 -2
- package/src/content-model.ts +44 -4
- package/src/index.ts +43 -1
- package/src/normalized-item.ts +106 -4
- package/src/pnp.ts +333 -0
- package/src/reference-skin/choice.ts +3 -0
- package/src/response-validity.ts +163 -0
- package/src/rp/evaluate.ts +280 -32
- package/src/rp/index.ts +5 -0
- package/src/rp/interpreter.ts +81 -1
- package/src/rp/lookup-table.ts +46 -0
- package/src/rp/template-processing.ts +41 -0
- package/src/rp/types.ts +75 -7
- package/src/runtime.ts +397 -20
- package/src/store.ts +146 -8
- package/src/test/controller.ts +856 -82
- package/src/test/index.ts +23 -0
- package/src/test/results.ts +378 -0
- package/src/test/session-store.ts +109 -1
- package/src/test/types.ts +172 -5
- package/src/types.ts +1 -0
- package/src/xspattern.d.ts +11 -0
package/src/test/types.ts
CHANGED
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
import type { CapabilityIssue } from "../capability";
|
|
9
9
|
import type { OutcomeDeclarationView, OutcomeValue, RpExpressionView } from "../rp";
|
|
10
10
|
import type { BodyNode } from "../runtime";
|
|
11
|
+
import type { ResponseValue } from "../types";
|
|
11
12
|
|
|
12
13
|
export interface BranchRuleView {
|
|
13
14
|
/** A target identifier in the same test part, or EXIT_TEST / EXIT_TESTPART / EXIT_SECTION. */
|
|
@@ -33,8 +34,10 @@ export interface ItemSessionControlView {
|
|
|
33
34
|
}
|
|
34
35
|
|
|
35
36
|
/**
|
|
36
|
-
* QTI `timeLimits` (seconds). The controller
|
|
37
|
-
*
|
|
37
|
+
* QTI `timeLimits` (seconds). The controller enforces these under an injectable clock
|
|
38
|
+
* (`TestControllerOptions.now`): expiry checks fold elapsed time at every transition,
|
|
39
|
+
* and consumers drive their own timers by calling `tick()` (ADR-0005, "Timing and
|
|
40
|
+
* time limits"). The values still surface on the plan for consumer-side countdowns.
|
|
38
41
|
*/
|
|
39
42
|
export interface TimeLimitsView {
|
|
40
43
|
readonly minTime?: number;
|
|
@@ -42,6 +45,16 @@ export interface TimeLimitsView {
|
|
|
42
45
|
readonly allowLateSubmission?: boolean;
|
|
43
46
|
}
|
|
44
47
|
|
|
48
|
+
/**
|
|
49
|
+
* "The default value of a template variable in an item can be overridden based on
|
|
50
|
+
* the test context in which the template is instantiated." (§5.152) The expression
|
|
51
|
+
* evaluates at test level (it may read other items' variables and test outcomes).
|
|
52
|
+
*/
|
|
53
|
+
export interface TemplateDefaultView {
|
|
54
|
+
readonly templateIdentifier: string;
|
|
55
|
+
readonly expression: RpExpressionView;
|
|
56
|
+
}
|
|
57
|
+
|
|
45
58
|
export interface AssessmentItemRefView {
|
|
46
59
|
readonly kind: "assessmentItemRef";
|
|
47
60
|
readonly identifier: string;
|
|
@@ -55,6 +68,7 @@ export interface AssessmentItemRefView {
|
|
|
55
68
|
readonly timeLimits?: TimeLimitsView;
|
|
56
69
|
/** Named weights for `testVariables`/aggregate weighting (missing names weigh 1). */
|
|
57
70
|
readonly weights?: ReadonlyArray<{ readonly identifier: string; readonly value: number }>;
|
|
71
|
+
readonly templateDefaults?: readonly TemplateDefaultView[];
|
|
58
72
|
}
|
|
59
73
|
|
|
60
74
|
export interface AssessmentSectionView {
|
|
@@ -64,6 +78,12 @@ export interface AssessmentSectionView {
|
|
|
64
78
|
readonly visible?: boolean;
|
|
65
79
|
readonly fixed?: boolean;
|
|
66
80
|
readonly required?: boolean;
|
|
81
|
+
/**
|
|
82
|
+
* For an invisible section under a shuffling parent: whether its children are
|
|
83
|
+
* "shuffled as a block or mixed up with the other children of the parent section"
|
|
84
|
+
* (§4.2.7). Default true (block).
|
|
85
|
+
*/
|
|
86
|
+
readonly keepTogether?: boolean;
|
|
67
87
|
readonly selection?: { readonly select: number; readonly withReplacement?: boolean };
|
|
68
88
|
readonly ordering?: { readonly shuffle?: boolean };
|
|
69
89
|
readonly preConditions?: readonly RpExpressionView[];
|
|
@@ -97,11 +117,16 @@ export interface OutcomeConditionBranch {
|
|
|
97
117
|
readonly rules: readonly OutcomeRuleView[];
|
|
98
118
|
}
|
|
99
119
|
|
|
100
|
-
/**
|
|
120
|
+
/**
|
|
121
|
+
* One outcome rule: outcomeCondition, setOutcomeValue, lookupOutcomeValue,
|
|
122
|
+
* outcomeProcessingFragment, or exitTest.
|
|
123
|
+
*/
|
|
101
124
|
export interface OutcomeRuleView {
|
|
102
125
|
readonly kind: string;
|
|
103
126
|
readonly identifier?: string;
|
|
104
127
|
readonly expression?: RpExpressionView;
|
|
128
|
+
/** Nested rules of an `outcomeProcessingFragment` (§5.103). */
|
|
129
|
+
readonly rules?: readonly OutcomeRuleView[];
|
|
105
130
|
readonly outcomeIf?: OutcomeConditionBranch;
|
|
106
131
|
readonly outcomeElseIfs?: readonly OutcomeConditionBranch[];
|
|
107
132
|
readonly outcomeElse?: { readonly rules: readonly OutcomeRuleView[] };
|
|
@@ -120,8 +145,17 @@ export interface AssessmentTestView {
|
|
|
120
145
|
// ---------- The resolved delivery plan (selection + ordering applied) ----------
|
|
121
146
|
|
|
122
147
|
export interface TestPlanItem {
|
|
123
|
-
/**
|
|
148
|
+
/**
|
|
149
|
+
* The session key: the ref identifier, or `identifier.n` when selection
|
|
150
|
+
* with-replacement instantiates the ref more than once — the spec's own instance
|
|
151
|
+
* addressing, where "a number that denotes the instance's place in the sequence of
|
|
152
|
+
* the item's instantiation is inserted between the item variable identifier and
|
|
153
|
+
* the item variable" (§2.11.1.2). Identifiers cannot contain periods, so the two
|
|
154
|
+
* forms never collide.
|
|
155
|
+
*/
|
|
124
156
|
readonly key: string;
|
|
157
|
+
/** 1-based instantiation number; present only when the ref has multiple instances. */
|
|
158
|
+
readonly instance?: number;
|
|
125
159
|
readonly ref: AssessmentItemRefView;
|
|
126
160
|
readonly partIdentifier: string;
|
|
127
161
|
readonly sectionPath: readonly string[];
|
|
@@ -141,9 +175,17 @@ export interface TestPlanPart {
|
|
|
141
175
|
readonly items: readonly TestPlanItem[];
|
|
142
176
|
}
|
|
143
177
|
|
|
178
|
+
/** A section that survived selection, keyed for duration tracking and time limits. */
|
|
179
|
+
export interface TestPlanSection {
|
|
180
|
+
readonly identifier: string;
|
|
181
|
+
readonly timeLimits?: TimeLimitsView;
|
|
182
|
+
}
|
|
183
|
+
|
|
144
184
|
export interface TestPlan {
|
|
145
185
|
readonly timeLimits?: TimeLimitsView;
|
|
146
186
|
readonly parts: readonly TestPlanPart[];
|
|
187
|
+
/** Every planned section by identifier (spec-unique across parts/sections/refs). */
|
|
188
|
+
readonly sections: Readonly<Record<string, TestPlanSection>>;
|
|
147
189
|
}
|
|
148
190
|
|
|
149
191
|
// ---------- Session state (consumer-persisted, plain JSON) ----------
|
|
@@ -159,10 +201,88 @@ export interface TestItemResult {
|
|
|
159
201
|
readonly responded?: boolean;
|
|
160
202
|
/** Adaptive items manage their own attempt lifecycle, so maxAttempts is ignored (spec). */
|
|
161
203
|
readonly adaptive?: boolean;
|
|
204
|
+
/**
|
|
205
|
+
* The item session's elapsed seconds (`AttemptSnapshot.durationSeconds`). Resolves
|
|
206
|
+
* the built-in `ITEM.duration` in outcome processing; unreported → NULL.
|
|
207
|
+
*/
|
|
208
|
+
readonly durationSeconds?: number;
|
|
209
|
+
/**
|
|
210
|
+
* Whether the responses satisfy the interaction constraints (response-validity).
|
|
211
|
+
* Under effective `validateResponses` in an individual-submission part, `false`
|
|
212
|
+
* makes the controller refuse the submission ("candidates are not allowed to
|
|
213
|
+
* submit the item until they have provided valid responses for all interactions").
|
|
214
|
+
*/
|
|
215
|
+
readonly valid?: boolean;
|
|
216
|
+
/**
|
|
217
|
+
* The candidate's responses as submitted — recorded into the attempt history so
|
|
218
|
+
* results reporting can emit `candidateResponse` values from persisted state.
|
|
219
|
+
*/
|
|
220
|
+
readonly responses?: Readonly<Record<string, ResponseValue>>;
|
|
221
|
+
/**
|
|
222
|
+
* The submission instant (epoch ms). Controller-stamped: callers never set it;
|
|
223
|
+
* it rides pending simultaneous results so the flush keeps submit-time stamps.
|
|
224
|
+
*/
|
|
225
|
+
readonly submittedAtMs?: number;
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
/**
|
|
229
|
+
* One committed attempt, recorded for results reporting: "A report may contain
|
|
230
|
+
* multiple results for the same instance of an item representing multiple attempts
|
|
231
|
+
* … each item result must have a different datestamp."
|
|
232
|
+
*/
|
|
233
|
+
export interface RecordedAttempt {
|
|
234
|
+
/** Submission instant (epoch ms) — the itemResult datestamp. */
|
|
235
|
+
readonly atMs: number;
|
|
236
|
+
readonly outcomes: Readonly<Record<string, OutcomeValue>>;
|
|
237
|
+
readonly responses?: Readonly<Record<string, ResponseValue>>;
|
|
238
|
+
/** The item session's elapsed seconds at this submission, when reported. */
|
|
239
|
+
readonly durationSeconds?: number;
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
/**
|
|
243
|
+
* Wall-clock accounting folded at every controller transition (and `tick()`).
|
|
244
|
+
* Durations are always "as of" `lastTransitionAtMs` — scoring and enforcement read
|
|
245
|
+
* recorded state only (ADR-0004 determinism). Until a suspend/resume API exists,
|
|
246
|
+
* these include all wall time between transitions.
|
|
247
|
+
*/
|
|
248
|
+
export interface TestTimingState {
|
|
249
|
+
/** Injected-clock milliseconds at the last fold. */
|
|
250
|
+
readonly lastTransitionAtMs: number;
|
|
251
|
+
/** Whole-test seconds (the bare `duration` built-in, §2.8.5). */
|
|
252
|
+
readonly testSeconds: number;
|
|
253
|
+
/** Seconds per test-part identifier (`P1.duration`). */
|
|
254
|
+
readonly partSeconds: Readonly<Record<string, number>>;
|
|
255
|
+
/** Seconds per section identifier — a leaf accrues to every ancestor (`S2.duration`). */
|
|
256
|
+
readonly sectionSeconds: Readonly<Record<string, number>>;
|
|
257
|
+
/**
|
|
258
|
+
* Seconds each item has been the current item — the enforcement clock for item
|
|
259
|
+
* minTime/maxTime. The `ITEM.duration` variable reads the consumer report instead.
|
|
260
|
+
*/
|
|
261
|
+
readonly itemSeconds: Readonly<Record<string, number>>;
|
|
262
|
+
}
|
|
263
|
+
|
|
264
|
+
export type TimingScopeRef =
|
|
265
|
+
| { readonly kind: "test" }
|
|
266
|
+
| { readonly kind: "part"; readonly identifier: string }
|
|
267
|
+
| { readonly kind: "section"; readonly identifier: string }
|
|
268
|
+
| { readonly kind: "item"; readonly key: string };
|
|
269
|
+
|
|
270
|
+
/** A late submission the controller refused (ADR-0003: no silent drops). */
|
|
271
|
+
export interface RejectedSubmission {
|
|
272
|
+
readonly itemKey: string;
|
|
273
|
+
/** The innermost exceeded scope whose allowLateSubmission did not permit it. */
|
|
274
|
+
readonly scope: TimingScopeRef;
|
|
275
|
+
/** Test-scope seconds at rejection (audit stamp on the session clock). */
|
|
276
|
+
readonly atTestSeconds: number;
|
|
162
277
|
}
|
|
163
278
|
|
|
164
279
|
export interface TestSessionState {
|
|
165
|
-
|
|
280
|
+
/**
|
|
281
|
+
* `suspended` stops every scope clock and blocks transitions until `resume()` —
|
|
282
|
+
* the gap never accrues to any duration ("minus any time the session was in the
|
|
283
|
+
* suspended state"). Pre-suspension persisted states only ever carry the other two.
|
|
284
|
+
*/
|
|
285
|
+
readonly status: "in-progress" | "suspended" | "ended";
|
|
166
286
|
readonly currentItemKey: string | null;
|
|
167
287
|
readonly itemOutcomes: Readonly<Record<string, Readonly<Record<string, OutcomeValue>>>>;
|
|
168
288
|
readonly attemptedItems: readonly string[];
|
|
@@ -181,9 +301,29 @@ export interface TestSessionState {
|
|
|
181
301
|
*/
|
|
182
302
|
readonly pendingItemResults: Readonly<Record<string, TestItemResult>>;
|
|
183
303
|
readonly testOutcomes: Readonly<Record<string, OutcomeValue>>;
|
|
304
|
+
/** Timing accumulators; absent on pre-timing persisted states (initialized lazily). */
|
|
305
|
+
readonly timing?: TestTimingState;
|
|
306
|
+
/**
|
|
307
|
+
* Evaluated `templateDefault` values per item key, recorded at the spec's times
|
|
308
|
+
* (§5.152: linear — when the item first becomes current; nonlinear — at testPart
|
|
309
|
+
* start) so item-store creation reads a stable, replayable value.
|
|
310
|
+
*/
|
|
311
|
+
readonly templateDefaultValues?: Readonly<Record<string, Readonly<Record<string, OutcomeValue>>>>;
|
|
312
|
+
/** Latest consumer-reported item-session duration per item key (feeds `ITEM.duration`). */
|
|
313
|
+
readonly itemDurationSeconds?: Readonly<Record<string, number>>;
|
|
314
|
+
readonly rejectedSubmissions?: readonly RejectedSubmission[];
|
|
315
|
+
/**
|
|
316
|
+
* Candidate comments per item key (allowComment): "feedback from the candidate to
|
|
317
|
+
* the other actors in the assessment process", never part of the assessed responses.
|
|
318
|
+
*/
|
|
319
|
+
readonly itemComments?: Readonly<Record<string, string>>;
|
|
320
|
+
/** Committed attempts per item key, in submission order (results reporting). */
|
|
321
|
+
readonly attemptHistory?: Readonly<Record<string, readonly RecordedAttempt[]>>;
|
|
184
322
|
}
|
|
185
323
|
|
|
186
324
|
export interface TestController {
|
|
325
|
+
/** The assessment test view this controller was created from. */
|
|
326
|
+
readonly test: AssessmentTestView;
|
|
187
327
|
readonly plan: TestPlan;
|
|
188
328
|
/** Static capability issues found in outcome processing, preconditions, and branch rules. */
|
|
189
329
|
readonly issues: readonly CapabilityIssue[];
|
|
@@ -199,5 +339,32 @@ export interface TestController {
|
|
|
199
339
|
readonly canSubmitItem: (state: TestSessionState, itemKey: string) => boolean;
|
|
200
340
|
readonly submitItem: (state: TestSessionState, itemKey: string, result: TestItemResult) => TestSessionState;
|
|
201
341
|
readonly end: (state: TestSessionState) => TestSessionState;
|
|
342
|
+
/**
|
|
343
|
+
* Fold elapsed time into the recorded durations and apply any max-time expiries
|
|
344
|
+
* (forced moves / forced end). Consumers run their own timers and call this;
|
|
345
|
+
* identity once the session has ended (the clock stops at end).
|
|
346
|
+
*/
|
|
347
|
+
readonly tick: (state: TestSessionState) => TestSessionState;
|
|
202
348
|
readonly visibleTestFeedbacks: (state: TestSessionState) => readonly TestFeedbackView[];
|
|
349
|
+
/**
|
|
350
|
+
* Post-end review (allowReview): whether the candidate may re-enter the item
|
|
351
|
+
* read-only — the session has ended, the item was presented, and its effective
|
|
352
|
+
* allowReview permits it.
|
|
353
|
+
*/
|
|
354
|
+
readonly canReview: (state: TestSessionState, itemKey: string) => boolean;
|
|
355
|
+
/** Navigate review: sets the current item without reopening the ended session. */
|
|
356
|
+
readonly review: (state: TestSessionState, itemKey: string) => TestSessionState;
|
|
357
|
+
/** Whether a comment may be recorded: effective allowComment, session in progress. */
|
|
358
|
+
readonly canComment: (state: TestSessionState, itemKey: string) => boolean;
|
|
359
|
+
readonly setItemComment: (state: TestSessionState, itemKey: string, comment: string) => TestSessionState;
|
|
360
|
+
/**
|
|
361
|
+
* Suspend the session: folds the clock up to this instant (applying any expiry
|
|
362
|
+
* that fold reveals), then stops it. Identity unless in progress.
|
|
363
|
+
*/
|
|
364
|
+
readonly suspend: (state: TestSessionState) => TestSessionState;
|
|
365
|
+
/**
|
|
366
|
+
* Resume a suspended session: re-stamps the clock at the current instant without
|
|
367
|
+
* folding the gap — suspended time never accrues. Identity unless suspended.
|
|
368
|
+
*/
|
|
369
|
+
readonly resume: (state: TestSessionState) => TestSessionState;
|
|
203
370
|
}
|
package/src/types.ts
CHANGED
|
@@ -56,6 +56,7 @@ export interface ResponseDeclarationView {
|
|
|
56
56
|
readonly identifier: string;
|
|
57
57
|
readonly cardinality: Cardinality;
|
|
58
58
|
readonly baseType?: string;
|
|
59
|
+
readonly defaultValue?: { readonly values: ReadonlyArray<{ readonly value: string | number | boolean }> };
|
|
59
60
|
readonly correctResponse?: CorrectResponseView;
|
|
60
61
|
readonly mapping?: MappingView;
|
|
61
62
|
readonly areaMapping?: AreaMappingView;
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ambient typing for xspattern 3.1.0: the package ships dist/xspattern.d.ts and a
|
|
3
|
+
* top-level "types" field, but its "exports" map has no "types" condition, so
|
|
4
|
+
* modern resolution cannot see them. Mirrors the shipped declarations verbatim;
|
|
5
|
+
* delete once upstream adds the condition (https://github.com/bwrrp/xspattern.js).
|
|
6
|
+
*/
|
|
7
|
+
declare module "xspattern" {
|
|
8
|
+
export type MatchFn = (str: string) => boolean;
|
|
9
|
+
export type Options = { language: "xsd" | "xpath" };
|
|
10
|
+
export function compile(pattern: string, options?: Options): MatchFn;
|
|
11
|
+
}
|