Files
xterm.js/test/playwright/SharedRendererTests.ts
2026-02-01 08:41:01 -08:00

1570 lines
73 KiB
TypeScript

/**
* Copyright (c) 2019 The xterm.js authors. All rights reserved.
* @license MIT
*/
import { IImage32, decodePng } from '@lunapaint/png-codec';
import { LocatorScreenshotOptions, test } from '@playwright/test';
import { ITheme } from '@xterm/xterm';
import { ITestContext, openTerminal, pollFor, pollForApproximate } from './TestUtils';
export interface ISharedRendererTestContext {
value: ITestContext;
skipDomExceptions?: boolean;
}
export function injectSharedRendererTests(ctx: ISharedRendererTestContext): void {
test.beforeEach(async () => {
await ctx.value.proxy.reset();
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('colors', () => {
test('foreground 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■`);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
});
test('foreground 0-7 drawBoldTextInBrightColors', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
brightGreen: '#070809',
brightYellow: '#0a0b0c',
brightBlue: '#0d0e0f',
brightMagenta: '#101112',
brightCyan: '#131415',
brightWhite: '#161718'
};
await ctx.value.page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.drawBoldTextInBrightColors = true;
`);
await ctx.value.proxy.write(`\x1b[1;30m■\x1b[1;31m■\x1b[1;32m■\x1b[1;33m■\x1b[1;34m■\x1b[1;35m■\x1b[1;36m■\x1b[1;37m■`);
await pollForCellColorFresh(ctx.value, 1, 1, [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('background 0-15', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[40m \x1b[41m \x1b[42m \x1b[43m \x1b[44m \x1b[45m \x1b[46m \x1b[47m `);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('foreground 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[7;30m \x1b[7;31m \x1b[7;32m \x1b[7;33m \x1b[7;34m \x1b[7;35m \x1b[7;36m \x1b[7;37m `);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('background 0-15 inverse', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[7;40m■\x1b[7;41m■\x1b[7;42m■\x1b[7;43m■\x1b[7;44m■\x1b[7;45m■\x1b[7;46m■\x1b[7;47m■`);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('foreground 0-15 invisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[8;30m \x1b[8;31m \x1b[8;32m \x1b[8;33m \x1b[8;34m \x1b[8;35m \x1b[8;36m \x1b[8;37m `);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);
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]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0, 0, 0, 255]);
});
test('background 0-15 invisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(`\x1b[8;40m■\x1b[8;41m■\x1b[8;42m■\x1b[8;43m■\x1b[8;44m■\x1b[8;45m■\x1b[8;46m■\x1b[8;47m■`));
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
await pollFor(ctx.value.page, () => getBgColor(1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getBgColor(2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getBgColor(3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getBgColor(4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getBgColor(5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getBgColor(6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getBgColor(7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getBgColor(8, 1), [22, 23, 24, 255]);
});
test('foreground 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
brightGreen: '#070809',
brightYellow: '#0a0b0c',
brightBlue: '#0d0e0f',
brightMagenta: '#101112',
brightCyan: '#131415',
brightWhite: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('background 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
brightRed: '#040506',
brightGreen: '#070809',
brightYellow: '#0a0b0c',
brightBlue: '#0d0e0f',
brightMagenta: '#101112',
brightCyan: '#131415',
brightWhite: '#161718'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[100m \x1b[101m \x1b[102m \x1b[103m \x1b[104m \x1b[105m \x1b[106m \x1b[107m `);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [1, 2, 3, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [4, 5, 6, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [7, 8, 9, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [10, 11, 12, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [13, 14, 15, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [16, 17, 18, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [19, 20, 21, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [22, 23, 24, 255]);
});
test('foreground 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[38;5;${16 + y * 16 + x}m■\x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('background 16-255', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[48;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('foreground 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[7;38;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('background 16-255 inverse', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[7;48;5;${16 + y * 16 + x}m■\x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('foreground 16-255 invisible', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[8;38;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]);
}
}
});
test('background 16-255 invisible', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[8;48;5;${16 + y * 16 + x}m■\x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
const firstCssColor = COLORS_16_TO_255[1];
const firstR = parseInt(firstCssColor.slice(1, 3), 16);
const firstG = parseInt(firstCssColor.slice(3, 5), 16);
const firstB = parseInt(firstCssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getBgColor(2, 1), [firstR, firstG, firstB, 255]);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('foreground 16-255 dim', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[2;38;5;${16 + y * 16 + x}m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
// It's difficult to assert the exact color due to rounding, just ensure the color differs
// to the regular color
await pollFor(ctx.value.page, async () => {
const c = await getCellColor(ctx.value, x + 1, y + 1);
return (
(c[0] === 0 || c[0] !== r) &&
(c[1] === 0 || c[1] !== g) &&
(c[2] === 0 || c[2] !== b)
);
}, true);
}
}
});
test('background 16-255 dim', async () => {
let data = '';
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
data += `\x1b[2;48;5;${16 + y * 16 + x}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 240 / 16; y++) {
for (let x = 0; x < 16; x++) {
const cssColor = COLORS_16_TO_255[y * 16 + x];
const r = parseInt(cssColor.slice(1, 3), 16);
const g = parseInt(cssColor.slice(3, 5), 16);
const b = parseInt(cssColor.slice(5, 7), 16);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [r, g, b, 255]);
}
}
});
test('foreground true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[38;2;${i};0;0m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
test('background true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[48;2;${i};0;0m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
test('foreground true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[38;2;0;${i};0m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);
}
}
});
test('background true color green', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[48;2;0;${i};0m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);
}
}
});
test('foreground true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[38;2;0;0;${i}m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);
}
}
});
test('background true color blue', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[48;2;0;0;${i}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);
}
}
});
test('foreground true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[38;2;${i};${i};${i}m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);
}
}
});
test('background true color grey', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[48;2;${i};${i};${i}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);
}
}
});
test('foreground true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;38;2;${i};0;0m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
test('background true color red inverse', async function(): Promise<void> {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;48;2;${i};0;0m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, 0, 0, 255]);
}
}
});
test('foreground true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;38;2;0;${i};0m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);
}
}
});
test('background true color green inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;48;2;0;${i};0m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, i, 0, 255]);
}
}
});
test('foreground true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;38;2;0;0;${i}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);
}
}
});
test('background true color blue inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;48;2;0;0;${i}m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, i, 255]);
}
}
});
test('foreground true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;38;2;${i};${i};${i}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);
}
}
});
test('background true color grey inverse', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[7;48;2;${i};${i};${i}m■\x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [i, i, i, 255]);
}
}
});
test('foreground true color grey invisible', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[8;38;2;${i};${i};${i}m \x1b[0m`;
}
data += '\r\n';
}
await ctx.value.proxy.write(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
await pollFor(ctx.value.page, () => getCellColor(ctx.value, x + 1, y + 1), [0, 0, 0, 255]);
}
}
});
test('background true color grey invisible', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
data += `\x1b[8;48;2;${i};${i};${i}m■\x1b[0m`;
}
data += '\r\n';
}
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(data));
const getBgColor = (col: number, row: number): Promise<[number, number, number, number]> => getCellDomBackgroundColor(ctx.value, col, row);
await pollFor(ctx.value.page, () => getBgColor(2, 1), [1, 1, 1, 255]);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(ctx.value.page, () => getBgColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
test.describe('minimumContrastRatio', async () => {
test('should adjust 0-15 colors on black background', async () => {
const theme: ITheme = {
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await ctx.value.page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
await ctx.value.proxy.write(
`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` +
`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`
);
// Validate before minimumContrastRatio is applied
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [176, 180, 180, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [238, 158, 158, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [152, 198, 110, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [208, 179, 49, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [161, 183, 215, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [191, 174, 194, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [110, 197, 198, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [211, 215, 207, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [183, 185, 183, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [249, 156, 156, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [138, 226, 52, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [252, 233, 79, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [154, 186, 221, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [203, 173, 199, 255]);
// Unchanged
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);
});
test('should adjust 0-15 colors on white background', async () => {
const theme: ITheme = {
background: '#ffffff',
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await ctx.value.page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
await ctx.value.proxy.write(
`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` +
`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`
);
// Validate before minimumContrastRatio is applied
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0x2e, 0x34, 0x36, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0xcc, 0x00, 0x00, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0x4e, 0x9a, 0x06, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [0xc4, 0xa0, 0x00, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [0x34, 0x65, 0xa4, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [0x75, 0x50, 0x7b, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0x06, 0x98, 0x9a, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [0xd3, 0xd7, 0xcf, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0x55, 0x57, 0x53, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0xef, 0x29, 0x29, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [0x8a, 0xe2, 0x34, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [0xfc, 0xe9, 0x4f, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [0x72, 0x9f, 0xcf, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [0xad, 0x7f, 0xa8, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [0x34, 0xe2, 0xe2, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [0xee, 0xee, 0xec, 255]);
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [46, 52, 54, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [132, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [36, 72, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 1), [72, 59, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 1), [32, 64, 106, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 1), [75, 51, 80, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 1), [0, 71, 72, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 1), [64, 64, 63, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [61, 63, 59, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [125, 19, 19, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 2), [40, 67, 13, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 4, 2), [67, 63, 19, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 5, 2), [45, 65, 87, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 6, 2), [81, 57, 78, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 7, 2), [13, 67, 67, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 8, 2), [64, 64, 64, 255]);
});
test('should enforce half the contrast for dim cells', async () => {
const theme: ITheme = {
background: '#ffffff',
black: '#2e3436',
red: '#cc0000',
green: '#4e9a06',
yellow: '#c4a000',
blue: '#3465a4',
magenta: '#75507b',
cyan: '#06989a',
white: '#d3d7cf',
brightBlack: '#555753',
brightRed: '#ef2929',
brightGreen: '#8ae234',
brightYellow: '#fce94f',
brightBlue: '#729fcf',
brightMagenta: '#ad7fa8',
brightCyan: '#34e2e2',
brightWhite: '#eeeeec'
};
await ctx.value.page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.minimumContrastRatio = 1;
`);
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.write(
'\x1b[2m' +
`\x1b[30m■\x1b[31m■\x1b[32m■\x1b[33m■\x1b[34m■\x1b[35m■\x1b[36m■\x1b[37m■\r\n` +
`\x1b[90m■\x1b[91m■\x1b[92m■\x1b[93m■\x1b[94m■\x1b[95m■\x1b[96m■\x1b[97m■`
));
// Validate before minimumContrastRatio is applied
const marginOfError = 1;
const background: [number, number, number, number] = [255, 255, 255, 255];
const compositeOverBackground = (fg: [number, number, number, number]): [number, number, number, number] => {
const alpha = fg[3] / 255;
const inv = 1 - alpha;
return [
Math.round(fg[0] * alpha + background[0] * inv),
Math.round(fg[1] * alpha + background[1] * inv),
Math.round(fg[2] * alpha + background[2] * inv),
255
];
};
const getFgColor = async (col: number, row: number): Promise<[number, number, number, number]> => compositeOverBackground(await getCellDomForegroundColor(ctx.value, col, row));
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [Math.floor((255 + 0x2e) / 2), Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x36) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [Math.floor((255 + 0xcc) / 2), Math.floor((255 + 0x00) / 2), Math.floor((255 + 0x00) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [Math.floor((255 + 0x4e) / 2), Math.floor((255 + 0x9a) / 2), Math.floor((255 + 0x06) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [Math.floor((255 + 0xc4) / 2), Math.floor((255 + 0xa0) / 2), Math.floor((255 + 0x00) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0x65) / 2), Math.floor((255 + 0xa4) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [Math.floor((255 + 0x75) / 2), Math.floor((255 + 0x50) / 2), Math.floor((255 + 0x7b) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [Math.floor((255 + 0x06) / 2), Math.floor((255 + 0x98) / 2), Math.floor((255 + 0x9a) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [Math.floor((255 + 0xd3) / 2), Math.floor((255 + 0xd7) / 2), Math.floor((255 + 0xcf) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [Math.floor((255 + 0x55) / 2), Math.floor((255 + 0x57) / 2), Math.floor((255 + 0x53) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [Math.floor((255 + 0xef) / 2), Math.floor((255 + 0x29) / 2), Math.floor((255 + 0x29) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [Math.floor((255 + 0x8a) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0x34) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [Math.floor((255 + 0xfc) / 2), Math.floor((255 + 0xe9) / 2), Math.floor((255 + 0x4f) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [Math.floor((255 + 0x72) / 2), Math.floor((255 + 0x9f) / 2), Math.floor((255 + 0xcf) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [Math.floor((255 + 0xad) / 2), Math.floor((255 + 0x7f) / 2), Math.floor((255 + 0xa8) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [Math.floor((255 + 0x34) / 2), Math.floor((255 + 0xe2) / 2), Math.floor((255 + 0xe2) / 2), 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xee) / 2), Math.floor((255 + 0xec) / 2), 255]);
// Setting and check for minimum contrast values, note that these are not
// exact to the contrast ratio, if the increase luminance algorithm
// changes then these will probably fail
await waitForRenderAfter(ctx.value, () => ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`));
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 1), [150, 153, 154, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 1), [229, 127, 127, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 1), [63, 124, 4, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 1), [127, 104, 0, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 1), [153, 178, 209, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 1), [186, 167, 189, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 1), [4, 122, 124, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 1), [110, 112, 108, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(1, 2), [170, 171, 169, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(2, 2), [215, 36, 36, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(3, 2), [72, 117, 25, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(4, 2), [117, 109, 36, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(5, 2), [72, 103, 135, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(6, 2), [125, 91, 121, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(7, 2), [25, 117, 117, 255]);
await pollForApproximate(ctx.value.page, marginOfError, () => getFgColor(8, 2), [111, 111, 110, 255]);
});
});
test.describe('selectionBackground', async () => {
test('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
const theme: ITheme = {
foreground: '#FF0000',
background: '#00FF00',
selectionBackground: '#0000FF'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write( ` ■\x1b[7m■\x1b[0m`);
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.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]);
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]);
});
});
test.describe('selectionInactiveBackground', async () => {
test('should render the the inactive selection when not focused', async () => {
const theme: ITheme = {
selectionBackground: '#FF000080',
selectionInactiveBackground: '#0000FF80'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.focus();
// Check both the cursor line and another line
await ctx.value.proxy.writeln('_ ');
await ctx.value.proxy.write('_ ');
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]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [128, 0, 0, 255]);
await ctx.value.page.evaluate(`document.activeElement.blur()`);
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, 128, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 128, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 2), [0, 0, 128, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 128, 255]);
});
});
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\x1b[0m');
await ctx.value.proxy.writeln('\x1b[7m inverse\x1b[0m');
await ctx.value.proxy.writeln('\x1b[31;7m red fg inverse\x1b[0m');
await ctx.value.proxy.writeln('\x1b[48:2:0:204:0:0m red truecolor bg\x1b[0m');
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]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 4), [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`));
test('transparent background inverse', async () => {
const theme: ITheme = {
background: '#ff000080'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
const data = `\x1b[7m■\x1b[0m`;
await ctx.value.proxy.write( data);
// Inverse background should be opaque
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
});
test.describe('selectionForeground', () => {
test('transparent background inverse', async () => {
const theme: ITheme = {
selectionForeground: '#ff0000'
};
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.proxy.selectAll();
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
});
test.describe('decoration color overrides', async () => {
test('foregroundColor', async () => {
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `■`;
await ctx.value.proxy.write( data);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
test('foregroundColor should ignore inverse', async () => {
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `\x1b[7m■\x1b[0m`;
await ctx.value.proxy.write( data);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
test('foregroundColor should ignore inverse (only fg on decoration)', async () => {
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
width: 2,
foregroundColor: '#ff0000'
});
`);
const data = `\x1b[7m■ \x1b[0m`;
await ctx.value.proxy.write( data);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]); // inverse foreground of '■' should be decoration fg override
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]); // inverse background of ' ' should be default foreground
});
test('backgroundColor', async () => {
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = ` `;
await ctx.value.proxy.write( data);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);
});
test('backgroundColor should ignore inverse', async () => {
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
foregroundColor: '#ff0000',
backgroundColor: '#0000ff'
});
`);
const data = `\x1b[7m \x1b[0m`;
await ctx.value.proxy.write( data);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);
});
test('backgroundColor should ignore inverse (only bg on decoration)', async () => {
const data = `\x1b[7m■ \x1b[0m`;
await ctx.value.proxy.write( data);
await ctx.value.page.evaluate(`
const marker = window.term.registerMarker(-window.term.buffer.active.cursorY);
window.term.registerDecoration({
marker,
width: 2,
backgroundColor: '#0000ff'
});
`);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]); // inverse foreground of '■' should be default
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]); // inverse background of ' ' should be decoration bg override
});
});
test.describe('regression tests', () => {
test('#4736: inactive selection background should replace regular cell background color', async () => {
const theme: ITheme = {
selectionBackground: '#FF0000',
selectionInactiveBackground: '#0000FF'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.writeln(' ');
await ctx.value.proxy.writeln(' O ');
await ctx.value.proxy.writeln(' ');
await ctx.value.proxy.focus();
await ctx.value.proxy.selectAll();
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [255, 0, 0, 255]);
await ctx.value.proxy.blur();
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 2), [0, 0, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 3), [0, 0, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 3), [0, 0, 255, 255]);
});
test('#4758: multiple invisible text characters without SGR change should not be rendered', async () => {
// Regression test: #4758 when multiple invisible characters are used
await ctx.value.proxy.writeln(`■\x1b[8m■■`);
// Full refresh as the before result is the same as after
const rows = await ctx.value.proxy.rows;
await waitForRenderAfter(ctx.value, () => ctx.value.proxy.refresh(0, rows - 1));
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 1, 1), '■');
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 2, 1), ' ');
await pollFor(ctx.value.page, () => getCellDomChar(ctx.value, 3, 1), ' ');
});
// 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'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[7m■■`);
// Validate before minimumContrastRatio is applied
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [51, 51, 51, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [51, 51, 51, 255]);
await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 0, 0, 255]);
});
test('#4759: minimum contrast ratio should be respected on selected inverse text', async () => {
const theme: ITheme = {
foreground: '#777777',
background: '#555555',
selectionBackground: '#666666' // Slightly more contrast needed for selection
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.write(`\x1b[7m■■`);
await ctx.value.proxy.selectAll();
// Validate before minimumContrastRatio is applied
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [85, 85, 85, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [85, 85, 85, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]);
await ctx.value.page.evaluate(`window.term.options.minimumContrastRatio = 10;`);
await ctx.value.proxy.selectAll();
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 255, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [255, 255, 255, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [102, 102, 102, 255]);
});
test('#4773: block cursor should render when the cell is selected', async () => {
const theme: ITheme = {
cursor: '#0000FF',
selectionBackground: '#FF0000'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.focus();
await ctx.value.proxy.selectAll();
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 255, 255]);
});
test('#4799: cursor should be in the correct position', async () => {
const theme: ITheme = {
cursor: '#0000FF'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
for (let index = 0; index < 160; index++) {
await ctx.value.proxy.writeln(``);
}
await ctx.value.proxy.focus();
await ctx.value.proxy.write('\x1b[A');
await ctx.value.proxy.write('\x1b[A');
await ctx.value.proxy.scrollLines(-2);
const rows = await ctx.value.proxy.rows;
// block cursor style
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows), [0, 0, 255, 255]);
await ctx.value.proxy.blur();
frameDetails = undefined;
// outlink cursor style
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows), [0, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, rows, CellColorPosition.FIRST), [0, 0, 255, 255]);
});
test('#4917 The selection should not be displayed if it is not within the scope of the viewport.', async () => {
const theme: ITheme = {
selectionBackground: '#FF0000'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
for (let index = 0; index < 160; index++) {
await ctx.value.proxy.writeln(``);
}
await ctx.value.proxy.scrollToBottom();
const rows = await ctx.value.proxy.buffer.active.length;
await ctx.value.proxy.selectLines(rows - 1, rows - 1);
await ctx.value.proxy.scrollLines(-2);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [0, 0, 0, 255]);
});
test('#5241 cursor with alpha should blend color with background color', async () => {
const theme: ITheme = {
cursor: '#FF000080'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.focus();
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]);
});
test('#5241 cursorAccent with alpha should blend color with background color', async () => {
const theme: ITheme = {
cursorAccent: '#FF000080'
};
await ctx.value.page.evaluate(`window.term.options.theme = ${JSON.stringify(theme)};`);
await ctx.value.proxy.focus();
await ctx.value.proxy.write('■');
await ctx.value.proxy.write('\x1b[1D');
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [128, 0, 0, 255]);
});
});
// TODO: These tests are a little too flaky atm
test.describe.skip('synchronized output', () => {
test.beforeEach(async () => {
const theme: ITheme = {
background: '#000000FF',
red: '#FF0000FF',
green: '#00FF00FF',
blue: '#0000FFFF'
};
await ctx.value.page.evaluate(`
window.term.options.theme = ${JSON.stringify(theme)};
window.term.options.cursorStyle = 'underline';
`);
});
test('defers rendering until ESU', async () => {
await ctx.value.proxy.write('\x1b[?2026h'); // BSU
await ctx.value.proxy.write('\x1b[31m■');
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {
equalityFn: (a, b) => {
return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);
}
});
await ctx.value.proxy.write('\x1b[?2026l'); // ESU
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
test('batches multiple writes', async () => {
await ctx.value.proxy.write('\x1b[?2026h'); // BSU
await ctx.value.proxy.write('\x1b[31m■\x1b[32m■\x1b[34m■');
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {
equalityFn: (a, b) => {
return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);
}
});
await ctx.value.proxy.write('\x1b[?2026l'); // ESU
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 255, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 3, 1), [0, 0, 255, 255]);
});
test('nested BSU is idempotent', async () => {
await ctx.value.proxy.write('\x1b[?2026h'); // BSU
await ctx.value.proxy.write('\x1b[31m■');
await ctx.value.proxy.write('\x1b[?2026h'); // BSU
await ctx.value.proxy.write('\x1b[32m■');
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {
equalityFn: (a, b) => {
return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);
}
});
await ctx.value.proxy.write('\x1b[?2026l'); // ESU
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 2, 1), [0, 255, 0, 255]);
});
test('timeout flushes without ESU', async () => {
await ctx.value.proxy.write('\x1b[?2026h'); // BSU
await ctx.value.proxy.write('\x1b[31m■');
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255], undefined, {
equalityFn: (a, b) => {
return !(a[0] === b[0] && a[1] === b[1] && a[2] === b[2] && a[3] === b[3]);
}
});
await ctx.value.page.waitForTimeout(1000); // Timeout hard coded
frameDetails = undefined;
await pollFor(ctx.value.page, () => getCellColor(ctx.value, 1, 1), [255, 0, 0, 255]);
});
});
}
async function waitForRender(ctx: ITestContext): Promise<void> {
await new Promise<void>(resolve => {
const disposable = ctx.proxy.onRender(() => {
disposable.dispose();
resolve();
});
});
}
async function waitForRenderAfter(ctx: ITestContext, action: () => Promise<void>): Promise<void> {
const renderPromise = waitForRender(ctx);
await action();
await renderPromise;
frameDetails = undefined;
}
async function pollForCellColorFresh(ctx: ITestContext, col: number, row: number, expected: [number, number, number, number], position: CellColorPosition = CellColorPosition.CENTER): Promise<void> {
await pollFor(ctx.page, () => getCellColor(ctx, col, row, position), expected, async () => {
frameDetails = undefined;
});
}
interface ICellDomInfo {
fg: string;
bg: string;
char: string;
}
function parseCssColor(value: string): [number, number, number, number] {
if (value === 'transparent') {
return [0, 0, 0, 0];
}
const match = value.match(/^rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([0-9.]+))?\)$/);
if (!match) {
throw new Error(`Unsupported color format: ${value}`);
}
const r = Number(match[1]);
const g = Number(match[2]);
const b = Number(match[3]);
const a = match[4] === undefined ? 255 : Math.round(Number(match[4]) * 255);
return [r, g, b, a];
}
async function getCellDomInfo(ctx: ITestContext, col: number, row: number): Promise<ICellDomInfo> {
const info = await ctx.page.evaluate(({ col, row }) => {
const rows = document.querySelectorAll('#terminal-container .xterm-rows > div');
const rowEl = rows[row - 1];
if (!rowEl) {
return undefined;
}
let currentCol = 1;
for (const el of Array.from(rowEl.children)) {
const text = el.textContent ?? '';
const chars = Array.from(text);
const nextCol = currentCol + chars.length;
if (col >= currentCol && col < nextCol) {
const style = window.getComputedStyle(el as HTMLElement);
return {
fg: style.color,
bg: style.backgroundColor,
char: chars[col - currentCol] ?? ''
};
}
currentCol = nextCol;
}
return undefined;
}, { col, row });
if (!info) {
throw new Error(`Cell not found: ${col},${row}`);
}
return info;
}
async function getCellDomBackgroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {
const info = await getCellDomInfo(ctx, col, row);
return parseCssColor(info.bg);
}
async function getCellDomForegroundColor(ctx: ITestContext, col: number, row: number): Promise<[number, number, number, number]> {
const info = await getCellDomInfo(ctx, col, row);
return parseCssColor(info.fg);
}
async function getCellDomChar(ctx: ITestContext, col: number, row: number): Promise<string> {
const info = await getCellDomInfo(ctx, col, row);
return info.char;
}
enum CellColorPosition {
CENTER = 0,
FIRST = 1
}
/**
* Injects shared renderer tests where it's required to re-initialize the terminal for each test.
* This is much slower than just calling `Terminal.reset` but testing some features needs this
* treatment.
*/
export function injectSharedRendererTestsStandalone(ctx: ISharedRendererTestContext, setupCb: () => Promise<void> | void): void {
const setupTests = ({ shadowDom }: { shadowDom: boolean }): void => {
test.beforeEach(async () => {
// Recreate terminal
await openTerminal(ctx.value, {}, { useShadowDom: shadowDom });
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;
});
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]);
});
});
};
test.describe('standalone tests', () => {
setupTests({ shadowDom: false });
});
test.describe('standalone tests (Shadow dom)', () => {
setupTests({ shadowDom: true });
});
}
/**
* Gets the color of the pixel in the center of a cell.
* @param ctx The test context.
* @param col The 1-based column index to get the color for.
* @param row The 1-based row index to get the color for.
*/
async function getCellColor(ctx: ITestContext, col: number, row: number, position: CellColorPosition = CellColorPosition.CENTER): Promise<[red: number, green: number, blue: number, alpha: number]> {
frameDetails ??= await getFrameDetails(ctx);
switch (position) {
case CellColorPosition.CENTER:
return getCellColorInner(frameDetails, col, row);
case CellColorPosition.FIRST:
return getCellColorFirstPoint(frameDetails, col, row);
}
}
let frameDetails: { cols: number, rows: number, decoded: IImage32 } | undefined = undefined;
async function getFrameDetails(ctx: ITestContext): Promise<{ cols: number, rows: number, decoded: IImage32 }> {
const screenshotOptions: LocatorScreenshotOptions | undefined = process.env.DEBUG ? { path: 'out-esbuild-test/playwright/screenshot.png' } : undefined;
const buffer = await ctx.page.locator('#terminal-container .xterm-screen').screenshot(screenshotOptions);
frameDetails = {
cols: await ctx.proxy.cols,
rows: await ctx.proxy.rows,
decoded: (await decodePng(new Uint8Array(buffer), { force32: true })).image
};
return frameDetails;
}
function getCellColorInner(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] {
const cellSize = {
width: frameDetails.decoded.width / frameDetails.cols,
height: frameDetails.decoded.height / frameDetails.rows
};
const x = Math.floor((col - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.width);
const y = Math.floor((row - 1/* 1- to 0-based index */ + 0.5/* middle of cell */) * cellSize.height);
const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */;
return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number];
}
function getCellColorFirstPoint(frameDetails: { cols: number, rows: number, decoded: IImage32 }, col: number, row: number): [red: number, green: number, blue: number, alpha: number] {
const cellSize = {
width: frameDetails.decoded.width / frameDetails.cols,
height: frameDetails.decoded.height / frameDetails.rows
};
const x = Math.floor((col - 1/* 1- to 0-based index */) * cellSize.width);
const y = Math.floor((row - 1/* 1- to 0-based index */) * cellSize.height);
const i = (y * frameDetails.decoded.width + x) * 4/* 4 channels per pixel */;
return Array.from(frameDetails.decoded.data.slice(i, i + 4)) as [number, number, number, number];
}
const COLORS_16_TO_255 = [
'#000000', '#00005f', '#000087', '#0000af', '#0000d7', '#0000ff', '#005f00', '#005f5f', '#005f87', '#005faf', '#005fd7', '#005fff', '#008700', '#00875f', '#008787', '#0087af',
'#0087d7', '#0087ff', '#00af00', '#00af5f', '#00af87', '#00afaf', '#00afd7', '#00afff', '#00d700', '#00d75f', '#00d787', '#00d7af', '#00d7d7', '#00d7ff', '#00ff00', '#00ff5f',
'#00ff87', '#00ffaf', '#00ffd7', '#00ffff', '#5f0000', '#5f005f', '#5f0087', '#5f00af', '#5f00d7', '#5f00ff', '#5f5f00', '#5f5f5f', '#5f5f87', '#5f5faf', '#5f5fd7', '#5f5fff',
'#5f8700', '#5f875f', '#5f8787', '#5f87af', '#5f87d7', '#5f87ff', '#5faf00', '#5faf5f', '#5faf87', '#5fafaf', '#5fafd7', '#5fafff', '#5fd700', '#5fd75f', '#5fd787', '#5fd7af',
'#5fd7d7', '#5fd7ff', '#5fff00', '#5fff5f', '#5fff87', '#5fffaf', '#5fffd7', '#5fffff', '#870000', '#87005f', '#870087', '#8700af', '#8700d7', '#8700ff', '#875f00', '#875f5f',
'#875f87', '#875faf', '#875fd7', '#875fff', '#878700', '#87875f', '#878787', '#8787af', '#8787d7', '#8787ff', '#87af00', '#87af5f', '#87af87', '#87afaf', '#87afd7', '#87afff',
'#87d700', '#87d75f', '#87d787', '#87d7af', '#87d7d7', '#87d7ff', '#87ff00', '#87ff5f', '#87ff87', '#87ffaf', '#87ffd7', '#87ffff', '#af0000', '#af005f', '#af0087', '#af00af',
'#af00d7', '#af00ff', '#af5f00', '#af5f5f', '#af5f87', '#af5faf', '#af5fd7', '#af5fff', '#af8700', '#af875f', '#af8787', '#af87af', '#af87d7', '#af87ff', '#afaf00', '#afaf5f',
'#afaf87', '#afafaf', '#afafd7', '#afafff', '#afd700', '#afd75f', '#afd787', '#afd7af', '#afd7d7', '#afd7ff', '#afff00', '#afff5f', '#afff87', '#afffaf', '#afffd7', '#afffff',
'#d70000', '#d7005f', '#d70087', '#d700af', '#d700d7', '#d700ff', '#d75f00', '#d75f5f', '#d75f87', '#d75faf', '#d75fd7', '#d75fff', '#d78700', '#d7875f', '#d78787', '#d787af',
'#d787d7', '#d787ff', '#d7af00', '#d7af5f', '#d7af87', '#d7afaf', '#d7afd7', '#d7afff', '#d7d700', '#d7d75f', '#d7d787', '#d7d7af', '#d7d7d7', '#d7d7ff', '#d7ff00', '#d7ff5f',
'#d7ff87', '#d7ffaf', '#d7ffd7', '#d7ffff', '#ff0000', '#ff005f', '#ff0087', '#ff00af', '#ff00d7', '#ff00ff', '#ff5f00', '#ff5f5f', '#ff5f87', '#ff5faf', '#ff5fd7', '#ff5fff',
'#ff8700', '#ff875f', '#ff8787', '#ff87af', '#ff87d7', '#ff87ff', '#ffaf00', '#ffaf5f', '#ffaf87', '#ffafaf', '#ffafd7', '#ffafff', '#ffd700', '#ffd75f', '#ffd787', '#ffd7af',
'#ffd7d7', '#ffd7ff', '#ffff00', '#ffff5f', '#ffff87', '#ffffaf', '#ffffd7', '#ffffff', '#080808', '#121212', '#1c1c1c', '#262626', '#303030', '#3a3a3a', '#444444', '#4e4e4e',
'#585858', '#626262', '#6c6c6c', '#767676', '#808080', '#8a8a8a', '#949494', '#9e9e9e', '#a8a8a8', '#b2b2b2', '#bcbcbc', '#c6c6c6', '#d0d0d0', '#dadada', '#e4e4e4', '#eeeeee'
];