diff --git a/addons/addon-canvas/test/CanvasRenderer.test.ts b/addons/addon-canvas/test/CanvasRenderer.test.ts index 2782081cab..c8b35c7a09 100644 --- a/addons/addon-canvas/test/CanvasRenderer.test.ts +++ b/addons/addon-canvas/test/CanvasRenderer.test.ts @@ -28,5 +28,10 @@ test.describe('Canvas Renderer Integration Tests', () => { test.skip(({ browserName }) => browserName === 'webkit'); injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, async () => { + await ctx.page.evaluate(` + window.addon = new window.CanvasAddon(true); + window.term.loadAddon(window.addon); + `); + }); }); diff --git a/addons/addon-webgl/test/WebglRenderer.test.ts b/addons/addon-webgl/test/WebglRenderer.test.ts index 4b73c08b57..d5f62fda78 100644 --- a/addons/addon-webgl/test/WebglRenderer.test.ts +++ b/addons/addon-webgl/test/WebglRenderer.test.ts @@ -29,5 +29,10 @@ test.describe('WebGL Renderer Integration Tests', async () => { } injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, async () => { + await ctx.page.evaluate(` + window.addon = new window.WebglAddon(true); + window.term.loadAddon(window.addon); + `); + }); }); diff --git a/src/browser/renderer/dom/DomRendererRowFactory.ts b/src/browser/renderer/dom/DomRendererRowFactory.ts index 6ab68e7d6e..50d3eb49e3 100644 --- a/src/browser/renderer/dom/DomRendererRowFactory.ts +++ b/src/browser/renderer/dom/DomRendererRowFactory.ts @@ -11,7 +11,7 @@ import { ICoreService, IDecorationService, IOptionsService } from 'common/servic import { color, rgba } from 'common/Color'; import { ICharacterJoinerService, ICoreBrowserService, IThemeService } from 'browser/services/Services'; import { JoinedCellData } from 'browser/services/CharacterJoinerService'; -import { excludeFromContrastRatioDemands } from 'browser/renderer/shared/RendererUtils'; +import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils'; import { AttributeData } from 'common/buffer/AttributeData'; import { WidthCache } from 'browser/renderer/dom/WidthCache'; import { IColorContrastCache } from 'browser/Types'; @@ -458,7 +458,7 @@ export class DomRendererRowFactory { } private _applyMinimumContrast(element: HTMLElement, bg: IColor, fg: IColor, cell: ICellData, bgOverride: IColor | undefined, fgOverride: IColor | undefined): boolean { - if (this._optionsService.rawOptions.minimumContrastRatio === 1 || excludeFromContrastRatioDemands(cell.getCode())) { + if (this._optionsService.rawOptions.minimumContrastRatio === 1 || treatGlyphAsBackgroundColor(cell.getCode())) { return false; } diff --git a/src/browser/renderer/shared/CellColorResolver.ts b/src/browser/renderer/shared/CellColorResolver.ts index 5837a675b2..5072510836 100644 --- a/src/browser/renderer/shared/CellColorResolver.ts +++ b/src/browser/renderer/shared/CellColorResolver.ts @@ -5,6 +5,8 @@ import { Attributes, BgFlags, ExtFlags, FgFlags, NULL_CELL_CODE, UnderlineStyle import { IDecorationService, IOptionsService } from 'common/services/Services'; import { ICellData } from 'common/Types'; import { Terminal } from '@xterm/xterm'; +import { rgba } from 'common/Color'; +import { treatGlyphAsBackgroundColor } from 'browser/renderer/shared/RendererUtils'; // Work variables to avoid garbage collection let $fg = 0; @@ -65,11 +67,11 @@ export class CellColorResolver { // Apply decorations on the bottom layer this._decorationService.forEachDecorationAtCell(x, y, 'bottom', d => { if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasBg = true; } if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasFg = true; } }); @@ -77,10 +79,94 @@ export class CellColorResolver { // Apply the selection color if needed $isSelected = this._selectionRenderModel.isCellSelected(this._terminal, x, y); if ($isSelected) { - $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & 0xFFFFFF; + // If the cell has a bg color, retain the color by blending it with the selection color + if ( + (this.result.fg & FgFlags.INVERSE) || + (this.result.bg & Attributes.CM_MASK) !== Attributes.CM_DEFAULT + ) { + // Resolve the standard bg color + if (this.result.fg & FgFlags.INVERSE) { + switch (this.result.fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $bg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $bg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + break; + case Attributes.CM_DEFAULT: + default: + $bg = this._themeService.colors.foreground.rgba; + } + } else { + switch (this.result.bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $bg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $bg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + break; + // No need to consider default bg color here as it's not possible + } + } + // Blend with selection bg color + $bg = rgba.blend( + $bg, + ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80 + ) >> 8 & Attributes.RGB_MASK; + } else { + $bg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK; + } $hasBg = true; + + // Apply explicit selection foreground if present if ($colors.selectionForeground) { - $fg = $colors.selectionForeground.rgba >> 8 & 0xFFFFFF; + $fg = $colors.selectionForeground.rgba >> 8 & Attributes.RGB_MASK; + $hasFg = true; + } + + // Overwrite fg as bg if it's a special decorative glyph (eg. powerline) + if (treatGlyphAsBackgroundColor(cell.getCode())) { + // Inverse default background should be treated as transparent + if ( + (this.result.fg & FgFlags.INVERSE) && + (this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT + ) { + $fg = (this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba >> 8 & Attributes.RGB_MASK; + } else { + + if (this.result.fg & FgFlags.INVERSE) { + switch (this.result.bg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $fg = this._themeService.colors.ansi[this.result.bg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $fg = this.result.bg & Attributes.RGB_MASK << 8 | 0xFF; + break; + // No need to consider default bg color here as it's not possible + } + } else { + switch (this.result.fg & Attributes.CM_MASK) { + case Attributes.CM_P16: + case Attributes.CM_P256: + $fg = this._themeService.colors.ansi[this.result.fg & Attributes.PCOLOR_MASK].rgba; + break; + case Attributes.CM_RGB: + $fg = (this.result.fg & Attributes.RGB_MASK) << 8 | 0xFF; + break; + case Attributes.CM_DEFAULT: + default: + $fg = this._themeService.colors.foreground.rgba; + } + } + + $fg = rgba.blend( + $fg, + ((this._coreBrowserService.isFocused ? $colors.selectionBackgroundOpaque : $colors.selectionInactiveBackgroundOpaque).rgba & 0xFFFFFF00) | 0x80 + ) >> 8 & Attributes.RGB_MASK; + } $hasFg = true; } } @@ -88,11 +174,11 @@ export class CellColorResolver { // Apply decorations on the top layer this._decorationService.forEachDecorationAtCell(x, y, 'top', d => { if (d.backgroundColorRGB) { - $bg = d.backgroundColorRGB.rgba >> 8 & 0xFFFFFF; + $bg = d.backgroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasBg = true; } if (d.foregroundColorRGB) { - $fg = d.foregroundColorRGB.rgba >> 8 & 0xFFFFFF; + $fg = d.foregroundColorRGB.rgba >> 8 & Attributes.RGB_MASK; $hasFg = true; } }); @@ -119,7 +205,7 @@ export class CellColorResolver { if ($hasBg && !$hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this.result.bg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | (($colors.background.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { $fg = (this.result.fg & ~(Attributes.RGB_MASK | FgFlags.INVERSE | Attributes.CM_MASK)) | this.result.bg & (Attributes.RGB_MASK | Attributes.CM_MASK); } @@ -128,7 +214,7 @@ export class CellColorResolver { if (!$hasBg && $hasFg) { // Resolve bg color type (default color has a different meaning in fg vs bg) if ((this.result.fg & Attributes.CM_MASK) === Attributes.CM_DEFAULT) { - $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & 0xFFFFFF) & Attributes.RGB_MASK) | Attributes.CM_RGB; + $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | (($colors.foreground.rgba >> 8 & Attributes.RGB_MASK) & Attributes.RGB_MASK) | Attributes.CM_RGB; } else { $bg = (this.result.bg & ~(Attributes.RGB_MASK | Attributes.CM_MASK)) | this.result.fg & (Attributes.RGB_MASK | Attributes.CM_MASK); } diff --git a/src/browser/renderer/shared/RendererUtils.ts b/src/browser/renderer/shared/RendererUtils.ts index 59b87b0e30..9a4bffe000 100644 --- a/src/browser/renderer/shared/RendererUtils.ts +++ b/src/browser/renderer/shared/RendererUtils.ts @@ -27,7 +27,7 @@ function isBoxOrBlockGlyph(codepoint: number): boolean { return 0x2500 <= codepoint && codepoint <= 0x259F; } -export function excludeFromContrastRatioDemands(codepoint: number): boolean { +export function treatGlyphAsBackgroundColor(codepoint: number): boolean { return isPowerlineGlyph(codepoint) || isBoxOrBlockGlyph(codepoint); } diff --git a/src/browser/renderer/shared/TextureAtlas.ts b/src/browser/renderer/shared/TextureAtlas.ts index 4af8685e50..9cd09cbf99 100644 --- a/src/browser/renderer/shared/TextureAtlas.ts +++ b/src/browser/renderer/shared/TextureAtlas.ts @@ -6,7 +6,7 @@ import { IColorContrastCache } from 'browser/Types'; import { DIM_OPACITY, TEXT_BASELINE } from 'browser/renderer/shared/Constants'; import { tryDrawCustomChar } from 'browser/renderer/shared/CustomGlyphs'; -import { computeNextVariantOffset, excludeFromContrastRatioDemands, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; +import { computeNextVariantOffset, treatGlyphAsBackgroundColor, isPowerlineGlyph, isRestrictedPowerlineGlyph, throwIfFalsy } from 'browser/renderer/shared/RendererUtils'; import { IBoundingBox, ICharAtlasConfig, IRasterizedGlyph, ITextureAtlas } from 'browser/renderer/shared/Types'; import { NULL_COLOR, color, rgba } from 'common/Color'; import { EventEmitter } from 'common/EventEmitter'; @@ -490,7 +490,7 @@ export class TextureAtlas implements ITextureAtlas { const powerlineGlyph = chars.length === 1 && isPowerlineGlyph(chars.charCodeAt(0)); const restrictedPowerlineGlyph = chars.length === 1 && isRestrictedPowerlineGlyph(chars.charCodeAt(0)); - const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, excludeFromContrastRatioDemands(chars.charCodeAt(0))); + const foregroundColor = this._getForegroundColor(bg, bgColorMode, bgColor, fg, fgColorMode, fgColor, inverse, dim, bold, treatGlyphAsBackgroundColor(chars.charCodeAt(0))); this._tmpCtx.fillStyle = foregroundColor.css; // For powerline glyphs left/top padding is excluded (https://github.com/microsoft/vscode/issues/120129) diff --git a/src/common/Color.test.ts b/src/common/Color.test.ts index 082c81c33d..e250950dca 100644 --- a/src/common/Color.test.ts +++ b/src/common/Color.test.ts @@ -271,6 +271,27 @@ describe('Color', () => { }); describe('rgba', () => { + describe('blend', () => { + it('should blend colors based on the alpha channel', () => { + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF00), 0x000000FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF10), 0x101010FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF20), 0x202020FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF30), 0x303030FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF40), 0x404040FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF50), 0x505050FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF60), 0x606060FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF70), 0x707070FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF80), 0x808080FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFF90), 0x909090FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFA0), 0xA0A0A0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFB0), 0xB0B0B0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFC0), 0xC0C0C0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFD0), 0xD0D0D0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFE0), 0xE0E0E0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFF0), 0xF0F0F0FF); + assert.deepEqual(rgba.blend(0x000000FF, 0xFFFFFFFF), 0xFFFFFFFF); + }); + }); describe('ensureContrastRatio', () => { it('should return undefined if the color already meets the contrast ratio (black bg)', () => { assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined); diff --git a/src/common/Color.ts b/src/common/Color.ts index 9bfed4e645..2291b7bef8 100644 --- a/src/common/Color.ts +++ b/src/common/Color.ts @@ -245,6 +245,23 @@ export namespace rgb { * Helper functions where the source type is "rgba" (number: 0xrrggbbaa). */ export namespace rgba { + export function blend(bg: number, fg: number): number { + $a = (fg & 0xFF) / 0xFF; + if ($a === 1) { + return fg; + } + const fgR = (fg >> 24) & 0xFF; + const fgG = (fg >> 16) & 0xFF; + const fgB = (fg >> 8) & 0xFF; + const bgR = (bg >> 24) & 0xFF; + const bgG = (bg >> 16) & 0xFF; + const bgB = (bg >> 8) & 0xFF; + $r = bgR + Math.round((fgR - bgR) * $a); + $g = bgG + Math.round((fgG - bgG) * $a); + $b = bgB + Math.round((fgB - bgB) * $a); + return channels.toRgba($r, $g, $b); + } + /** * Given a foreground color and a background color, either increase or reduce the luminance of the * foreground color until the specified contrast ratio is met. If pure white or black is hit diff --git a/test/playwright/Renderer.test.ts b/test/playwright/Renderer.test.ts index 77bf794166..119832d6dd 100644 --- a/test/playwright/Renderer.test.ts +++ b/test/playwright/Renderer.test.ts @@ -8,7 +8,10 @@ import { ITestContext, createTestContext, openTerminal } from './TestUtils'; import { ISharedRendererTestContext, injectSharedRendererTestsStandalone, injectSharedRendererTests } from './SharedRendererTests'; let ctx: ITestContext; -const ctxWrapper: ISharedRendererTestContext = { value: undefined } as any; +const ctxWrapper: ISharedRendererTestContext = { + value: undefined, + skipDomExceptions: true +} as any; test.beforeAll(async ({ browser }) => { ctx = await createTestContext(browser); ctxWrapper.value = ctx; @@ -18,5 +21,5 @@ test.afterAll(async () => await ctx.page.close()); test.describe('DOM Renderer Integration Tests', () => { injectSharedRendererTests(ctxWrapper); - injectSharedRendererTestsStandalone(ctxWrapper); + injectSharedRendererTestsStandalone(ctxWrapper, () => {}); }); diff --git a/test/playwright/SharedRendererTests.ts b/test/playwright/SharedRendererTests.ts index aa747277f7..657fba4441 100644 --- a/test/playwright/SharedRendererTests.ts +++ b/test/playwright/SharedRendererTests.ts @@ -11,6 +11,7 @@ import { ITestContext, MaybeAsync, openTerminal, pollFor, pollForApproximate } f export interface ISharedRendererTestContext { value: ITestContext; skipCanvasExceptions?: boolean; + skipDomExceptions?: boolean; } export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void { @@ -945,7 +946,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 255, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 255, 0, 255]); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); frameDetails = undefined; // Selection only cell needs to be first to ensure renderer has kicked in await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); @@ -965,7 +966,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void // Check both the cursor line and another line await ctx.value.proxy.writeln('_ '); await ctx.value.proxy.write('_ '); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [128, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [128, 0, 0, 255]); @@ -980,6 +981,44 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); }); + (ctx.skipCanvasExceptions || ctx.skipDomExceptions ? test.describe.skip : test.describe)('selection blending', () => { + test('background', async () => { + const theme: ITheme = { + red: '#CC0000', + selectionBackground: '#FFFFFF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.writeln('\x1b[41m red bg'); + await ctx.value.proxy.writeln('\x1b[7m inverse'); + await ctx.value.proxy.writeln('\x1b[31;7m red fg inverse'); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [230,128,128,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [255,255,255,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [230,128,128,255]); + }); + test('powerline decorative symbols', async () => { + const theme: ITheme = { + red: '#CC0000', + green: '#00CC00', + selectionBackground: '#FFFFFF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await ctx.value.proxy.focus(); + await ctx.value.proxy.writeln('\u{E0B4} plain\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[31;42m\u{E0B4} red fg green bg\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[32;41m\u{E0B4} green fg red bg\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[31;42;7m\u{E0B4} red fg green bg inverse\x1b[0m'); + await ctx.value.proxy.writeln('\x1b[32;41;7m\u{E0B4} green fg red bg inverse\x1b[0m'); + await ctx.value.proxy.selectAll(); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255,255,255,255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [230, 128, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 3), [128, 230, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [128, 230, 128, 255]); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 5), [230, 128, 128, 255]); + }); + }); + test.describe('allowTransparency', async () => { test.beforeEach(() => ctx.value.page.evaluate(`term.options.allowTransparency = true`)); @@ -1003,7 +1042,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); const data = `\x1b[7m■\x1b[0m`; await ctx.value.proxy.write( data); - await ctx.value.page.evaluate(`window.term.selectAll()`); + await ctx.value.proxy.selectAll(); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); }); }); @@ -1092,7 +1131,7 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void }); test.describe('regression tests', () => { - test('#4736: inactive selection background should replace regular cell background color', async () => { + (ctx.skipCanvasExceptions ? test.skip : test)('#4736: inactive selection background should replace regular cell background color', async () => { const theme: ITheme = { selectionBackground: '#FF0000', selectionInactiveBackground: '#0000FF' @@ -1126,7 +1165,8 @@ export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]); await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 0, 255]); }); - test('#4759: minimum contrast ratio should be respected on inverse text', async () => { + // HACK: It's not clear why DOM is failing here + (ctx.skipDomExceptions ? test.skip : test)('#4759: minimum contrast ratio should be respected on inverse text', async () => { const theme: ITheme = { foreground: '#aaaaaa', background: '#333333' @@ -1205,31 +1245,34 @@ enum CellColorPosition { * This is much slower than just calling `Terminal.reset` but testing some features needs this * treatment. */ -export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext): void { - test.beforeEach(async () => { - // Recreate terminal - await openTerminal(ctx.value); - ctx.value.page.evaluate(` - window.term.options.minimumContrastRatio = 1; - window.term.options.allowTransparency = false; - window.term.options.theme = undefined; - `); - // Clear the cached screenshot before each test - frameDetails = undefined; - }); - test.describe('regression tests', () => { - test('#4790: cursor should not be displayed before focusing', async () => { - const theme: ITheme = { - cursor: '#0000FF' - }; - await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); - await ctx.value.proxy.focus(); - frameDetails = undefined; - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); - await ctx.value.proxy.blur(); +export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext, setupCb: () => Promise | void): void { + test.describe('standalone tests', () => { + test.beforeEach(async () => { + // Recreate terminal + await openTerminal(ctx.value); + await ctx.value.page.evaluate(` + window.term.options.minimumContrastRatio = 1; + window.term.options.allowTransparency = false; + window.term.options.theme = undefined; + `); + await setupCb(); + // Clear the cached screenshot before each test frameDetails = undefined; - await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); + test.describe('regression tests', () => { + test('#4790: cursor should not be displayed before focusing', async () => { + const theme: ITheme = { + cursor: '#0000FF' + }; + await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`); + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + await ctx.value.proxy.focus(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]); + await ctx.value.proxy.blur(); + frameDetails = undefined; + await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); + }); }); }); }