From 8420baf0b077eaf4e7b216f24abacde8f084eaca Mon Sep 17 00:00:00 2001 From: Neil Date: Sat, 19 Sep 2026 01:15:23 -0700 Subject: [PATCH] test(terminal): remove IME assertion lint findings --- ...rminal-ime-xterm-preedit-cell-grid.test.ts | 76 +++++++++++++------ .../terminal-ime-preedit-cell-grid.spec.ts | 13 +--- 2 files changed, 54 insertions(+), 35 deletions(-) diff --git a/src/renderer/src/components/terminal-pane/terminal-ime-xterm-preedit-cell-grid.test.ts b/src/renderer/src/components/terminal-pane/terminal-ime-xterm-preedit-cell-grid.test.ts index 82efc4baf3a..bc60584894c 100644 --- a/src/renderer/src/components/terminal-pane/terminal-ime-xterm-preedit-cell-grid.test.ts +++ b/src/renderer/src/components/terminal-pane/terminal-ime-xterm-preedit-cell-grid.test.ts @@ -3,6 +3,11 @@ import { Unicode11Addon } from '@xterm/addon-unicode11' import { Terminal } from '@xterm/xterm' import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest' +type ImeTerminalCore = { + _renderService: { dimensions: { css: { cell: { width: number } } } } + _compositionHelper: { updateCompositionElements: (dontRecurse: boolean) => void } +} + let terminal: Terminal let view: HTMLElement let assignedSpacing: WeakMap @@ -27,19 +32,39 @@ function compose(text: string): HTMLElement { function runs(preedit: HTMLElement): { text: string | null; spacing: string | undefined }[] { return Array.from(preedit.children, (child) => ({ text: child.textContent, - spacing: assignedSpacing.get((child as HTMLElement).style) + spacing: assignedSpacing.get(elementStyle(child)) })) } +function elementStyle(element: Element): CSSStyleDeclaration { + if (!(element instanceof HTMLElement)) { + throw new Error('Expected an HTML element') + } + return element.style +} + function rendering() { + const core: unknown = Reflect.get(terminal, '_core') + if (!isImeTerminalCore(core)) { + throw new Error('xterm internals are unavailable') + } + return core +} + +function isImeTerminalCore(value: unknown): value is ImeTerminalCore { + const renderService = objectProperty(value, '_renderService') + const dimensions = objectProperty(renderService, 'dimensions') + const css = objectProperty(dimensions, 'css') + const cell = objectProperty(css, 'cell') + const compositionHelper = objectProperty(value, '_compositionHelper') return ( - terminal as unknown as { - _core: { - _renderService: { dimensions: { css: { cell: { width: number } } } } - _compositionHelper: { updateCompositionElements: (dontRecurse: boolean) => void } - } - } - )._core + typeof objectProperty(cell, 'width') === 'number' && + typeof objectProperty(compositionHelper, 'updateCompositionElements') === 'function' + ) +} + +function objectProperty(value: unknown, key: string): unknown { + return typeof value === 'object' && value !== null ? Reflect.get(value, key) : undefined } describe('IME preedit advances on the terminal cell grid (#19315)', () => { @@ -58,22 +83,23 @@ describe('IME preedit advances on the terminal cell grid (#19315)', () => { setter.call(this, value) } ) - vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockImplementation( - () => - ({ - font: '13px monospace', - measureText(this: { font: string }, text: string) { - measurements.push(text) - const fontSize = Number(this.font.match(/([\d.]+)px/)?.[1] ?? 13) - const naturalWidth = /^[\uac00-\ud7a3]$/u.test(text) - ? 11.25 - : /^[\x20-\x7e\uff61-\uff9f]$/u.test(text) - ? 6.5 - : 13 - return { width: naturalWidth * (fontSize / 13) * fontScale } - } - }) as unknown as CanvasRenderingContext2D - ) + vi.spyOn(HTMLCanvasElement.prototype, 'getContext').mockImplementation(() => { + const context: CanvasRenderingContext2D = Object.create(null) + context.font = '13px monospace' + context.measureText = function (text: string) { + measurements.push(text) + const fontSize = Number(this.font.match(/([\d.]+)px/)?.[1] ?? 13) + const naturalWidth = /^[\uac00-\ud7a3]$/u.test(text) + ? 11.25 + : /^[\x20-\x7e\uff61-\uff9f]$/u.test(text) + ? 6.5 + : 13 + return Object.assign(Object.create(null), { + width: naturalWidth * (fontSize / 13) * fontScale + }) + } + return context + }) const container = document.createElement('div') document.body.appendChild(container) terminal = new Terminal({ cols: 80, rows: 24, fontSize: 13, allowProposedApi: true }) @@ -100,7 +126,7 @@ describe('IME preedit advances on the terminal cell grid (#19315)', () => { { text: 'ア', spacing: 'calc(var(--xterm-composition-cell-width) * 1 - 6.5px)' } ]) for (const child of Array.from(preedit.children)) { - const style = (child as HTMLElement).style + const style = elementStyle(child) expect(style.position).toBe('') expect(style.display).toBe('') expect(style.width).toBe('') diff --git a/tests/e2e/terminal-ime-preedit-cell-grid.spec.ts b/tests/e2e/terminal-ime-preedit-cell-grid.spec.ts index 5fdeb34ba68..6fc5520f926 100644 --- a/tests/e2e/terminal-ime-preedit-cell-grid.spec.ts +++ b/tests/e2e/terminal-ime-preedit-cell-grid.spec.ts @@ -6,10 +6,6 @@ import { writeToActiveTerminal } from './terminal-ime-midline-occlusion-probe' -type TerminalGrid = { - _core: { _renderService: { dimensions: { css: { cell: { width: number } } } } } -} - for (const dpr of [1, 1.25, 2]) { for (const gpu of ['on', 'off'] as const) { test.describe(`IME preedit grid DPR ${dpr} GPU ${gpu}`, () => { @@ -59,8 +55,7 @@ for (const dpr of [1, 1.25, 2]) { const screen = terminal.element!.querySelector('.xterm-screen')! const preedit = screen.querySelector('.xterm-composition-preedit')! // The canvas width rounds independently of fractional WebGL cell widths. - const cellWidth = (terminal as unknown as TerminalGrid)._core._renderService - .dimensions.css.cell.width + const cellWidth = terminal._core._renderService.dimensions.css.cell.width const line = terminal.buffer.active.getLine(terminal.buffer.active.baseY)! const committed: { text: string; column: number; width: number }[] = [] const end = line.translateToString(true).length @@ -169,8 +164,7 @@ for (const dpr of [1, 1.25, 2]) { const terminal = window .__paneManagers!.get(state.activeTabId!)! .getActivePane()!.terminal - const cellWidth = (terminal as unknown as TerminalGrid)._core._renderService - .dimensions.css.cell.width + const cellWidth = terminal._core._renderService.dimensions.css.cell.width const preedit = terminal.element!.querySelector('.xterm-composition-preedit')! return Math.abs(preedit.getBoundingClientRect().width - 16 * cellWidth) }) @@ -222,8 +216,7 @@ test('preserves native shaping for mixed text, complex scripts, and emoji', asyn const actual = await preedit.screenshot() // Compare against the original single-text-node browser rendering. - await preedit.evaluate((element, text) => { - const span = element as HTMLElement + await preedit.evaluate((span: HTMLElement, text) => { span.textContent = `‎${text}‎` for (const property of ['width', 'white-space', 'display', 'position']) { span.style.removeProperty(property)