96 lines
4.9 KiB
TypeScript
96 lines
4.9 KiB
TypeScript
import { expect, test, type Page } from "@playwright/test";
|
|
import fs from "node:fs";
|
|
import path from "node:path";
|
|
|
|
const basicBlend = path.resolve(import.meta.dirname, "../../../tests/files/web/basic_scene.blend");
|
|
|
|
async function waitForEngine(page: Page): Promise<void> {
|
|
await page.goto("/");
|
|
await expect(page.getByTestId("engine-status")).toContainText("ready, open a .blend file", { timeout: 20_000 });
|
|
}
|
|
|
|
test("M7-02 rejects a repeated open before it can replace the first project owner", async ({ page }) => {
|
|
await waitForEngine(page);
|
|
const bytes = Array.from(fs.readFileSync(basicBlend));
|
|
await page.evaluate(({ blendBytes }) => {
|
|
const input = document.querySelector<HTMLInputElement>("[data-testid=blend-file-input]");
|
|
if (!input) throw new Error("blend input missing");
|
|
const dispatchOpen = (name: string): void => {
|
|
const transfer = new DataTransfer();
|
|
transfer.items.add(new File([new Uint8Array(blendBytes)], name, { type: "application/octet-stream" }));
|
|
input.files = transfer.files;
|
|
input.dispatchEvent(new Event("change", { bubbles: true }));
|
|
};
|
|
dispatchOpen("first-open.blend");
|
|
dispatchOpen("second-open.blend");
|
|
}, { blendBytes: bytes });
|
|
|
|
const app = page.locator(".blender-app");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict", "USER_ACTION_CONFLICT");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-reason", "REPEATED_OPEN");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-requested", "OPEN");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-owner", "OPEN");
|
|
await expect(app).toHaveAttribute("data-user-action-open-status", "SUCCEEDED");
|
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
|
expect(await page.evaluate(() => localStorage.getItem("blender-web:last-project-id"))).toBe("first-open");
|
|
});
|
|
|
|
test("M7-02 grants only one concurrent manual save and emits one download", async ({ page }) => {
|
|
await waitForEngine(page);
|
|
await page.getByTestId("blend-file-input").setInputFiles(basicBlend);
|
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
|
let downloadCount = 0;
|
|
page.on("download", () => { downloadCount += 1; });
|
|
|
|
await page.evaluate(() => {
|
|
const save = document.querySelector<HTMLButtonElement>('button[aria-label="保存项目"]');
|
|
if (!save) throw new Error("save button missing");
|
|
save.click();
|
|
save.click();
|
|
});
|
|
|
|
const app = page.locator(".blender-app");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict", "USER_ACTION_CONFLICT");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-reason", "CONCURRENT_SAVE");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-requested", "SAVE");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-owner", "SAVE");
|
|
await expect(app).toHaveAttribute("data-user-action-save-status", "SUCCEEDED");
|
|
await expect(app).toHaveAttribute("data-user-action-save-as-status", "SUCCEEDED");
|
|
await expect.poll(() => downloadCount).toBe(1);
|
|
});
|
|
|
|
test("M7-02 blocks close until save commit completes without terminating either worker", async ({ page }) => {
|
|
await waitForEngine(page);
|
|
await page.getByTestId("blend-file-input").setInputFiles(basicBlend);
|
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
|
const firstDownload = page.waitForEvent("download");
|
|
|
|
await page.evaluate(() => {
|
|
const save = document.querySelector<HTMLButtonElement>('button[aria-label="保存项目"]');
|
|
const close = document.querySelector<HTMLButtonElement>('button[aria-label="关闭项目"]');
|
|
if (!save || !close) throw new Error("project action button missing");
|
|
save.click();
|
|
close.click();
|
|
});
|
|
|
|
const app = page.locator(".blender-app");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict", "USER_ACTION_CONFLICT");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-reason", "CLOSE_DURING_SAVE");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-requested", "CLOSE");
|
|
await expect(app).toHaveAttribute("data-project-action-conflict-owner", "SAVE");
|
|
await firstDownload;
|
|
await expect(app).toHaveAttribute("data-user-action-save-status", "SUCCEEDED");
|
|
await expect(page.getByText("BasicCube", { exact: true })).toBeVisible();
|
|
|
|
await page.getByRole("button", { name: "添加立方体" }).click();
|
|
await expect(page.getByTestId("scene-stats")).toContainText("Objects 4");
|
|
const secondDownload = page.waitForEvent("download");
|
|
await page.getByRole("button", { name: "保存项目" }).click();
|
|
await secondDownload;
|
|
await expect(app).not.toHaveAttribute("data-project-action-conflict");
|
|
|
|
await page.getByRole("button", { name: "关闭项目" }).click();
|
|
await expect(page.getByTestId("scene-stats")).toContainText("Objects 0");
|
|
await expect(page.getByRole("button", { name: "关闭项目" })).toBeDisabled();
|
|
});
|