Skip to content

Stroop Task never calls done, so it can never be completed #1634

Open

Description

@joshunrau

The built-in Stroop Task can never be completed. StroopTask takes a done prop but never reads it, because the component is declared as React.FC<StroopTaskProps> = () => { ... }. When the 60-second countdown reaches zero, it shows "Time is up! Your final score is N" and stops there. Nothing is ever submitted, the instrument never moves on to its summary, and no record is created. The instrument is not seeded into demo instances (apps/api/src/demo/demo.service.ts has no import for it), so this only affects someone who uploads it from the library. Anyone who does gets an instrument that cannot be completed.

There is a related gap in DNP_STROOP_TASK/index.tsx: render creates a React root and never unmounts it, so the 1s setInterval keeps running. Today that has no effect, since done is never called and the host tears down the iframe after a submit. It is worth handling in the same change.

Where

packages/instrument-library/src/interactive/DNP_STROOP_TASK/StroopTask.tsx:14:

export const StroopTask: React.FC<StroopTaskProps> = () => {

packages/instrument-library/src/interactive/DNP_STROOP_TASK/StroopTask.tsx:20-31: the countdown stops at 0 and calls nothing.

packages/instrument-library/src/interactive/DNP_STROOP_TASK/index.tsx:15-20: render passes done into a component that ignores it.

Reproduce

  1. Upload the built DNP_STROOP_TASK bundle to an instance and start it for a subject.
  2. Answer a few prompts and wait 60 seconds.

Actual: "Time is up! Your final score is N" stays on screen indefinitely. No record is saved and the summary never appears.
Expected: when time runs out, the task calls done({ score }) and the renderer moves on to the summary with the record saved.

Tests

  • packages/instrument-library/src/__tests__/StroopTask.test.tsx renders <StroopTask done={vi.fn()} /> but never checks the mock. When fixing, add should report the final score through done once time runs out, asserting done is called once with { score } after advanceSeconds(60), and not called again on later ticks.
  • packages/instrument-library/src/__tests__/DNP_STROOP_TASK.test.tsx: should forward the score the task reports to the done callback calls the captured prop directly, so it passes even though the component never calls it. Its afterEach wraps createRoot to unmount roots the source leaves mounted. If render starts unmounting its own root on completion, that workaround can go.

Suggested fix

Destructure done in StroopTask, and call done({ score }) once from an effect when timeLeft reaches 0, guarded so it fires only once.

Found while raising unit test coverage in #1632.

Activity

  1. added
    BugType: existing behavior is wrong
    Difficulty: LowIsolated change, about 2 hours or less
    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in library
    Good First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributor
    on Oct 5, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Area: InstrumentsInstrument authoring API, bundling, runtime, rendering, measures and the built-in libraryBugType: existing behavior is wrongDifficulty: LowIsolated change, about 2 hours or lessGood First IssueWell-scoped, low-risk and self-contained; a good entry point for a new contributorPriority: MediumReal value; schedule normally

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions