freezmo/tests/run-all.mjs
Martin Dvorak 6036238bf0 Teach it back: the AI becomes the curious student
A new mode where the user explains a topic in their own words and the AI
plays a curious classmate: short reactions, exactly one follow-up question
at a time, never "correct"/"wrong", never lecturing, and everything it knows
comes from the user's own decks and notes.

- Entry points: a sidebar row next to Exam mode and a "Teach it back" action
  in every deck's menu (which prefills the topic and scope).
- Start screen: deck or typed topic, friendly/skeptical student, the same
  context and model chips as Chat (/teach?deck=<id> pre-selects the deck).
- Session: the student prompt is a mode of the existing /api/chat route, so
  model resolution, allowlists, rate limits and the streaming client are the
  same code; the personality lives in lib/prompts.ts (TEACH_*). The deck's
  cards travel as a numbered checklist and the student is told to ask about
  the ideas that were skipped; it must say so rather than guess when the
  notes cover nothing. One constant (TEACH_MAX_EXCHANGES = 10) ends the
  session, or the Finish button does.
- Recap: a teach_summary call (JSON, one repair retry) yields well / gaps
  with card references / next steps; each gap links to the card it means.
  "Add missed cards to review" schedules those activities due now; "Turn
  gaps into cards" generates one flashcard per gap from the deck's notes and
  shows an editable review list before saving.
- Sessions are chat conversations with a mode:"teach" marker + session
  record: same IndexedDB history, backup and sync, filtered out of the Chat
  screens; the recent list reopens a recap or continues a session.
- Reused pieces: MessageRow, Composer (now optional attach/scope chips and a
  mic), Markdown/KaTeX, AiDisclaimer, ContextPicker/ModelPicker, plus a
  shared useChatModels hook and useKeyboardInset extracted from Chat.
- Dictation: Web Speech API recognition (lib/speech-input.ts); the mic only
  renders where the browser supports it, and dictated phrases land in the
  draft.
- All strings in en + cs. teach-test: 44 checks (API prompts and guards,
  both entry points, the session, recap actions, history, exchange limit,
  360/390px layouts, mic on/off). Version 0.8.1, SW v103.
2026-10-16 23:05:24 +00:00

52 lines
1.5 KiB
JavaScript

/**
* Runs every Freezmo browser suite in order.
*
* npm run test:e2e # everything
* npm run test:e2e -- phase1 # only suites whose name contains "phase1"
*
* Each suite is a standalone script that talks to the running app on :3207
* (FREEZMO_URL overrides). Suites that need a stub Ollama or extra server
* instance start their own on their own port and clean up after themselves.
*/
import { spawn } from "node:child_process";
import path from "node:path";
const SUITES = [
"regression",
"activities-test",
"aero-music-test",
"bubbles-test",
"mobile-test",
"wizard-test",
"native-test",
"i18n-test",
"freeze-loading-test",
"phase1-test",
"phase2-test",
"phase3-test",
"podcast-test",
"teach-test",
"security-test",
"chat-test",
"providers-test",
"review-test",
];
const filters = process.argv.slice(2);
const list = filters.length ? SUITES.filter((name) => filters.some((f) => name.includes(f))) : SUITES;
const here = path.dirname(new URL(import.meta.url).pathname);
const repo = path.resolve(here, "..");
let failed = 0;
for (const suite of list) {
console.log(`\n===== ${suite} =====`);
const code = await new Promise((resolve) => {
const child = spawn(process.execPath, [path.join(here, `${suite}.mjs`)], { cwd: repo, stdio: "inherit" });
child.on("close", resolve);
});
if (code !== 0) failed += 1;
}
console.log(`\n${list.length - failed}/${list.length} suites passed`);
process.exit(failed ? 1 : 0);