testMirrorJsxMakesTheFileTsx

tcmts.cli.test.e2e.mirror.testMirrorJsxMakesTheFileTsx · #wyx5kehx · term

JSX coming to a namespace whose file is .ts makes it .tsx (JSX only parses there), and imports of it follow; the JSX is written, and edited, as it is.

import { okIn, tempDir } from "#codebase/tcmts/cli/test/e2e/harness";
import assert from "node:assert/strict";
import { spawnSync } from "node:child_process";
import fs from "node:fs";
import path from "node:path";

export const testMirrorJsxMakesTheFileTsx = () => {
  const proj = path.join(tempDir("mirror tsx"), "proj");
  const cbDir = path.join(proj, "codebase");
  // a stand-in for React 19 (what JSX compiles to), so nothing is installed in the project
  const react = path.join(proj, "node_modules", "react");
  fs.mkdirSync(react, { recursive: true });
  fs.mkdirSync(cbDir);
  fs.writeFileSync(path.join(react, "package.json"), JSON.stringify({ name: "react", version: "19.0.0", type: "module", exports: { "./jsx-runtime": { types: "./jsx-runtime.d.ts", default: "./jsx-runtime.js" } } }));
  fs.writeFileSync(path.join(react, "jsx-runtime.js"), `export const jsx = (type, props) => ({ type, props });\nexport const jsxs = jsx;\nexport const Fragment = "fragment";\n`);
  fs.writeFileSync(path.join(react, "jsx-runtime.d.ts"), `export declare function jsx(type: unknown, props: unknown): JSX.Element;\nexport declare const jsxs: typeof jsx;\nexport declare const Fragment: unique symbol;\nexport declare namespace JSX {\n  type Element = { type: unknown; props: unknown };\n  interface IntrinsicElements { [name: string]: Record<string, unknown> }\n}\n`);
  fs.writeFileSync(path.join(proj, ".gitignore"), "node_modules/\ncodebase/\n");
  fs.writeFileSync(path.join(proj, "package.json"), JSON.stringify({ type: "module", dependencies: { react: "19.0.0" } }));
  fs.writeFileSync(path.join(proj, "tsconfig.json"), JSON.stringify({ compilerOptions: { strict: true, module: "nodenext", jsx: "react-jsx", noEmit: true, allowImportingTsExtensions: true } }));
  fs.writeFileSync(path.join(proj, "ui.ts"), "export const shout = (s: string): string => s.toUpperCase();\n");
  fs.writeFileSync(path.join(proj, "main.ts"), `import { shout } from "./ui.ts";\n\nexport const hi = (): string => shout("hi");\n`);
  assert.equal(spawnSync("git", ["-C", proj, "init", "-q"]).status, 0);
  const ok = (...args: string[]) => okIn(cbDir, ...args);
  ok("init");
  fs.rmSync(path.join(cbDir, "scratch.ts"));
  ok("import", "..", "--into", "app", "--mirror");
  const title = `export const Title = ({ text }: { text: string }) => <>{text && <b className="t">{shout(text)}</b>}</>;`;
  fs.writeFileSync(path.join(cbDir, "scratch.tsx"), `// tcm: namespace app.ui\nimport { shout } from "#codebase/app/ui";\n\n${title}\n`);
  assert.match(ok("add", "scratch.tsx"), /mirror: wrote ui\.ts \(now ui\.tsx\)/);
  assert.ok(!fs.existsSync(path.join(proj, "ui.ts")));
  assert.equal(fs.readFileSync(path.join(proj, "ui.tsx"), "utf8"), `export const shout = (s: string): string => s.toUpperCase();\n\n${title}\n`);
  assert.match(fs.readFileSync(path.join(proj, "main.ts"), "utf8"), /import \{ shout \} from "\.\/ui\.tsx";/);
  assert.match(ok("mirror", "--check"), /the files hash to what the codebase holds/);
  ok("edit", "app.ui.Title", "--file", "edit.tsx");
  assert.match(fs.readFileSync(path.join(cbDir, "edit.tsx"), "utf8"), /\nexport const Title = \(\{ text \}: \{ text: string \}\) => <>\{text && <b className="t">\{shout\(text\)\}<\/b>\}<\/>/);
}

uses