← Files Biological Sequence & Alignment ViewerARCHIVED FILE

src/workbench-state.test.ts

9.66 KB · Sep 30, 2026 · 23:01 UTC

↓ Download file

import { describe, expect, it } from "vitest";

import { parseMsa } from "./msa/parser";
import { parseSequenceDocument } from "./sequence/parser";
import {
  alignmentWorkbenchReducer,
  createAlignmentWorkbenchState,
  createSequenceWorkbenchState,
  parseWorkbenchSession,
  sequenceWorkbenchReducer,
  serializeWorkbenchSession,
  settleRestoredJobs,
  summarizeWorkbenchArtifact,
  type WorkbenchSession,
} from "./workbench-state";

describe("shared workbench state", () => {
  it("keeps artifact bytes out of bounded live job results", () => {
    const summary = summarizeWorkbenchArtifact({
      content: "A".repeat(600 * 1_024),
      createdAt: 1,
      format: "aligned-fasta",
      id: "artifact",
      mediaType: "text/x-fasta",
      name: "large.aln-fasta",
      provenance: {
        engine: "test",
        parameters: {},
        sourceRevision: 1,
      },
    });

    expect(summary).not.toHaveProperty("content");
    expect(summary).toMatchObject({
      format: "aligned-fasta",
      name: "large.aln-fasta",
    });
  });

  it("applies sequence edits through bounded undo/redo history", () => {
    const original = sequenceDocument("ACGT");
    const edited = sequenceDocument("AAGT");
    let state = createSequenceWorkbenchState(original);
    state = sequenceWorkbenchReducer(state, {
      description: "replace residue 2",
      document: edited,
      type: "apply-sequence-document",
    });
    expect(state).toMatchObject({ dirty: true, revision: 1 });
    expect(state.document.records[0]?.sequence).toBe("AAGT");
    expect(state.document.records[0]?.evidenceCoordinatesStale).toBe(true);

    state = sequenceWorkbenchReducer(state, { type: "undo-sequence-document" });
    expect(state.document.records[0]?.sequence).toBe("ACGT");
    expect(state.document.records[0]?.evidenceCoordinatesStale).not.toBe(true);
    state = sequenceWorkbenchReducer(state, { type: "redo-sequence-document" });
    expect(state.document.records[0]?.sequence).toBe("AAGT");
    expect(state.document.records[0]?.evidenceCoordinatesStale).toBe(true);
  });

  it("preserves valid evidence on annotation-only/no-op edits and verifies restored records against the opened source", () => {
    const original = sequenceDocument("ACGT");
    const annotated = {
      ...original,
      records: original.records.map((record) => ({
        ...record,
        description: "An annotation-only change",
      })),
    };
    let state = sequenceWorkbenchReducer(
      createSequenceWorkbenchState(original),
      {
        type: "apply-sequence-document",
        document: annotated,
        description: "annotations",
      },
    );
    expect(state.document.records[0]?.evidenceCoordinatesStale).not.toBe(true);
    state = sequenceWorkbenchReducer(state, {
      type: "apply-sequence-document",
      document: state.document,
      description: "exact no-op",
    });
    expect(state.document.records[0]?.evidenceCoordinatesStale).not.toBe(true);
    state = sequenceWorkbenchReducer(state, {
      type: "apply-sequence-document",
      document: sequenceDocument("TACGT"),
      description: "insert",
    });
    expect(state.document.records[0]?.evidenceCoordinatesStale).toBe(true);
    state = sequenceWorkbenchReducer(state, {
      type: "restore-sequence-document",
      document: original,
      description: "restore original",
    });
    expect(state.sourceDocument).toBe(original);
    expect(state.document.records[0]?.evidenceCoordinatesStale).not.toBe(true);
    const changedSnapshot = sequenceDocument("TACGT");
    changedSnapshot.records = changedSnapshot.records.map((record) => ({
      ...record,
      evidenceCoordinatesStale: false,
    }));
    state = sequenceWorkbenchReducer(state, {
      type: "restore-sequence-document",
      document: changedSnapshot,
      description: "restore edited snapshot",
    });
    expect(state.document.records[0]?.evidenceCoordinatesStale).toBe(true);
    const reconstructed = sequenceWorkbenchReducer(state, {
      type: "reset-sequence-document",
      document: changedSnapshot,
      sourceDocument: original,
    });
    expect(reconstructed.sourceDocument).toBe(original);
    expect(reconstructed.history).toHaveLength(0);
    expect(reconstructed.document.records[0]?.evidenceCoordinatesStale).toBe(
      true,
    );
  });

  it("invalidates selected alignment rows that disappear in an edit", () => {
    const original = alignmentDocument(`>a\nAAAA\n>b\nAAAT\n`);
    const edited = {
      ...original,
      rows: [original.rows[0] as (typeof original.rows)[number]],
    };
    let state = createAlignmentWorkbenchState(original);
    state = alignmentWorkbenchReducer(state, {
      rowIds: original.rows.map(({ id }) => id),
      type: "select-alignment-rows",
    });
    state = alignmentWorkbenchReducer(state, {
      description: "remove b",
      document: edited,
      type: "apply-alignment-document",
    });
    expect(state.selectedRows).toEqual([original.rows[0]?.id]);
  });

  it("tracks cancellable jobs and ignores unknown stale completions", () => {
    let state = createSequenceWorkbenchState(sequenceDocument("ACGT"));
    state = sequenceWorkbenchReducer(state, {
      job: {
        id: "job-1",
        kind: "statistics",
        message: "Running",
        parameters: {},
        progress: 0,
        startedAt: 1,
        status: "running",
      },
      type: "start-job",
    });
    state = sequenceWorkbenchReducer(state, {
      completedAt: 2,
      id: "stale-job",
      message: "stale",
      result: {},
      type: "complete-job",
    });
    expect(state.jobs[0]?.status).toBe("running");
    state = sequenceWorkbenchReducer(state, {
      id: "job-1",
      type: "cancel-job",
    });
    expect(state.jobs[0]?.status).toBe("cancelled");
  });

  it("round-trips a versioned bounded session contract", () => {
    const session: WorkbenchSession = {
      artifacts: [
        {
          content: ">derived\nACGT\n",
          createdAt: 1,
          format: "fasta",
          id: "11111111-1111-4111-8111-111111111111",
          mediaType: "text/x-fasta",
          name: "derived.fasta",
          provenance: {
            engine: "test",
            parameters: {},
            sourceRevision: 2,
          },
        },
      ],
      createdAt: 1,
      dirty: false,
      jobs: [],
      revision: 3,
      schemaVersion: 1,
      source: { fileName: "demo.fasta", format: "fasta" },
      tracks: [],
      view: {
        mode: "sequence",
        sequence: {
          geneticCodeId: 1,
          layout: "linear",
          orientation: "forward",
          paletteId: "neutral",
          selectedFeatureId: null,
          selectedRecordId: "record-1",
          selection: null,
          showFeatures: true,
          showQuality: true,
          showTranslation: true,
          synchronizedViews: true,
          viewport: null,
          wrapWidth: 60,
        },
      },
    };
    expect(parseWorkbenchSession(serializeWorkbenchSession(session))).toEqual(
      session,
    );
    const snapshot = sequenceDocument("ACGT");
    snapshot.records = snapshot.records.map((record) => ({
      ...record,
      evidenceCoordinatesStale: true,
    }));
    const snapshotSession = {
      ...session,
      snapshot: { sequenceDocument: snapshot },
      view: {
        ...session.view,
        sequence: {
          ...session.view.sequence!,
          selectedRecordId: snapshot.records[0]?.id ?? "",
        },
      },
    };
    expect(
      parseWorkbenchSession(serializeWorkbenchSession(snapshotSession)).snapshot
        ?.sequenceDocument?.records[0]?.evidenceCoordinatesStale,
    ).toBe(true);
    expect(() =>
      parseWorkbenchSession(
        JSON.stringify({
          ...snapshotSession,
          snapshot: {
            sequenceDocument: {
              ...snapshot,
              records: snapshot.records.map((record) => ({
                ...record,
                evidenceCoordinatesStale: "false",
              })),
            },
          },
        }),
      ),
    ).toThrow(/stale status must be boolean/);
    const qualitySession: WorkbenchSession = {
      ...session,
      jobs: [{
        completedAt: 2,
        id: "22222222-2222-4222-8222-222222222222",
        kind: "quality-report",
        message: "quality-report completed.",
        parameters: { analysis: "quality-report" },
        progress: 1,
        result: { analysis: "quality-report", scope: { analyzedReads: 2, populationReads: 2 } },
        startedAt: 1,
        status: "completed",
      }],
      source: { fileName: "synthetic-quality-session.fastq", format: "fastq" },
    };
    expect(parseWorkbenchSession(serializeWorkbenchSession(qualitySession))).toEqual(qualitySession);
    expect(() => parseWorkbenchSession('{"schemaVersion":2}')).toThrow(
      "not a compatible",
    );
  });
});

describe("restored workbench jobs", () => {
  it("does not revive jobs that no longer have a running executor", () => {
    expect(
      settleRestoredJobs(
        [
          {
            id: "11111111-1111-4111-8111-111111111111",
            kind: "statistics",
            message: "Running",
            parameters: {},
            progress: 0.5,
            startedAt: 1,
            status: "running",
          },
        ],
        10,
      ),
    ).toEqual([
      expect.objectContaining({
        completedAt: 10,
        message: expect.stringContaining("restored"),
        status: "cancelled",
      }),
    ]);
  });
});

function sequenceDocument(sequence: string) {
  return parseSequenceDocument({
    contents: `>demo\n${sequence}\n`,
    fileName: "demo.fasta",
  });
}

function alignmentDocument(contents: string) {
  const result = parseMsa(contents, "demo.aln-fasta");
  if (result.status !== "success") throw new Error(result.message);
  return result.document;
}

SHA-256: db3ead08ddb45a1c0b12d0a81ce836101c2b5c1095bd0cfd609b86d9db2652be