|
|
|
|
import "@testing-library/jest-dom/vitest";
|
|
|
|
|
import { cleanup } from "@testing-library/react";
|
|
|
|
|
import { afterEach } from "vitest";
|
|
|
|
|
|
|
|
|
|
// With `globals: false`, @testing-library/react does not auto-register a
|
|
|
|
|
// cleanup hook, so unmount rendered trees between tests explicitly. This keeps
|
|
|
|
|
// `screen.getByTestId` from seeing DOM from prior tests in the same file.
|
|
|
|
|
afterEach(() => {
|
|
|
|
|
cleanup();
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
// ProseMirror probes layout APIs jsdom doesn't implement.
|
|
|
|
|
if (typeof document !== "undefined") {
|
|
|
|
|
if (!document.elementFromPoint) {
|
|
|
|
|
document.elementFromPoint = () => null;
|
|
|
|
|
}
|
|
|
|
|
if (typeof Range !== "undefined" && !Range.prototype.getClientRects) {
|
|
|
|
|
Range.prototype.getClientRects = () => [] as unknown as DOMRectList;
|
|
|
|
|
Range.prototype.getBoundingClientRect = () =>
|
|
|
|
|
({ x: 0, y: 0, top: 0, left: 0, bottom: 0, right: 0, width: 0, height: 0, toJSON: () => ({}) }) as DOMRect;
|
|
|
|
|
}
|
refactor: rebuild the editor on CodeMirror as decorated source
Rebuild the memo editor as a single CodeMirror 6 "decorated source"
editor. The document is the raw markdown, stored verbatim and styled in
place (markers stay visible), so the editor never serializes a tree back
to markdown — removing the round-trip fidelity bug class (inline images,
setext headings, ordered-list indentation, HTML entities) that the old
editor needed per-case patches for.
- MemoEditor/Editor: CodeMirror 6 + lang-markdown (GFM). Tokens, heading
lines, #tag/@mention, and the autocomplete popover are styled in plain
CSS (Editor/editor.css) with theme tokens, not a CSS-in-JS theme.
- Tab/Shift-Tab nest/outdent list items (marker-aware, ordered items
renumbered so nesting is CommonMark-valid); Escape blurs; #tag
autocomplete sourced from useTagCounts.
- Focus-mode toolbar reimplemented as markdown-text ops; active state read
from the Lezer tree via the backend-agnostic formatting/commands catalog.
- Remove the old serialize-back-to-markdown editor (its Editor dir,
PlainEditor, the editor-mode system) and its now-unused dependencies
(marked, textarea-caret, and the rich-text editor packages).
- Consolidate toolbar components under Toolbar/. Read-only MemoContent
rendering is unchanged.
2 weeks ago
|
|
|
// CodeMirror probes additional layout APIs jsdom doesn't implement while measuring.
|
|
|
|
|
if (typeof Element !== "undefined" && !Element.prototype.getClientRects) {
|
|
|
|
|
Element.prototype.getClientRects = () => [] as unknown as DOMRectList;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// jsdom runs with an opaque origin (no URL set), so its built-in localStorage
|
|
|
|
|
// implementation throws SecurityError on every access. Install a Map-backed shim
|
|
|
|
|
// so any test that touches localStorage directly can call getItem/setItem/removeItem/clear
|
|
|
|
|
// without additional per-file setup. Tests that need special behavior (hooks.test.tsx,
|
|
|
|
|
// memo-editor-cache.test.ts) override this via Object.defineProperty / vi.stubGlobal.
|
|
|
|
|
if (typeof globalThis.localStorage === "undefined" || typeof globalThis.localStorage.clear !== "function") {
|
|
|
|
|
let _store = new Map<string, string>();
|
|
|
|
|
const localStorageShim: Storage = {
|
|
|
|
|
get length() {
|
|
|
|
|
return _store.size;
|
|
|
|
|
},
|
|
|
|
|
getItem: (key: string) => _store.get(key) ?? null,
|
|
|
|
|
setItem: (key: string, value: string) => _store.set(key, value),
|
|
|
|
|
removeItem: (key: string) => _store.delete(key),
|
|
|
|
|
clear: () => {
|
|
|
|
|
_store = new Map<string, string>();
|
|
|
|
|
},
|
|
|
|
|
key: (index: number) => Array.from(_store.keys())[index] ?? null,
|
|
|
|
|
};
|
|
|
|
|
Object.defineProperty(globalThis, "localStorage", {
|
|
|
|
|
configurable: true,
|
|
|
|
|
value: localStorageShim,
|
|
|
|
|
});
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Defensive shim: `@/auth-state` constructs a BroadcastChannel at module load
|
|
|
|
|
// to coordinate token refreshes across tabs. jsdom historically has not shipped
|
|
|
|
|
// BroadcastChannel, so any future test that transitively imports auth-state
|
|
|
|
|
// would otherwise throw. Current tests avoid that import path on purpose, but
|
|
|
|
|
// installing the shim keeps authoring new tests frictionless. No-op when jsdom
|
|
|
|
|
// already provides an implementation.
|
|
|
|
|
if (typeof globalThis.BroadcastChannel === "undefined") {
|
|
|
|
|
class NoopBroadcastChannel {
|
|
|
|
|
readonly name: string;
|
|
|
|
|
onmessage: ((event: MessageEvent) => void) | null = null;
|
|
|
|
|
|
|
|
|
|
constructor(name: string) {
|
|
|
|
|
this.name = name;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
postMessage(_data: unknown): void {}
|
|
|
|
|
|
|
|
|
|
close(): void {}
|
|
|
|
|
|
|
|
|
|
addEventListener(): void {}
|
|
|
|
|
|
|
|
|
|
removeEventListener(): void {}
|
|
|
|
|
|
|
|
|
|
dispatchEvent(): boolean {
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// @ts-expect-error — attach the shim to the global scope for tests.
|
|
|
|
|
globalThis.BroadcastChannel = NoopBroadcastChannel;
|
|
|
|
|
}
|