tscircuit/ti

ti tscircuit library

Version
1.0.198
License
unset
Stars
0

system-block-ui/src/rendering/source-downloads.test.ts

import { describe, expect, test } from "bun:test";
import type { CircuitJson } from "circuit-json";
import { strFromU8, unzipSync } from "fflate";
import {
  createCircuitJsonDownloadBlob,
  getCircuitJsonDownloadFileName,
} from "./export-circuit-json";
import {
  createTscircuitTsxZipBlob,
  getTscircuitTsxZipFileName,
} from "./export-tscircuit-tsx";
import {
  GENERATED_SYSTEM_MAIN_FILE_NAME,
  type GeneratedSystemSourceArtifacts,
} from "./generated-source-files";

const circuitJson = [
  {
    type: "source_component",
    source_component_id: "source_component_0",
    name: "R1",
    ftype: "simple_resistor",
  },
  {
    type: "schematic_graphic",
    schematic_graphic_id: "schematic_graphic_0",
    schematic_sheet_id: "schematic_sheet_0",
    svg_content: "<svg><text>System overview</text></svg>",
  },
] as unknown as CircuitJson;

const artifacts: GeneratedSystemSourceArtifacts = {
  tsx: [
    'import { SYSTEM_DIAGRAM_SVG } from "./GeneratedSystem.system-diagram"',
    "",
    "export default () => (",
    "  <board>",
    "    <schematicgraphic svgContent={SYSTEM_DIAGRAM_SVG} />",
    "  </board>",
    ")",
    "",
  ].join("\n"),
  systemDiagramModuleFileName: "GeneratedSystem.system-diagram.ts",
  systemDiagramModuleSource:
    'export const SYSTEM_DIAGRAM_SVG = "<svg>System diagram</svg>"\n',
};

describe("browser source downloads", () => {
  test("creates human-readable Circuit JSON with a final newline", async () => {
    const blob = createCircuitJsonDownloadBlob(circuitJson);
    const downloadedJson = await blob.text();

    // Bun appends an explicit UTF-8 charset to JSON Blob media types.
    expect(blob.type.split(";", 1)[0]).toBe("application/json");
    expect(downloadedJson).toBe(
      [
        "[",
        "  {",
        '    "type": "source_component",',
        '    "source_component_id": "source_component_0",',
        '    "name": "R1",',
        '    "ftype": "simple_resistor"',
        "  },",
        "  {",
        '    "type": "schematic_graphic",',
        '    "schematic_graphic_id": "schematic_graphic_0",',
        '    "schematic_sheet_id": "schematic_sheet_0",',
        '    "svg_content": "<svg><text>System overview</text></svg>"',
        "  }",
        "]",
        "",
      ].join("\n"),
    );
    expect(downloadedJson.endsWith("\n")).toBe(true);
    expect(JSON.parse(downloadedJson)).toEqual(circuitJson);
    expect(getCircuitJsonDownloadFileName({ projectName: "demo board" })).toBe(
      "demo-board.circuit.json",
    );
  });

  test("creates a deterministic ZIP with exactly the generated TSX files", async () => {
    const firstBlob = createTscircuitTsxZipBlob(artifacts);
    const secondBlob = createTscircuitTsxZipBlob(artifacts);
    const firstBytes = new Uint8Array(await firstBlob.arrayBuffer());
    const secondBytes = new Uint8Array(await secondBlob.arrayBuffer());
    const archive = unzipSync(firstBytes);

    expect(firstBlob.type).toBe("application/zip");
    expect([...firstBytes.slice(0, 4)]).toEqual([0x50, 0x4b, 0x03, 0x04]);
    expect(firstBytes).toEqual(secondBytes);
    expect(Object.keys(archive).sort()).toEqual(
      [
        GENERATED_SYSTEM_MAIN_FILE_NAME,
        artifacts.systemDiagramModuleFileName,
      ].sort(),
    );
    expect(
      strFromU8(archive[GENERATED_SYSTEM_MAIN_FILE_NAME] ?? new Uint8Array()),
    ).toBe(artifacts.tsx);
    expect(
      strFromU8(
        archive[artifacts.systemDiagramModuleFileName] ?? new Uint8Array(),
      ),
    ).toBe(artifacts.systemDiagramModuleSource);
    expect(getTscircuitTsxZipFileName({ projectName: "../../CON" })).toBe(
      "project-CON.tscircuit-tsx.zip",
    );
  });
});