Add full shell, replaceable Omics adapter and bilingual interaction

Implement approved specification #32 and tickets #33-#37. Keep host authentication server-verified and pin session interaction language. Compile scoped base selectors for browser compatibility and retain full gutters during CSS pruning.
This commit is contained in:
Codex
2026-09-13 14:26:39 +02:00
parent 2d1b714ebe
commit d8a29bfbdd
207 changed files with 8570 additions and 2164 deletions
@@ -1,6 +1,29 @@
import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { FreetextWidget } from "./FreetextWidget";
function reloadIsBlocked() {
const event = new Event("beforeunload", { cancelable: true });
window.dispatchEvent(event);
return event.defaultPrevented;
}
test("protects an answer draft until cleared or unmounted, including after sending", async () => {
const onRespond = vi.fn();
const { unmount } = render(<FreetextWidget descriptor={{ id: "u1", widget: "freetext" }} onRespond={onRespond} />);
expect(reloadIsBlocked()).toBe(false);
await userEvent.type(screen.getByRole("textbox"), "Draft answer");
expect(reloadIsBlocked()).toBe(true);
await userEvent.clear(screen.getByRole("textbox"));
expect(reloadIsBlocked()).toBe(false);
await userEvent.type(screen.getByRole("textbox"), "Answer{Enter}");
expect(onRespond).toHaveBeenCalledWith({ id: "u1", kind: "freetext", text: "Answer" });
expect(reloadIsBlocked()).toBe(true);
await userEvent.type(screen.getByRole("textbox"), " updated");
expect(reloadIsBlocked()).toBe(true);
unmount();
expect(reloadIsBlocked()).toBe(false);
});
test("submits typed text", async () => {
const onRespond = vi.fn();
render(<FreetextWidget descriptor={{ id: "u1", widget: "freetext" }} onRespond={onRespond} />);