test(terminal): remove IME assertion lint findings

This commit is contained in:
Neil
2026-09-19 16:30:45 -07:00
parent 9f41729232
commit 8420baf0b0
2 changed files with 54 additions and 35 deletions
@@ -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<CSSStyleDeclaration, string>
@@ -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('')
@@ -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<HTMLElement>('.xterm-screen')!
const preedit = screen.querySelector<HTMLElement>('.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)