test(web): vitest suites for markdown, filters, password rules, api, storage, toast
Test infrastructure uses the vite-plus stack: vp test (bundled Vitest 5),
jsdom environment configured in vite.config.ts, colocated web/src/*.test.ts
files that tsc type-checks, vp fmt formats, and the bundler never imports.
- markdown.test.ts pins the notes XSS defenses: parser-level html escaping,
DOMPurify FORBID_TAGS, scheme checks, footnote rendering, html->markdown
round-trips incl. the headingless-table rule.
- filter_builder.test.ts pins the deep-link wire contract (ruleToFlatParam),
smart-serialization rules (isFlatable), and pruneIncomplete semantics.
- password_validation.test.ts covers the rules fetch + fallback and the
full submit-gating matrix through the real DOM flow (debounce included).
- api.test.ts pins response/error contracts incl. the HTTP-status and
Unknown-error fallbacks and the 404-as-null progress contract.
- storage.test.ts covers token/theme/library round-trips, the __all__
sentinel, and the SSR cookie.
- toast.test.ts pins config completeness for every ToastType (the missing
'warning' entry regression) and render behavior.
Found + fixed while writing these: the no-useless-escape fix in
d7d14957 had de-escaped the ']' in hasSpecialChar's character class,
closing it early — special-character validation silently stopped
matching most specials. The new password suite caught it immediately.
jsdom chosen over happy-dom after empirical comparison: DOMPurify's
sanitizer output is mangled under happy-dom (forbidden tags survive),
which would have made the security suite test nothing.
Note: AGENTS.md gains a 'Web (TypeScript) tests' section documenting the
workflow and the happy-dom gotcha.
This commit is contained in:
1 parent
86c4940426
commit
e5315f1b94
12 files changed
+685
-6
No files matched your search
@@ -0,0 +1,45 @@
|
||||
import { describe, expect, it } from "vitest";
|
||||
|
||||
import { getToastConfig, showToast, type ToastType } from "./toast";
|
||||
|
||||
// getToastConfig drives every toast's styling; a missing entry for a
|
||||
// ToastType member crashes showToast at runtime (that bug shipped once —
|
||||
// the 'warning' variant — which is why this suite exists).
|
||||
|
||||
const ALL_TYPES: ToastType[] = ["error", "success", "info", "warning"];
|
||||
|
||||
describe("getToastConfig", () => {
|
||||
it("has a complete config for every ToastType", () => {
|
||||
for (const type of ALL_TYPES) {
|
||||
const config = getToastConfig(type);
|
||||
expect(config, `missing config for "${type}"`).toBeDefined();
|
||||
expect(config.bgClass, `missing bgClass for "${type}"`).toBeTruthy();
|
||||
expect(config.icon, `missing icon for "${type}"`).toBeTruthy();
|
||||
}
|
||||
});
|
||||
|
||||
it("styles the warning variant with the /90 opacity used by siblings", () => {
|
||||
expect(getToastConfig("warning").bgClass).toBe("bg-yellow-500/90");
|
||||
});
|
||||
|
||||
it("keeps distinct visuals per type", () => {
|
||||
const classes = ALL_TYPES.map((type) => getToastConfig(type).bgClass);
|
||||
expect(new Set(classes).size).toBe(ALL_TYPES.length);
|
||||
});
|
||||
});
|
||||
|
||||
describe("showToast", () => {
|
||||
it("creates the container and a styled toast", () => {
|
||||
showToast("Saved", "success");
|
||||
const container = document.getElementById("toast-container");
|
||||
expect(container).not.toBeNull();
|
||||
expect(container?.innerHTML).toContain("Saved");
|
||||
expect(container?.innerHTML).toContain("bg-green-500/90");
|
||||
});
|
||||
|
||||
it("renders the warning variant without throwing", () => {
|
||||
// Regression: 'warning' had no config entry and crashed on render.
|
||||
expect(() => showToast("Careful", "warning")).not.toThrow();
|
||||
expect(document.getElementById("toast-container")?.innerHTML).toContain("bg-yellow-500/90");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user