first commit
This commit is contained in:
@@ -0,0 +1,30 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { mkdtempSync, existsSync, readFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { generateDocker } from "../src/docker.ts";
|
||||
|
||||
test("generateDocker scaffolds Dockerfile, .dockerignore, compose", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-docker-"));
|
||||
generateDocker(dir);
|
||||
expect(existsSync(join(dir, "Dockerfile"))).toBe(true);
|
||||
expect(existsSync(join(dir, ".dockerignore"))).toBe(true);
|
||||
expect(existsSync(join(dir, "docker-compose.yml"))).toBe(true);
|
||||
|
||||
const dockerfile = readFileSync(join(dir, "Dockerfile"), "utf8");
|
||||
expect(dockerfile).toContain("FROM oven/bun:1 AS build");
|
||||
expect(dockerfile).toContain("bun run build");
|
||||
expect(dockerfile).toContain("/healthz"); // health check uses the app endpoint
|
||||
expect(dockerfile).toContain('CMD ["bun", "dist/server.js"]');
|
||||
|
||||
expect(readFileSync(join(dir, "docker-compose.yml"), "utf8")).toContain("postgres:16-alpine");
|
||||
});
|
||||
|
||||
test("generateDocker does not overwrite existing files", () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-docker-"));
|
||||
generateDocker(dir);
|
||||
const before = readFileSync(join(dir, "Dockerfile"), "utf8");
|
||||
// second run must leave the file untouched
|
||||
generateDocker(dir);
|
||||
expect(readFileSync(join(dir, "Dockerfile"), "utf8")).toBe(before);
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { mkdirSync, mkdtempSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { inspectProject } from "../src/doctor.ts";
|
||||
|
||||
test("doctor reports a healthy minimal project", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-doctor-"));
|
||||
mkdirSync(join(root, "app", "pages"), { recursive: true });
|
||||
writeFileSync(join(root, "package.json"), JSON.stringify({ name: "app" }));
|
||||
writeFileSync(join(root, "wrnexus.config.ts"), "export default {};");
|
||||
const checks = inspectProject(root);
|
||||
expect(checks.every((check) => check.ok)).toBe(true);
|
||||
});
|
||||
|
||||
test("doctor returns actionable missing-project checks", () => {
|
||||
const root = mkdtempSync(join(tmpdir(), "wrnexus-doctor-"));
|
||||
const checks = inspectProject(root);
|
||||
expect(checks.find((check) => check.name === "package.json")?.ok).toBe(false);
|
||||
expect(checks.find((check) => check.name === "app/pages")?.detail).toBe("Create app/pages");
|
||||
});
|
||||
@@ -0,0 +1,34 @@
|
||||
import { test, expect } from "bun:test";
|
||||
import { scaffold } from "../src/generate.ts";
|
||||
|
||||
test("scaffold page: kebab file, PascalCase page name", () => {
|
||||
const f = scaffold("page", "about-us");
|
||||
expect(f.path).toBe("pages/about-us.wrn");
|
||||
expect(f.content).toContain("page AboutUs {");
|
||||
expect(f.content).toContain('layout = "public"');
|
||||
});
|
||||
|
||||
test("scaffold component: props + class", () => {
|
||||
const f = scaffold("component", "user-card");
|
||||
expect(f.path).toBe("components/user-card.wrn");
|
||||
expect(f.content).toContain("component UserCard {");
|
||||
expect(f.content).toContain('class="wire-user-card"');
|
||||
});
|
||||
|
||||
test("scaffold api: nested path keeps directories, exports GET", () => {
|
||||
const f = scaffold("api", "users/list");
|
||||
expect(f.path).toBe("api/users/list.ts");
|
||||
expect(f.content).toContain("export async function GET");
|
||||
});
|
||||
|
||||
test("scaffold schema: validation object", () => {
|
||||
const f = scaffold("schema", "signup");
|
||||
expect(f.path).toBe("schemas/signup.ts");
|
||||
expect(f.content).toContain('import { v } from "@wrnexus/validation"');
|
||||
expect(f.content).toContain("export default v.object(");
|
||||
});
|
||||
|
||||
test("scaffold strips extensions from the given name", () => {
|
||||
expect(scaffold("page", "home.wrn").path).toBe("pages/home.wrn");
|
||||
expect(scaffold("api", "ping.ts").path).toBe("api/ping.ts");
|
||||
});
|
||||
@@ -0,0 +1,100 @@
|
||||
import { expect, test } from "bun:test";
|
||||
import { existsSync, mkdirSync, mkdtempSync, readFileSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { generateMobile, mobileOptions } from "../src/mobile.ts";
|
||||
import { runMobileCommand } from "../src/mobile-command.ts";
|
||||
import { nativeCatalog, runNativeCommand } from "../src/native-command.ts";
|
||||
|
||||
test("generateMobile scaffolds a configurable Capacitor project", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-mobile-"));
|
||||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "@acme/store-front" }));
|
||||
|
||||
await generateMobile(dir, {
|
||||
appId: "com.acme.store",
|
||||
appName: "Acme Store",
|
||||
serverUrl: "https://store.example.com",
|
||||
});
|
||||
|
||||
const config = readFileSync(join(dir, "mobile", "capacitor.config.ts"), "utf8");
|
||||
expect(config).toContain('appId: mobile.appId ?? "com.acme.store"');
|
||||
expect(config).toContain('appName: mobile.appName ?? "Acme Store"');
|
||||
expect(config).toContain("...(mobile.capacitor ?? {})");
|
||||
expect(config).not.toContain("errorPath:");
|
||||
expect(config).toContain('"https://store.example.com"');
|
||||
expect(existsSync(join(dir, "mobile", "web", "index.html"))).toBe(true);
|
||||
|
||||
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
|
||||
expect(pkg.dependencies["@capacitor/android"]).toBe("^8.0.0");
|
||||
expect(pkg.scripts["open:ios"]).toBe("cap open ios");
|
||||
});
|
||||
|
||||
test("generateMobile derives defaults and does not overwrite files", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-mobile-"));
|
||||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "demo-app" }));
|
||||
await generateMobile(dir);
|
||||
const path = join(dir, "mobile", "capacitor.config.ts");
|
||||
const before = readFileSync(path, "utf8");
|
||||
expect(before).toContain('appId: mobile.appId ?? "com.example.demoapp"');
|
||||
expect(before).toContain('"http://localhost:3000"');
|
||||
await generateMobile(dir, { appName: "Changed" });
|
||||
expect(readFileSync(path, "utf8")).toBe(before);
|
||||
});
|
||||
|
||||
test("mobileOptions parses generator flags", () => {
|
||||
expect(
|
||||
mobileOptions(["--app-id=com.acme.app", "--app-name=Acme", "--url=https://app.test"]),
|
||||
).toEqual({
|
||||
appId: "com.acme.app",
|
||||
appName: "Acme",
|
||||
serverUrl: "https://app.test",
|
||||
mode: undefined,
|
||||
});
|
||||
});
|
||||
|
||||
test("generateMobile scaffolds a WebView-free native project from config", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-native-"));
|
||||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-demo" }));
|
||||
writeFileSync(
|
||||
join(dir, "wrnexus.config.mjs"),
|
||||
`export default { mobile: { mode: "native", appId: "com.acme.native", apiUrl: "https://api.example.com" } }`,
|
||||
);
|
||||
await generateMobile(dir);
|
||||
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
|
||||
expect(pkg.wrnexus.mode).toBe("native");
|
||||
expect(pkg.dependencies.expo).toBeDefined();
|
||||
expect(existsSync(join(dir, "mobile", "app", "index.tsx"))).toBe(true);
|
||||
expect(readFileSync(join(dir, "mobile", "src", "wrnexus.ts"), "utf8")).toContain(
|
||||
"https://api.example.com",
|
||||
);
|
||||
expect(existsSync(join(dir, "mobile", "capacitor.config.ts"))).toBe(false);
|
||||
});
|
||||
|
||||
test("mobile compile turns wrn pages into Expo routes", async () => {
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-native-compile-"));
|
||||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-compile" }));
|
||||
writeFileSync(join(dir, "wrnexus.config.mjs"), `export default { mobile: { mode: "native" } }`);
|
||||
await generateMobile(dir);
|
||||
const pages = join(dir, "app", "pages");
|
||||
const nested = join(pages, "account");
|
||||
mkdirSync(nested, { recursive: true });
|
||||
writeFileSync(
|
||||
join(nested, "profile.wrn"),
|
||||
"page Profile { view { <main><h1>Profile</h1></main> } }",
|
||||
);
|
||||
await runMobileCommand(dir, "compile");
|
||||
const output = readFileSync(join(dir, "mobile", "app", "account", "profile.tsx"), "utf8");
|
||||
expect(output).toContain("function Profile()");
|
||||
expect(output).toContain("<View><Text>Profile</Text></View>");
|
||||
});
|
||||
|
||||
test("native catalog uses built-in React Native sharing without installing expo-sharing", async () => {
|
||||
expect(nativeCatalog.share?.expo).toBeUndefined();
|
||||
const dir = mkdtempSync(join(tmpdir(), "wire-native-share-"));
|
||||
writeFileSync(join(dir, "package.json"), JSON.stringify({ name: "native-share" }));
|
||||
writeFileSync(join(dir, "wrnexus.config.mjs"), `export default { mobile: { mode: "native" } }`);
|
||||
await generateMobile(dir);
|
||||
await runNativeCommand(dir, "add", ["share"]);
|
||||
const pkg = JSON.parse(readFileSync(join(dir, "mobile", "package.json"), "utf8"));
|
||||
expect(pkg.dependencies["expo-sharing"]).toBeUndefined();
|
||||
});
|
||||
Reference in New Issue
Block a user