The pushed copy pass renames the library heading to "Your freezer"
("Tvůj mrazák" in Czech) and reworks the gate subtitle plus several
Czech strings. Five suites pinned the old heading — regression, i18n,
native, wizard and providers — and now assert the new one; the README
prose and project tree follow. Key parity is unchanged.
Version 0.5.9, SW -> freezmo-v81.
213 lines
9.9 KiB
JavaScript
213 lines
9.9 KiB
JavaScript
/**
|
|
* Core regression: library, study feed, homework entry, review card, and the
|
|
* desktop shell + settings panel.
|
|
*/
|
|
import { BASE, DECK, launchBrowser, makeChecker, makePage, seed } from "./lib.mjs";
|
|
|
|
const { check, finish } = makeChecker();
|
|
const browser = await launchBrowser();
|
|
|
|
/* 1. desktop shell --------------------------------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser, { width: 1280, height: 900, deviceScaleFactor: 1 });
|
|
await seed(page, { decks: [DECK], folders: [{ id: "fold_1", name: "Biology", createdAt: 1 }] });
|
|
await page.goto(BASE, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(900);
|
|
|
|
check("library title", await page.getByRole("heading", { name: "Your freezer" }).isVisible());
|
|
const sidebar = page.getByRole("complementary", { name: "Sidebar" });
|
|
for (const row of ["New deck", "New homework", "All decks", "Unorganized", "Biology", "Progress", "Exam mode", "Settings"]) {
|
|
check(`sidebar row: ${row}`, (await sidebar.getByText(row, { exact: true }).count()) >= 1);
|
|
}
|
|
check("desktop hides the tab bar", !(await page.locator("nav.tabbar").isVisible()));
|
|
check("desktop keeps the import button", await page.getByRole("button", { name: "Import JSON" }).isVisible());
|
|
|
|
await page.getByRole("button", { name: "Collapse sidebar" }).click();
|
|
await page.waitForTimeout(400);
|
|
check("sidebar collapses", await page.getByRole("button", { name: "Expand sidebar" }).isVisible());
|
|
await page.getByRole("button", { name: "Expand sidebar" }).click();
|
|
await page.waitForTimeout(400);
|
|
check("sidebar expands again", await page.getByRole("button", { name: "Collapse sidebar" }).isVisible());
|
|
|
|
// Settings from the sidebar footer.
|
|
await sidebar.getByRole("button", { name: "Settings" }).click();
|
|
const dialog = page.getByRole("dialog");
|
|
await dialog.waitFor({ timeout: 6000 });
|
|
await page.waitForTimeout(700);
|
|
check("settings: connection card", await dialog.getByText("Connection").isVisible());
|
|
check("settings: key field", await dialog.locator("#ollama-api-key").count() === 1);
|
|
check("settings: learning section", await dialog.getByText("Learning").isVisible());
|
|
check("settings: data section", await dialog.getByText("Data & backup").isVisible());
|
|
check("settings: sync section", await dialog.getByText("Sync").first().isVisible());
|
|
|
|
// Health panel: it can take a while while the Ollama probe answers.
|
|
const visionRow = dialog.getByText("Vision model");
|
|
await visionRow.waitFor({ timeout: 25_000 }).catch(() => {});
|
|
check("health panel present (vision row)", (await visionRow.count()) >= 1);
|
|
check("text model row", (await dialog.getByText("Text model").count()) >= 1);
|
|
await page.keyboard.press("Escape");
|
|
await page.waitForTimeout(500);
|
|
await context.close();
|
|
}
|
|
|
|
/* 2. library: tabs, deck card, menu, folders ------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser);
|
|
await seed(page, { decks: [DECK], folders: [{ id: "fold_1", name: "Biology", createdAt: 1 }] });
|
|
await page.goto(BASE, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(900);
|
|
|
|
check("library tabs present", await page.getByRole("link", { name: "Study Decks", exact: true }).isVisible());
|
|
check("deck card title", await page.getByText("Sky deck").first().isVisible());
|
|
check("deck meta line", (await page.getByText(/2 activities/).count()) >= 1);
|
|
check("folder pills visible with folders", (await page.locator(".folder-pill").count()) >= 1);
|
|
check("review card has no exam link", (await page.getByRole("link", { name: /Exam mode/ }).count()) === 0);
|
|
check("review card keeps due + mistakes", (await page.getByText(/Review due ·/).count()) >= 1 && (await page.getByText(/Mistakes ·/).count()) >= 1);
|
|
|
|
await page.getByRole("button", { name: "Deck actions" }).click();
|
|
await page.waitForTimeout(300);
|
|
for (const item of ["Rename", "Export JSON", "Share", "Regenerate deck", "Delete deck"]) {
|
|
check(`deck menu: ${item}`, (await page.getByRole("button", { name: item, exact: true }).count()) >= 1);
|
|
}
|
|
await page.keyboard.press("Escape");
|
|
await page.waitForTimeout(400);
|
|
|
|
await page.getByRole("link", { name: "Homework", exact: true }).click();
|
|
await page.waitForTimeout(600);
|
|
check("homework tab empty state", await page.getByRole("link", { name: "New homework", exact: true }).isVisible());
|
|
await context.close();
|
|
}
|
|
|
|
/* 3. study feed ------------------------------------------------------ */
|
|
{
|
|
const { context, page } = await makePage(browser);
|
|
await seed(page, { decks: [DECK] });
|
|
await page.goto(`${BASE}/deck/seed_deck`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(900);
|
|
const firstSlide = page.getByRole("region", { name: /Activity 1 of/ });
|
|
check("feed: first activity", await firstSlide.getByText("What colour is a clear daytime sky?").isVisible());
|
|
await page.getByRole("button", { name: "Green" }).click();
|
|
await page.waitForTimeout(500);
|
|
check("feed: wrong answer shows the correct one", await page.getByText(/Correct answer/i).first().isVisible());
|
|
await page.getByRole("button", { name: "Next" }).click();
|
|
await page.waitForTimeout(800);
|
|
await page.getByRole("button", { name: "Scattering" }).click();
|
|
await page.waitForTimeout(500);
|
|
await page.getByRole("button", { name: "Next" }).click();
|
|
await page.waitForTimeout(900);
|
|
check("feed: results appear", await page.getByText(/50%/).first().isVisible());
|
|
check("feed: results summary", (await page.getByText(/You got 1 of 2 right/).count()) >= 1);
|
|
await context.close();
|
|
}
|
|
|
|
/* 4. homework entry -------------------------------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser);
|
|
await seed(page, { decks: [] });
|
|
await page.goto(`${BASE}/homework`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(800);
|
|
check("homework: step headline", await page.getByRole("heading", { name: "The assignment" }).isVisible());
|
|
check("homework: empty placeholder", await page.getByText("Nothing added yet").isVisible());
|
|
await context.close();
|
|
}
|
|
|
|
/* 5. legal pages + the cookie notice --------------------------------- */
|
|
{
|
|
const { context, page } = await makePage(browser);
|
|
// consent: false — this block is the one place the notice must be visible.
|
|
await seed(page, { decks: [], consent: false });
|
|
for (const [path, heading] of [
|
|
["/privacy", "Privacy policy"],
|
|
["/terms", "Terms of service"],
|
|
["/cookies", "Cookie policy"],
|
|
]) {
|
|
await page.goto(`${BASE}${path}`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(600);
|
|
check(`legal: ${path} renders`, await page.getByRole("heading", { name: heading, level: 1 }).isVisible());
|
|
}
|
|
check("legal: the pages carry real sections", (await page.locator("article section").count()) >= 3);
|
|
|
|
await page.goto(BASE, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(700);
|
|
const notice = page.getByRole("region", { name: "About cookies" });
|
|
check("consent: the notice shows once", (await notice.count()) === 1);
|
|
await page.getByRole("button", { name: "Got it" }).click();
|
|
await page.waitForTimeout(400);
|
|
await page.reload({ waitUntil: "networkidle" });
|
|
await page.waitForTimeout(800);
|
|
check("consent: dismissing is remembered", (await notice.count()) === 0);
|
|
await context.close();
|
|
}
|
|
|
|
/* 6. custom 404 ------------------------------------------------------ */
|
|
{
|
|
const { context, page } = await makePage(browser, { width: 390, height: 844, deviceScaleFactor: 1 });
|
|
await seed(page, { decks: [DECK] });
|
|
const response = await page.goto(`${BASE}/no-such-page`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(800);
|
|
check("404: an unknown URL answers 404", response !== null && response.status() === 404);
|
|
check("404: the custom screen renders", await page.getByRole("heading", { name: "This page melted away" }).isVisible());
|
|
check("404: it offers the way home", await page.getByRole("link", { name: "Back to library" }).isVisible());
|
|
check("404: the chat escape hatch is there", await page.getByRole("link", { name: "Ask the chat instead" }).isVisible());
|
|
check("404: it rides on the glass card", (await page.locator("main .card").count()) === 1);
|
|
await context.close();
|
|
|
|
const cs = await makePage(browser, { width: 390, height: 844, deviceScaleFactor: 1 });
|
|
await seed(cs.page, { decks: [DECK], locale: "cs" });
|
|
await cs.page.goto(`${BASE}/no-such-page`, { waitUntil: "networkidle" });
|
|
await cs.page.waitForTimeout(800);
|
|
check("404: Czech copy renders", await cs.page.getByRole("heading", { name: "Tahle stránka se rozpustila" }).isVisible());
|
|
await cs.context.close();
|
|
}
|
|
|
|
/* 7. typed fill-blank + checked homework card ------------------------ */
|
|
{
|
|
const typedDeck = {
|
|
...DECK,
|
|
id: "typed_fill_deck",
|
|
activities: [
|
|
{
|
|
id: "act_typed_fill",
|
|
type: "fill_blank",
|
|
topic: "Optics",
|
|
sentence: "Short wavelengths scatter ____ than long ones.",
|
|
answer: "more",
|
|
explanation: "Blue light scatters more strongly.",
|
|
xp: 15,
|
|
},
|
|
],
|
|
};
|
|
const { context, page } = await makePage(browser);
|
|
await seed(page, {
|
|
decks: [typedDeck],
|
|
extra: {
|
|
"freezmo.homework.v1": JSON.stringify([
|
|
{
|
|
id: "hw_check_1",
|
|
mode: "check",
|
|
title: "Linear equations check",
|
|
sourceText: "2x + 2 = 6",
|
|
verdict: "One sign error.",
|
|
correct: ["Good setup"],
|
|
wrong: [],
|
|
corrected: "x = 2",
|
|
createdAt: 1,
|
|
updatedAt: 2,
|
|
},
|
|
]),
|
|
},
|
|
});
|
|
await page.goto(`${BASE}/deck/typed_fill_deck`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(700);
|
|
const input = page.getByLabel("Type the missing word");
|
|
await input.fill("more");
|
|
check("fill-blank: typed input displays the value", (await input.inputValue()) === "more");
|
|
|
|
await page.goto(`${BASE}/?tab=homework`, { waitUntil: "networkidle" });
|
|
await page.waitForTimeout(700);
|
|
check("homework card: check mode shows Checked badge", await page.getByText("Checked").first().isVisible());
|
|
await context.close();
|
|
}
|
|
|
|
await browser.close();
|
|
finish();
|