Skip to content

Latest commit

 

History

525 Commits

Folders and files

NameName
Last commit message
Last commit date
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

qti3

qti3 is a dependency-light, framework-neutral TypeScript reference implementation for QTI 3 assessment items. The core engine and CLI ship with zero third-party runtime dependencies.

Use the interactive qti3 manual to load reference fixtures, QTI packages, or item XML and inspect rendering, scoring, validation, state, diagnostics, and accessibility evidence. The shared vocabulary gallery provides runnable matrix fixtures for presentation-class review.

The project publishes packages on npm for parsing, validating, rendering, scoring, serializing, restoring, and testing QTI 3 items. The core has no UI framework dependency.

qti3 covers QTI item and question-type conformance. Host products provide runners, controllers, LMS shells, candidate attempt policy, analytics, proctoring, rostering, and gradebook integrations.

Longsight maintains qti3 as part of its open-source educational software work. The implementation also supports the open-standards direction used by QFlowLearn's QTI 3 assessment platform. Product-specific authoring and delivery remain outside this repository.

Project shape

qti3 is item-focused: the core owns QTI semantics, the player renders one item at a time, tooling proves conformance and accessibility behavior, and host products own the surrounding assessment application.

flowchart LR
  content["QTI item XML<br/>and package assets"]
  host["Host product<br/>runner, policy, LMS shell, analytics"]
  review["CI, release, and<br/>certification checks"]

  subgraph qti3["qti3 packages"]
    core["core<br/>parse, validate, process,<br/>score, serialize state"]
    player["player<br/>native web component<br/>item renderer"]
    fixtures["fixtures<br/>synthetic reference items"]
    conformance["conformance<br/>fixture runner and<br/>support matrix"]
    a11y["a11y<br/>keyboard and accessibility<br/>proof contracts"]
    cli["cli<br/>validate, score, inspect,<br/>write fixtures"]
    pnp["pnp<br/>normalize and resolve<br/>candidate preferences"]
    writer["writer<br/>QTI-shaped authoring<br/>XML output"]
    migrator["migrator<br/>QTI 1.2 and 2.x<br/>to QTI 3 authoring"]
    transcoder["transcoder<br/>QTI 3 to versioned<br/>standard and LMS profiles"]
  end

  content --> core
  content --> cli
  content --> migrator
  content --> transcoder
  core --> player
  core --> cli
  core --> pnp
  core --> writer
  writer --> migrator
  writer --> transcoder
  fixtures --> conformance
  fixtures --> cli
  conformance --> review
  a11y --> review
  cli --> review
  pnp --> host
  writer --> host
  migrator --> host
  transcoder --> host
  player --> host
  core --> host
Loading

Interaction families

The parser stores each interaction in the normalized QtiInteraction model and validates its response contract. The browser player selects a renderer from the response shape and interaction model.

Family Interactions Shared implementation
Identifier choices Choice, Inline Choice, Hot Text, Hotspot Choice parsing, identifier response checks, choice metadata
Ordered choices Order, Graphic Order Ordered identifier contract and reorder behavior
Pairing and matching Associate, Match, Graphic Associate Source/target choices, token controls, selected pair chips
Gap assignment Gap Match, Graphic Gap Match Source choices assigned to text or graphic gaps
Graphic and coordinate UI Hotspot, Graphic Order, Graphic Associate, Select Point, Position Object Responsive surfaces, object images, hotspot/point placement
Text responses Text Entry, Extended Text String response contract and text-entry renderer
File responses Upload, Drawing File response contract
Scalar and host-controlled Slider, Media, End Attempt, Portable Custom Small specialized renderers and host event bridges

Question-type support

The support matrix tracks the current public QTI 3 item interaction set described by the 1EdTech QTI 3 Implementation Guide with element names from the QTI 3 XML Binding and tracked internally as the QTI 3.0.1 ASI item profile.

In this README, "Supported" has a specific meaning. The interaction must parse into the typed model, validate against its response and element contract, render in the browser player, score in the core runtime, ship with a public reference fixture, pass fixture and conformance tests, include accessibility metadata, and run through browser rendering tests.

Spec interaction QTI element qti3 status Evidence
Choice qti-choice-interaction Supported choice-reference.xml; core, fixture, conformance, a11y, browser tests
Text Entry qti-text-entry-interaction Supported textEntry-reference.xml; core, fixture, conformance, a11y, browser tests
Extended Text qti-extended-text-interaction Supported extendedText-reference.xml; core, fixture, conformance, a11y, browser tests
Gap Match qti-gap-match-interaction Supported gapMatch-reference.xml; core, fixture, conformance, a11y, browser tests
Hotspot qti-hotspot-interaction Supported hotspot-reference.xml; core, fixture, conformance, a11y, browser tests
Hot Text qti-hottext-interaction Supported hottext-reference.xml; core, fixture, conformance, a11y, browser tests
Inline Choice qti-inline-choice-interaction Supported inlineChoice-reference.xml; core, fixture, conformance, a11y, browser tests
Match qti-match-interaction Supported match-reference.xml; core, fixture, conformance, a11y, browser tests
Order qti-order-interaction Supported order-reference.xml; core, fixture, conformance, a11y, browser tests
Graphic Order qti-graphic-order-interaction Supported graphicOrder-reference.xml; core, fixture, conformance, a11y, browser tests
Associate qti-associate-interaction Supported associate-reference.xml; core, fixture, conformance, a11y, browser tests
Graphic Associate qti-graphic-associate-interaction Supported graphicAssociate-reference.xml; core, fixture, conformance, a11y, browser tests
Graphic Gap Match qti-graphic-gap-match-interaction Supported graphicGapMatch-reference.xml; core, fixture, conformance, a11y, browser tests
Media qti-media-interaction Supported media-reference.xml; core, fixture, conformance, a11y, browser tests
Position Object qti-position-object-interaction Supported positionObject-reference.xml; core, fixture, conformance, a11y, browser tests
Select Point qti-select-point-interaction Supported selectPoint-reference.xml; core, fixture, conformance, a11y, browser tests
Slider qti-slider-interaction Supported slider-reference.xml; core, fixture, conformance, a11y, browser tests
Upload qti-upload-interaction Supported upload-reference.xml; core, fixture, conformance, a11y, browser tests
Drawing qti-drawing-interaction Supported drawing-reference.xml; core, fixture, conformance, a11y, browser tests
Portable Custom qti-portable-custom-interaction Supported host contract portableCustom-reference.xml; core, fixture, conformance, a11y, browser tests
Custom qti-custom-interaction Deprecated diagnostic Parsed for explicit warning; not a supported runtime target
End Attempt qti-end-attempt-interaction Supported endAttempt-reference.xml; core, fixture, conformance, a11y, browser tests

For automated review, the same support matrix is available as JSON:

node packages/cli/dist/index.js support-matrix

Goals

  • Implement the latest public QTI 3 item behavior explicitly, tracking QTI 3.0.1 ASI documents where applicable.
  • Support all QTI 3 interaction/question types in the target item profile.
  • Make scoring and response processing runnable in Node without a browser.
  • Publish an accessible, style-neutral web component player for host applications.
  • Publish a reusable conformance test suite.
  • Load QTI package ZIPs and assessment-test item references for import tests and item-focused tooling.
  • Transcode QTI 3 items and packages to explicit, versioned QTI 1.2, QTI 2.1, QTI 2.2, and product-specific import profiles.
  • Resolve host-provided QTI 3 PNP data into player-neutral delivery intents. Hosts retain responsibility for identity, storage, authorization, and institutional policy.
  • Keep qti3-core and qti3-cli free of third-party runtime dependencies.
  • Make unsupported or invalid behavior visible through structured diagnostics.

Non-goals

  • The core and browser player do not depend on React, Vue, or another UI framework.
  • The browser player uses native custom elements without Lit.
  • The project does not provide a full assessment-test runner, reusable LMS controller, navigation UI, or delivery shell. The LMS, assessment engine, or harness owns that.
  • The project does not provide shared stimulus delivery (S-*), full test delivery (T-*), timing policy, proctoring, analytics, rostering, gradebook, or LTI integration.
  • The PNP package does not fetch, store, transmit, or authorize candidate preference records. Hosts provide PNP data and own privacy, consent, policy, and service access.
  • The project does not provide product chrome, branding, candidate navigation, or a host product design system.
  • Production configuration must be explicit. The project should fail fast instead of using hidden fallbacks.
  • QTI XML is not compiled as framework templates.
  • There is no global singleton state store. Multiple players should not share a brain.
  • Runtime XSD validation is out of scope.

Packages

Package Path Purpose
@longsightgroup/qti3-core packages/core Zero-third-party-runtime-dependency parser, typed model, validation, processing, scoring, state
@longsightgroup/qti3-player packages/player Native custom element browser player
@longsightgroup/qti3-player-react packages/player-react React adapter for the native web component
@longsightgroup/qti3-player-preact packages/player-preact Preact adapter for the native web component
@longsightgroup/qti3-conformance packages/conformance Fixture runner and support matrix tooling
@longsightgroup/qti3-a11y packages/a11y Accessibility contracts and automated checks
@longsightgroup/qti3-fixtures packages/fixtures QTI item fixtures and expected outcomes
@longsightgroup/qti3-pnp packages/pnp Dependency-free QTI 3 PNP parser, normalizer, resolver, and diagnostics
@longsightgroup/qti3-writer packages/writer Framework-neutral QTI-shaped authoring XML and item-bank package writer with typed diagnostics
@longsightgroup/qti3-migrator packages/migrator QTI 1.2 and QTI 2.x package/item migration into QTI 3 authoring items, XML, and package input
@longsightgroup/qti3-transcoder packages/transcoder Profile-driven QTI 3 output for QTI 1.2, QTI 2.1, QTI 2.2, Canvas Classic/New Quizzes, and Moodle XML
@longsightgroup/qti3-cli packages/cli Zero-third-party-runtime-dependency validation, trusted scoring, delivery preparation, package inspection, and evidence CLI

QTI package and assessment-test support belongs in tooling, fixtures, and examples for import, inspection, validation, and item loading. The browser player renders one assessment item at a time.

The transcoder converts QTI 3 items and packages through versioned profiles. Standard QTI output and product-specific compatibility use separate profiles. Each conversion returns typed mapping diagnostics instead of selecting an LMS dialect or fallback automatically.

Framework adapters wrap the native web component or core API. The repository includes React and Preact adapters. Other adapters can be added without moving framework dependencies into qti3-core.

The browser player is a native web component:

<script type="module" src="/qti3-player.js"></script>
<qti-assessment-item-player id="player"></qti-assessment-item-player>
const player = document.getElementById("player");

await player.loadXml(xml, {
  status: "interacting",
  sessionOptions: {
    randomSeed: "candidate-42-item-1",
  },
  sessionControl: {
    validateResponses: true,
    showFeedback: false,
  },
});

// Attempt JSON stores QTI state, not host functions or capability registries. Pass the same
// sessionOptions again when loading saved state into a new player instance. reset() and restore()
// on this player retain the options object supplied above.

await player.loadUrl("/items/item-1.xml", {
  fetchXml: async (url) => {
    const response = await fetch(url);
    if (!response.ok) throw new Error(`Unable to load ${url}`);
    return response.text();
  },
});

await player.loadXml(packageItemXml, {
  resolveAsset: (url) => packageAssetUrlFor(url),
  resolveStylesheet: (stylesheet) => ({
    href: packageStylesheetUrlFor(stylesheet.href),
  }),
});

// Host-controlled opt-in. qti3 preserves qti-keyword-emphasis by default, but only
// applies extra visual emphasis when the host has resolved candidate AfA/PNP support.
player.keywordEmphasisEnabled = true;
// Equivalent DOM API:
player.setAttribute("data-keyword-emphasis", "true");

player.addEventListener("qti-statechange", (event) => {
  saveState(event.detail.state);
});

player.addEventListener("qti-responsechange", (event) => {
  console.log(event.detail.responseIdentifier, event.detail.value);
});

player.addEventListener("qti-validation", (event) => {
  console.log(event.detail.validationMessages, event.detail.state);
});

resolveAsset is a host hook for package or virtual-file environments. The player calls it for relative src, href, and data asset URLs after rendering the item, and also when hosts resolve companion-material file references through getCompanionMaterialsResolution() or catalog support content through getCatalogDeliveryResolution(). Items whose assets are available at browser URLs can omit it. Use the hook for package-backed media, graphic, and drawing assets so controls and serialized responses resolve authored references.

qti-stylesheet delivery is a separate host contract. Core preserves stylesheet metadata, and the browser player attaches item stylesheets only when loadXml / loadUrl receives resolveStylesheet. The hook must return a candidate-safe stylesheet URL for package-local CSS or undefined to decline delivery. Package import, path validation, authorization, immutable asset preservation, and unsafe URL rejection remain host responsibilities.

When resolveStylesheet is omitted, the player treats stylesheets as disabled. It does not attach them or emit player.stylesheet.unresolved diagnostics.

Interaction response contracts

  • qti-media-interaction records play experiences as a single / integer response. The player supports the QTI shared vocabulary data-qti-media-player-controls tokens none, default, play, rewind, captions, and audioDescription. none suppresses native media controls; all other recognized tokens render native browser controls and are preserved on the rendered <audio> or <video> element for host styling or custom enhancement. The player also supports data-qti-media-player-pause-delay and data-qti-media-player-pause-duration as second-based timers around playback start and user-initiated pauses.
  • qti-slider-interaction uses a typed numeric definition for bounds, step size, orientation, reverse direction, response validation, and keyboard operation. The player keeps display values and scored values on the same decimal-safe scale.
  • qti-drawing-interaction requires a single / file response and serializes candidate drawings as image file data URLs. The player exposes a native pen color input; the toolbar-palette-none interaction class hides it and locks the pen to black.
  • qti-portable-custom-interaction supports the Portable Custom Interaction (PCI) host contract: parsing and validating launch metadata, interaction markup, template/context bindings, stylesheets, catalog info, opaque suspend/resume state, and response/state events. The player exposes a qti3-portable-custom-host element with small launch metadata and emits qti-portable-custom-mount with the full parsed definition so a host-provided PCI runtime can attach the module. Production module loading, sandboxing, CSP, tenant allowlists, and audit policy remain host responsibilities.

Styling

The browser player ships structural styles for layout, focus visibility, forced-colors support, and accessible interaction behavior. Host products own product chrome, branding, layout density, typography, colors, page-level spacing, candidate navigation, and broader candidate experience styling.

The player renders in light DOM, so host CSS can style it directly:

qti-assessment-item-player {
  font:
    16px/1.5 system-ui,
    sans-serif;
  color: #1f2937;
}

qti-assessment-item-player .qti3-interaction {
  margin-block: 1rem;
}

qti-assessment-item-player .qti3-choice-option[data-selected="true"] {
  border-color: currentColor;
}

Rendered elements use qti3-* class names for player structure, such as qti3-player, qti3-item-body, qti3-interaction, and interaction-specific classes like qti3-choice, qti3-textEntry, and qti3-hotspot. Authored QTI shared-vocabulary classes that start with qti- are preserved on rendered interactions where applicable.

The player exposes theme-aware CSS custom properties for structural controls. For order interaction rows, hosts can set --qti3-order-row-border-color, --qti3-order-row-background, and --qti3-order-row-background-hover on the player or an ancestor. Their defaults use system colors, so they work in light mode, dark mode, and forced-colors environments.

QTI shared vocabulary classes define portable item presentation preferences. Classes such as qti-labels-none, qti-labels-decimal, qti-input-control-hidden, and qti-unselected-hidden are parsed, validated, preserved, and implemented according to the support matrix. Host applications provide product-specific CSS. The machine-readable support matrix is the source of truth for shipped shared vocabulary coverage. Inspect the sharedVocabularyClasses section for each class name, scope, interaction surface, support level, fixture evidence, and test evidence:

node packages/cli/dist/index.js support-matrix

See the 1EdTech QTI 3 Standardized Shared Vocabulary and CSS Classes document for the normative shared vocabulary and example CSS.

Platform

  • ESM-only packages.
  • Node.js 22+.
  • Modern browsers.
  • Deno 2+.
  • Light DOM for the default player, rendered into the page DOM so host CSS and tooling can inspect and style it directly.

Tooling choices

  • TypeScript 6+
  • pnpm
  • Vite 8+
  • Vitest
  • Playwright
  • axe-core
  • oxfmt
  • oxlint

Checks

Every change should pass the same release gate locally, in CI, and before publish:

pnpm release:check

pnpm release:check runs formatting, typecheck, lint, unit and conformance tests, accessibility checks, dependency policy, build, source-map validation, package export checks, browser coverage, support metadata, and the built CLI fixture runner. It does not require official 1EdTech certification artifacts.

Run certification checks separately:

QTI3_EXTERNAL_QTI_DIR=/path/to/official/qti \
pnpm certification:check

pnpm test:external remains optional for local development and skips when QTI3_EXTERNAL_QTI_DIR is not configured. pnpm test:external:required and pnpm certification:check fail fast unless official external QTI content is provided. QTI3_EXTERNAL_VALIDATOR_REPORT may be supplied as supplemental evidence, but Basic IMPORT proof is generated by importing official item and test package zips.

Start the browser harness with:

pnpm dev

Open the shared vocabulary gallery from the same Vite dev server:

pnpm dev
open "http://127.0.0.1:5173/sv-gallery?case=choice-selections-dark"

From a source checkout, run pnpm build before using the built CLI entry point. Published packages expose the same commands through the qti3 binary.

The CLI can parse local QTI directories, including external reference sets:

node packages/cli/dist/index.js parse-dir /path/to/items

Use validation when diagnostics should fail the command:

node packages/cli/dist/index.js validate-dir /path/to/items

Score each item with its declared correct responses:

node packages/cli/dist/index.js score-correct-dir /path/to/items

Use a server-trusted response file to score one item:

node packages/cli/dist/index.js score item.xml --responses trusted-responses.json

Prepare static or server-materialized adaptive candidate XML with:

node packages/cli/dist/index.js prepare-delivery item.xml --out candidate.xml
node packages/cli/dist/index.js prepare-delivery adaptive.xml \
  --mode server-materialized-adaptive \
  --state trusted-state.json \
  --out candidate.xml

Response and state JSON are server-trusted inputs, not raw browser submissions. Hosts manage adaptive turns through the library API and versioned attempt-state contract.

Inspect a package with:

node packages/cli/dist/index.js inspect-package /path/to/package.zip

This enumerates XML files, assets, manifest/test item references, and parse diagnostics for loadable assessment items.

Use strict package validation for conformance-oriented package checks:

node packages/cli/dist/index.js validate-package /path/to/package.zip

Strict package validation requires imsmanifest.xml, requires manifest or assessment-test item references, and fails direct item XML files that are not referenced by the package metadata.

Write standalone reference items for targeted interactions, processing patterns, and adaptive behavior:

node packages/cli/dist/index.js write-fixtures packages/fixtures/xml

The support matrix is machine-readable. It includes evidence for supported interactions, deprecated interactions, processing elements, and shared vocabulary classes:

node packages/cli/dist/index.js support-matrix

The accessibility proof matrix is also machine-readable. It lists each interaction's role, keyboard contract, automated evidence, and manual assistive-technology scripts:

node packages/cli/dist/index.js a11y-proof

Before publishing, verify these requirements:

  • Supported interactions need parser, validation, scoring, rendering, keyboard, and accessibility evidence.
  • Accessibility checks cover real operation as well as automated scans.
  • Dependencies stay small, exact, and reviewed.
  • Published packages use explicit npm files allowlists so package contents stay small and deliberate.
  • Release checks must pass before publishing. Certification evidence is checked separately.

Attempt state

Serialized attempt state uses qti3.attempt-state.v1. It captures responses, outcomes, generated template values, validation messages, lifecycle status, and QTI's built-in completionStatus outcome. PCI suspend/resume data is stored as opaque JSON under interactionStates keyed by response identifier.

  • Hosts can save, restore, and review attempts through this state contract.
  • Hosts can check restored JSON with isQtiAttemptStateV1() or assertQtiAttemptStateV1().
  • Non-adaptive items reset authored outcomes before each scoring run.
  • Adaptive items retain outcome values across response-processing runs.
  • For non-adaptive items, endAttempt() completes the item after a valid score run.
  • For adaptive items, endAttempt() runs response processing and leaves the item open unless processing sets completionStatus to "completed".
  • Templated items restore saved template values before deriving generated correct responses, so resume does not require the original random seed.

Randomized item instances

qti3 supports QTI-native randomized item instances through template processing. Authors can use qti-random-integer, generated template variables, printed variables, and qti-set-correct-response to build deterministic randomized math-style items. The canonical random-integer-template-reference fixture proves this path by rendering a generated equation, storing the generated values in templateValues, and scoring the generated numeric answer.

Hosts should create a variant once, persist the full qti3.attempt-state.v1 value, and restore from that state on resume. A randomSeed is useful for deterministic initial generation, but saved templateValues are the authority after an attempt exists. Static candidate-safe XML redaction remains conservative for template-processing items. Server-materialized adaptive delivery can render safe template-derived presentation values from authoritative session state while stripping generated answer keys, template and response processing, mappings, lookup tables, and declaration defaults from the candidate XML.

Coverage

qti3 includes public synthetic fixtures for every current, non-deprecated QTI 3 item interaction. The canonical examples use MIT-licensed classroom prompts instead of placeholder QTI terminology. The fixtures cover response shape, scoring, browser rendering, keyboard operation, and accessibility evidence.

Processing coverage includes response processing, template processing, feedback, printed variables, MathML/template variables, catalogs, shared CSS vocabulary, advanced numeric/container/point expressions, and adaptive completionStatus behavior.

The manual harness exposes debugger panels for responses, outcomes, template values, diagnostics, validation messages, serialized state, package item navigation, action history, and accessibility proof scripts.

Publishing

The packages listed above publish under the longsightgroup npm organization. Releases publish from the longsightgroup/qti3 repository after pnpm release:check passes. Package tarballs come from the same checked build output that CI verifies.

Certification

The project is not certified. pnpm certification:check requires official 1EdTech external content and validator evidence.

About

Dependency-light, framework-neutral TypeScript reference implementation for QTI 3 assessment items.

Topics

Resources

Stars

5 stars

Watchers

1 watching

Forks

Releases

Packages

Contributors

Languages