mirror of
https://github.com/stablyai/orca.git
synced 2026-10-18 16:03:17 +00:00
fix(terminal): honor Bold Text color override (#8595)
Pin xterm + addon-webgl to resolve theme.bold for bold cells that still use the default foreground (Ghostty bold-color / iTerm Bold Color). DOM path sets an inline color when min-contrast leaves the cell unstyled so high-contrast bold overrides still paint. Explicit ANSI/RGB foregrounds and inverse cells keep prior behavior. Credit: @rodboev (PR #8952) for the core ThemeService/WebGL approach.
This commit is contained in:
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
+2
-1
@@ -272,7 +272,8 @@
|
||||
"node-pty@1.1.0": "config/patches/node-pty@1.1.0.patch",
|
||||
"@xterm/addon-ligatures@0.11.0-beta.287": "config/patches/@xterm__addon-ligatures@0.11.0-beta.287.patch",
|
||||
"@xterm/addon-webgl@0.20.0-beta.286": "config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch",
|
||||
"@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch"
|
||||
"@xterm/addon-serialize@0.15.0-beta.287": "config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch",
|
||||
"@xterm/xterm@6.1.0-beta.287": "config/patches/@xterm__xterm@6.1.0-beta.287.patch"
|
||||
}
|
||||
},
|
||||
"reactDoctor": {
|
||||
|
||||
Generated
+27
-24
@@ -15,8 +15,11 @@ patchedDependencies:
|
||||
hash: 81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9
|
||||
path: config/patches/@xterm__addon-serialize@0.15.0-beta.287.patch
|
||||
'@xterm/addon-webgl@0.20.0-beta.286':
|
||||
hash: cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64
|
||||
hash: 3bcf13573a2c994edbeeac7f32e1b9249f0d6721ba031a61f8f8c446a17f360c
|
||||
path: config/patches/@xterm__addon-webgl@0.20.0-beta.286.patch
|
||||
'@xterm/xterm@6.1.0-beta.287':
|
||||
hash: d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc
|
||||
path: config/patches/@xterm__xterm@6.1.0-beta.287.patch
|
||||
node-pty@1.1.0:
|
||||
hash: 407ae07e1e0e2ff2e8b58696449c54c31e51d87535bc6aa4a7a7b0b561407282
|
||||
path: config/patches/node-pty@1.1.0.patch
|
||||
@@ -42,7 +45,7 @@ importers:
|
||||
version: 2.5.6
|
||||
'@xterm/addon-serialize':
|
||||
specifier: 0.15.0-beta.287
|
||||
version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/headless':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287
|
||||
@@ -202,25 +205,25 @@ importers:
|
||||
version: 5.2.0(vite@7.3.6(@types/node@25.6.0)(jiti@2.7.0)(lightningcss@1.32.0)(yaml@2.8.4))
|
||||
'@xterm/addon-fit':
|
||||
specifier: 0.12.0-beta.287
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/addon-ligatures':
|
||||
specifier: 0.11.0-beta.287
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/addon-search':
|
||||
specifier: 0.17.0-beta.287
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/addon-unicode11':
|
||||
specifier: 0.10.0-beta.287
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/addon-web-links':
|
||||
specifier: 0.13.0-beta.287
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/addon-webgl':
|
||||
specifier: 0.20.0-beta.286
|
||||
version: 0.20.0-beta.286(patch_hash=cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64)(@xterm/xterm@6.1.0-beta.287)
|
||||
version: 0.20.0-beta.286(patch_hash=3bcf13573a2c994edbeeac7f32e1b9249f0d6721ba031a61f8f8c446a17f360c)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))
|
||||
'@xterm/xterm':
|
||||
specifier: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287
|
||||
version: 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
class-variance-authority:
|
||||
specifier: ^0.7.1
|
||||
version: 0.7.1
|
||||
@@ -9577,39 +9580,39 @@ snapshots:
|
||||
|
||||
'@xmldom/xmldom@0.9.10': {}
|
||||
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-fit@0.12.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-ligatures@0.11.0-beta.287(patch_hash=47405b9994b5acf1b4e90b49250358c1ca03649854d59560e7732b72fe336920)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
lru-cache: 11.5.1
|
||||
opentype.js: 2.0.0
|
||||
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-search@0.17.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-serialize@0.15.0-beta.287(patch_hash=81575700d58b62f9262d302aa4ae43b445a6273d579cd75dd6729c8883011ab9)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-unicode11@0.10.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-web-links@0.13.0-beta.287(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=cf737f93f193124ffc7f302bbbf81c2290b29b562335f24263209274caaf8e64)(@xterm/xterm@6.1.0-beta.287)':
|
||||
'@xterm/addon-webgl@0.20.0-beta.286(patch_hash=3bcf13573a2c994edbeeac7f32e1b9249f0d6721ba031a61f8f8c446a17f360c)(@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc))':
|
||||
dependencies:
|
||||
'@xterm/xterm': 6.1.0-beta.287
|
||||
'@xterm/xterm': 6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)
|
||||
|
||||
'@xterm/headless@6.1.0-beta.287': {}
|
||||
|
||||
'@xterm/xterm@6.1.0-beta.287': {}
|
||||
'@xterm/xterm@6.1.0-beta.287(patch_hash=d57bfbffd34019dfcbc803b418ee7e2d27998439aa763e821b5dc70778bc99dc)': {}
|
||||
|
||||
abbrev@4.0.0: {}
|
||||
|
||||
|
||||
@@ -48,6 +48,18 @@ describe('composeActiveTerminalTheme', () => {
|
||||
expect(result!.background).toBe('#101010')
|
||||
})
|
||||
|
||||
it('preserves the Bold Text override for the pinned xterm theme.bold slot', () => {
|
||||
// Why: #8595 — overrides.bold used to be spread into options.theme and
|
||||
// then ignored by xterm; composition must keep shipping the key.
|
||||
const base = { background: '#101010', foreground: '#fafafa' }
|
||||
const result = composeActiveTerminalTheme(
|
||||
base,
|
||||
settingsWith({ terminalColorOverrides: { bold: '#ffc600' } })
|
||||
)
|
||||
expect(result!.bold).toBe('#ffc600')
|
||||
expect(result!.foreground).toBe('#fafafa')
|
||||
})
|
||||
|
||||
it('applies background opacity by converting the hex background to rgba', () => {
|
||||
const base = { background: '#112233' }
|
||||
const result = composeActiveTerminalTheme(
|
||||
|
||||
@@ -0,0 +1,116 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import { createRequire } from 'node:module'
|
||||
import { dirname, join } from 'node:path'
|
||||
import { describe, expect, it } from 'vitest'
|
||||
import type { ITheme } from '@xterm/xterm'
|
||||
import { Terminal } from '@xterm/xterm'
|
||||
import { composeActiveTerminalTheme } from './terminal-appearance'
|
||||
import type { GlobalSettings } from '../../../../shared/types'
|
||||
|
||||
/**
|
||||
* Regression for #8595: Settings "Bold Text" / Ghostty bold-color was stored
|
||||
* on terminalColorOverrides.bold and spread into options.theme, but upstream
|
||||
* xterm ITheme had no `bold` key — renderers always used theme.foreground for
|
||||
* bold default-FG cells.
|
||||
*
|
||||
* The pinned xterm + addon-webgl patches accept theme.bold and use it as the
|
||||
* default foreground for bold, non-inverse cells. Explicit ANSI/RGB
|
||||
* foregrounds stay on their own palette path.
|
||||
*/
|
||||
describe('issue #8595 bold theme color', () => {
|
||||
const require = createRequire(import.meta.url)
|
||||
|
||||
function settingsWith(partial: Partial<GlobalSettings>): GlobalSettings {
|
||||
return {
|
||||
terminalColorOverrides: undefined,
|
||||
terminalCursorOpacity: undefined,
|
||||
terminalBackgroundOpacity: undefined,
|
||||
...partial
|
||||
} as GlobalSettings
|
||||
}
|
||||
|
||||
function packageRoot(specifier: string): string {
|
||||
return dirname(require.resolve(`${specifier}/package.json`))
|
||||
}
|
||||
|
||||
it('ITheme accepts bold and Terminal options preserve the override', () => {
|
||||
const theme: ITheme = {
|
||||
foreground: '#fafafa',
|
||||
bold: '#ffc600',
|
||||
background: '#101010'
|
||||
}
|
||||
// Why: if the pin regresses and drops ITheme.bold, this assignment fails
|
||||
// typecheck / the runtime options bag no longer carries the key.
|
||||
expect(theme.bold).toBe('#ffc600')
|
||||
|
||||
const term = new Terminal({ allowProposedApi: true, theme })
|
||||
try {
|
||||
expect(term.options.theme?.bold).toBe('#ffc600')
|
||||
term.options.theme = {
|
||||
foreground: '#eeeeee',
|
||||
background: '#111111',
|
||||
bold: '#00ffaa'
|
||||
}
|
||||
expect(term.options.theme?.bold).toBe('#00ffaa')
|
||||
term.options.theme = {
|
||||
foreground: '#eeeeee',
|
||||
background: '#111111'
|
||||
}
|
||||
expect(term.options.theme?.bold).toBeUndefined()
|
||||
} finally {
|
||||
term.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('composeActiveTerminalTheme keeps terminalColorOverrides.bold on the live theme', () => {
|
||||
const composed = composeActiveTerminalTheme(
|
||||
{ foreground: '#fafafa', background: '#101010' },
|
||||
settingsWith({ terminalColorOverrides: { bold: '#ff00aa' } })
|
||||
)
|
||||
expect(composed?.bold).toBe('#ff00aa')
|
||||
expect(composed?.foreground).toBe('#fafafa')
|
||||
|
||||
const term = new Terminal({ allowProposedApi: true, theme: composed ?? undefined })
|
||||
try {
|
||||
expect(term.options.theme?.bold).toBe('#ff00aa')
|
||||
} finally {
|
||||
term.dispose()
|
||||
}
|
||||
})
|
||||
|
||||
it('pinned xterm package resolves theme.bold into the ThemeService color set', () => {
|
||||
const root = packageRoot('@xterm/xterm')
|
||||
const typings = readFileSync(join(root, 'typings/xterm.d.ts'), 'utf8')
|
||||
const themeService = readFileSync(join(root, 'src/browser/services/ThemeService.ts'), 'utf8')
|
||||
const domRowFactory = readFileSync(
|
||||
join(root, 'src/browser/renderer/dom/DomRendererRowFactory.ts'),
|
||||
'utf8'
|
||||
)
|
||||
const lib = readFileSync(join(root, 'lib/xterm.js'), 'utf8')
|
||||
|
||||
expect(typings).toMatch(/export interface ITheme \{[\s\S]*?bold\?: string/)
|
||||
// Why: fallback to foreground when bold is omitted matches Ghostty/iTerm
|
||||
// "no bold color configured" behavior.
|
||||
expect(themeService).toContain('colors.bold = parseColor(theme.bold, colors.foreground)')
|
||||
// Why: default FG inherits the row container color; bold default FG must
|
||||
// still paint when min-contrast does not emit an inline color.
|
||||
expect(domRowFactory).toContain('cell.isBold() && !isInverse ? colors.bold : colors.foreground')
|
||||
expect(domRowFactory).toContain('color:${colors.bold.css}')
|
||||
expect(lib).toContain('bold.css')
|
||||
})
|
||||
|
||||
it('pinned webgl atlas uses colors.bold only for bold default-FG (not explicit ANSI)', () => {
|
||||
const root = packageRoot('@xterm/addon-webgl')
|
||||
const atlas = readFileSync(join(root, 'src/TextureAtlas.ts'), 'utf8')
|
||||
const charAtlas = readFileSync(join(root, 'src/CharAtlasUtils.ts'), 'utf8')
|
||||
|
||||
expect(charAtlas).toContain('bold: colors.bold')
|
||||
expect(charAtlas).toContain('a.colors.bold.rgba === b.colors.bold.rgba')
|
||||
// Default-FG branch selects bold when the bold attribute is set.
|
||||
expect(atlas).toMatch(/else if \(bold\) \{\s*result = this\._config\.colors\.bold/)
|
||||
expect(atlas).toMatch(/if \(bold\) \{\s*return this\._config\.colors\.bold\.rgba/)
|
||||
// Explicit palette path still uses ANSI indices (drawBoldTextInBrightColors),
|
||||
// not the theme bold color.
|
||||
expect(atlas).toContain('if (this._config.drawBoldTextInBrightColors && bold && fgColor < 8)')
|
||||
})
|
||||
})
|
||||
+2
-3
@@ -2437,9 +2437,8 @@ export type TerminalColorOverrides = {
|
||||
brightMagenta?: string
|
||||
brightCyan?: string
|
||||
brightWhite?: string
|
||||
// Why: xterm.js ITheme does not expose a `bold` key, but Ghostty users
|
||||
// expect the setting to be preserved so a future renderer CSS override
|
||||
// or xterm upgrade can honour it without a migration.
|
||||
// Why: pinned xterm ThemeService accepts `bold` as the default FG for
|
||||
// bold cells (Ghostty bold-color / iTerm "Bold Color" semantics).
|
||||
bold?: string
|
||||
}
|
||||
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
printf '\033[1mBOLD DEFAULT FG\033[0m\n'
|
||||
@@ -0,0 +1,179 @@
|
||||
import { readFileSync } from 'node:fs'
|
||||
import path from 'node:path'
|
||||
import { PNG } from 'pngjs'
|
||||
import type { Page } from '@stablyai/playwright-test'
|
||||
import { test, expect } from './helpers/orca-app'
|
||||
import { ensureTerminalVisible, waitForActiveWorktree, waitForSessionReady } from './helpers/store'
|
||||
import { nodeTerminalCommand } from './terminal-node-command'
|
||||
import {
|
||||
sendToTerminal,
|
||||
waitForActivePanePtyId,
|
||||
waitForActiveTerminalManager,
|
||||
waitForTerminalOutput
|
||||
} from './helpers/terminal'
|
||||
|
||||
function countPixels(
|
||||
buffer: Buffer,
|
||||
matcher: (red: number, green: number, blue: number, alpha: number) => boolean
|
||||
): number {
|
||||
const image = PNG.sync.read(buffer)
|
||||
let count = 0
|
||||
for (let y = 0; y < image.height; y += 1) {
|
||||
for (let x = 0; x < image.width; x += 1) {
|
||||
const offset = (y * image.width + x) * 4
|
||||
if (
|
||||
matcher(
|
||||
image.data[offset] ?? 0,
|
||||
image.data[offset + 1] ?? 0,
|
||||
image.data[offset + 2] ?? 0,
|
||||
image.data[offset + 3] ?? 0
|
||||
)
|
||||
) {
|
||||
count += 1
|
||||
}
|
||||
}
|
||||
}
|
||||
return count
|
||||
}
|
||||
|
||||
function isNormalForeground(red: number, green: number, blue: number, alpha: number): boolean {
|
||||
return (
|
||||
alpha > 180 &&
|
||||
red > 180 &&
|
||||
green > 180 &&
|
||||
blue > 180 &&
|
||||
Math.abs(red - green) <= 28 &&
|
||||
Math.abs(red - blue) <= 28 &&
|
||||
Math.abs(green - blue) <= 28
|
||||
)
|
||||
}
|
||||
|
||||
function isBoldOverrideMagenta(red: number, green: number, blue: number, alpha: number): boolean {
|
||||
return alpha > 180 && red > 180 && green < 20 && blue > 120
|
||||
}
|
||||
|
||||
function isBoldOverrideCyan(red: number, green: number, blue: number, alpha: number): boolean {
|
||||
return alpha > 180 && red < 20 && green > 120 && blue > 70 && blue < 110
|
||||
}
|
||||
|
||||
function isExplicitAnsiRed(red: number, green: number, blue: number, alpha: number): boolean {
|
||||
return alpha > 180 && red > 180 && green < 80 && blue < 80
|
||||
}
|
||||
|
||||
async function configureRenderer(page: Page, mode: 'dom' | 'webgl'): Promise<void> {
|
||||
await page.evaluate((renderer) => {
|
||||
const state = window.__store?.getState()
|
||||
const manager = state?.activeTabId ? window.__paneManagers?.get(state.activeTabId) : null
|
||||
manager?.setTerminalGpuAcceleration?.(renderer === 'webgl' ? 'on' : 'off')
|
||||
}, mode)
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate((renderer) => {
|
||||
const state = window.__store?.getState()
|
||||
const manager = state?.activeTabId ? window.__paneManagers?.get(state.activeTabId) : null
|
||||
return (
|
||||
manager
|
||||
?.getRenderingDiagnostics?.()
|
||||
.some((entry) => entry.hasWebgl === (renderer === 'webgl')) ?? false
|
||||
)
|
||||
}, mode),
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
.toBe(true)
|
||||
}
|
||||
|
||||
async function setBoldOverride(page: Page, bold: string | undefined): Promise<void> {
|
||||
await page.evaluate(async (boldColor) => {
|
||||
const store = window.__store
|
||||
const state = store?.getState()
|
||||
if (!store || !state) {
|
||||
throw new Error('Store unavailable')
|
||||
}
|
||||
const terminalColorOverrides = { ...state.settings.terminalColorOverrides }
|
||||
if (boldColor) {
|
||||
terminalColorOverrides.bold = boldColor
|
||||
} else {
|
||||
delete terminalColorOverrides.bold
|
||||
}
|
||||
await state.updateSettings({
|
||||
terminalColorOverrides:
|
||||
Object.keys(terminalColorOverrides).length > 0 ? terminalColorOverrides : undefined
|
||||
})
|
||||
}, bold)
|
||||
|
||||
await expect
|
||||
.poll(
|
||||
() =>
|
||||
page.evaluate(() => {
|
||||
const state = window.__store?.getState()
|
||||
const manager = state?.activeTabId ? window.__paneManagers?.get(state.activeTabId) : null
|
||||
const pane = manager?.getActivePane?.() ?? manager?.getPanes?.()[0]
|
||||
return {
|
||||
setting: state?.settings.terminalColorOverrides?.bold,
|
||||
theme: pane?.terminal.options.theme.bold
|
||||
}
|
||||
}, bold),
|
||||
{ timeout: 10_000 }
|
||||
)
|
||||
.toEqual({ setting: bold, theme: bold })
|
||||
}
|
||||
|
||||
test.describe('Bold Text default foreground', () => {
|
||||
test('honors the override in DOM and WebGL while preserving explicit colors', async ({
|
||||
orcaPage
|
||||
}, testInfo) => {
|
||||
await waitForSessionReady(orcaPage)
|
||||
await waitForActiveWorktree(orcaPage)
|
||||
await ensureTerminalVisible(orcaPage)
|
||||
await waitForActiveTerminalManager(orcaPage, 30_000)
|
||||
|
||||
const reproPath = process.env.ORCA_BOLD_REPRO_PATH
|
||||
const fixturePath = path.resolve(
|
||||
process.cwd(),
|
||||
'tests/e2e/fixtures/terminal-bold-default-foreground.txt'
|
||||
)
|
||||
const repro = readFileSync(reproPath ?? fixturePath)
|
||||
const fixture = readFileSync(fixturePath)
|
||||
expect(repro.equals(fixture), 'reproduction artifact must match the committed fixture').toBe(
|
||||
true
|
||||
)
|
||||
|
||||
const ptyId = await waitForActivePanePtyId(orcaPage)
|
||||
const renderScript = `process.stdout.write(${JSON.stringify(
|
||||
'\u001b[2J\u001b[H\u001b[?25l\u001b[1mBOLD DEFAULT FG\u001b[0m\n\u001b[1;31mEXPLICIT BOLD FG\u001b[0m\nNON-BOLD DEFAULT FG\n'
|
||||
)})`
|
||||
await sendToTerminal(orcaPage, ptyId, `${nodeTerminalCommand(['-e', renderScript])}\r`)
|
||||
await waitForTerminalOutput(orcaPage, 'NON-BOLD DEFAULT FG')
|
||||
|
||||
for (const mode of ['dom', 'webgl'] as const) {
|
||||
await configureRenderer(orcaPage, mode)
|
||||
for (const [state, color, matcher] of [
|
||||
['add', '#ff00aa', isBoldOverrideMagenta],
|
||||
['change', '#00ffaa', isBoldOverrideCyan],
|
||||
['remove', undefined, isNormalForeground]
|
||||
] as const) {
|
||||
await setBoldOverride(orcaPage, color)
|
||||
await orcaPage.waitForTimeout(250)
|
||||
const screenshotPath = process.env.ORCA_BOLD_SCREENSHOT_PATH
|
||||
? `${process.env.ORCA_BOLD_SCREENSHOT_PATH}-${mode}-${state}.png`
|
||||
: testInfo.outputPath(`terminal-bold-default-foreground-${mode}-${state}.png`)
|
||||
const screenshot = await orcaPage.locator('.xterm-screen').first().screenshot({
|
||||
path: screenshotPath
|
||||
})
|
||||
expect(
|
||||
countPixels(screenshot, matcher),
|
||||
`${mode} ${state} screenshot should contain the expected bold-default color`
|
||||
).toBeGreaterThan(0)
|
||||
expect(
|
||||
countPixels(screenshot, isNormalForeground),
|
||||
`${mode} ${state} screenshot should still contain normal foreground text`
|
||||
).toBeGreaterThan(0)
|
||||
expect(
|
||||
countPixels(screenshot, isExplicitAnsiRed),
|
||||
`${mode} ${state} screenshot should keep the explicit ANSI red row`
|
||||
).toBeGreaterThan(0)
|
||||
}
|
||||
}
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user