Merge branch 'feat/serialize-addon' into feat/serialize-addon

This commit is contained in:
Daniel Imms
2020-02-03 11:54:11 -08:00
committed by GitHub
41 changed files with 1291 additions and 849 deletions
+3
View File
@@ -156,6 +156,9 @@ Xterm.js is used in several world-class applications to provide great terminal e
- [**tty-share**](https://tty-share.com): Extremely simple terminal sharing over the Internet.
- [**Ten Hands**](https://github.com/saisandeepvaddi/ten-hands): One place to run your command-line tasks.
- [**WebAssembly.sh**](https://webassembly.sh): A WebAssembly WASI browser terminal
- [**Gus**](https://gus.jp): A shared coding pad where you can run Python with xterm.js
- [**Linode**](https://linode.com): Linode uses xterm.js to provide users a web console for their Linode instances.
- [**FluffOS**](https://www.fluffos.info): Active maintained LPMUD driver with websocket support.
[And much more...](https://github.com/xtermjs/xterm.js/network/dependents)
+1 -1
View File
@@ -1,7 +1,7 @@
{
"compilerOptions": {
"module": "commonjs",
"target": "es2015",
"target": "es5",
"lib": [
"dom",
"es2015"
+1 -1
View File
@@ -1,6 +1,6 @@
{
"name": "xterm-addon-webgl",
"version": "0.4.0",
"version": "0.4.1",
"author": {
"name": "The xterm.js authors",
"url": "https://xtermjs.org/"
+36 -4
View File
@@ -6,13 +6,14 @@
import { createProgram, PROJECTION_MATRIX, throwIfFalsy } from './WebglUtils';
import { WebglCharAtlas } from './atlas/WebglCharAtlas';
import { IWebGL2RenderingContext, IWebGLVertexArrayObject, IRenderModel, IRasterizedGlyph } from './Types';
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET } from './RenderModel';
import { COMBINED_CHAR_BIT_MASK, RENDER_MODEL_INDICIES_PER_CELL, RENDER_MODEL_FG_OFFSET, RENDER_MODEL_BG_OFFSET } from './RenderModel';
import { fill } from 'common/TypedArrayUtils';
import { slice } from './TypedArray';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes } from 'common/buffer/Constants';
import { NULL_CELL_CODE, WHITESPACE_CELL_CODE, Attributes, FgFlags } from 'common/buffer/Constants';
import { Terminal, IBufferLine } from 'xterm';
import { IColorSet } from 'browser/Types';
import { IColorSet, IColor } from 'browser/Types';
import { IRenderDimensions } from 'browser/renderer/Types';
import { AttributeData } from 'common/buffer/AttributeData';
interface IVertices {
attributes: Float32Array;
@@ -254,6 +255,30 @@ export class GlyphRenderer {
for (let x = startCol; x < endCol; x++) {
const offset = (y * this._terminal.cols + x) * RENDER_MODEL_INDICIES_PER_CELL;
const code = model.cells[offset];
let fg = model.cells[offset + RENDER_MODEL_FG_OFFSET];
if (fg & FgFlags.INVERSE) {
const workCell = new AttributeData();
workCell.fg = fg;
workCell.bg = model.cells[offset + RENDER_MODEL_BG_OFFSET];
// Get attributes from fg (excluding inverse) and resolve inverse by pullibng rgb colors
// from bg. This is needed since the inverse fg color should be based on the original bg
// color, not on the selection color
fg = (fg & ~(Attributes.CM_MASK | Attributes.RGB_MASK | FgFlags.INVERSE));
switch (workCell.getBgColorMode()) {
case Attributes.CM_P16:
case Attributes.CM_P256:
const c = this._getColorFromAnsiIndex(workCell.getBgColor()).rgba;
fg |= (c >> 8) & Attributes.RED_MASK | (c >> 8) & Attributes.GREEN_MASK | (c >> 8) & Attributes.BLUE_MASK;
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(workCell.getBgColor());
fg |= arr[0] << Attributes.RED_SHIFT | arr[1] << Attributes.GREEN_SHIFT | arr[2] << Attributes.BLUE_SHIFT;
case Attributes.CM_DEFAULT:
default:
const c2 = this._colors.background.rgba;
fg |= (c2 >> 8) & Attributes.RED_MASK | (c2 >> 8) & Attributes.GREEN_MASK | (c2 >> 8) & Attributes.BLUE_MASK;
}
fg |= Attributes.CM_RGB;
}
if (code & COMBINED_CHAR_BIT_MASK) {
if (!line) {
line = terminal.buffer.getLine(row);
@@ -261,11 +286,18 @@ export class GlyphRenderer {
const chars = line!.getCell(x)!.getChars();
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET], chars);
} else {
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, model.cells[offset + RENDER_MODEL_FG_OFFSET]);
this._updateCell(this._vertices.selectionAttributes, x, y, model.cells[offset], bg, fg);
}
}
}
private _getColorFromAnsiIndex(idx: number): IColor {
if (idx >= this._colors.ansi.length) {
throw new Error('No color found for idx ' + idx);
}
return this._colors.ansi[idx];
}
public onResize(): void {
const terminal = this._terminal;
const gl = this._gl;
+8 -1
View File
@@ -10,6 +10,7 @@ import { IColorSet } from 'browser/Types';
export class WebglAddon implements ITerminalAddon {
private _terminal?: Terminal;
private _renderer?: WebglRenderer;
constructor(
private _preserveDrawingBuffer?: boolean
@@ -22,7 +23,8 @@ export class WebglAddon implements ITerminalAddon {
this._terminal = terminal;
const renderService: IRenderService = (<any>terminal)._core._renderService;
const colors: IColorSet = (<any>terminal)._core._colorManager.colors;
renderService.setRenderer(new WebglRenderer(terminal, colors, this._preserveDrawingBuffer));
this._renderer = new WebglRenderer(terminal, colors, this._preserveDrawingBuffer);
renderService.setRenderer(this._renderer);
}
public dispose(): void {
@@ -32,5 +34,10 @@ export class WebglAddon implements ITerminalAddon {
const renderService: IRenderService = (<any>this._terminal)._core._renderService;
renderService.setRenderer((<any>this._terminal)._core._createRenderer());
renderService.onResize(this._terminal.cols, this._terminal.rows);
this._renderer = undefined;
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._renderer?.textureAtlas;
}
}
@@ -148,6 +148,52 @@ describe('WebGL Renderer Integration Tests', function(): void {
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 inivisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\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(page, () => getCellColor(1, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(4, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(5, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(6, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(7, 1), [0, 0, 0, 255]);
await pollFor(page, () => getCellColor(8, 1), [0, 0, 0, 255]);
});
it('background 0-15 inivisible', async () => {
const theme: ITheme = {
black: '#010203',
red: '#040506',
green: '#070809',
yellow: '#0a0b0c',
blue: '#0d0e0f',
magenta: '#101112',
cyan: '#131415',
white: '#161718'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[8;40m█\\x1b[8;41m█\\x1b[8;42m█\\x1b[8;43m█\\x1b[8;44m█\\x1b[8;45m█\\x1b[8;46m█\\x1b[8;47m█`);
await pollFor(page, () => getCellColor(1, 1), [1, 2, 3, 255]);
await pollFor(page, () => getCellColor(2, 1), [4, 5, 6, 255]);
await pollFor(page, () => getCellColor(3, 1), [7, 8, 9, 255]);
await pollFor(page, () => getCellColor(4, 1), [10, 11, 12, 255]);
await pollFor(page, () => getCellColor(5, 1), [13, 14, 15, 255]);
await pollFor(page, () => getCellColor(6, 1), [16, 17, 18, 255]);
await pollFor(page, () => getCellColor(7, 1), [19, 20, 21, 255]);
await pollFor(page, () => getCellColor(8, 1), [22, 23, 24, 255]);
});
it('foreground 0-15 bright', async () => {
const theme: ITheme = {
brightBlack: '#010203',
@@ -274,6 +320,46 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
});
it('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 writeSync(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.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
}
}
});
it('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 writeSync(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.substr(1, 2), 16);
const g = parseInt(cssColor.substr(3, 2), 16);
const b = parseInt(cssColor.substr(5, 2), 16);
await pollFor(page, () => getCellColor(x + 1, y + 1), [r, g, b, 255]);
}
}
});
it('foreground true color red', async () => {
let data = '';
for (let y = 0; y < 16; y++) {
@@ -561,6 +647,42 @@ describe('WebGL Renderer Integration Tests', function(): void {
}
}
});
it('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 writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [0, 0, 0, 255]);
}
}
});
it('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 writeSync(data);
for (let y = 0; y < 16; y++) {
for (let x = 0; x < 16; x++) {
const i = y * 16 + x;
await pollFor(page, () => getCellColor(x + 1, y + 1), [i, i, i, 255]);
}
}
});
});
describe('minimumContrastRatio', async () => {
@@ -703,6 +825,30 @@ describe('WebGL Renderer Integration Tests', function(): void {
});
});
describe('selection', async () => {
before(async () => setupBrowser());
after(async () => browser.close());
beforeEach(async () => page.evaluate(`window.term.reset()`));
it('should resolve the inverse foreground color based on the original background color, not the selection', async () => {
const theme: ITheme = {
foreground: '#FF0000',
background: '#00FF00',
selection: '#0000FF'
};
await page.evaluate(`window.term.setOption('theme', ${JSON.stringify(theme)});`);
await writeSync(`\\x1b[7m█\\x1b[0m`);
await pollFor(page, () => getCellColor(1, 1), [0, 255, 0, 255]);
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
await page.evaluate(`window.term.selectAll()`);
// Selection only cell needs to be first to ensure renderer has kicked in
await pollFor(page, () => getCellColor(1, 1), [0, 0, 255, 255]);
await pollFor(page, () => getCellColor(2, 1), [255, 0, 0, 255]);
await pollFor(page, () => getCellColor(3, 1), [0, 255, 0, 255]);
});
});
describe('allowTransparency', async () => {
before(async () => setupBrowser({ rendererType: 'dom', allowTransparency: true}));
after(async () => browser.close());
@@ -100,6 +100,10 @@ export class WebglRenderer extends Disposable implements IRenderer {
super.dispose();
}
public get textureAtlas(): HTMLCanvasElement | undefined {
return this._charAtlas?.cacheCanvas;
}
public setColors(colors: IColorSet): void {
this._colors = colors;
// Clear layers and force a full render
@@ -11,7 +11,7 @@ import { throwIfFalsy } from '../WebglUtils';
import { IColor } from 'browser/Types';
import { IDisposable } from 'xterm';
import { AttributeData } from 'common/buffer/AttributeData';
import { toCss, ensureContrastRatioRgba } from 'browser/Color';
import { channels, rgba } from 'browser/Color';
// In practice we're probably never going to exhaust a texture this large. For debugging purposes,
// however, it can be useful to set this to a really tiny value, to verify that LRU eviction works.
@@ -86,9 +86,6 @@ export class WebglCharAtlas implements IDisposable {
this._tmpCanvas.width = this._config.scaledCharWidth * 2 + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCanvas.height = this._config.scaledCharHeight + TMP_CANVAS_GLYPH_PADDING * 2;
this._tmpCtx = throwIfFalsy(this._tmpCanvas.getContext('2d', {alpha: this._config.allowTransparency}));
// This is useful for debugging
document.body.appendChild(this.cacheCanvas);
}
public dispose(): void {
@@ -224,7 +221,7 @@ export class WebglCharAtlas implements IDisposable {
return this._getColorFromAnsiIndex(fgColor).css;
case Attributes.CM_RGB:
const arr = AttributeData.toColorRGB(fgColor);
return toCss(arr[0], arr[1], arr[2]);
return channels.toCss(arr[0], arr[1], arr[2]);
case Attributes.CM_DEFAULT:
default:
if (inverse) {
@@ -287,14 +284,14 @@ export class WebglCharAtlas implements IDisposable {
const bgRgba = this._resolveBackgroundRgba(bgColorMode, bgColor, inverse);
const fgRgba = this._resolveForegroundRgba(fgColorMode, fgColor, inverse, bold);
const result = ensureContrastRatioRgba(bgRgba, fgRgba, this._config.minimumContrastRatio);
const result = rgba.ensureContrastRatio(bgRgba, fgRgba, this._config.minimumContrastRatio);
if (!result) {
this._config.colors.contrastCache.setCss(bg, fg, null);
return undefined;
}
const css = toCss(
const css = channels.toCss(
(result >> 24) & 0xFF,
(result >> 16) & 0xFF,
(result >> 8) & 0xFF
@@ -316,6 +313,11 @@ export class WebglCharAtlas implements IDisposable {
this._workAttributeData.fg = fg;
this._workAttributeData.bg = bg;
const invisible = !!this._workAttributeData.isInvisible();
if (invisible) {
return NULL_RASTERIZED_GLYPH;
}
const bold = !!this._workAttributeData.isBold();
const inverse = !!this._workAttributeData.isInverse();
const dim = !!this._workAttributeData.isDim();
@@ -153,11 +153,11 @@ export abstract class BaseRenderLayer implements IRenderLayer {
* @param x The column to fill.
* @param y The row to fill.
*/
protected _fillLeftLineAtCell(x: number, y: number): void {
protected _fillLeftLineAtCell(x: number, y: number, width: number): void {
this._ctx.fillRect(
x * this._scaledCellWidth,
y * this._scaledCellHeight,
window.devicePixelRatio,
window.devicePixelRatio * width,
this._scaledCellHeight);
}
@@ -204,7 +204,7 @@ export class CursorRenderLayer extends BaseRenderLayer {
private _renderBarCursor(terminal: Terminal, x: number, y: number, cell: ICellData): void {
this._ctx.save();
this._ctx.fillStyle = this._colors.cursor.css;
this._fillLeftLineAtCell(x, y);
this._fillLeftLineAtCell(x, y, terminal.getOption('cursorWidth'));
this._ctx.restore();
}
@@ -10,6 +10,8 @@ declare module 'xterm-addon-webgl' {
* An xterm.js addon that provides search functionality.
*/
export class WebglAddon implements ITerminalAddon {
public textureAtlas?: HTMLCanvasElement;
constructor(preserveDrawingBuffer?: boolean);
/**
+8
View File
@@ -353,7 +353,15 @@ function initAddons(term: TerminalType): void {
if (checkbox.checked) {
addon.instance = new addon.ctor();
term.loadAddon(addon.instance);
if (name === 'webgl') {
setTimeout(() => {
document.body.appendChild((addon.instance as WebglAddon).textureAtlas);
}, 0);
}
} else {
if (name === 'webgl') {
document.body.removeChild((addon.instance as WebglAddon).textureAtlas);
}
addon.instance!.dispose();
addon.instance = undefined;
}
+1 -1
View File
@@ -46,7 +46,7 @@
"glob": "^7.0.5",
"jsdom": "^11.11.0",
"mocha": "^6.1.4",
"node-pty": "0.7.6",
"node-pty": "^0.9.0",
"nyc": "13",
"puppeteer": "^1.15.0",
"source-map-loader": "^0.2.4",
+78 -15
View File
@@ -7,13 +7,13 @@ import { assert, expect } from 'chai';
import { InputHandler } from './InputHandler';
import { MockInputHandlingTerminal, TestTerminal } from './TestUtils.test';
import { Terminal } from './Terminal';
import { IBufferLine } from 'common/Types';
import { IBufferLine, IAttributeData } from 'common/Types';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { CellData } from 'common/buffer/CellData';
import { Attributes } from 'common/buffer/Constants';
import { AttributeData } from 'common/buffer/AttributeData';
import { Params } from 'common/parser/Params';
import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService } from 'common/TestUtils.test';
import { MockCoreService, MockBufferService, MockDirtyRowService, MockOptionsService, MockLogService, MockCoreMouseService, MockCharsetService } from 'common/TestUtils.test';
import { IBufferService } from 'common/services/Services';
import { DEFAULT_OPTIONS } from 'common/services/OptionsService';
import { clone } from 'common/Clone';
@@ -33,34 +33,38 @@ function getLines(term: TestTerminal, limit: number = term.rows): string[] {
return res;
}
class TestInputHandler extends InputHandler {
get curAttrData(): IAttributeData { return (this as any)._curAttrData; }
}
describe('InputHandler', () => {
describe('save and restore cursor', () => {
const terminal = new MockInputHandlingTerminal();
terminal.curAttrData.fg = 3;
const bufferService = new MockBufferService(80, 30);
bufferService.buffer.x = 1;
bufferService.buffer.y = 2;
bufferService.buffer.ybase = 0;
const inputHandler = new InputHandler(terminal, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new TestInputHandler(terminal, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
inputHandler.curAttrData.fg = 3;
// Save cursor position
inputHandler.saveCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(terminal.curAttrData.fg, 3);
assert.equal(inputHandler.curAttrData.fg, 3);
// Change cursor position
bufferService.buffer.x = 10;
bufferService.buffer.y = 20;
terminal.curAttrData.fg = 30;
inputHandler.curAttrData.fg = 30;
// Restore cursor position
inputHandler.restoreCursor();
assert.equal(bufferService.buffer.x, 1);
assert.equal(bufferService.buffer.y, 2);
assert.equal(terminal.curAttrData.fg, 3);
assert.equal(inputHandler.curAttrData.fg, 3);
});
describe('setCursorStyle', () => {
it('should call Terminal.setOption with correct params', () => {
const optionsService = new MockOptionsService();
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService());
const inputHandler = new InputHandler(new MockInputHandlingTerminal(), new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), optionsService, new MockCoreMouseService());
inputHandler.setCursorStyle(Params.fromArray([0]));
assert.equal(optionsService.options['cursorStyle'], 'block');
@@ -101,7 +105,7 @@ describe('InputHandler', () => {
it('should toggle Terminal.bracketedPasteMode', () => {
const terminal = new MockInputHandlingTerminal();
terminal.bracketedPasteMode = false;
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(terminal, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// Set bracketed paste mode
inputHandler.setModePrivate(Params.fromArray([2004]));
assert.equal(terminal.bracketedPasteMode, true);
@@ -120,7 +124,7 @@ describe('InputHandler', () => {
it('insertChars', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
@@ -158,7 +162,7 @@ describe('InputHandler', () => {
it('deleteChars', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// insert some data in first and second line
inputHandler.parse(Array(bufferService.cols - 9).join('a'));
@@ -199,7 +203,7 @@ describe('InputHandler', () => {
it('eraseInLine', function(): void {
const term = new Terminal();
const bufferService = new MockBufferService(80, 30);
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// fill 6 lines to test 3 different states
inputHandler.parse(Array(bufferService.cols + 1).join('a'));
@@ -228,7 +232,7 @@ describe('InputHandler', () => {
it('eraseInDisplay', function(): void {
const term = new Terminal({cols: 80, rows: 7});
const bufferService = new MockBufferService(80, 7);
const inputHandler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
// fill display with a's
for (let i = 0; i < bufferService.rows; ++i) inputHandler.parse(Array(bufferService.cols + 1).join('a'));
@@ -363,7 +367,7 @@ describe('InputHandler', () => {
describe('print', () => {
it('should not cause an infinite loop (regression test)', () => {
const term = new Terminal();
const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const inputHandler = new InputHandler(term, new MockBufferService(80, 30), new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
const container = new Uint32Array(10);
container[0] = 0x200B;
inputHandler.print(container, 0, 1);
@@ -378,7 +382,7 @@ describe('InputHandler', () => {
beforeEach(() => {
term = new Terminal();
bufferService = new MockBufferService(80, 30);
handler = new InputHandler(term, bufferService, new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
handler = new InputHandler(term, bufferService, new MockCharsetService(), new MockCoreService(), new MockDirtyRowService(), new MockLogService(), new MockOptionsService(), new MockCoreMouseService());
});
it('should handle DECSET/DECRST 47 (alt screen buffer)', () => {
handler.parse('\x1b[?47h\r\n\x1b[31mJUNK\x1b[?47lTEST');
@@ -1266,4 +1270,63 @@ describe('InputHandler', () => {
[131072, 131072], [131072, 131072], [131072, 300000 - 131072 - 131072]
]);
});
describe('should correctly reset cells taken by wide chars', () => {
let term: TestTerminal;
beforeEach(() => {
term = new TestTerminal({cols: 10, rows: 5, scrollback: 1});
term.writeSync('¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥¥');
});
it('print', () => {
term.writeSync('\x1b[H#');
assert.deepEqual(getLines(term), ['# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[1;6H######');
assert.deepEqual(getLines(term), ['# ¥ #####', '# ¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('#');
assert.deepEqual(getLines(term), ['# ¥ #####', '##¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('#');
assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[3;9H#');
assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥#', '¥¥¥¥¥', '']);
term.writeSync('#');
assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '¥¥¥¥¥', '']);
term.writeSync('#');
assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥¥', '']);
term.writeSync('\x1b[4;10H#');
assert.deepEqual(getLines(term), ['# ¥ #####', '### ¥¥¥', '¥¥¥¥##', '# ¥¥¥ #', '']);
});
it('EL', () => {
term.writeSync('\x1b[1;6H\x1b[K#');
assert.deepEqual(getLines(term), ['¥¥ #', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[2;5H\x1b[1K');
assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[3;6H\x1b[1K');
assert.deepEqual(getLines(term), ['¥¥ #', ' ¥¥', ' ¥¥', '¥¥¥¥¥', '']);
});
it('ICH', () => {
term.writeSync('\x1b[1;6H\x1b[@');
assert.deepEqual(getLines(term), ['¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[2;4H\x1b[2@');
assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[3;4H\x1b[3@');
assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[4;4H\x1b[4@');
assert.deepEqual(getLines(term), ['¥¥ ¥', '¥ ¥¥', '¥ ¥', '¥ ¥', '']);
});
it('DCH', () => {
term.writeSync('\x1b[1;6H\x1b[P');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[2;6H\x1b[2P');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[3;6H\x1b[3P');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
it('ECH', () => {
term.writeSync('\x1b[1;6H\x1b[X');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[2;6H\x1b[2X');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥¥¥¥', '¥¥¥¥¥', '']);
term.writeSync('\x1b[3;6H\x1b[3X');
assert.deepEqual(getLines(term), ['¥¥ ¥¥', '¥¥ ¥', '¥¥ ¥', '¥¥¥¥¥', '']);
});
});
});
+115 -94
View File
File diff suppressed because it is too large Load Diff
+53 -19
View File
@@ -112,7 +112,7 @@ describe('Terminal', () => {
assert.equal(typeof e, 'number');
done();
});
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
});
it('should fire the onTitleChange event', (done) => {
term.onTitleChange(e => {
@@ -397,7 +397,7 @@ describe('Terminal', () => {
term.buffer.lines.get(0).setCell(0, CellData.fromCharData([0, 'a', 0, 'a'.charCodeAt(0)]));
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'b', 0, 'b'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(INIT_ROWS - 1).loadCell(0, new CellData()).getChars(), 'b');
@@ -410,7 +410,7 @@ describe('Terminal', () => {
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
@@ -424,7 +424,7 @@ describe('Terminal', () => {
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS + 1);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a', '\'a\' should be pushed to the scrollback');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'b');
@@ -443,7 +443,7 @@ describe('Terminal', () => {
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
@@ -465,7 +465,7 @@ describe('Terminal', () => {
term.buffer.lines.get(INIT_ROWS - 1).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
assert.equal(term.buffer.lines.length, INIT_ROWS);
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
// 'a' gets pushed out of buffer
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
@@ -480,7 +480,7 @@ describe('Terminal', () => {
term.buffer.lines.get(2).setCell(0, CellData.fromCharData([0, 'c', 0, 'c'.charCodeAt(0)]));
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
@@ -494,7 +494,7 @@ describe('Terminal', () => {
term.buffer.lines.get(4).setCell(0, CellData.fromCharData([0, 'e', 0, 'e'.charCodeAt(0)]));
term.buffer.y = 3;
term.buffer.scrollBottom = 3;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'b');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c');
@@ -512,7 +512,7 @@ describe('Terminal', () => {
term.buffer.y = INIT_ROWS - 1; // Move cursor to last line
term.buffer.scrollTop = 1;
term.buffer.scrollBottom = 3;
term.scroll();
term.scroll(DEFAULT_ATTR_DATA.clone());
assert.equal(term.buffer.lines.length, INIT_ROWS);
assert.equal(term.buffer.lines.get(0).loadCell(0, new CellData()).getChars(), 'a');
assert.equal(term.buffer.lines.get(1).loadCell(0, new CellData()).getChars(), 'c', '\'b\' should be removed from the buffer');
@@ -747,7 +747,7 @@ describe('Terminal', () => {
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = true;
term.writeSync('a' + high + String.fromCharCode(i));
expect(term.buffer.lines.get(0).loadCell(term.cols - 1, cell).getChars()).eql('a');
expect(term.buffer.lines.get(1).loadCell(0, cell).getChars()).eql(high + String.fromCharCode(i));
@@ -761,7 +761,7 @@ describe('Terminal', () => {
const cell = new CellData();
for (let i = 0xDC00; i <= 0xDCFF; ++i) {
term.buffer.x = term.cols - 1;
term.wraparoundMode = false;
term.writeSync('\x1b[?7l'); // Disable wraparound mode
const width = wcwidth((0xD800 - 0xD800) * 0x400 + i - 0xDC00 + 0x10000);
if (width !== 1) {
continue;
@@ -812,7 +812,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(1);
});
it('multiple combined é', () => {
term.wraparoundMode = true;
term.writeSync(Array(100).join('e\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
@@ -826,7 +825,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(1);
});
it('multiple surrogate with combined', () => {
term.wraparoundMode = true;
term.writeSync(Array(100).join('\uD800\uDC00\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
@@ -855,7 +853,6 @@ describe('Terminal', () => {
expect(term.buffer.x).eql(3);
});
it('line of ¥ even', () => {
term.wraparoundMode = true;
term.writeSync(Array(50).join('¥'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
@@ -875,7 +872,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
});
it('line of ¥ odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.writeSync(Array(50).join('¥'));
for (let i = 1; i < term.cols - 1; ++i) {
@@ -900,7 +896,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.writeSync(Array(50).join('¥\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
@@ -925,7 +920,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
});
it('line of ¥ with combining even', () => {
term.wraparoundMode = true;
term.writeSync(Array(50).join('¥\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
@@ -945,7 +939,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining odd', () => {
term.wraparoundMode = true;
term.buffer.x = 1;
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 1; i < term.cols - 1; ++i) {
@@ -970,7 +963,6 @@ describe('Terminal', () => {
expect(cell.getWidth()).eql(2);
});
it('line of surrogate fullwidth with combining even', () => {
term.wraparoundMode = true;
term.writeSync(Array(50).join('\ud843\ude6d\u0301'));
for (let i = 0; i < term.cols; ++i) {
term.buffer.lines.get(0).loadCell(i, cell);
@@ -1367,6 +1359,48 @@ describe('Terminal', () => {
}).to.not.throw();
});
});
describe('Windows Mode', () => {
it('should mark lines as wrapped when the line ends in a non-null character after a LF', () => {
const data = [
'aaaaaaaaaa\n\r', // cannot wrap as it's the first
'aaaaaaaaa\n\r', // wrapped (windows mode only)
'aaaaaaaaa' // not wrapped
];
const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false});
normalTerminal.writeSync(data.join(''));
assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false);
const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true});
windowsModeTerminal.writeSync(data.join(''));
assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false);
});
it('should mark lines as wrapped when the line ends in a non-null character after a CUP', () => {
const data = [
'aaaaaaaaaa\x1b[2;1H', // cannot wrap as it's the first
'aaaaaaaaa\x1b[3;1H', // wrapped (windows mode only)
'aaaaaaaaa' // not wrapped
];
const normalTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: false});
normalTerminal.writeSync(data.join(''));
assert.equal(normalTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(1).isWrapped, false);
assert.equal(normalTerminal.buffer.lines.get(2).isWrapped, false);
const windowsModeTerminal = new TestTerminal({rows: 5, cols: 10, windowsMode: true});
windowsModeTerminal.writeSync(data.join(''));
assert.equal(windowsModeTerminal.buffer.lines.get(0).isWrapped, false);
assert.equal(windowsModeTerminal.buffer.lines.get(1).isWrapped, true, 'This line should wrap in Windows mode as the previous line ends in a non-null character');
assert.equal(windowsModeTerminal.buffer.lines.get(2).isWrapped, false);
});
});
});
class TestLinkifier extends Linkifier {
+38 -88
View File
@@ -39,21 +39,20 @@ import { MouseZoneManager } from 'browser/MouseZoneManager';
import { AccessibilityManager } from './AccessibilityManager';
import { ITheme, IMarker, IDisposable, ISelectionPosition } from 'xterm';
import { DomRenderer } from 'browser/renderer/dom/DomRenderer';
import { IKeyboardEvent, KeyboardResultType, ICharset, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
import { IKeyboardEvent, KeyboardResultType, IBufferLine, IAttributeData, CoreMouseEventType, CoreMouseButton, CoreMouseAction } from 'common/Types';
import { evaluateKeyboardEvent } from 'common/input/Keyboard';
import { EventEmitter, IEvent } from 'common/EventEmitter';
import { DEFAULT_ATTR_DATA } from 'common/buffer/BufferLine';
import { handleWindowsModeLineFeed } from 'common/WindowsMode';
import { updateWindowsModeWrappedState } from 'common/WindowsMode';
import { ColorManager } from 'browser/ColorManager';
import { RenderService } from 'browser/services/RenderService';
import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService } from 'common/services/Services';
import { IOptionsService, IBufferService, ICoreMouseService, ICoreService, ILogService, IDirtyRowService, IInstantiationService, ICharsetService } from 'common/services/Services';
import { OptionsService } from 'common/services/OptionsService';
import { ICharSizeService, IRenderService, IMouseService, ISelectionService, ISoundService, ICoreBrowserService } from 'browser/services/Services';
import { CharSizeService } from 'browser/services/CharSizeService';
import { BufferService, MINIMUM_COLS, MINIMUM_ROWS } from 'common/services/BufferService';
import { Disposable } from 'common/Lifecycle';
import { IBufferSet, IBuffer } from 'common/buffer/Types';
import { Attributes } from 'common/buffer/Constants';
import { MouseService } from 'browser/services/MouseService';
import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { CoreService } from 'common/services/CoreService';
@@ -64,6 +63,7 @@ import { InstantiationService } from 'common/services/InstantiationService';
import { CoreMouseService } from 'common/services/CoreMouseService';
import { WriteBuffer } from 'common/input/WriteBuffer';
import { CoreBrowserService } from 'browser/services/CoreBrowserService';
import { CharsetService } from 'common/services/CharsetService';
// Let it work inside Node.js for automated testing purposes.
const document = (typeof window !== 'undefined') ? window.document : null;
@@ -74,10 +74,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
public element: HTMLElement;
public screenElement: HTMLElement;
/**
* The HTMLElement that the terminal is created in, set by Terminal.open.
*/
private _parent: HTMLElement | null;
private _document: Document;
private _viewportScrollArea: HTMLElement;
private _viewportElement: HTMLElement;
@@ -96,6 +92,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// common services
private _bufferService: IBufferService;
private _coreService: ICoreService;
private _charsetService: ICharsetService;
private _coreMouseService: ICoreMouseService;
private _dirtyRowService: IDirtyRowService;
private _instantiationService: IInstantiationService;
@@ -110,19 +107,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _soundService: ISoundService;
// modes
public applicationKeypad: boolean;
public originMode: boolean;
public insertMode: boolean;
public wraparoundMode: boolean; // defaults: xterm - true, vt100 - false
public bracketedPasteMode: boolean;
// charset
// The current charset
public charset: ICharset;
public gcharset: number;
public glevel: number;
public charsets: ICharset[];
// mouse properties
public mouseEvents: CoreMouseEventType = CoreMouseEventType.NONE;
public sendFocus: boolean;
@@ -130,12 +117,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
// misc
public savedCols: number;
public curAttrData: IAttributeData;
private _eraseAttrData: IAttributeData;
public params: (string | number)[];
public currentParam: string | number;
// write buffer
private _writeBuffer: WriteBuffer;
@@ -228,6 +209,8 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._instantiationService.setService(ICoreMouseService, this._coreMouseService);
this._dirtyRowService = this._instantiationService.createInstance(DirtyRowService);
this._instantiationService.setService(IDirtyRowService, this._dirtyRowService);
this._charsetService = this._instantiationService.createInstance(CharsetService);
this._instantiationService.setService(ICharsetService, this._charsetService);
this._setupOptionsListeners();
this._setup();
@@ -249,39 +232,30 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
}
private _setup(): void {
this._parent = document ? document.body : null;
this._customKeyEventHandler = null;
// modes
this.applicationKeypad = false;
this.originMode = false;
this.insertMode = false;
this.wraparoundMode = true; // defaults: xterm - true, vt100 - false
this.bracketedPasteMode = false;
// charset
this.charset = null;
this.gcharset = null;
this.glevel = 0;
// TODO: Can this be just []?
this.charsets = [null];
this.curAttrData = DEFAULT_ATTR_DATA.clone();
this._eraseAttrData = DEFAULT_ATTR_DATA.clone();
this.params = [];
this.currentParam = 0;
this._userScrolling = false;
// Register input handler and refire/handle events
this._inputHandler = new InputHandler(this, this._bufferService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService);
this._inputHandler.onCursorMove(() => this._onCursorMove.fire());
this._inputHandler.onLineFeed(() => this._onLineFeed.fire());
this.register(this._inputHandler);
if (this._inputHandler) {
this._inputHandler.reset();
} else {
// Register input handler and refire/handle events
this._inputHandler = new InputHandler(this, this._bufferService, this._charsetService, this._coreService, this._dirtyRowService, this._logService, this.optionsService, this._coreMouseService);
this._inputHandler.onRequestBell(() => this.bell());
this._inputHandler.onRequestRefreshRows((start, end) => this.refresh(start, end));
this._inputHandler.onRequestReset(() => this.reset());
this._inputHandler.onCursorMove(() => this._onCursorMove.fire());
this._inputHandler.onLineFeed(() => this._onLineFeed.fire());
this.register(this._inputHandler);
}
this.linkifier = this.linkifier || new Linkifier(this._bufferService, this._logService);
if (!this.linkifier) {
this.linkifier = new Linkifier(this._bufferService, this._logService);
}
if (this.options.windowsMode) {
this._enableWindowsMode();
@@ -290,7 +264,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
private _enableWindowsMode(): void {
if (!this._windowsMode) {
this._windowsMode = this.onLineFeed(handleWindowsModeLineFeed.bind(null, this._bufferService));
const disposables: IDisposable[] = [];
disposables.push(this.onLineFeed(updateWindowsModeWrappedState.bind(null, this._bufferService)));
disposables.push(this.addCsiHandler({ final: 'H' }, () => {
updateWindowsModeWrappedState(this._bufferService);
return false;
}));
this._windowsMode = {
dispose: () => {
disposables.forEach(d => d.dispose());
}
};
}
}
@@ -305,15 +289,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
return this._bufferService.buffers;
}
/**
* back_color_erase feature for xterm.
*/
public eraseAttrData(): IAttributeData {
this._eraseAttrData.bg &= ~(Attributes.CM_MASK | 0xFFFFFF);
this._eraseAttrData.bg |= this.curAttrData.bg & ~0xFC000000;
return this._eraseAttrData;
}
/**
* Focus the terminal. Delegates focus handling to the terminal's DOM element.
*/
@@ -485,9 +460,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* @param parent The element to create the terminal within.
*/
public open(parent: HTMLElement): void {
this._parent = parent || this._parent;
if (!this._parent) {
if (!parent) {
throw new Error('Terminal requires a parent element.');
}
@@ -495,7 +468,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this._logService.warn('Terminal.open was called on an element that was not attached to the DOM');
}
this._document = this._parent.ownerDocument;
this._document = parent.ownerDocument;
// Create main element container
this.element = this._document.createElement('div');
@@ -503,7 +476,7 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.element.classList.add('terminal');
this.element.classList.add('xterm');
this.element.setAttribute('tabindex', '0');
this._parent.appendChild(this.element);
parent.appendChild(this.element);
// Performance: Use a document fragment to build the terminal
// viewport and helper elements detached from the DOM
@@ -958,10 +931,9 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
* Scroll the terminal down 1 row, creating a blank line.
* @param isWrapped Whether the new line is wrapped from the previous line.
*/
public scroll(isWrapped: boolean = false): void {
public scroll(eraseAttr: IAttributeData, isWrapped: boolean = false): void {
let newLine: IBufferLine;
newLine = this._blankLine;
const eraseAttr = this.eraseAttrData();
if (!newLine || newLine.length !== this.cols || newLine.getFg(0) !== eraseAttr.fg || newLine.getBg(0) !== eraseAttr.bg) {
newLine = this.buffer.getBlankLine(eraseAttr, isWrapped);
this._blankLine = newLine;
@@ -1306,27 +1278,6 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
return thirdLevelKey && (!ev.keyCode || ev.keyCode > 47);
}
/**
* Set the G level of the terminal
* @param g
*/
public setgLevel(g: number): void {
this.glevel = g;
this.charset = this.charsets[g];
}
/**
* Set the charset for the given G level of the terminal
* @param g
* @param charset
*/
public setgCharset(g: number, charset: ICharset): void {
this.charsets[g] = charset;
if (this.glevel === g) {
this.charset = charset;
}
}
protected _keyUp(ev: KeyboardEvent): void {
if (this._customKeyEventHandler && this._customKeyEventHandler(ev) === false) {
return;
@@ -1512,18 +1463,17 @@ export class Terminal extends Disposable implements ITerminal, IDisposable, IInp
this.options.rows = this.rows;
this.options.cols = this.cols;
const customKeyEventHandler = this._customKeyEventHandler;
const inputHandler = this._inputHandler;
const userScrolling = this._userScrolling;
this._setup();
this._bufferService.reset();
this._charsetService.reset();
this._coreService.reset();
this._coreMouseService.reset();
this._selectionService?.reset();
// reattach
this._customKeyEventHandler = customKeyEventHandler;
this._inputHandler = inputHandler;
this._userScrolling = userScrolling;
// do a full screen refresh
+3 -89
View File
@@ -6,7 +6,7 @@
import { IRenderer, IRenderDimensions, CharacterJoinerHandler, IRequestRefreshRowsEvent } from 'browser/renderer/Types';
import { IInputHandlingTerminal, ICompositionHelper, ITerminal, IBrowser, ITerminalOptions } from './Types';
import { IBuffer, IBufferStringIterator, IBufferSet } from 'common/buffer/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset, CoreMouseEventType } from 'common/Types';
import { IBufferLine, ICellData, IAttributeData, ICircularList, XtermListener, ICharset } from 'common/Types';
import { Buffer } from 'common/buffer/Buffer';
import * as Browser from 'common/Platform';
import { IDisposable, IMarker, IEvent, ISelectionPosition } from 'xterm';
@@ -19,6 +19,7 @@ import { IParams, IFunctionIdentifier } from 'common/parser/Types';
import { ISelectionService } from 'browser/services/Services';
export class TestTerminal extends Terminal {
get curAttrData(): IAttributeData { return (this as any)._inputHandler._curAttrData; }
keyDown(ev: any): boolean { return this._keyDown(ev); }
keyPress(ev: any): boolean { return this._keyPress(ev); }
}
@@ -197,112 +198,25 @@ export class MockTerminal implements ITerminal {
export class MockInputHandlingTerminal implements IInputHandlingTerminal {
onA11yCharEmitter: EventEmitter<string>;
onA11yTabEmitter: EventEmitter<number>;
element: HTMLElement;
options: ITerminalOptions = {};
cols: number;
rows: number;
charset: { [key: string]: string; };
gcharset: number;
glevel: number;
charsets: { [key: string]: string; }[];
applicationKeypad: boolean;
applicationCursor: boolean;
originMode: boolean;
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
curAttrData = new AttributeData();
savedCols: number;
x10Mouse: boolean;
vt200Mouse: boolean;
normalMouse: boolean;
mouseEvents: CoreMouseEventType;
sendFocus: boolean;
utfMouse: boolean;
sgrMouse: boolean;
urxvtMouse: boolean;
cursorHidden: boolean;
buffers: IBufferSet;
buffer: IBuffer = new MockBuffer();
viewport: IViewport;
selectionService: ISelectionService;
focus(): void {
throw new Error('Method not implemented.');
}
convertEol: boolean;
bell(): void {
throw new Error('Method not implemented.');
}
updateRange(y: number): void {
throw new Error('Method not implemented.');
}
scroll(isWrapped?: boolean): void {
throw new Error('Method not implemented.');
}
nextStop(x?: number): number {
throw new Error('Method not implemented.');
}
setgLevel(g: number): void {
throw new Error('Method not implemented.');
}
eraseAttrData(): IAttributeData {
throw new Error('Method not implemented.');
}
eraseRight(x: number, y: number): void {
throw new Error('Method not implemented.');
}
eraseLine(y: number): void {
throw new Error('Method not implemented.');
}
eraseLeft(x: number, y: number): void {
throw new Error('Method not implemented.');
}
prevStop(x?: number): number {
scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void {
throw new Error('Method not implemented.');
}
is(term: string): boolean {
throw new Error('Method not implemented.');
}
setgCharset(g: number, charset: { [key: string]: string; }): void {
throw new Error('Method not implemented.');
}
resize(x: number, y: number): void {
throw new Error('Method not implemented.');
}
log(text: string, data?: any): void {
throw new Error('Method not implemented.');
}
reset(): void {
throw new Error('Method not implemented.');
}
showCursor(): void {
throw new Error('Method not implemented.');
}
refresh(start: number, end: number): void {
throw new Error('Method not implemented.');
}
matchColor(r1: number, g1: number, b1: number): number {
throw new Error('Method not implemented.');
}
error(text: string, data?: any): void {
throw new Error('Method not implemented.');
}
setOption(key: string, value: any): void {
(<any>this.options)[key] = value;
}
on(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
off(type: string, listener: XtermListener): void {
throw new Error('Method not implemented.');
}
emit(type: string, data?: any): void {
throw new Error('Method not implemented.');
}
addDisposableListener(type: string, handler: XtermListener): IDisposable {
throw new Error('Method not implemented.');
}
handler(data: string): void {
throw new Error('Method not implemented.');
}
+2 -22
View File
@@ -21,22 +21,9 @@ export type LineData = CharData[];
* InputHandler cleanly from the ITerminal interface.
*/
export interface IInputHandlingTerminal {
element: HTMLElement;
options: ITerminalOptions;
cols: number;
rows: number;
charset: ICharset;
gcharset: number;
glevel: number;
charsets: ICharset[];
applicationKeypad: boolean;
originMode: boolean;
insertMode: boolean;
wraparoundMode: boolean;
bracketedPasteMode: boolean;
curAttrData: IAttributeData;
savedCols: number;
mouseEvents: CoreMouseEventType;
sendFocus: boolean;
buffers: IBufferSet;
@@ -46,17 +33,10 @@ export interface IInputHandlingTerminal {
onA11yCharEmitter: IEventEmitter<string>;
onA11yTabEmitter: IEventEmitter<number>;
bell(): void;
focus(): void;
scroll(isWrapped?: boolean): void;
setgLevel(g: number): void;
eraseAttrData(): IAttributeData;
scroll(eraseAttr: IAttributeData, isWrapped?: boolean): void;
is(term: string): boolean;
setgCharset(g: number, charset: ICharset): void;
resize(x: number, y: number): void;
reset(): void;
showCursor(): void;
refresh(start: number, end: number): void;
handleTitle(title: string): void;
}
@@ -141,7 +121,7 @@ export interface IInputHandler {
/** ESC D */ index(): void;
/** ESC H */ tabSet(): void;
/** ESC M */ reverseIndex(): void;
/** ESC c */ reset(): void;
/** ESC c */ fullReset(): void;
/** ESC n
ESC o
ESC |
+232 -219
View File
@@ -4,50 +4,243 @@
*/
import { assert } from 'chai';
import { blend, fromCss, toPaddedHex, toCss, toRgba, fromRgba, opaque, rgbRelativeLuminance, contrastRatio, ensureContrastRatioRgba } from 'browser/Color';
import { channels, color, css, rgb, rgba, toPaddedHex, contrastRatio } from 'browser/Color';
describe('Color', () => {
describe('blend', () => {
it('should blend colors based on the alpha channel', () => {
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
assert.deepEqual(blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
describe('channels', () => {
describe('toCss', () => {
it('should convert an rgb array to css hex string', () => {
assert.equal(channels.toCss(0x00, 0x00, 0x00), '#000000');
assert.equal(channels.toCss(0x10, 0x10, 0x10), '#101010');
assert.equal(channels.toCss(0x20, 0x20, 0x20), '#202020');
assert.equal(channels.toCss(0x30, 0x30, 0x30), '#303030');
assert.equal(channels.toCss(0x40, 0x40, 0x40), '#404040');
assert.equal(channels.toCss(0x50, 0x50, 0x50), '#505050');
assert.equal(channels.toCss(0x60, 0x60, 0x60), '#606060');
assert.equal(channels.toCss(0x70, 0x70, 0x70), '#707070');
assert.equal(channels.toCss(0x80, 0x80, 0x80), '#808080');
assert.equal(channels.toCss(0x90, 0x90, 0x90), '#909090');
assert.equal(channels.toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
assert.equal(channels.toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
assert.equal(channels.toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
assert.equal(channels.toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
assert.equal(channels.toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
assert.equal(channels.toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
assert.equal(channels.toCss(0xff, 0xff, 0xff), '#ffffff');
});
});
describe('toRgba', () => {
it('should convert an rgb array to an rgba number', () => {
assert.equal(channels.toRgba(0x00, 0x00, 0x00), 0x000000FF);
assert.equal(channels.toRgba(0x10, 0x10, 0x10), 0x101010FF);
assert.equal(channels.toRgba(0x20, 0x20, 0x20), 0x202020FF);
assert.equal(channels.toRgba(0x30, 0x30, 0x30), 0x303030FF);
assert.equal(channels.toRgba(0x40, 0x40, 0x40), 0x404040FF);
assert.equal(channels.toRgba(0x50, 0x50, 0x50), 0x505050FF);
assert.equal(channels.toRgba(0x60, 0x60, 0x60), 0x606060FF);
assert.equal(channels.toRgba(0x70, 0x70, 0x70), 0x707070FF);
assert.equal(channels.toRgba(0x80, 0x80, 0x80), 0x808080FF);
assert.equal(channels.toRgba(0x90, 0x90, 0x90), 0x909090FF);
assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
assert.equal(channels.toRgba(0xff, 0xff, 0xff), 0xffffffFF);
});
it('should convert an rgba array to an rgba number', () => {
assert.equal(channels.toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
assert.equal(channels.toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
assert.equal(channels.toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
assert.equal(channels.toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
assert.equal(channels.toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
assert.equal(channels.toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
assert.equal(channels.toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
assert.equal(channels.toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
assert.equal(channels.toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
assert.equal(channels.toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
assert.equal(channels.toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
assert.equal(channels.toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
assert.equal(channels.toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
assert.equal(channels.toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
assert.equal(channels.toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
assert.equal(channels.toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
assert.equal(channels.toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
});
});
});
describe('fromCss', () => {
it('should covert a CSS string to an IColor', () => {
assert.deepEqual(fromCss('#000000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(fromCss('#101010'), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(fromCss('#202020'), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(fromCss('#303030'), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(fromCss('#404040'), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(fromCss('#505050'), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(fromCss('#606060'), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(fromCss('#707070'), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(fromCss('#808080'), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(fromCss('#909090'), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(fromCss('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(fromCss('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(fromCss('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(fromCss('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(fromCss('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(fromCss('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(fromCss('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
describe('color', () => {
describe('blend', () => {
it('should blend colors based on the alpha channel', () => {
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF00', rgba: 0xFFFFFF00 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF10', rgba: 0xFFFFFF10 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF20', rgba: 0xFFFFFF20 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF30', rgba: 0xFFFFFF30 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF40', rgba: 0xFFFFFF40 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF50', rgba: 0xFFFFFF50 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF60', rgba: 0xFFFFFF60 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF70', rgba: 0xFFFFFF70 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF80', rgba: 0xFFFFFF80 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFF90', rgba: 0xFFFFFF90 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFA0', rgba: 0xFFFFFFA0 }), { css: '#a0a0a0', rgba: 0xA0A0A0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFB0', rgba: 0xFFFFFFB0 }), { css: '#b0b0b0', rgba: 0xB0B0B0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFC0', rgba: 0xFFFFFFC0 }), { css: '#c0c0c0', rgba: 0xC0C0C0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFD0', rgba: 0xFFFFFFD0 }), { css: '#d0d0d0', rgba: 0xD0D0D0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFE0', rgba: 0xFFFFFFE0 }), { css: '#e0e0e0', rgba: 0xE0E0E0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFF0', rgba: 0xFFFFFFF0 }), { css: '#f0f0f0', rgba: 0xF0F0F0FF });
assert.deepEqual(color.blend({ css: '#000000', rgba: 0x000000FF }, { css: '#FFFFFFFF', rgba: 0xFFFFFFFF }), { css: '#FFFFFFFF', rgba: 0xFFFFFFFF });
});
});
describe('opaque', () => {
it('should make the color opaque', () => {
assert.deepEqual(color.opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(color.opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(color.opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(color.opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(color.opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(color.opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(color.opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(color.opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(color.opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(color.opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(color.opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(color.opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(color.opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(color.opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(color.opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(color.opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(color.opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
});
});
});
describe('css', () => {
describe('toColor', () => {
it('should covert a CSS string to an IColor', () => {
assert.deepEqual(css.toColor('#000000'), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(css.toColor('#101010'), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(css.toColor('#202020'), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(css.toColor('#303030'), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(css.toColor('#404040'), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(css.toColor('#505050'), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(css.toColor('#606060'), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(css.toColor('#707070'), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(css.toColor('#808080'), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(css.toColor('#909090'), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(css.toColor('#a0a0a0'), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(css.toColor('#b0b0b0'), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(css.toColor('#c0c0c0'), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(css.toColor('#d0d0d0'), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(css.toColor('#e0e0e0'), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(css.toColor('#f0f0f0'), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(css.toColor('#ffffff'), { css: '#ffffff', rgba: 0xffffffFF });
});
});
});
describe('rgb', () => {
describe('relativeLuminance', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(rgb.relativeLuminance(0x000000), 0);
assert.equal(rgb.relativeLuminance(0x101010).toFixed(4), '0.0052');
assert.equal(rgb.relativeLuminance(0x202020).toFixed(4), '0.0144');
assert.equal(rgb.relativeLuminance(0x303030).toFixed(4), '0.0296');
assert.equal(rgb.relativeLuminance(0x404040).toFixed(4), '0.0513');
assert.equal(rgb.relativeLuminance(0x505050).toFixed(4), '0.0802');
assert.equal(rgb.relativeLuminance(0x606060).toFixed(4), '0.1170');
assert.equal(rgb.relativeLuminance(0x707070).toFixed(4), '0.1620');
assert.equal(rgb.relativeLuminance(0x808080).toFixed(4), '0.2159');
assert.equal(rgb.relativeLuminance(0x909090).toFixed(4), '0.2789');
assert.equal(rgb.relativeLuminance(0xA0A0A0).toFixed(4), '0.3515');
assert.equal(rgb.relativeLuminance(0xB0B0B0).toFixed(4), '0.4342');
assert.equal(rgb.relativeLuminance(0xC0C0C0).toFixed(4), '0.5271');
assert.equal(rgb.relativeLuminance(0xD0D0D0).toFixed(4), '0.6308');
assert.equal(rgb.relativeLuminance(0xE0E0E0).toFixed(4), '0.7454');
assert.equal(rgb.relativeLuminance(0xF0F0F0).toFixed(4), '0.8714');
assert.equal(rgb.relativeLuminance(0xFFFFFF), 1);
});
});
});
describe('rgba', () => {
describe('ensureContrastRatio', () => {
it('should return undefined if the color already meets the contrast ratio (black bg)', () => {
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 1), undefined);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 2), undefined);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 3), undefined);
});
it('should return a color that meets the contrast ratio (black bg)', () => {
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 4), 0x707070ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 7), 0x989898ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 9), 0xadadadff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 11), 0xbebebeff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 20), 0xfafafaff);
assert.equal(rgba.ensureContrastRatio(0x000000ff, 0x606060ff, 21), 0xffffffff);
});
it('should return undefined if the color already meets the contrast ratio (white bg)', () => {
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 1), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 2), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 3), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 4), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 5), undefined);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 6), undefined);
});
it('should return a color that meets the contrast ratio (white bg)', () => {
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 7), 0x565656ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 9), 0x454545ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 11), 0x373737ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 12), 0x313131ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 13), 0x313131ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 14), 0x272727ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 15), 0x232323ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 18), 0x151515ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 19), 0x101010ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 20), 0x080808ff);
assert.equal(rgba.ensureContrastRatio(0xffffffff, 0x606060ff, 21), 0x000000ff);
});
});
describe('toChannels', () => {
it('should convert an rgba number to an rgba array', () => {
assert.deepEqual(rgba.toChannels(0x00000000), [0x00, 0x00, 0x00, 0x00]);
assert.deepEqual(rgba.toChannels(0x10101010), [0x10, 0x10, 0x10, 0x10]);
assert.deepEqual(rgba.toChannels(0x20202020), [0x20, 0x20, 0x20, 0x20]);
assert.deepEqual(rgba.toChannels(0x30303030), [0x30, 0x30, 0x30, 0x30]);
assert.deepEqual(rgba.toChannels(0x40404040), [0x40, 0x40, 0x40, 0x40]);
assert.deepEqual(rgba.toChannels(0x50505050), [0x50, 0x50, 0x50, 0x50]);
assert.deepEqual(rgba.toChannels(0x60606060), [0x60, 0x60, 0x60, 0x60]);
assert.deepEqual(rgba.toChannels(0x70707070), [0x70, 0x70, 0x70, 0x70]);
assert.deepEqual(rgba.toChannels(0x80808080), [0x80, 0x80, 0x80, 0x80]);
assert.deepEqual(rgba.toChannels(0x90909090), [0x90, 0x90, 0x90, 0x90]);
assert.deepEqual(rgba.toChannels(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);
assert.deepEqual(rgba.toChannels(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);
assert.deepEqual(rgba.toChannels(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);
assert.deepEqual(rgba.toChannels(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);
assert.deepEqual(rgba.toChannels(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);
assert.deepEqual(rgba.toChannels(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);
assert.deepEqual(rgba.toChannels(0xffffffff), [0xff, 0xff, 0xff, 0xff]);
});
});
});
@@ -73,134 +266,6 @@ describe('Color', () => {
});
});
describe('toCss', () => {
it('should convert an rgb array to css hex string', () => {
assert.equal(toCss(0x00, 0x00, 0x00), '#000000');
assert.equal(toCss(0x10, 0x10, 0x10), '#101010');
assert.equal(toCss(0x20, 0x20, 0x20), '#202020');
assert.equal(toCss(0x30, 0x30, 0x30), '#303030');
assert.equal(toCss(0x40, 0x40, 0x40), '#404040');
assert.equal(toCss(0x50, 0x50, 0x50), '#505050');
assert.equal(toCss(0x60, 0x60, 0x60), '#606060');
assert.equal(toCss(0x70, 0x70, 0x70), '#707070');
assert.equal(toCss(0x80, 0x80, 0x80), '#808080');
assert.equal(toCss(0x90, 0x90, 0x90), '#909090');
assert.equal(toCss(0xa0, 0xa0, 0xa0), '#a0a0a0');
assert.equal(toCss(0xb0, 0xb0, 0xb0), '#b0b0b0');
assert.equal(toCss(0xc0, 0xc0, 0xc0), '#c0c0c0');
assert.equal(toCss(0xd0, 0xd0, 0xd0), '#d0d0d0');
assert.equal(toCss(0xe0, 0xe0, 0xe0), '#e0e0e0');
assert.equal(toCss(0xf0, 0xf0, 0xf0), '#f0f0f0');
assert.equal(toCss(0xff, 0xff, 0xff), '#ffffff');
});
});
describe('toRgba', () => {
it('should convert an rgb array to an rgba number', () => {
assert.equal(toRgba(0x00, 0x00, 0x00), 0x000000FF);
assert.equal(toRgba(0x10, 0x10, 0x10), 0x101010FF);
assert.equal(toRgba(0x20, 0x20, 0x20), 0x202020FF);
assert.equal(toRgba(0x30, 0x30, 0x30), 0x303030FF);
assert.equal(toRgba(0x40, 0x40, 0x40), 0x404040FF);
assert.equal(toRgba(0x50, 0x50, 0x50), 0x505050FF);
assert.equal(toRgba(0x60, 0x60, 0x60), 0x606060FF);
assert.equal(toRgba(0x70, 0x70, 0x70), 0x707070FF);
assert.equal(toRgba(0x80, 0x80, 0x80), 0x808080FF);
assert.equal(toRgba(0x90, 0x90, 0x90), 0x909090FF);
assert.equal(toRgba(0xa0, 0xa0, 0xa0), 0xa0a0a0FF);
assert.equal(toRgba(0xb0, 0xb0, 0xb0), 0xb0b0b0FF);
assert.equal(toRgba(0xc0, 0xc0, 0xc0), 0xc0c0c0FF);
assert.equal(toRgba(0xd0, 0xd0, 0xd0), 0xd0d0d0FF);
assert.equal(toRgba(0xe0, 0xe0, 0xe0), 0xe0e0e0FF);
assert.equal(toRgba(0xf0, 0xf0, 0xf0), 0xf0f0f0FF);
assert.equal(toRgba(0xff, 0xff, 0xff), 0xffffffFF);
});
it('should convert an rgba array to an rgba number', () => {
assert.equal(toRgba(0x00, 0x00, 0x00, 0x00), 0x00000000);
assert.equal(toRgba(0x10, 0x10, 0x10, 0x10), 0x10101010);
assert.equal(toRgba(0x20, 0x20, 0x20, 0x20), 0x20202020);
assert.equal(toRgba(0x30, 0x30, 0x30, 0x30), 0x30303030);
assert.equal(toRgba(0x40, 0x40, 0x40, 0x40), 0x40404040);
assert.equal(toRgba(0x50, 0x50, 0x50, 0x50), 0x50505050);
assert.equal(toRgba(0x60, 0x60, 0x60, 0x60), 0x60606060);
assert.equal(toRgba(0x70, 0x70, 0x70, 0x70), 0x70707070);
assert.equal(toRgba(0x80, 0x80, 0x80, 0x80), 0x80808080);
assert.equal(toRgba(0x90, 0x90, 0x90, 0x90), 0x90909090);
assert.equal(toRgba(0xa0, 0xa0, 0xa0, 0xa0), 0xa0a0a0a0);
assert.equal(toRgba(0xb0, 0xb0, 0xb0, 0xb0), 0xb0b0b0b0);
assert.equal(toRgba(0xc0, 0xc0, 0xc0, 0xc0), 0xc0c0c0c0);
assert.equal(toRgba(0xd0, 0xd0, 0xd0, 0xd0), 0xd0d0d0d0);
assert.equal(toRgba(0xe0, 0xe0, 0xe0, 0xe0), 0xe0e0e0e0);
assert.equal(toRgba(0xf0, 0xf0, 0xf0, 0xf0), 0xf0f0f0f0);
assert.equal(toRgba(0xff, 0xff, 0xff, 0xff), 0xffffffff);
});
});
describe('fromRgba', () => {
it('should convert an rgba number to an rgba array', () => {
assert.deepEqual(fromRgba(0x00000000), [0x00, 0x00, 0x00, 0x00]);
assert.deepEqual(fromRgba(0x10101010), [0x10, 0x10, 0x10, 0x10]);
assert.deepEqual(fromRgba(0x20202020), [0x20, 0x20, 0x20, 0x20]);
assert.deepEqual(fromRgba(0x30303030), [0x30, 0x30, 0x30, 0x30]);
assert.deepEqual(fromRgba(0x40404040), [0x40, 0x40, 0x40, 0x40]);
assert.deepEqual(fromRgba(0x50505050), [0x50, 0x50, 0x50, 0x50]);
assert.deepEqual(fromRgba(0x60606060), [0x60, 0x60, 0x60, 0x60]);
assert.deepEqual(fromRgba(0x70707070), [0x70, 0x70, 0x70, 0x70]);
assert.deepEqual(fromRgba(0x80808080), [0x80, 0x80, 0x80, 0x80]);
assert.deepEqual(fromRgba(0x90909090), [0x90, 0x90, 0x90, 0x90]);
assert.deepEqual(fromRgba(0xa0a0a0a0), [0xa0, 0xa0, 0xa0, 0xa0]);
assert.deepEqual(fromRgba(0xb0b0b0b0), [0xb0, 0xb0, 0xb0, 0xb0]);
assert.deepEqual(fromRgba(0xc0c0c0c0), [0xc0, 0xc0, 0xc0, 0xc0]);
assert.deepEqual(fromRgba(0xd0d0d0d0), [0xd0, 0xd0, 0xd0, 0xd0]);
assert.deepEqual(fromRgba(0xe0e0e0e0), [0xe0, 0xe0, 0xe0, 0xe0]);
assert.deepEqual(fromRgba(0xf0f0f0f0), [0xf0, 0xf0, 0xf0, 0xf0]);
assert.deepEqual(fromRgba(0xffffffff), [0xff, 0xff, 0xff, 0xff]);
});
});
describe('opaque', () => {
it('should make the color opaque', () => {
assert.deepEqual(opaque({ css: '#00000000', rgba: 0x00000000 }), { css: '#000000', rgba: 0x000000FF });
assert.deepEqual(opaque({ css: '#10101010', rgba: 0x10101010 }), { css: '#101010', rgba: 0x101010FF });
assert.deepEqual(opaque({ css: '#20202020', rgba: 0x20202020 }), { css: '#202020', rgba: 0x202020FF });
assert.deepEqual(opaque({ css: '#30303030', rgba: 0x30303030 }), { css: '#303030', rgba: 0x303030FF });
assert.deepEqual(opaque({ css: '#40404040', rgba: 0x40404040 }), { css: '#404040', rgba: 0x404040FF });
assert.deepEqual(opaque({ css: '#50505050', rgba: 0x50505050 }), { css: '#505050', rgba: 0x505050FF });
assert.deepEqual(opaque({ css: '#60606060', rgba: 0x60606060 }), { css: '#606060', rgba: 0x606060FF });
assert.deepEqual(opaque({ css: '#70707070', rgba: 0x70707070 }), { css: '#707070', rgba: 0x707070FF });
assert.deepEqual(opaque({ css: '#80808080', rgba: 0x80808080 }), { css: '#808080', rgba: 0x808080FF });
assert.deepEqual(opaque({ css: '#90909090', rgba: 0x90909090 }), { css: '#909090', rgba: 0x909090FF });
assert.deepEqual(opaque({ css: '#a0a0a0a0', rgba: 0xa0a0a0a0 }), { css: '#a0a0a0', rgba: 0xa0a0a0FF });
assert.deepEqual(opaque({ css: '#b0b0b0b0', rgba: 0xb0b0b0b0 }), { css: '#b0b0b0', rgba: 0xb0b0b0FF });
assert.deepEqual(opaque({ css: '#c0c0c0c0', rgba: 0xc0c0c0c0 }), { css: '#c0c0c0', rgba: 0xc0c0c0FF });
assert.deepEqual(opaque({ css: '#d0d0d0d0', rgba: 0xd0d0d0d0 }), { css: '#d0d0d0', rgba: 0xd0d0d0FF });
assert.deepEqual(opaque({ css: '#e0e0e0e0', rgba: 0xe0e0e0e0 }), { css: '#e0e0e0', rgba: 0xe0e0e0FF });
assert.deepEqual(opaque({ css: '#f0f0f0f0', rgba: 0xf0f0f0f0 }), { css: '#f0f0f0', rgba: 0xf0f0f0FF });
assert.deepEqual(opaque({ css: '#ffffffff', rgba: 0xffffffff }), { css: '#ffffff', rgba: 0xffffffFF });
});
});
describe('rgbRelativeLuminance', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(rgbRelativeLuminance(0x000000), 0);
assert.equal(rgbRelativeLuminance(0x101010).toFixed(4), '0.0052');
assert.equal(rgbRelativeLuminance(0x202020).toFixed(4), '0.0144');
assert.equal(rgbRelativeLuminance(0x303030).toFixed(4), '0.0296');
assert.equal(rgbRelativeLuminance(0x404040).toFixed(4), '0.0513');
assert.equal(rgbRelativeLuminance(0x505050).toFixed(4), '0.0802');
assert.equal(rgbRelativeLuminance(0x606060).toFixed(4), '0.1170');
assert.equal(rgbRelativeLuminance(0x707070).toFixed(4), '0.1620');
assert.equal(rgbRelativeLuminance(0x808080).toFixed(4), '0.2159');
assert.equal(rgbRelativeLuminance(0x909090).toFixed(4), '0.2789');
assert.equal(rgbRelativeLuminance(0xA0A0A0).toFixed(4), '0.3515');
assert.equal(rgbRelativeLuminance(0xB0B0B0).toFixed(4), '0.4342');
assert.equal(rgbRelativeLuminance(0xC0C0C0).toFixed(4), '0.5271');
assert.equal(rgbRelativeLuminance(0xD0D0D0).toFixed(4), '0.6308');
assert.equal(rgbRelativeLuminance(0xE0E0E0).toFixed(4), '0.7454');
assert.equal(rgbRelativeLuminance(0xF0F0F0).toFixed(4), '0.8714');
assert.equal(rgbRelativeLuminance(0xFFFFFF), 1);
});
});
describe('contrastRatio', () => {
it('should calculate the relative luminance of the color', () => {
assert.equal(contrastRatio(0, 0), 1);
@@ -212,56 +277,4 @@ describe('Color', () => {
assert.equal(contrastRatio(1, 0), 21);
});
});
describe('ensureContrastRatioRgba', () => {
it('should return undefined if the color already meets the contrast ratio (black bg)', () => {
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 1), undefined);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 2), undefined);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 3), undefined);
});
it('should return a color that meets the contrast ratio (black bg)', () => {
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 4), 0x707070ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 5), 0x7f7f7fff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 6), 0x8c8c8cff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 7), 0x989898ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 8), 0xa3a3a3ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 9), 0xadadadff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 10), 0xb6b6b6ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 11), 0xbebebeff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 12), 0xc5c5c5ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 13), 0xd1d1d1ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 14), 0xd6d6d6ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 15), 0xdbdbdbff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 16), 0xe3e3e3ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 17), 0xe9e9e9ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 18), 0xeeeeeeff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 19), 0xf4f4f4ff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 20), 0xfafafaff);
assert.equal(ensureContrastRatioRgba(0x000000ff, 0x606060ff, 21), 0xffffffff);
});
it('should return undefined if the color already meets the contrast ratio (white bg)', () => {
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 1), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 2), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 3), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 4), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 5), undefined);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 6), undefined);
});
it('should return a color that meets the contrast ratio (white bg)', () => {
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 7), 0x565656ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 8), 0x4d4d4dff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 9), 0x454545ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 10), 0x3e3e3eff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 11), 0x373737ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 12), 0x313131ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 13), 0x313131ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 14), 0x272727ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 15), 0x232323ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 16), 0x1f1f1fff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 17), 0x1b1b1bff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 18), 0x151515ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 19), 0x101010ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 20), 0x080808ff);
assert.equal(ensureContrastRatioRgba(0xffffffff, 0x606060ff, 21), 0x000000ff);
});
});
});

Some files were not shown because too many files have changed in this diff Show More