Files
plainleaf/client/styles/plainleaf_workspace.test.ts
T
2026-08-13 10:48:11 +08:00

138 lines
5.6 KiB
TypeScript

import { readFileSync } from "node:fs";
import { expect, test } from "vitest";
const workspaceStyles = readFileSync(
new URL("./plainleaf_workspace.scss", import.meta.url),
"utf8",
);
const clientHtml = readFileSync(
new URL("../html/index.html", import.meta.url),
"utf8",
);
const authHtml = readFileSync(
new URL("../html/auth.html", import.meta.url),
"utf8",
);
const sharedSkinStyles = readFileSync(
new URL("./_plainleaf_skin.scss", import.meta.url),
"utf8",
);
const componentStyles = readFileSync(
new URL("./components.scss", import.meta.url),
"utf8",
);
test("keeps mobile workspace within the dynamic viewport", () => {
expect(workspaceStyles).toContain("height: 100dvh;");
expect(workspaceStyles).not.toContain("100dvh - 62px");
expect(workspaceStyles).not.toContain(".pl-mobile-navigation");
expect(workspaceStyles).toMatch(
/@media only screen and \(max-width: 700px\)[\s\S]*?\.pl-sidebar\s*\{[^}]*width: min\(82vw, 320px\);/,
);
expect(clientHtml).toContain("viewport-fit=cover");
});
test("uses internal vertical scrolling without widening the page", () => {
expect(workspaceStyles).toMatch(
/\.pl-home-surface,\s*\.pl-settings-surface,\s*\.pl-task-surface\s*\{[^}]*overflow-x: hidden;[^}]*overflow-y: auto;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-home-inner\s*\{[^}]*box-sizing: border-box;[^}]*max-width: 100%;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-settings-inner\s*\{[^}]*box-sizing: border-box;[^}]*max-width: 100%;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-sidebar-scroll\s*\{[^}]*overflow-x: hidden;[^}]*overflow-y: auto;/s,
);
});
test("keeps the Markdown toolbar inside the mobile viewport", () => {
expect(workspaceStyles).toMatch(
/\.pl-editor-layer\.is-hidden > #sb-top,\s*\.pl-editor-layer\.is-hidden > #sb-main,\s*\.pl-editor-layer\.is-hidden > \.pl-markdown-toolbar,\s*\.pl-editor-layer\.is-hidden > \.pl-markdown-preview\s*\{[^}]*pointer-events: none;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-markdown-toolbar\s*\{[^}]*min-width: 0;[^}]*overflow-x: auto;[^}]*overflow-y: hidden;/s,
);
expect(workspaceStyles).toMatch(
/@media only screen and \(max-width: 700px\)[\s\S]*?\.pl-markdown-toolbar\s*\{[^}]*height: 44px;/,
);
});
test("styles folder actions and the Markdown preview as stable workspace controls", () => {
expect(workspaceStyles).toMatch(
/\.pl-folder-actions > summary\s*\{[^}]*width: 28px;[^}]*height: 28px;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-tree-folder-row,\s*\.pl-tree-page-row\s*\{[^}]*grid-template-columns: minmax\(0, 1fr\) 28px;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-page-actions > div\s*\{[^}]*width: 176px;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-editor-mode-switch\s*\{[^}]*display: flex;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-markdown-preview\s*\{[^}]*min-width: 0;[^}]*min-height: 0;[^}]*overflow: auto;/s,
);
expect(workspaceStyles).toMatch(
/\.pl-editor-layer\.is-preview > #sb-main\s*\{[^}]*display: none;/s,
);
});
test("gives the task center a full workspace and mobile viewport", () => {
expect(workspaceStyles).toMatch(
/\.pl-task-surface\s*\{[^}]*grid-template-columns: minmax\(360px, 1fr\) auto;/s,
);
expect(workspaceStyles).toMatch(
/@media only screen and \(max-width: 700px\)[\s\S]*?\.pl-task-surface\s*\{[^}]*height: 100dvh;[^}]*overflow: hidden;/,
);
expect(workspaceStyles).toMatch(
/\.pl-task-content\s*\{[^}]*min-width: 0;[^}]*min-height: 0;[^}]*overflow: hidden;/s,
);
});
test("boots the shared Cloudleaf skin before rendering and keeps login responsive", () => {
expect(clientHtml).toContain('localStorage.getItem("plainleaf.skin")');
expect(authHtml).toContain('localStorage.getItem("plainleaf.skin")');
expect(sharedSkinStyles).toContain(
'html[data-skin="cloudleaf"] .pl-login-scene',
);
expect(sharedSkinStyles).toContain("cloudleaf-login-desktop.webp");
expect(sharedSkinStyles).toContain("cloudleaf-login-mobile.webp");
expect(sharedSkinStyles).toMatch(
/body\.sb-standalone\.pl-login-page #root\s*\{[^}]*width: 100%;[^}]*height: 100vh;[^}]*height: 100dvh;/s,
);
});
test("gives Cloudleaf secondary controls the same game-surface states", () => {
expect(workspaceStyles).toMatch(
/button:active:not\(:disabled\)\s*\{[^}]*transform: translateY\(1px\);/s,
);
expect(workspaceStyles).toContain(".pl-task-dialog");
expect(workspaceStyles).toContain(
".pl-task-calendar-grid button.is-selected::after",
);
expect(workspaceStyles).toContain(".pl-switch-button:hover");
expect(componentStyles).toMatch(
/html\[data-skin="cloudleaf"\][\s\S]*?\.sb-input,[\s\S]*?\.sb-select,[\s\S]*?border: 1px solid var\(--button-border-color\);/s,
);
expect(componentStyles).toContain(".sb-button-primary:active");
expect(componentStyles).toContain("#sb-top .sb-actions button");
expect(componentStyles).toContain(".sb-modal-box");
expect(componentStyles).toContain(".sb-notifications > div");
expect(componentStyles).toContain("#sb-editor .cm-panels-bottom .cm-button");
expect(sharedSkinStyles).toContain(".sb-standalone .sb-logout");
});
test("carries the adventure scene and readable progress into the workspace", () => {
expect(workspaceStyles).toMatch(
/html\[data-skin="cloudleaf"\] \.pl-editor-layer > #sb-top\s*\{[^}]*cloudleaf-login-desktop\.webp/s,
);
expect(workspaceStyles).toContain(".sb-sync-progress .progress-wrapper");
expect(workspaceStyles).toContain(".sb-sync-progress.is-complete");
expect(workspaceStyles).toMatch(
/@media only screen and \(max-width: 700px\)[\s\S]*?\.pl-editor-layer > #sb-top\s*\{[^}]*cloudleaf-login-mobile\.webp/,
);
});