Add copy-only, color swatches, DB speed pragmas, and living docs
- Copy only (right-click -> Copy): write to clipboard without auto-pasting or hiding the window - Color swatches: hex/rgb/hsl entries show a preview chip - SQLite WAL + synchronous=NORMAL so the UI can read while the clipboard poller writes - Memoize list rows (React.memo) so selection/focus changes don't re-render every row's preview - Add docs/GUIDE.md (living install + usage doc), docs/SHARING.md, docs/TESTING.md, docs/LAUNCH-AT-LOGIN.md, docs/PRODUCT.md, CHANGELOG.md, ROADMAP.md - Add homelab Gitea Actions CI (.gitea/workflows/ci.yml) + gitleaks allowlist - Bump version to 0.2.0
This commit is contained in:
@@ -0,0 +1,102 @@
|
||||
/**
|
||||
* Headed visual walkthrough — opens Chromium (headless: false) so you can watch.
|
||||
*
|
||||
* npm run test:visual
|
||||
*/
|
||||
import { chromium } from "playwright";
|
||||
import { spawn } from "node:child_process";
|
||||
import { setTimeout as sleep } from "node:timers/promises";
|
||||
|
||||
const PORT = 1420;
|
||||
const BASE = `http://127.0.0.1:${PORT}/visual.html`;
|
||||
|
||||
async function portOpen(port) {
|
||||
try {
|
||||
const res = await fetch(`http://127.0.0.1:${port}/visual.html`);
|
||||
return res.status < 500;
|
||||
} catch {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
async function ensureVite() {
|
||||
if (await portOpen(PORT)) return null;
|
||||
const child = spawn("npm", ["run", "dev", "--", "--host", "127.0.0.1"], {
|
||||
cwd: process.cwd(),
|
||||
stdio: "ignore",
|
||||
detached: true,
|
||||
});
|
||||
for (let i = 0; i < 40; i++) {
|
||||
await sleep(250);
|
||||
if (await portOpen(PORT)) return child;
|
||||
}
|
||||
throw new Error("Vite did not start on :1420");
|
||||
}
|
||||
|
||||
async function main() {
|
||||
const vite = await ensureVite();
|
||||
console.log("Opening headed Chromium — watch the window…\n");
|
||||
|
||||
const browser = await chromium.launch({
|
||||
headless: false,
|
||||
slowMo: 400,
|
||||
});
|
||||
const page = await browser.newPage({ viewport: { width: 480, height: 640 } });
|
||||
await page.goto(BASE, { waitUntil: "networkidle" });
|
||||
|
||||
// 1) Show banner
|
||||
await page.waitForSelector('[data-testid="banner"]');
|
||||
await sleep(800);
|
||||
|
||||
// 2) Type in search
|
||||
const input = page.getByPlaceholder("Search…");
|
||||
await input.click();
|
||||
await input.fill("Meeting");
|
||||
await sleep(900);
|
||||
await input.fill("");
|
||||
await sleep(500);
|
||||
|
||||
// 3) Hover / focus list rows
|
||||
await page.getByText("Meeting notes").hover();
|
||||
await sleep(600);
|
||||
|
||||
// 4) Open context menu + Paste plain
|
||||
await page.getByTestId("open-menu").click();
|
||||
await sleep(700);
|
||||
await page.getByText("Paste plain").click();
|
||||
await sleep(1000);
|
||||
|
||||
// 5) Click redacted password row (pastes full secret into banner)
|
||||
await page.getByText("(password)").click({ modifiers: [] });
|
||||
await sleep(1200);
|
||||
|
||||
// 6) Alt-click card row for plain
|
||||
await page.getByText("(card)").click({ modifiers: ["Alt"] });
|
||||
await sleep(1200);
|
||||
|
||||
// 7) Show transform menu → Pretty JSON not relevant; show uppercase on meeting
|
||||
await page.getByText("Meeting notes").click({ button: "right" });
|
||||
await sleep(600);
|
||||
await page.getByText("UPPERCASE").click();
|
||||
await sleep(1500);
|
||||
|
||||
const banner = await page.getByTestId("banner").innerText();
|
||||
console.log("Final banner:", banner);
|
||||
console.log("\nLeaving the window open for 5s so you can look…");
|
||||
await sleep(5000);
|
||||
|
||||
await browser.close();
|
||||
if (vite) {
|
||||
try {
|
||||
process.kill(-vite.pid);
|
||||
} catch {
|
||||
/* ignore */
|
||||
}
|
||||
}
|
||||
console.log("Visual walkthrough finished.");
|
||||
}
|
||||
|
||||
main().catch((err) => {
|
||||
console.error(err);
|
||||
process.exit(1);
|
||||
});
|
||||
Reference in New Issue
Block a user