chore: establish YCode replication baseline and TODO

This commit is contained in:
xiaohan.yuan committed 2026-10-17 02:43:49 +08:00
commit 6dc59f89ba
1118 files changed
+196002

No files matched your search

+395
View File
@@ -0,0 +1,395 @@
import assert from 'node:assert/strict'
import { getFontLevel, getFontLevelSpec, initFontSize } from '../src/renderer/src/fontSize'
import {
buildFontSizeOverrideCSS,
buildWbFontSizeTokenCSS,
createFontSizeDomAdapter,
getFontSizeShortcutAction
} from '../src/renderer/src/workbuddy/typography/font-size'
let assertions = 0
function equal<T>(actual: T, expected: T, message: string): void {
assert.equal(actual, expected, message)
assertions += 1
}
function includes(value: string, expected: string, message: string): void {
assert.ok(value.includes(expected), message)
assertions += 1
}
function excludes(value: string, forbidden: string, message: string): void {
assert.ok(!value.includes(forbidden), message)
assertions += 1
}
class FakeStyleDeclaration {
private readonly entries: Array<[string, string]>
constructor(values: Record<string, string>) {
this.entries = Object.entries(values)
}
get length(): number {
return this.entries.length
}
item(index: number): string {
return this.entries[index]?.[0] ?? ''
}
getPropertyValue(name: string): string {
return this.entries.find(([key]) => key === name)?.[1] ?? ''
}
}
function styleRule(selectorText: string, values: Record<string, string>): CSSRule {
return {
type: 1,
selectorText,
style: new FakeStyleDeclaration(values)
} as unknown as CSSRule
}
function mediaRule(conditionText: string, cssRules: CSSRule[]): CSSRule {
return {
type: 4,
conditionText,
cssRules: cssRules as unknown as CSSRuleList
} as unknown as CSSRule
}
function stylesheet(cssRules: CSSRule[], ownerId?: string): CSSStyleSheet {
return {
ownerNode: ownerId ? ({ id: ownerId } as unknown as Element) : null,
cssRules: cssRules as unknown as CSSRuleList
} as unknown as CSSStyleSheet
}
function testRuleConstruction(): void {
const rules = [
styleRule('.card, .dialog', { 'font-size': '14px' }),
styleRule('.fallback', { 'font-size': 'var(--unresolved-size, 13.5px)' }),
styleRule(':root', { '--stable-size': '18px' }),
styleRule('.stable', { 'font-size': 'var(--stable-size, 14px)' }),
styleRule(':root', { '--dynamic-size': 'calc(15px + var(--yc-font-offset) * 1px)' }),
styleRule('.dynamic', { 'font-size': 'var(--dynamic-size, 15px)' }),
styleRule('.already-dynamic', { 'font-size': 'calc(16px + var(--cb-font-size-offset))' }),
styleRule('.token', { 'font-size': 'var(--yc-text-body-size)' }),
styleRule('.relative', { 'font-size': '1rem' }),
styleRule('.repeated', { 'font-size': '11px' }),
mediaRule('(min-width: 720px)', [styleRule('.media', { 'font-size': '12px' })]),
mediaRule('(min-width: 720px)', [styleRule('.media', { 'font-size': '12px' })]),
mediaRule('(max-width: 500px)', [styleRule('.repeated', { 'font-size': '11px' })])
]
const css = buildFontSizeOverrideCSS([stylesheet(rules), stylesheet([styleRule('.repeated', { 'font-size': '11px' })])])
includes(css, '.card:not(.cb-font-size-fixed):not(.cb-font-size-fixed *)', 'adds the fixed-zone exclusion to the first selector')
includes(css, '.dialog:not(.cb-font-size-fixed):not(.cb-font-size-fixed *)', 'splits grouped selectors before adding the fixed-zone exclusion')
includes(css, 'font-size: max(8px, calc(14px + var(--cb-font-size-offset) * 1px)) !important', 'scales a literal px size with an 8px floor')
includes(css, 'calc(13.5px + var(--cb-font-size-offset) * 1px)', 'uses the numeric px fallback from var() declarations')
includes(css, 'calc(14px + var(--cb-font-size-offset) * 1px)', 'uses the declared static fallback without resolving the custom property')
includes(css, '.dynamic:not(.cb-font-size-fixed)', 'matches the reference fallback behavior for var() declarations')
includes(css, '@media (min-width: 720px)', 'keeps media rules scoped')
includes(css, '@media (max-width: 500px)', 'keeps separate media conditions distinct')
excludes(css, '.already-dynamic:not(', 'does not rescale an existing calc() declaration')
excludes(css, '.token:not(', 'does not replace a token-only font-size declaration')
excludes(css, '.relative:not(', 'does not convert relative units to px')
equal((css.match(/\.repeated:not\(/g) ?? []).length, 2, 'dedupes within a CSS context but retains a different media context')
equal((css.match(/\.media:not\(/g) ?? []).length, 2, 'uses a fresh dedupe set for each media rule')
const inaccessible = {
get cssRules(): CSSRuleList {
throw new Error('cross-origin stylesheet')
}
} as unknown as CSSStyleSheet
equal(buildFontSizeOverrideCSS([inaccessible]), '', 'skips stylesheets whose cssRules cannot be read')
}
function testTokenCSS(): void {
const css = buildWbFontSizeTokenCSS()
includes(css, '--wb-font-display-size: calc(32px + var(--wb-font-size-offset, 0) * 1px)', 'provides the display token override')
includes(css, '--wb-button-icon-size-medium: calc(16px + var(--wb-font-size-offset, 0) * 1px)', 'scales button icon tokens with the same offset')
includes(css, '--wb-button-height-large: calc(var(--wb-button-line-height-large) + 13px * 2)', 'derives the large button height from line height and padding')
includes(css, '.cb-font-size-fixed,\n.cb-font-size-fixed *', 'creates the fixed-zone token reset')
includes(css, '--wb-font-body-size: 14px', 'restores token base sizes inside fixed zones')
includes(css, '--wb-button-line-height-small: 20px', 'restores button line-height tokens inside fixed zones')
}
class FakeStyleElement {
id = ''
textContent: string | null = ''
readonly tagName = 'STYLE'
parent: FakeHead | null = null
remove(): void {
this.parent?.removeChild(this)
}
}
class FakeHead {
readonly children: FakeStyleElement[] = []
appendChild(element: FakeStyleElement): FakeStyleElement {
element.parent = this
this.children.push(element)
return element
}
removeChild(element: FakeStyleElement): void {
const index = this.children.indexOf(element)
if (index >= 0) this.children.splice(index, 1)
element.parent = null
}
}
class FakeMutationObserver {
disconnected = false
private readonly callback: MutationCallback
constructor(callback: MutationCallback) {
this.callback = callback
}
observe(_target: Node, _options?: MutationObserverInit): void {}
disconnect(): void {
this.disconnected = true
}
triggerAddedNode(node: Node): void {
const record = { addedNodes: [node] as unknown as NodeList }
this.callback([record as MutationRecord], this as unknown as MutationObserver)
}
}
function testDOMAdapter(): void {
const head = new FakeHead()
const properties = new Map<string, string>()
const styleSheets: CSSStyleSheet[] = [stylesheet([styleRule('.live', { 'font-size': '14px' })])]
const rootStyle = {
setProperty: (name: string, value: string): void => properties.set(name, value),
removeProperty: (name: string): void => {
properties.delete(name)
},
getPropertyValue: (name: string): string => properties.get(name) ?? '',
get length(): number {
return properties.size
},
item: (index: number): string => [...properties.keys()][index] ?? ''
} as unknown as CSSStyleDeclaration
const documentRef = {
documentElement: { style: rootStyle, dataset: {} },
styleSheets,
head,
body: null,
getElementById: (id: string): FakeStyleElement | null => head.children.find((child) => child.id === id) ?? null,
createElement: (name: string): FakeStyleElement => {
assert.equal(name, 'style')
return new FakeStyleElement()
}
} as unknown as Document
const observerInstances: FakeMutationObserver[] = []
const timers = new Map<number, () => void>()
let timerId = 0
const windowRef = {
MutationObserver: class extends FakeMutationObserver {
constructor(callback: MutationCallback) {
super(callback)
observerInstances.push(this)
}
},
setTimeout: (callback: () => void): number => {
timerId += 1
timers.set(timerId, callback)
return timerId
},
clearTimeout: (timer: number): void => {
timers.delete(timer)
}
} as unknown as Window & typeof globalThis
const adapter = createFontSizeDomAdapter(documentRef, windowRef)
adapter.apply(3, '18.286px')
equal(properties.get('--yc-font-offset-global'), '3', 'writes the unbounded YCode offset')
equal(properties.get('--cb-font-size-offset-global'), '3', 'writes the unbounded compatibility offset')
equal(properties.get('--wb-font-size-offset'), 'clamp(-1, var(--cb-font-size-offset-global, 0), 2)', 'clamps token consumers independently')
equal(properties.get('--yc-md-root'), '18.286px', 'keeps the YCode markdown root')
equal(properties.get('--cb-md-root'), '18.286px', 'keeps the compatibility markdown root')
includes(head.children.find((child) => child.id === 'yc-font-size-override')?.textContent ?? '', '.live:not(', 'injects scanned CSS rules at nonzero levels')
includes(head.children.find((child) => child.id === 'yc-wb-token-font-size-override')?.textContent ?? '', '--wb-font-body-size', 'injects WB token rules at nonzero levels')
equal(observerInstances.length, 1, 'starts one observer for active global sizing')
const initialOverride = documentRef.getElementById('yc-font-size-override')
adapter.apply(4, '20.571px')
equal(documentRef.getElementById('yc-font-size-override'), initialOverride, 'reuses scanned CSS between nonzero levels')
equal(head.children.filter((child) => child.id === 'yc-wb-token-font-size-override').length, 1, 'does not duplicate token overrides')
styleSheets.push(stylesheet([styleRule('.late-style', { 'font-size': '15px' })]))
observerInstances[0].triggerAddedNode({ nodeType: 1, id: 'late-style-node', tagName: 'STYLE' } as unknown as Node)
equal(timers.size, 1, 'debounces a newly inserted stylesheet')
for (const callback of timers.values()) callback()
timers.clear()
includes(head.children.find((child) => child.id === 'yc-font-size-override')?.textContent ?? '', '.late-style:not(', 'rebuilds scanned overrides after a style insertion')
adapter.apply(0, '17.143px')
equal(documentRef.getElementById('yc-font-size-override'), null, 'removes scanned CSS overrides at zero offset')
equal(documentRef.getElementById('yc-wb-token-font-size-override'), null, 'removes WB token overrides at zero offset')
adapter.dispose()
equal(observerInstances[0].disconnected, true, 'disconnects the observer during cleanup')
}
function testPublicMappingAndShortcuts(): void {
equal(getFontLevelSpec(1).offset, -1, 'level one keeps the raw -1 offset')
equal(getFontLevelSpec(2).offset, 0, 'level two is the zero-offset default')
equal(getFontLevelSpec(5).offset, 3, 'level five retains raw +3 before consumer clamping')
equal(getFontLevelSpec(6).offset, 4, 'level six retains raw +4 before consumer clamping')
equal(getFontSizeShortcutAction({ key: '-', code: 'Minus', shiftKey: false }), 'decrease', 'maps the minus key')
equal(getFontSizeShortcutAction({ key: 'Unidentified', code: 'Equal', shiftKey: true }), 'increase', 'maps the Equal code fallback')
equal(getFontSizeShortcutAction({ key: 'Unidentified', code: 'Digit0', shiftKey: false }), 'reset', 'maps the Digit0 code fallback')
equal(getFontSizeShortcutAction({ key: '-', code: 'Minus', shiftKey: true }), null, 'does not treat shifted minus as decrease')
equal(getFontSizeShortcutAction({ key: '0', code: 'Digit0', shiftKey: true }), null, 'does not treat shifted zero as reset')
}
function testKeyboardIntegration(): void {
const descriptors = new Map<string, PropertyDescriptor | undefined>()
const originalGlobal = globalThis as unknown as Record<string, unknown>
const styleValues = new Map<string, string>()
const head = new FakeHead()
const listeners = new Map<string, { listener: EventListener; capture: boolean }>()
const eventNames: string[] = []
const timeoutCallbacks = new Map<number, () => void>()
let nextTimerId = 0
const documentRef = {
documentElement: {
style: {
setProperty: (name: string, value: string): void => styleValues.set(name, value),
removeProperty: (name: string): void => {
styleValues.delete(name)
},
getPropertyValue: (name: string): string => styleValues.get(name) ?? '',
get length(): number {
return styleValues.size
},
item: (index: number): string => [...styleValues.keys()][index] ?? ''
},
dataset: {}
},
styleSheets: [],
head,
body: null,
getElementById: (id: string): FakeStyleElement | null => head.children.find((child) => child.id === id) ?? null,
createElement: (): FakeStyleElement => new FakeStyleElement(),
addEventListener: (type: string, listener: EventListener, options?: boolean | AddEventListenerOptions): void => {
listeners.set(type, { listener, capture: options === true || (typeof options === 'object' && options.capture === true) })
},
removeEventListener: (type: string): void => {
listeners.delete(type)
}
} as unknown as Document
class TestCustomEvent {
readonly type: string
readonly detail: unknown
constructor(type: string, init?: { detail?: unknown }) {
this.type = type
this.detail = init?.detail
}
}
const windowRef = {
MutationObserver: FakeMutationObserver,
setTimeout: (callback: () => void): number => {
nextTimerId += 1
timeoutCallbacks.set(nextTimerId, callback)
return nextTimerId
},
clearTimeout: (id: number): void => {
timeoutCallbacks.delete(id)
},
dispatchEvent: (event: Event): boolean => {
eventNames.push((event as unknown as TestCustomEvent).type)
return true
}
} as unknown as Window & typeof globalThis
const storage = {
value: '2',
getItem(_key: string): string | null {
return this.value
},
setItem(_key: string, value: string): void {
this.value = value
}
}
const globals: Record<string, unknown> = {
document: documentRef,
window: windowRef,
localStorage: storage,
CustomEvent: TestCustomEvent
}
for (const [key, value] of Object.entries(globals)) {
descriptors.set(key, Object.getOwnPropertyDescriptor(globalThis, key))
Object.defineProperty(originalGlobal, key, { value, configurable: true, writable: true })
}
try {
const cleanup = initFontSize()
equal(listeners.get('keydown')?.capture, true, 'registers shortcuts in capture phase')
const listener = listeners.get('keydown')?.listener
assert.ok(listener, 'registers a keydown listener')
assertions += 1
const sendKey = (key: string, code: string, options: { ctrl?: boolean; meta?: boolean; alt?: boolean; shift?: boolean; target?: EventTarget | null } = {}): { prevented: boolean } => {
let prevented = false
listener!({
key,
code,
ctrlKey: options.ctrl ?? true,
metaKey: options.meta ?? false,
altKey: options.alt ?? false,
shiftKey: options.shift ?? false,
target: options.target ?? null,
preventDefault: () => {
prevented = true
}
} as unknown as KeyboardEvent)
return { prevented }
}
const focusedComposer = { tagName: 'TEXTAREA' } as unknown as EventTarget
equal(sendKey('+', 'Equal', { target: focusedComposer }).prevented, true, 'keeps shortcuts available while a composer input is focused')
equal(getFontLevel(), 3, 'increases the current level from the key handler')
equal(storage.value, '3', 'persists the selected YCode level')
equal(sendKey('-', 'Minus', { alt: true }).prevented, false, 'ignores Alt-modified shortcut combinations')
equal(getFontLevel(), 3, 'Alt-modified input leaves the current level unchanged')
equal(sendKey('Unidentified', 'Minus').prevented, true, 'uses the physical Minus code when key is unavailable')
equal(getFontLevel(), 2, 'decreases the current level from the code fallback')
equal(eventNames.filter((name) => name === 'ycode:font-level-change').length, 2, 'preserves the YCode public event')
for (const callback of timeoutCallbacks.values()) callback()
timeoutCallbacks.clear()
equal(eventNames.filter((name) => name === 'cb-font-size-change').length, 1, 'dispatches the compatible offset event after coalescing changes')
equal(eventNames.filter((name) => name === 'cb-font-size-level-change').length, 1, 'dispatches the compatible level event after coalescing changes')
cleanup()
equal(listeners.has('keydown'), false, 'removes the keydown listener during cleanup')
} finally {
for (const [key, descriptor] of descriptors) {
if (descriptor) Object.defineProperty(originalGlobal, key, descriptor)
else delete originalGlobal[key]
}
}
}
testRuleConstruction()
testTokenCSS()
testDOMAdapter()
testPublicMappingAndShortcuts()
testKeyboardIntegration()
console.log(`Font-size regression passed (${assertions} assertions).`)