118 lines
7 KiB
TypeScript
118 lines
7 KiB
TypeScript
import { describe, expect, test } from "bun:test";
|
|
import { mkdir, mkdtemp, readFile, writeFile } from "node:fs/promises";
|
|
import { join } from "node:path";
|
|
import { tmpdir } from "node:os";
|
|
import { analyzeReactComponent, astroToSpug, backportProject, cssToStcss, htmlToSpug, reactToSpug, zolaToSpug } from "../core/backport.ts";
|
|
import { shadcnIntegration } from "../integrations/shadcn.ts";
|
|
import { sassLoader } from "../views/css/sass.ts";
|
|
import { TemplateEngine } from "../views/engine/runtime.ts";
|
|
|
|
describe("backport", () => {
|
|
test("rebuilds normalized CSS as STCSS", () => {
|
|
const result = cssToStcss(".card { color: red; &:hover { color: blue; } }");
|
|
expect(result).toContain(".card {");
|
|
expect(result).toContain("color: red;");
|
|
});
|
|
|
|
test("turns HTML structures into indented SPUG", () => {
|
|
const result = htmlToSpug('<main class="page"><h1>Hello</h1><img src="hero.png"></main>');
|
|
expect(result).toContain("main.page");
|
|
expect(result).toContain(" h1");
|
|
expect(result).toContain('img(src="hero.png")');
|
|
});
|
|
|
|
test("ports Astro layouts, slots and imported components", () => {
|
|
const page = astroToSpug(`---\nimport Layout from "../layouts/Layout.astro";\nimport Card from "../components/Card.astro";\n---\n<Layout title="Hello"><Card title={title} /><p>Body</p></Layout>`, "/site/src/pages/index.astro", "/site/src/pages");
|
|
expect(page).toContain("extends layouts/Layout.spug");
|
|
expect(page).toContain("block content");
|
|
expect(page).toContain("component card");
|
|
expect(page).toContain("title: title");
|
|
expect(astroToSpug("<main><slot /></main>")).toContain("block content");
|
|
});
|
|
|
|
test("ports Zola inheritance, partials and template expressions", () => {
|
|
const page = zolaToSpug(`{% extends "base.html" %}\n{% block content %}<h1>{{ page.title }}</h1>{% include "partials/nav.html" %}{% endblock %}`);
|
|
expect(page).toContain("extends ./base.spug");
|
|
expect(page).toContain("block content");
|
|
expect(page).toContain("include ./partials/nav.spug");
|
|
expect(page).toContain("#{ page.title }");
|
|
});
|
|
|
|
test("ports React JSX components through the TypeScript AST", () => {
|
|
const component = reactToSpug('import { Button } from "./button"; export function Card({ title }) { return <section className="card"><h2>{title}</h2><Button variant="outline">Open</Button></section>; }');
|
|
expect(component).toContain("section(class=\"card\")");
|
|
expect(component).toContain("= title");
|
|
expect(component).toContain("component button");
|
|
expect(component).toContain('variant: "outline"');
|
|
});
|
|
|
|
test("analyzes hooks and lowers static state into a javascript block", () => {
|
|
const source = 'function Toggle() { const [open] = useState(false); useEffect(() => {}, []); return <button>{open}</button>; }';
|
|
expect(analyzeReactComponent(source).hooks).toEqual(["useEffect", "useState"]);
|
|
expect(reactToSpug(source)).toContain("javascript!");
|
|
expect(reactToSpug(source)).toContain("const open = false;");
|
|
});
|
|
|
|
test("creates a Muddle project from an Astro folder", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "muddle-backport-"));
|
|
await Bun.write(join(root, "astro.config.mjs"), "export default {}");
|
|
await mkdir(join(root, "src/pages"), { recursive: true });
|
|
await mkdir(join(root, "src/styles"), { recursive: true });
|
|
await mkdir(join(root, "src/content/posts"), { recursive: true });
|
|
await mkdir(join(root, "src/layouts"), { recursive: true });
|
|
await mkdir(join(root, "src/components"), { recursive: true });
|
|
await Bun.write(join(root, "src/pages/index.astro"), "<main><h1>Hi</h1></main>");
|
|
await Bun.write(join(root, "src/pages/about.astro"), "---\nimport Shell from '../layouts/Shell.astro';\n---\n<Shell><p>About</p></Shell>");
|
|
await Bun.write(join(root, "src/layouts/Shell.astro"), "<html><body><slot /></body></html>");
|
|
await Bun.write(join(root, "src/components/Card.astro"), "<article><slot /></article>");
|
|
await Bun.write(join(root, "src/styles/site.css"), ".page { color: red; }");
|
|
await Bun.write(join(root, "src/content/posts/hello.md"), "---\ntitle: Hello\n---\n\nBody");
|
|
const destination = join(root, "muddle");
|
|
const result = await backportProject(root, destination);
|
|
expect(result.source).toBe("astro");
|
|
expect(await readFile(join(destination, "modules/app/views/index.spug"), "utf8")).toContain("main");
|
|
expect(await readFile(join(destination, "styles/site.stcss"), "utf8")).toContain(".page");
|
|
expect(await readFile(join(destination, "content/posts/hello.md"), "utf8")).toContain("title: Hello");
|
|
expect(await readFile(join(destination, "ct.config.ts"), "utf8")).toContain('name: "posts"');
|
|
const engine = new TemplateEngine({ viewsDir: join(destination, "modules/app/views") });
|
|
expect(await engine.render("index.spug")).toContain("<main><h1>Hi</h1>");
|
|
expect(await engine.render("about.spug")).toContain("<html><body><p>About</p>");
|
|
});
|
|
|
|
test("carries Zola configuration and partial templates into the Muddle project", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "muddle-zola-"));
|
|
await mkdir(join(root, "templates/partials"), { recursive: true });
|
|
await mkdir(join(root, "content/posts"), { recursive: true });
|
|
await Bun.write(join(root, "config.toml"), 'title = "Zola notes"\nbase_url = "https://notes.example"');
|
|
await Bun.write(join(root, "templates/index.html"), '{% extends "base.html" %}{% block content %}<h1>{{ config.title }}</h1>{% endblock %}');
|
|
await Bun.write(join(root, "templates/partials/nav.html"), "<nav>Nav</nav>");
|
|
await Bun.write(join(root, "content/posts/first.md"), "---\ntitle: First\n---\n\nPost");
|
|
const destination = join(root, "muddle");
|
|
await backportProject(root, destination);
|
|
expect(await readFile(join(destination, "modules/app/site.config.ts"), "utf8")).toContain("Zola notes");
|
|
expect(await readFile(join(destination, "modules/app/views/partials/nav.spug"), "utf8")).toContain("nav");
|
|
expect(await readFile(join(destination, "modules/app/views/index.spug"), "utf8")).toContain("extends ./base.spug");
|
|
});
|
|
|
|
test("compiles Sass before handing CSS to STCSS", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "muddle-sass-"));
|
|
const path = join(root, "site.scss");
|
|
await writeFile(path, "$ink: #123456; .card { color: $ink; }");
|
|
const loaded = await sassLoader().load({ path, relativePath: "styles/site.scss", source: await readFile(path, "utf8") });
|
|
expect(loaded.content).toContain("color: #123456;");
|
|
});
|
|
|
|
test("pulls declarative ShadCN registry components into Spug and STCSS", async () => {
|
|
const root = await mkdtemp(join(tmpdir(), "muddle-shadcn-"));
|
|
const integration = shadcnIntegration({
|
|
components: ["button"],
|
|
fetch: async () => new Response(JSON.stringify({ files: [
|
|
{ path: "button.tsx", content: "export function Button() { return <button className=\"btn\">Save</button>; }" },
|
|
{ path: "button.css", content: ".btn { color: red; }" },
|
|
] })),
|
|
});
|
|
await integration.setup?.({ config: { root } as any, route() {}, loader() {} });
|
|
expect(await readFile(join(root, "modules/app/views/components/button.spug"), "utf8")).toContain("button");
|
|
expect(await readFile(join(root, "styles/shadcn/button.stcss"), "utf8")).toContain(".btn");
|
|
});
|
|
});
|