276 lines
9.9 KiB
TypeScript
276 lines
9.9 KiB
TypeScript
import { test, expect } from "@playwright/test";
|
|
import { readFileSync } from "fs";
|
|
|
|
function spyViewer() {
|
|
return `
|
|
Object.defineProperty(window, "__bimrViewer", {
|
|
configurable: true,
|
|
set(v) {
|
|
const record = [];
|
|
(window).__addedModels = record;
|
|
v.addModel = (name, m) => { record.push({ name, m }); };
|
|
this._v = v;
|
|
},
|
|
get() { return this._v; },
|
|
});
|
|
`;
|
|
}
|
|
|
|
test("open .ifcx file displays in viewer and comments the editor", async ({ page }) => {
|
|
// Intercept the viewer registration (render.mjs) to record every addModel call.
|
|
await page.addInitScript(spyViewer());
|
|
|
|
await page.goto("/");
|
|
|
|
const ifcx = JSON.stringify({ header: { id: "test.ifcx" }, layers: [] });
|
|
|
|
await page.locator("#file-open").setInputFiles({
|
|
name: "test.ifcx",
|
|
mimeType: "application/json",
|
|
buffer: Buffer.from(ifcx),
|
|
});
|
|
|
|
await expect(page.locator("#editor .cm-content")).toContainText("# test.ifcx");
|
|
await expect(page.locator("#output")).toContainText("Loaded: test.ifcx");
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter(
|
|
(a: { name: string }) => a.name === "loaded",
|
|
),
|
|
),
|
|
)
|
|
.toHaveLength(1);
|
|
const loaded = await page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "loaded"),
|
|
);
|
|
expect(loaded[0].m.header.id).toBe("test.ifcx");
|
|
});
|
|
|
|
test("open .py file still loads into the editor", async ({ page }) => {
|
|
await page.goto("/");
|
|
await page.locator("#file-open").setInputFiles({
|
|
name: "script.py",
|
|
mimeType: "text/x-python",
|
|
buffer: Buffer.from("print('hi')\n"),
|
|
});
|
|
await expect(page.locator("#editor .cm-content")).toContainText("print('hi')");
|
|
const name = await page.evaluate(() => (window as any).__bimrActiveTabName());
|
|
expect(name).toBe("script.py");
|
|
});
|
|
|
|
test("New clears stale bimr/ifcx buffers", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.addInitScript(() => localStorage.setItem("bimr.runMode", "manual"));
|
|
await page.goto("/");
|
|
|
|
// Produce data in the buffers by running the default script.
|
|
await page.locator("#btn-run").click();
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
await page.locator("#btn-view-ifcx").click();
|
|
await expect(page.locator("#editor .cm-content")).toContainText('"data"');
|
|
|
|
// New -> Erase current
|
|
await page.locator("#menu-file-trigger").click();
|
|
await page.locator("#menu-new").click();
|
|
await page.locator("#new-dialog-erase").click();
|
|
|
|
// py buffer emptied.
|
|
await expect(page.locator("#editor .cm-content")).toHaveText("");
|
|
// ifcx buffer cleared — shows the placeholder, not stale data.
|
|
await page.locator("#btn-view-ifcx").click();
|
|
await expect(page.locator("#editor .cm-content")).toContainText("no model yet");
|
|
});
|
|
|
|
test("Save submenu disables unavailable buffers and downloads the chosen one", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.addInitScript(() => localStorage.setItem("bimr.runMode", "manual"));
|
|
await page.goto("/");
|
|
|
|
// Wait for the idle warm-up run to settle, then New clears the buffers.
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
await page.locator("#menu-file-trigger").click();
|
|
await page.locator("#menu-new").click();
|
|
await page.locator("#new-dialog-erase").click();
|
|
|
|
// Cleared buffers -> bimr/ifcx saves disabled, python always enabled.
|
|
await page.locator("#menu-file-trigger").click();
|
|
await page.locator("#menu-save").click();
|
|
await expect(page.locator("#menu-save-python")).not.toBeDisabled();
|
|
await expect(page.locator("#menu-save-bimr")).toBeDisabled();
|
|
await expect(page.locator("#menu-save-ifcx")).toBeDisabled();
|
|
|
|
// Load a real sample and run it to populate the bimr/ifcx buffers.
|
|
await page.locator("#btn-view-python").click();
|
|
await page.locator("#menu-examples-trigger").click();
|
|
await page.locator("#menu-examples-dropdown li button").first().click();
|
|
await expect(page.locator("#editor .cm-content")).toContainText("Welcome to BIMR.NET!");
|
|
await page.locator("#btn-run").click();
|
|
await expect(page.locator("#menu-save-bimr")).not.toBeDisabled({ timeout: 20000 });
|
|
await expect(page.locator("#menu-save-ifcx")).not.toBeDisabled();
|
|
|
|
// Download the bimr buffer.
|
|
await page.locator("#menu-file-trigger").click();
|
|
await page.locator("#menu-save").click();
|
|
const downloadPromise = page.waitForEvent("download");
|
|
await page.locator("#menu-save-bimr").click();
|
|
const download = await downloadPromise;
|
|
expect(download.suggestedFilename()).toBe("building.bimr");
|
|
const bimrText = readFileSync((await download.path())!, "utf8");
|
|
expect(bimrText.length).toBeGreaterThan(0);
|
|
});
|
|
|
|
test("opening a real .ifcx does not auto-run or empty the ifcx view", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.goto("/");
|
|
|
|
// Wait for the idle auto-run to have produced a live model.
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
|
|
// Capture the real app-generated IFCX via File > Save > Ifcx.
|
|
const downloadPromise = page.waitForEvent("download");
|
|
await page.locator("#menu-file-trigger").click();
|
|
await page.locator("#menu-save").click();
|
|
await page.locator("#menu-save-ifcx").click();
|
|
const download = await downloadPromise;
|
|
const ifcxText = readFileSync((await download.path())!, "utf8");
|
|
expect(ifcxText).toContain('"data"');
|
|
|
|
const liveBefore = await page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
);
|
|
|
|
await page.locator("#file-open").setInputFiles({
|
|
name: "my-model.ifcx",
|
|
mimeType: "application/json",
|
|
buffer: Buffer.from(ifcxText),
|
|
});
|
|
|
|
await expect(page.locator("#editor .cm-content")).toContainText("# my-model.ifcx");
|
|
await expect(page.locator("#output")).toContainText("Loaded: my-model.ifcx");
|
|
|
|
// Wait past the run debounce — opening an ifcx must NOT trigger a new run.
|
|
await page.waitForTimeout(800);
|
|
const liveAfter = await page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
);
|
|
expect(liveAfter).toBe(liveBefore);
|
|
|
|
// The ifcx view must show the loaded model's data, not an empty ifcx.
|
|
await page.locator("#btn-view-ifcx").click();
|
|
await expect(page.locator("#editor .cm-content")).toContainText('"data"');
|
|
});
|
|
|
|
test("open .bimr file compiles, displays in viewer and loads source into the editable bimr view", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.goto("/");
|
|
|
|
const bimrSrc = `p1 = Point(0, 0, 0)
|
|
p2 = Point(1000, 0, 0)
|
|
p3 = Point(1000, 800, 0)
|
|
p4 = Point(0, 800, 0)
|
|
l1 = Line(p1, p2)
|
|
l2 = Line(p2, p3)
|
|
l3 = Line(p3, p4)
|
|
l4 = Line(p4, p1)
|
|
w1 = Wall(l1, 20, 300)
|
|
w2 = Wall(l2, 20, 300)
|
|
w3 = Wall(l3, 20, 300)
|
|
w4 = Wall(l4, 20, 300)`;
|
|
|
|
await page.locator("#file-open").setInputFiles({
|
|
name: "house.bimr",
|
|
mimeType: "text/plain",
|
|
buffer: Buffer.from(bimrSrc),
|
|
});
|
|
|
|
await expect(page.locator("#output")).toContainText("Loaded: house.bimr");
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter(
|
|
(a: { name: string }) => a.name === "loaded",
|
|
),
|
|
),
|
|
)
|
|
.toHaveLength(1);
|
|
const loaded = await page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "loaded"),
|
|
);
|
|
const data = loaded[0].m.data as unknown[];
|
|
expect(data.length).toBe(4);
|
|
|
|
// The source must be loaded into the editable bimr view, ready to tweak.
|
|
await expect(page.locator("#editor .cm-content")).toContainText("w1 = Wall(l1, 20, 300)");
|
|
await expect(page.locator("#btn-view-bimr")).toHaveClass(/active/);
|
|
});
|
|
|
|
test("bimr mode: editing DSL source and running compiles to a model", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.goto("/");
|
|
|
|
await page.locator("#btn-view-bimr").click();
|
|
const editor = page.locator("#editor .cm-content");
|
|
await editor.click();
|
|
await page.keyboard.press("Control+a");
|
|
await page.keyboard.insertText(
|
|
"p1 = Point(0,0,0)\np2 = Point(5000,0,0)\nl1 = Line(p1,p2)\nw1 = Wall(l1,200,3000)",
|
|
);
|
|
|
|
await expect(page.locator("#output")).toContainText("Compiled 1 entity", { timeout: 15000 });
|
|
await expect
|
|
.poll(() =>
|
|
page.evaluate(() =>
|
|
((window as any).__addedModels ?? []).filter((a: { name: string }) => a.name === "live").length,
|
|
),
|
|
)
|
|
.toBeGreaterThan(0);
|
|
await expect(page.locator("#menu-save-bimr")).not.toBeDisabled();
|
|
});
|
|
|
|
test("bimr mode: invalid DSL source shows an error", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.addInitScript(() => localStorage.setItem("bimr.runMode", "manual"));
|
|
await page.goto("/");
|
|
|
|
await page.locator("#btn-view-bimr").click();
|
|
const editor = page.locator("#editor .cm-content");
|
|
await editor.click();
|
|
await page.keyboard.press("Control+a");
|
|
await page.keyboard.insertText("w1 = Wall(missing)");
|
|
await page.keyboard.press("Control+Enter");
|
|
|
|
await expect(page.locator("#output")).toContainText("Error:", { ignoreCase: true });
|
|
});
|
|
|
|
test("open invalid .bimr file shows an error and does not touch the editor", async ({ page }) => {
|
|
await page.addInitScript(spyViewer());
|
|
await page.goto("/");
|
|
|
|
await page.locator("#file-open").setInputFiles({
|
|
name: "broken.bimr",
|
|
mimeType: "text/plain",
|
|
buffer: Buffer.from("p1 = Point(0, 0, 0)\nunknown = Frobnicate(p1)\n"),
|
|
});
|
|
|
|
await expect(page.locator("#output")).toContainText("Error");
|
|
await expect(page.locator("#editor .cm-content")).not.toContainText("# broken.bimr");
|
|
});
|