← Files Biological Sequence & Alignment ViewerARCHIVED FILE

src/sequence/sequence-workbench-panel.test.tsx

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

↓ Download file

import { cleanup, render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { afterEach, describe, expect, it, vi } from "vitest";

import { createSequenceWorkbenchState } from "../workbench-state";
import { parseSequenceDocument } from "./parser";
import { SequenceWorkbenchPanel } from "./sequence-workbench-panel";
import { parseSequenceTrack } from "./tracks";
import { applySequenceAnnotationRequest } from "./workbench-controller";

afterEach(() => cleanup());

describe("compound chromosome annotations", () => {
  it("keeps an edit draft mounted while another contextual tool is open", async () => {
    renderPublicChromosomeFeatures();

    expect(
      screen.queryByRole("textbox", {
        name: "Replacement or inserted sequence",
      }),
    ).not.toBeInTheDocument();
    await userEvent.click(screen.getByRole("button", { name: "Edit copy" }));
    const draft = screen.getByRole("textbox", {
      name: "Replacement or inserted sequence",
    });
    await userEvent.type(draft, "ACGT");
    await userEvent.click(screen.getByRole("button", { name: "Analyze" }));

    expect(draft.isConnected).toBe(true);
    expect(draft.closest(".bio-workbench-tool-panel")).toHaveAttribute(
      "hidden",
    );
    expect(draft.closest(".bio-workbench-tool-panel")).toHaveAttribute("inert");
    expect(
      screen.getByRole("combobox", { name: "Genetic code" }),
    ).toBeVisible();

    await userEvent.click(screen.getByRole("button", { name: "Edit copy" }));
    expect(
      screen.getByRole("textbox", { name: "Replacement or inserted sequence" }),
    ).toBe(draft);
    expect(draft).toHaveValue("ACGT");
    expect(
      screen.queryByRole("combobox", { name: "Genetic code" }),
    ).not.toBeInTheDocument();
  });

  it("displays genuine UCSC chr22 exon counts on both transcript strands", async () => {
    const { record } = renderPublicChromosomeFeatures();

    await userEvent.click(screen.getByRole("button", { name: "Edit copy" }));

    expect(
      screen.getByRole("button", {
        name: "mRNA1 region · 1,001–5,000 · + · 2 exons",
      }),
    ).toBeInTheDocument();
    expect(
      screen.getByRole("button", {
        name: "mRNA2 region · 2,001–6,000 · - · 2 exons",
      }),
    ).toBeInTheDocument();
    expect(record.features.map(({ label }) => label)).toEqual(
      expect.arrayContaining(["mRNA1", "mRNA2"]),
    );
  });

  it("does not invent a compound exon label for a single-segment feature", async () => {
    renderPublicChromosomeFeatures({ includeSingleSegmentControl: true });

    await userEvent.click(screen.getByRole("button", { name: "Edit copy" }));

    const control = screen.getByRole("button", {
      name: "QA singleton control region · 20–30 · +",
    });
    expect(control).not.toHaveTextContent(/exons?/iu);
    expect(
      screen.getByRole("button", {
        name: "mRNA1 region · 1,001–5,000 · + · 2 exons",
      }),
    ).toBeInTheDocument();
  });
});

function renderPublicChromosomeFeatures({
  includeSingleSegmentControl = false,
}: { includeSingleSegmentControl?: boolean } = {}) {
  // Only the reference coordinate scaffold is synthetic. Both BED12 rows are
  // copied unchanged from Biopython commit
  // c9489604d1d9607602ca9199a3852c1219ed330f, Tests/Blat/bed12.bed.
  // Original public fixture SHA-256:
  // 2abf5cf6d9a42792af2cbdede04e63a869a6a4083e77272cd17ef26ca9211a2c.
  const scaffold = parseSequenceDocument({
    contents: `>chr22 QA-SYNTHETIC-COORDINATE-SCAFFOLD-ONLY\n${"A".repeat(6_000)}`,
    fileName: "QA-SYNTHETIC-CHROMOSOME-COORDINATE-SCAFFOLD.fasta",
  });
  const originalRecord = scaffold.records[0];
  if (originalRecord == null) {
    throw new Error("Expected a bounded chromosome coordinate scaffold.");
  }
  const publicTrack = parseSequenceTrack({
    content: [
      "chr22\t1000\t5000\tmRNA1\t960\t+\t1200\t4900\t255,0,0\t2\t567,488,\t0,3512,",
      "chr22\t2000\t6000\tmRNA2\t900\t-\t2300\t5960\t0,255,0\t2\t433,399,\t0,3601,",
    ].join("\n"),
    displayName: "public-ucsc-chr22-bed12.bed",
    format: "bed",
    id: "public-ucsc-chr22-bed12",
    requestedReference: originalRecord.sourceLabel,
  });
  const imported = applySequenceAnnotationRequest({
    document: scaffold,
    request: { action: "import", trackId: publicTrack.id },
    selectedRecordId: originalRecord.id,
    tracks: [publicTrack],
  }).document;
  const importedRecord = imported.records[0];
  if (importedRecord == null) {
    throw new Error("Expected imported genuine UCSC chromosome annotations.");
  }
  const record = includeSingleSegmentControl
    ? {
        ...importedRecord,
        features: [
          ...importedRecord.features,
          {
            end: 30,
            id: "QA-SYNTHETIC-SINGLE-SEGMENT-NEGATIVE-CONTROL",
            label: "QA singleton control",
            qualifiers: {},
            segments: [{ end: 30, start: 20 }],
            start: 20,
            strand: "+" as const,
            type: "region",
          },
        ],
      }
    : importedRecord;
  const document = { ...imported, records: [record] };
  const state = createSequenceWorkbenchState(document);

  render(
    <SequenceWorkbenchPanel
      geneticCodeId={1}
      onAddAnnotation={vi.fn()}
      onAlignRecords={vi.fn()}
      onCancelJob={vi.fn()}
      onDeleteAnnotation={vi.fn()}
      onEdit={vi.fn()}
      onExport={vi.fn()}
      onGeneticCodeChange={vi.fn()}
      onImportTrack={vi.fn()}
      onLoadTrack={vi.fn()}
      onRedo={vi.fn()}
      onRemoveTrack={vi.fn()}
      onRestoreSession={vi.fn()}
      onRunAnalysis={vi.fn()}
      onSaveSession={vi.fn()}
      onSelectFeature={vi.fn()}
      onUndo={vi.fn()}
      onUpdateAnnotation={vi.fn()}
      record={record}
      recordCount={1}
      state={state}
    />,
  );

  return { record, state };
}

SHA-256: 67ff47ecf01719c77ccbf88aaed990f46dda01b917419343dc6ee2324a249cff