← Files Biological Sequence & Alignment ViewerARCHIVED FILE

src/msa/exports.test.ts

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

↓ Download file

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

import {
  buildAlignedFasta,
  buildReferenceFasta,
  buildSearchHitsTsv,
  buildVisibleRangeSvg,
  buildVisibleRangeText,
} from "./exports";
import type { MsaSequenceRow } from "./types";

const rows: Array<MsaSequenceRow> = [
  {
    alignedSequence: "AC-G",
    id: "alpha",
    label: "alpha",
    ungappedLength: 3,
  },
  {
    alignedSequence: "ATCG",
    id: "beta",
    label: "beta",
    ungappedLength: 4,
  },
];

describe("MSA export helpers", () => {
  it("builds FASTA, reference FASTA, and visible-range text payloads", () => {
    expect(buildAlignedFasta(rows)).toBe(">alpha\nAC-G\n>beta\nATCG");
    expect(buildReferenceFasta({ label: "consensus", sequence: "ACCG" })).toBe(
      ">consensus\nACCG",
    );
    expect(buildVisibleRangeText({ endColumn: 3, rows, startColumn: 1 })).toBe(
      "alpha\tC-\nbeta\tTC",
    );
  });

  it("builds tabular hit exports and SVG range snapshots", () => {
    expect(
      buildSearchHitsTsv([
        {
          alignmentEndColumn: 3,
          alignmentStartColumn: 1,
          orientation: "forward",
          rowId: "alpha",
          rowLabel: "alpha",
          ungappedEndPosition: 3,
          ungappedStartPosition: 2,
        },
      ]),
    ).toContain("alpha\tforward\t2\t4\t2\t3");
    const svg = buildVisibleRangeSvg({
      endColumn: 4,
      rows,
      startColumn: 0,
    });
    expect(svg).toContain("<svg");
    expect(svg).toContain("alpha");
    expect(svg).toContain("AC-G");
  });

  it("neutralizes adversarial public-row labels in motif-hit TSV exports", () => {
    const hit = {
      alignmentEndColumn: 3,
      alignmentStartColumn: 1,
      orientation: "forward" as const,
      rowId: "CCNA2_MOUSE/171-297",
      rowLabel: "\u200b\t=HYPERLINK(\"https://example.invalid\")\r\n@SUM(1)",
      ungappedEndPosition: 3,
      ungappedStartPosition: 2,
    };

    const [header, row] = buildSearchHitsTsv([hit]).split("\n");
    expect(header).toBe(
      "row_label\torientation\talignment_start\talignment_end\tungapped_start\tungapped_end",
    );
    expect(row?.split("\t")).toEqual([
      "'\u200b =HYPERLINK(\"https://example.invalid\") @SUM(1)",
      "forward",
      "2",
      "4",
      "2",
      "3",
    ]);
  });

  it.each([
    ["=SUM(1,2)", "'=SUM(1,2)"],
    [" \t@SUM(1)", "'  @SUM(1)"],
    ["\ufeff\u200b-SUM(1)", "'\ufeff\u200b-SUM(1)"],
    ["\uff1dSUM(1)", "'\uff1dSUM(1)"],
    ["\u2003\uff20SUM(1)", "'\u2003\uff20SUM(1)"],
  ])("neutralizes copied alignment formula label %j", (label, expected) => {
    const row = {
      alignedSequence: "AC-G",
      id: "CCNA2_MOUSE/171-297",
      label,
      ungappedLength: 3,
    };

    expect(
      buildVisibleRangeText({
        endColumn: 3,
        rows: [row],
        startColumn: 1,
      }),
    ).toBe(`${expected}\tC-`);
  });

  it("prevents copied alignment labels from injecting spreadsheet rows or columns", () => {
    const maliciousRow = {
      alignedSequence: "AC-G",
      id: "CCNA2_MOUSE/171-297",
      label:
        "=HYPERLINK(\"https://example.invalid\")\r\n@SUM(1)\t=SUM(2)\r=SUM(3)\n+SUM(4)",
      ungappedLength: 3,
    };
    const genuinePublicRow = {
      alignedSequence: "A--G",
      id: "URS0000D6941A",
      label: "URS0000D6941A",
      ungappedLength: 2,
    };

    const result = buildVisibleRangeText({
      endColumn: 4,
      rows: [maliciousRow, genuinePublicRow],
      startColumn: 1,
    });
    const outputRows = result.split("\n");

    expect(outputRows).toHaveLength(2);
    expect(outputRows[0]?.split("\t")).toEqual([
      "'=HYPERLINK(\"https://example.invalid\") @SUM(1) =SUM(2) =SUM(3) +SUM(4)",
      "C-G",
    ]);
    expect(outputRows[1]?.split("\t")).toEqual(["URS0000D6941A", "--G"]);
  });

  it("does not change accession labels in FASTA or SVG exports", () => {
    const adversarialRow = {
      alignedSequence: "AC-G",
      id: "public-rfam-row",
      label: "=public-adversarial-label",
      ungappedLength: 3,
    };
    expect(buildAlignedFasta([adversarialRow])).toBe(
      ">=public-adversarial-label\nAC-G",
    );
    expect(
      buildVisibleRangeSvg({
        endColumn: 4,
        rows: [adversarialRow],
        startColumn: 0,
      }),
    ).toContain(">=public-adversarial-label</text>");
  });
});

SHA-256: e7de1d1d8539d879f4316c2cf795eb51607834850bc06f584a24b7c06e564da8