Files
Notes/tests/unit/editor-theme.test.js
2026-09-21 02:30:48 +08:00

153 lines
6.8 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Stage 7 tests: editor-theme.js
//
// 覆盖:
// - pickEditorTheme 按 body[data-theme] 返回对应的 extensions 数组
// - 暗色叠加 oneDark;亮色只用自己的覆盖
// - backgroundColor / color 必须带 !important(oneDark 后续注入的同 specificity
// 规则会赢,必须用 important 强制覆盖,否则编辑区会显示 #282c34 而不是 --bg-viewer)
//
// editorTheme / editorThemeLight 是 EditorView.theme() 返回的 extension 对象,
// 内部是 CM6 私有结构,不展开断言 —— 只验证 pickEditorTheme 的行为即可。
import { describe, it, expect } from 'vitest';
import { pickEditorTheme, editorTheme, editorThemeLight, __editorThemeSpecForTests } from '../../src/editor-theme.js';
describe('pickEditorTheme', () => {
it('theme === "light" → 只返回 editorThemeLight', () => {
const exts = pickEditorTheme('light');
expect(exts).toHaveLength(1);
expect(exts[0]).toBe(editorThemeLight);
});
it('theme === "dark" → 返回 [oneDark, editorTheme]', () => {
const exts = pickEditorTheme('dark');
expect(exts).toHaveLength(2);
expect(exts[1]).toBe(editorTheme);
});
it('theme === undefined → 当暗色处理', () => {
const exts = pickEditorTheme(undefined);
expect(exts).toHaveLength(2);
expect(exts[1]).toBe(editorTheme);
});
it('theme === "auto"(旧版本残留) → 当暗色处理(防御性)', () => {
const exts = pickEditorTheme('auto');
expect(exts).toHaveLength(2);
});
it('theme === ""(空字符串) → 当暗色处理', () => {
const exts = pickEditorTheme('');
expect(exts).toHaveLength(2);
});
it('theme === null → 当暗色处理', () => {
const exts = pickEditorTheme(null);
expect(exts).toHaveLength(2);
});
});
describe('editorThemeSpec background overrides (oneDark / base theme 互斥)', () => {
// oneDark 的 .cm-editor { background-color: #282c34 } 和 .cm-gutters 同色,
// 在 dark theme 下会导致编辑区底色跟 .editor-pane / .viewer 的 --bg-viewer 不一致。
// 必须用 !important 强制覆盖,否则 oneDark 后注入的同 specificity 规则会赢。
it('.cm-editor backgroundColor 必须是 var(--bg-viewer) !important', () => {
const v = __editorThemeSpecForTests.backgroundColor;
expect(v).toMatch(/var\(--bg-viewer\)/);
expect(v).toMatch(/!important/);
});
it('.cm-editor color 必须是 var(--fg-primary) !important', () => {
const v = __editorThemeSpecForTests.color;
expect(v).toMatch(/var\(--fg-primary\)/);
expect(v).toMatch(/!important/);
});
it('.cm-gutters backgroundColor 必须是 var(--bg-viewer) !important', () => {
const v = __editorThemeSpecForTests.gutterBackgroundColor;
expect(v).toMatch(/var\(--bg-viewer\)/);
expect(v).toMatch(/!important/);
});
it('.cm-gutters color 必须是 var(--fg-tertiary) !important', () => {
const v = __editorThemeSpecForTests.gutterColor;
expect(v).toMatch(/var\(--fg-tertiary\)/);
expect(v).toMatch(/!important/);
});
// @codemirror/view 的 base theme 用 `&light .cm-activeLineGutter` / `&dark` 规则
//(特异性 (0,2,0))给 active 行 gutter 涂硬编码色(亮 #e2f2ff / 暗 #222227)。
// 我们的 .cm-activeLineGutter 是 (0,1,0),必须用 !important 才能让 var(--bg-hover) 稳定生效。
// 否则亮色下「活动行 gutter 变浅蓝色块」就是这个原因。
it('.cm-activeLine backgroundColor 必须是 var(--bg-hover) !important', () => {
const v = __editorThemeSpecForTests.activeLineBackgroundColor;
expect(v).toMatch(/var\(--bg-hover\)/);
expect(v).toMatch(/!important/);
});
it('.cm-activeLineGutter backgroundColor 必须是 var(--bg-hover) !important', () => {
const v = __editorThemeSpecForTests.activeLineGutterBackgroundColor;
expect(v).toMatch(/var\(--bg-hover\)/);
expect(v).toMatch(/!important/);
});
it('.cm-activeLineGutter color 必须是 var(--fg-secondary) !important', () => {
const v = __editorThemeSpecForTests.activeLineGutterColor;
expect(v).toMatch(/var\(--fg-secondary\)/);
expect(v).toMatch(/!important/);
});
// base theme 给 .cm-cursor 设黑色,&dark 下设 #528bff(oneDark 的 cursor 蓝)。
// 我们用 var(--accent),需要 !important 才能不被覆盖。
it('.cm-cursor/.cm-dropCursor borderLeftColor 必须是 var(--accent) !important', () => {
const v = __editorThemeSpecForTests.cursorBorderLeftColor;
expect(v).toMatch(/var\(--accent\)/);
expect(v).toMatch(/!important/);
});
// .cm-content caretColor 同样被 base theme 影响 —— 加 !important 兜底。
it('.cm-content caretColor 必须是 var(--accent) !important', () => {
const v = __editorThemeSpecForTests.caretColor;
expect(v).toMatch(/var\(--accent\)/);
expect(v).toMatch(/!important/);
});
// base theme `&light .cm-panels { background: #f5f5f5; color: black }` /
// `&dark { background: #333338; color: white }` 会盖住搜索面板的颜色。
it('.cm-panels backgroundColor 必须是 var(--bg-elevated) !important', () => {
const v = __editorThemeSpecForTests.panelsBackgroundColor;
expect(v).toMatch(/var\(--bg-elevated\)/);
expect(v).toMatch(/!important/);
});
it('.cm-panels color 必须是 var(--fg-primary) !important', () => {
const v = __editorThemeSpecForTests.panelsColor;
expect(v).toMatch(/var\(--fg-primary\)/);
expect(v).toMatch(/!important/);
});
// base theme 给 .cm-line 设了 `padding: 0 2px 0 6px`,左侧 6px 内边距会
// 形成「行首死区」——鼠标点击这一段 6px 区域时光标无法可靠地放到该行最前面。
// 我们的覆盖必须是左内边距 0 + !important(与 base theme 同特异性 (0,1,0),
// 源序胜负不可靠,!important 兜底)。
it('.cm-line padding 必须覆盖 base theme 的左内边距 6px → 0(带 !important)', () => {
const v = __editorThemeSpecForTests.linePadding;
// 期望值形如 "0 2px 0 0 !important",左 0、右 2px、上 0、下 0
expect(v).toMatch(/^0\s+2px\s+0\s+0/);
expect(v).toMatch(/!important/);
});
// basicSetup 的 foldGutter() 渲染的 chevron 列默认宽度 ~16px,会从
// .cm-gutters 容器(CSS width 钉死 32px)溢出 ~10px,盖在 cm-line 的开头
// 字符上,导致点击「行首最前面」时 hit target 是 gutter SPAN 而非 cm-line
// —— CM6 监听挂在 view.contentDOM 上,gutter 不在 contentDOM 子树内,
// mousedown 不会冒泡过去,光标位置不变。
// 我们用 display:none + !important 隐藏 chevron 渲染部分。foldGutter()
// 的 keymap(Ctrl+Shift+[ / ])仍然有效,只是视觉上看不到 chevron。
it('.cm-foldGutter 必须 display:none(带 !important)— 隐藏溢出 chevron', () => {
const v = __editorThemeSpecForTests.foldGutterDisplay;
expect(v).toMatch(/^none/);
expect(v).toMatch(/!important/);
});
});