diff --git a/.gitignore b/.gitignore index 92ac095..26f5cce 100644 --- a/.gitignore +++ b/.gitignore @@ -2,6 +2,8 @@ .checked-*.js .DS_Store .vscode +/coverage /dist /node_modules /tmp +__diff_output__ diff --git a/jest.config.js b/jest.config.js index 088a4a8..7df423e 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,6 +1,7 @@ module.exports = { 'moduleNameMapper': { '^js/(.*)': '/js/$1', + '^test/(.*)': '/test/$1', }, 'roots': [ 'js/', @@ -9,8 +10,17 @@ module.exports = { 'testMatch': [ '**/?(*.)+(spec|test).+(ts|js)' ], + 'transform': { '^.+\\.js$': 'babel-jest', '^.+\\.ts$': 'ts-jest' }, + 'setupFilesAfterEnv': [ + '/test/jest-setup.js' + ], + 'coveragePathIgnorePatterns': [ + '/node_modules/', + '/js/roms/', + '/test/', + ] }; diff --git a/js/apple2.ts b/js/apple2.ts index 619c211..aca0e47 100644 --- a/js/apple2.ts +++ b/js/apple2.ts @@ -82,11 +82,11 @@ export class Apple2 implements Restorable, DebuggerContainer { const VideoModes = options.gl ? VideoModesGL : VideoModes2D; this.cpu = new CPU6502({ '65C02': options.enhanced }); - this.gr = new LoresPage(1, options.characterRom, options.e); - this.gr2 = new LoresPage(2, options.characterRom, options.e); - this.hgr = new HiresPage(1); - this.hgr2 = new HiresPage(2); - this.vm = new VideoModes(this.gr, this.hgr, this.gr2, this.hgr2, options.canvas, options.e); + this.vm = new VideoModes(options.canvas, options.e); + this.gr = new LoresPage(this.vm, 1, options.characterRom, options.e); + this.gr2 = new LoresPage(this.vm, 2, options.characterRom, options.e); + this.hgr = new HiresPage(this.vm, 1); + this.hgr2 = new HiresPage(this.vm, 2); this.io = new Apple2IO(this.cpu, this.vm); this.tick = options.tick; diff --git a/js/canvas.ts b/js/canvas.ts index c3c27b7..0c12ba9 100644 --- a/js/canvas.ts +++ b/js/canvas.ts @@ -22,46 +22,6 @@ import { pageNo } from './videomodes'; -let textMode = true; -let mixedMode = false; -let hiresMode = false; -let pageMode: pageNo = 1; -let _80colMode = false; -let altCharMode = false; -let an3 = false; -let doubleHiresMode = false; -let monoDHRMode = false; -const colorDHRMode = false; -let mixedDHRMode = false; -let highColorHGRMode = false; -let highColorTextMode = false; -let oneSixtyMode = false; - -const tmpCanvas = document.createElement('canvas'); -const tmpContext = tmpCanvas.getContext('2d'); - -const buildScreen = (mainData: ImageData, mixData?: ImageData | null) => { - if (!tmpContext) { - throw new Error('No 2d context'); - } - - const { width, height } = { width: 560, height: 192 }; - const { x, y } = _80colMode ? { x: 0, y: 0 } : { x: 0, y: 0 }; - - tmpCanvas.width = width; - tmpCanvas.height = height; - tmpContext.fillStyle = 'rgba(0,0,0,1)'; - tmpContext.fillRect(0, 0, width, height); - - if (mixData) { - tmpContext.putImageData(mainData, x, y, 0, 0, 560, 160); - tmpContext.putImageData(mixData, x, y, 0, 160, 560, 32); - } else { - tmpContext.putImageData(mainData, x, y); - } - return tmpCanvas; -}; - const dim = (c: Color): Color => { return [ c[0] * 0.75 & 0xff, @@ -119,7 +79,7 @@ const r4 = [ 11, // Light Blue 13, // Yellow 15 // White -]; +] as const; const dcolors: Color[] = [ [0, 0, 0], // 0x0 black @@ -145,7 +105,7 @@ const notDirty: Region = { bottom: -1, left: 561, right: -1 -}; +} as const; /**************************************************************************** * @@ -160,21 +120,25 @@ export class LoresPage2D implements LoresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; private _blink = false; + private highColorTextMode = false + dirty: Region = {...notDirty} imageData: ImageData; - constructor(private page: number, + constructor( + private vm: VideoModes, + private page: pageNo, private readonly charset: rom, - private readonly e: boolean) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private readonly e: boolean + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x4); this._buffer[1] = allocMemPages(0x4); + + this.vm.setLoresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -194,7 +158,7 @@ export class LoresPage2D implements LoresPage { private _checkInverse(val: byte) { let inverse = false; if (this.e) { - if (!_80colMode && !altCharMode) { + if (!this.vm._80colMode && !this.vm.altCharMode) { inverse = ((val & 0xc0) == 0x40) && this._blink; } } else { @@ -264,14 +228,14 @@ export class LoresPage2D implements LoresPage { x += 14; if (x > this.dirty.right) { this.dirty.right = x; } - if (textMode || hiresMode || (mixedMode && row > 19)) { - if (_80colMode) { + if (this.vm.textMode || this.vm.hiresMode || (this.vm.mixedMode && row > 19)) { + if (this.vm._80colMode) { const inverse = this._checkInverse(val); fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -295,13 +259,13 @@ export class LoresPage2D implements LoresPage { fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } let offset = (col * 14 + row * 560 * 8) * 4; - if (highColorTextMode) { + if (this.highColorTextMode) { fore = _colors[this._buffer[1][base] >> 4]; back = _colors[this._buffer[1][base] & 0x0f]; } @@ -318,7 +282,7 @@ export class LoresPage2D implements LoresPage { offset += 546 * 4; } } else { - const colorMode = mixedMode && !textMode && !this._monoMode; + const colorMode = this.vm.mixedMode && !this.vm.textMode && !this.vm.monoMode; // var val0 = col > 0 ? _buffer[0][base - 1] : 0; // var val2 = col < 39 ? _buffer[0][base + 1] : 0; @@ -356,24 +320,20 @@ export class LoresPage2D implements LoresPage { } } } else { - if (!_80colMode && bank == 1) { - return; - } - if (_80colMode && !an3) { + if (this.vm._80colMode && !this.vm.an3State) { let offset = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8) * 4; - if (this._monoMode) { - fore = whiteCol; - back = blackCol; + if (this.vm.monoMode) { for (let jdx = 0; jdx < 8; jdx++) { - let b = (jdx < 8) ? (val & 0x0f) : (val >> 4); + let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); b |= (b << 4); - if (bank & 0x1) { - b <<= 1; + b |= (b << 8); + if (col & 0x1) { + b >>= 2; } for (let idx = 0; idx < 7; idx++) { - const color = (b & 0x80) ? fore : back; + const color = (b & 0x01) ? whiteCol : blackCol; this._drawHalfPixel(data, offset, color); - b <<= 1; + b >>= 1; offset += 4; } offset += 553 * 4; @@ -387,28 +347,26 @@ export class LoresPage2D implements LoresPage { (val & 0x0f) : (val >> 4)]; for (let idx = 0; idx < 7; idx++) { this._drawHalfPixel(data, offset, color); - off += 4; + offset += 4; } offset += 553 * 4; } } - } else { + } else if (bank === 0) { let offset = (col * 14 + row * 560 * 8) * 4; - if (this._monoMode) { - fore = whiteCol; - back = blackCol; + if (this.vm.monoMode) { for (let jdx = 0; jdx < 8; jdx++) { let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); b |= (b << 4); b |= (b << 8); if (col & 0x1) { - b <<= 2; + b >>= 2; } for (let idx = 0; idx < 14; idx++) { - const color = (b & 0x8000) ? fore : back; + const color = (b & 0x0001) ? whiteCol : blackCol; this._drawHalfPixel(data, offset, color); - b <<= 1; + b >>= 1; offset += 4; } offset += 546 * 4; @@ -429,21 +387,19 @@ export class LoresPage2D implements LoresPage { } refresh() { + this.highColorTextMode = !this.vm.an3State && this.vm.textMode && !this.vm._80colMode; + let addr = 0x400 * this.page; this._refreshing = true; for (let idx = 0; idx < 0x400; idx++, addr++) { this._write(addr >> 8, addr & 0xff, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(addr >> 8, addr & 0xff, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - blink() { let addr = 0x400 * this.page; this._refreshing = true; @@ -476,8 +432,6 @@ export class LoresPage2D implements LoresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -486,8 +440,6 @@ export class LoresPage2D implements LoresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -518,7 +470,7 @@ export class LoresPage2D implements LoresPage { for (row = 0; row < 24; row++) { base = this.rowToBase(row); line = ''; - if (this.e && _80colMode) { + if (this.e && this.vm._80colMode) { for (col = 0; col < 80; col++) { charCode = this.mapCharCode(this._buffer[1 - col % 2][base + Math.floor(col / 2)]); line += String.fromCharCode(charCode); @@ -548,16 +500,23 @@ export class HiresPage2D implements HiresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; + + highColorHGRMode: boolean; + oneSixtyMode: boolean; + mixedDHRMode: boolean; + monoDHRMode: boolean; + colorDHRMode: boolean = true; constructor( - private page: number) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private vm: VideoModes, + private page: pageNo, + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x20); this._buffer[1] = allocMemPages(0x20); + + this.vm.setHiresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -645,7 +604,7 @@ export class HiresPage2D implements HiresPage { const data = this.imageData.data; let dx, dy; - if ((rowa < 24) && (col < 40) && hiresMode) { + if ((rowa < 24) && (col < 40) && this.vm.hiresMode) { let y = rowa << 4 | rowb << 1; if (y < this.dirty.top) { this.dirty.top = y; } y += 1; @@ -657,7 +616,7 @@ export class HiresPage2D implements HiresPage { dy = rowa << 4 | rowb << 1; let bz, b0, b1, b2, b3, b4, c, hb; - if (oneSixtyMode && !this._monoMode) { + if (this.oneSixtyMode && !this.vm.monoMode) { // 1 byte = two pixels, but 3:4 ratio const c3 = val & 0xf; const c4 = val >> 4; @@ -667,7 +626,7 @@ export class HiresPage2D implements HiresPage { this._draw3Pixel(data, offset, dcolors[c3]); this._draw4Pixel(data, offset + 12, dcolors[c4]); - } else if (doubleHiresMode) { + } else if (this.vm.doubleHiresMode) { val &= 0x7f; // Every 4 bytes is 7 pixels @@ -719,7 +678,7 @@ export class HiresPage2D implements HiresPage { let offset = dx * 4 + dy * 280 * 4; let monoColor = null; - if (this._monoMode || monoDHRMode) { + if (this.vm.monoMode || this.monoDHRMode) { monoColor = whiteCol; } @@ -734,7 +693,7 @@ export class HiresPage2D implements HiresPage { } else { this._drawHalfPixel(data, offset, blackCol); } - } else if (mixedDHRMode) { + } else if (this.mixedDHRMode) { if (hbs) { this._drawHalfPixel(data, offset, dcolor); } else { @@ -744,7 +703,7 @@ export class HiresPage2D implements HiresPage { this._drawHalfPixel(data, offset, blackCol); } } - } else if (colorDHRMode) { + } else if (this.colorDHRMode) { this._drawHalfPixel(data, offset, dcolor); } else if ( ((c[idx] != c[idx - 1]) && (c[idx] != c[idx + 1])) && @@ -793,7 +752,7 @@ export class HiresPage2D implements HiresPage { let offset = dx * 4 + dy * 280 * 4; - const monoColor = this._monoMode ? whiteCol : null; + const monoColor = this.vm.monoMode ? whiteCol : null; for (let idx = 0; idx < 9; idx++, offset += 8) { val >>= 1; @@ -801,7 +760,7 @@ export class HiresPage2D implements HiresPage { if (v1) { if (monoColor) { color = monoColor; - } else if (highColorHGRMode) { + } else if (this.highColorHGRMode) { color = dcolors[this._buffer[1][base] >> 4]; } else if (v0 || v2) { color = whiteCol; @@ -811,7 +770,7 @@ export class HiresPage2D implements HiresPage { } else { if (monoColor) { color = blackCol; - } else if (highColorHGRMode) { + } else if (this.highColorHGRMode) { color = dcolors[this._buffer[1][base] & 0x0f]; } else if (odd && v2 && v0) { color = v0 ? dim(evenCol) : evenCol; @@ -837,23 +796,24 @@ export class HiresPage2D implements HiresPage { } refresh() { + this.highColorHGRMode = !this.vm.an3State && this.vm.hiresMode && !this.vm._80colMode; + this.oneSixtyMode = this.vm.flag == 1 && this.vm.doubleHiresMode; + this.mixedDHRMode = this.vm.flag == 2 && this.vm.doubleHiresMode; + this.monoDHRMode = this.vm.flag == 3 && this.vm.doubleHiresMode; + let addr = 0x2000 * this.page; this._refreshing = true; for (let idx = 0; idx < 0x2000; idx++, addr++) { const page = addr >> 8; const off = addr & 0xff; this._write(page, off, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(page, off, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - start() { return this._start(); } @@ -872,8 +832,6 @@ export class HiresPage2D implements HiresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -882,8 +840,6 @@ export class HiresPage2D implements HiresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -892,37 +848,53 @@ export class HiresPage2D implements HiresPage { } export class VideoModes2D implements VideoModes { - private _grs: LoresPage[]; - private _hgrs: HiresPage[]; - private _flag = 0; - private _context: CanvasRenderingContext2D | null; + private _grs: LoresPage[] = []; + private _hgrs: HiresPage[] = []; + private _screenContext: CanvasRenderingContext2D; + private _canvas: HTMLCanvasElement; private _left: number; private _top: number; private _refreshFlag: boolean = true; public ready = Promise.resolve(); + textMode: boolean; + mixedMode: boolean; + hiresMode: boolean; + pageMode: pageNo; + _80colMode: boolean; + altCharMode: boolean; + an3State: boolean; + doubleHiresMode: boolean; + + flag = 0; + monoMode = false; + + context: CanvasRenderingContext2D; + constructor( - gr: LoresPage, - hgr: HiresPage, - gr2: LoresPage, - hgr2: HiresPage, - private canvas: HTMLCanvasElement, - private e: boolean) { - this._grs = [gr, gr2]; - this._hgrs = [hgr, hgr2]; - this._context = this.canvas.getContext('2d'); - this._left = (this.canvas.width - 560) / 2; - this._top = (this.canvas.height - 384) / 2; + private screen: HTMLCanvasElement, + private e: boolean + ) { + this._canvas = document.createElement('canvas'); + const context = this._canvas.getContext('2d'); + const screenContext = this.screen.getContext('2d'); + if (!context || !screenContext) { + throw new Error('No 2d context'); + } + this.context = context; + + const { width, height } = { width: 560, height: 192 }; + this._canvas.width = width; + this._canvas.height = height; + + this._screenContext = screenContext; + this._left = (this.screen.width - 560) / 2; + this._top = (this.screen.height - 384) / 2; } - private _refresh() { - highColorTextMode = !an3 && textMode && !_80colMode; - highColorHGRMode = !an3 && hiresMode && !_80colMode; - doubleHiresMode = !an3 && hiresMode && _80colMode; - oneSixtyMode = this._flag == 1 && doubleHiresMode; - mixedDHRMode = this._flag == 2 && doubleHiresMode; - monoDHRMode = this._flag == 3 && doubleHiresMode; + _refresh() { + this.doubleHiresMode = !this.an3State && this.hiresMode && this._80colMode; this._refreshFlag = true; } @@ -932,25 +904,33 @@ export class VideoModes2D implements VideoModes { } reset() { - textMode = true; - mixedMode = false; - hiresMode = true; - pageMode = 1; + this.textMode = true; + this.mixedMode = false; + this.hiresMode = true; + this.pageMode = 1; - _80colMode = false; - altCharMode = false; + this._80colMode = false; + this.altCharMode = false; - this._flag = 0; - an3 = true; + this.flag = 0; + this.an3State = true; this._refresh(); } + setLoresPage(page: pageNo, lores: LoresPage) { + this._grs[page - 1] = lores; + } + + setHiresPage(page: pageNo, hires: HiresPage) { + this._hgrs[page - 1] = hires; + } + text(on: boolean) { - const old = textMode; - textMode = on; + const old = this.textMode; + this.textMode = on; if (on) { - this._flag = 0; + this.flag = 0; } if (old != on) { this._refresh(); @@ -960,29 +940,29 @@ export class VideoModes2D implements VideoModes { _80col(on: boolean) { if (!this.e) { return; } - const old = _80colMode; - _80colMode = on; + const old = this._80colMode; + this._80colMode = on; if (old != on) { this._refresh(); } } - altchar(on: boolean) { + altChar(on: boolean) { if (!this.e) { return; } - const old = altCharMode; - altCharMode = on; + const old = this.altCharMode; + this.altCharMode = on; if (old != on) { this._refresh(); } } hires(on: boolean) { - const old = hiresMode; - hiresMode = on; + const old = this.hiresMode; + this.hiresMode = on; if (!on) { - this._flag = 0; + this.flag = 0; } if (old != on) { @@ -993,11 +973,11 @@ export class VideoModes2D implements VideoModes { an3(on: boolean) { if (!this.e) { return; } - const old = an3; - an3 = on; + const old = this.an3State; + this.an3State = on; if (on) { - this._flag = ((this._flag << 1) | (_80colMode ? 0x0 : 0x1)) & 0x3; + this.flag = ((this.flag << 1) | (this._80colMode ? 0x0 : 0x1)) & 0x3; } if (old != on) { @@ -1010,47 +990,60 @@ export class VideoModes2D implements VideoModes { } mixed(on: boolean) { - const old = mixedMode; - mixedMode = on; + const old = this.mixedMode; + this.mixedMode = on; if (old != on) { this._refresh(); } } page(pageNo: pageNo) { - const old = pageMode; - pageMode = pageNo; + const old = this.pageMode; + this.pageMode = pageNo; if (old != pageNo) { this._refresh(); } } isText() { - return textMode; + return this.textMode; } isMixed() { - return mixedMode; + return this.mixedMode; } isPage2() { - return pageMode == 2; + return this.pageMode == 2; } isHires() { - return hiresMode; + return this.hiresMode; } isDoubleHires() { - return doubleHiresMode; + return this.doubleHiresMode; } is80Col() { - return _80colMode; + return this._80colMode; } isAltChar() { - return altCharMode; + return this.altCharMode; + } + + buildScreen(mainData: ImageData, mixData?: ImageData | null) { + // TODO(whscullin): - figure out 80 column offset + const { x, y } = this._80colMode ? { x: 0, y: 0 } : { x: 0, y: 0 }; + + if (mixData) { + this.context.putImageData(mainData, x, y, 0, 0, 560, 160); + this.context.putImageData(mixData, x, y, 0, 160, 560, 32); + } else { + this.context.putImageData(mainData, x, y); + } + return this._canvas; } updateImage( @@ -1059,14 +1052,11 @@ export class VideoModes2D implements VideoModes { mixData?: ImageData | null, mixDirty?: Region | null ) { - if (!this._context) { - throw new Error('No 2D context'); - } let blitted = false; if (mainDirty.bottom !== -1 || (mixDirty && mixDirty.bottom !== -1)) { - const imageData = buildScreen(mainData, mixData); - this._context.drawImage( + const imageData = this.buildScreen(mainData, mixData); + this._screenContext.drawImage( imageData, 0, 0, 560, 192, this._left, this._top, 560, 384 @@ -1078,8 +1068,8 @@ export class VideoModes2D implements VideoModes { blit(altData?: ImageData) { let blitted = false; - const hgr = this._hgrs[pageMode - 1]; - const gr = this._grs[pageMode - 1]; + const hgr = this._hgrs[this.pageMode - 1]; + const gr = this._grs[this.pageMode - 1]; if (this._refreshFlag) { hgr.refresh(); @@ -1092,12 +1082,12 @@ export class VideoModes2D implements VideoModes { altData, { top: 0, left: 0, right: 560, bottom: 192 } ); - } else if (hiresMode && !textMode) { + } else if (this.hiresMode && !this.textMode) { blitted = this.updateImage( hgr.imageData, hgr.dirty, - mixedMode ? gr.imageData : null, - mixedMode ? gr.dirty : null + this.mixedMode ? gr.imageData : null, + this.mixedMode ? gr.dirty : null ); } else { blitted = this.updateImage( @@ -1114,24 +1104,26 @@ export class VideoModes2D implements VideoModes { return { grs: [this._grs[0].getState(), this._grs[1].getState()], hgrs: [this._hgrs[0].getState(), this._hgrs[1].getState()], - textMode: textMode, - mixedMode: mixedMode, - hiresMode: hiresMode, - pageMode: pageMode, - _80colMode: _80colMode, - altCharMode: altCharMode, - an3: an3 + textMode: this.textMode, + mixedMode: this.mixedMode, + hiresMode: this.hiresMode, + pageMode: this.pageMode, + _80colMode: this._80colMode, + altCharMode: this.altCharMode, + an3State: this.an3State, + flag: this.flag }; } setState(state: VideoModesState) { - textMode = state.textMode; - mixedMode = state.mixedMode; - hiresMode = state.hiresMode; - pageMode = state.pageMode; - _80colMode = state._80colMode; - altCharMode = state.altCharMode; - an3 = state.an3; + this.textMode = state.textMode; + this.mixedMode = state.mixedMode; + this.hiresMode = state.hiresMode; + this.pageMode = state.pageMode; + this._80colMode = state._80colMode; + this.altCharMode = state.altCharMode; + this.an3State = state.an3State; + this.flag = state.flag; this._grs[0].setState(state.grs[0]); this._grs[1].setState(state.grs[1]); @@ -1142,20 +1134,17 @@ export class VideoModes2D implements VideoModes { mono(on: boolean) { if (on) { - this.canvas.classList.add('mono'); + this.screen.classList.add('mono'); } else { - this.canvas.classList.remove('mono'); + this.screen.classList.remove('mono'); } - this._grs[0].mono(on); - this._grs[1].mono(on); - this._hgrs[0].mono(on); - this._hgrs[1].mono(on); + this.monoMode = on; this._refresh(); } scanlines(on: boolean) { // Can't apply scanline filter to canvas - const parent = this.canvas.parentElement; + const parent = this.screen.parentElement; if (parent) { if (on) { parent.classList.add('scanlines'); @@ -1166,6 +1155,6 @@ export class VideoModes2D implements VideoModes { } getText() { - return this._grs[pageMode - 1].getText(); + return this._grs[this.pageMode - 1].getText(); } } diff --git a/js/gl.ts b/js/gl.ts index 270dc98..9ace807 100644 --- a/js/gl.ts +++ b/js/gl.ts @@ -25,41 +25,6 @@ import { pageNo } from './videomodes'; -let textMode = true; -let mixedMode = false; -let hiresMode = false; -let pageMode: pageNo = 1; -let _80colMode = false; -let altCharMode = false; -let an3 = false; -let doubleHiresMode = false; - -const tmpCanvas = document.createElement('canvas'); -const tmpContext = tmpCanvas.getContext('2d'); - -const buildScreen = (mainData: ImageData, mixData?: ImageData | null) => { - if (!tmpContext) { - throw new Error('No 2d context'); - } - - const details = screenEmu.C.NTSC_DETAILS; - const { width, height } = details.imageSize; - const { x, y } = _80colMode ? details.topLeft80Col : details.topLeft; - - tmpCanvas.width = width; - tmpCanvas.height = height; - tmpContext.fillStyle = 'rgba(0,0,0,1)'; - tmpContext.fillRect(0, 0, width, height); - - if (mixData) { - tmpContext.putImageData(mainData, x, y, 0, 0, 560, 160); - tmpContext.putImageData(mixData, x, y, 0, 160, 560, 32); - } else { - tmpContext.putImageData(mainData, x, y); - } - return tmpContext.getImageData(0, 0, width, height); -}; - // Color constants const whiteCol: Color = [255, 255, 255]; const blackCol: Color = [0, 0, 0]; @@ -84,21 +49,23 @@ export class LoresPageGL implements LoresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; private _blink = false; dirty: Region = {...notDirty} imageData: ImageData; - constructor(private page: number, + constructor( + private vm: VideoModes, + private page: pageNo, private readonly charset: rom, - private readonly e: boolean) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private readonly e: boolean + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x4); this._buffer[1] = allocMemPages(0x4); + + this.vm.setLoresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -118,7 +85,7 @@ export class LoresPageGL implements LoresPage { private _checkInverse(val: byte) { let inverse = false; if (this.e) { - if (!_80colMode && !altCharMode) { + if (!this.vm._80colMode && !this.vm.altCharMode) { inverse = ((val & 0xc0) == 0x40) && this._blink; } } else { @@ -188,14 +155,14 @@ export class LoresPageGL implements LoresPage { x += 14; if (x > this.dirty.right) { this.dirty.right = x; } - if (textMode || hiresMode || (mixedMode && row > 19)) { - if (_80colMode) { + if (this.vm.textMode || this.vm.hiresMode || (this.vm.mixedMode && row > 19)) { + if (this.vm._80colMode) { const inverse = this._checkInverse(val); fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -219,7 +186,7 @@ export class LoresPageGL implements LoresPage { fore = inverse ? blackCol : whiteCol; back = inverse ? whiteCol : blackCol; - if (!altCharMode) { + if (!this.vm.altCharMode) { val = (val >= 0x40 && val < 0x80) ? val - 0x40 : val; } @@ -251,7 +218,7 @@ export class LoresPageGL implements LoresPage { } } } else { - if (_80colMode && !an3) { + if (this.vm._80colMode && !this.vm.an3State) { let offset = (col * 14 + (bank ? 0 : 1) * 7 + row * 560 * 8) * 4; for (let jdx = 0; jdx < 8; jdx++) { let b = (jdx < 4) ? (val & 0x0f) : (val >> 4); @@ -295,17 +262,13 @@ export class LoresPageGL implements LoresPage { this._refreshing = true; for (let idx = 0; idx < 0x400; idx++, addr++) { this._write(addr >> 8, addr & 0xff, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(addr >> 8, addr & 0xff, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - blink() { let addr = 0x400 * this.page; this._refreshing = true; @@ -338,8 +301,6 @@ export class LoresPageGL implements LoresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -348,8 +309,6 @@ export class LoresPageGL implements LoresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -380,7 +339,7 @@ export class LoresPageGL implements LoresPage { for (row = 0; row < 24; row++) { base = this.rowToBase(row); line = ''; - if (this.e && _80colMode) { + if (this.e && this.vm._80colMode) { for (col = 0; col < 80; col++) { charCode = this.mapCharCode(this._buffer[1 - col % 2][base + Math.floor(col / 2)]); line += String.fromCharCode(charCode); @@ -410,16 +369,17 @@ export class HiresPageGL implements HiresPage { private _buffer: memory[] = []; private _refreshing = false; - private _monoMode = false; constructor( - private page: number) { - this.imageData = new ImageData(560, 192); - for (let idx = 0; idx < 560 * 192 * 4; idx++) { - this.imageData.data[idx] = 0xff; - } + private vm: VideoModes, + private page: pageNo, + ) { + this.imageData = this.vm.context.createImageData(560, 192); + this.imageData.data.fill(0xff); this._buffer[0] = allocMemPages(0x20); this._buffer[1] = allocMemPages(0x20); + + this.vm.setHiresPage(page, this); } private _drawPixel(data: Uint8ClampedArray, off: number, color: Color) { @@ -486,7 +446,7 @@ export class HiresPageGL implements HiresPage { rowb = base >> 10; const data = this.imageData.data; - if ((rowa < 24) && (col < 40) && hiresMode) { + if ((rowa < 24) && (col < 40) && this.vm.hiresMode) { let y = rowa << 3 | rowb; if (y < this.dirty.top) { this.dirty.top = y; } y += 1; @@ -497,7 +457,7 @@ export class HiresPageGL implements HiresPage { if (x > this.dirty.right) { this.dirty.right = x; } const dy = rowa << 3 | rowb; - if (doubleHiresMode) { + if (this.vm.doubleHiresMode) { const dx = col * 14 + (bank ? 0 : 7); let offset = dx * 4 + dy * 280 * 4 * 2; @@ -554,17 +514,13 @@ export class HiresPageGL implements HiresPage { const page = addr >> 8; const off = addr & 0xff; this._write(page, off, this._buffer[0][idx], 0); - if (_80colMode) { + if (this.vm._80colMode) { this._write(page, off, this._buffer[1][idx], 1); } } this._refreshing = false; } - mono(on: boolean) { - this._monoMode = on; - } - start() { return this._start(); } @@ -583,8 +539,6 @@ export class HiresPageGL implements HiresPage { getState(): GraphicsState { return { - page: this.page, - mono: this._monoMode, buffer: [ new Uint8Array(this._buffer[0]), new Uint8Array(this._buffer[1]), @@ -593,8 +547,6 @@ export class HiresPageGL implements HiresPage { } setState(state: GraphicsState) { - this.page = state.page; - this._monoMode = state.mono; this._buffer[0] = new Uint8Array(state.buffer[0]); this._buffer[1] = new Uint8Array(state.buffer[1]); @@ -603,26 +555,44 @@ export class HiresPageGL implements HiresPage { } export class VideoModesGL implements VideoModes { - private _grs: LoresPage[]; - private _hgrs: HiresPage[]; - private _sv: any; + private _grs: LoresPage[] = []; + private _hgrs: HiresPage[] = []; + private _sv: screenEmu.ScreenView; private _displayConfig: screenEmu.DisplayConfiguration; - private _monoMode: boolean = false; private _scanlines: boolean = false; private _refreshFlag: boolean = true; + private _canvas: HTMLCanvasElement; public ready: Promise + public textMode: boolean; + public mixedMode: boolean; + public hiresMode: boolean; + public pageMode: pageNo; + public _80colMode: boolean; + public altCharMode: boolean; + public an3State: boolean; + public doubleHiresMode: boolean; + + public flag = 0; + public monoMode: boolean = false; + + public context: CanvasRenderingContext2D; + constructor( - gr: LoresPage, - hgr: HiresPage, - gr2: LoresPage, - hgr2: HiresPage, - private canvas: HTMLCanvasElement, - private e: boolean) { - this._grs = [gr, gr2]; - this._hgrs = [hgr, hgr2]; - this._sv = new screenEmu.ScreenView(this.canvas); + private screen: HTMLCanvasElement, + private e: boolean + ) { + this._canvas = document.createElement('canvas'); + const context = this._canvas.getContext('2d'); + if (!context) { + throw new Error('no 2d context'); + } + const { width, height } = screenEmu.C.NTSC_DETAILS.imageSize; + this._canvas.width = width; + this._canvas.height = height; + this.context = context; + this._sv = new screenEmu.ScreenView(this.screen); this.ready = this.init(); } @@ -630,15 +600,13 @@ export class VideoModesGL implements VideoModes { async init() { await this._sv.initOpenGL(); - (window as any)._sv = this._sv; - this._displayConfig = this.defaultMonitor(); this._sv.displayConfiguration = this._displayConfig; } private defaultMonitor(): screenEmu.DisplayConfiguration { const config = new screenEmu.DisplayConfiguration(); - config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height); + config.displayResolution = new screenEmu.Size(this.screen.width, this.screen.height); config.displayScanlineLevel = 0.5; config.videoWhiteOnly = true; config.videoSaturation = 0.8; @@ -651,15 +619,15 @@ export class VideoModesGL implements VideoModes { private monitorII(): screenEmu.DisplayConfiguration { // Values taken from openemulator/libemulation/res/library/Monitors/Apple Monitor II.xml const config = new screenEmu.DisplayConfiguration(); - config.displayResolution = new screenEmu.Size(this.canvas.width, this.canvas.height); + config.displayResolution = new screenEmu.Size(this.screen.width, this.screen.height); config.videoDecoder = 'CANVAS_MONOCHROME'; config.videoBrightness = 0.15; config.videoContrast = 0.8; config.videoSaturation = 1.45; config.videoHue = 0.27; - config.videoCenter = new screenEmu.Point(0, 0); - config.videoSize = new screenEmu.Size(1.05, 1.05); - config.videoBandwidth = 6000000; + config.videoCenter = new screenEmu.Point(0.01, 0.02); + config.videoSize = new screenEmu.Size(1.25, 1.15); + config.videoBandwidth = 9000000; config.displayBarrel = 0.1; config.displayScanlineLevel = 0.5; config.displayCenterLighting = 0.5; @@ -668,12 +636,12 @@ export class VideoModesGL implements VideoModes { } private _refresh() { - doubleHiresMode = !an3 && hiresMode && _80colMode; + this.doubleHiresMode = !this.an3State && this.hiresMode && this._80colMode; this._refreshFlag = true; if (this._displayConfig) { - this._displayConfig.videoWhiteOnly = textMode || this._monoMode; + this._displayConfig.videoWhiteOnly = this.textMode || this.monoMode; this._displayConfig.displayScanlineLevel = this._scanlines ? 0.5 : 0; this._sv.displayConfiguration = this._displayConfig; } @@ -684,22 +652,30 @@ export class VideoModesGL implements VideoModes { } reset() { - textMode = true; - mixedMode = false; - hiresMode = true; - pageMode = 1; + this.textMode = true; + this.mixedMode = false; + this.hiresMode = true; + this.pageMode = 1; - _80colMode = false; - altCharMode = false; + this._80colMode = false; + this.altCharMode = false; - an3 = true; + this.an3State = true; this._refresh(); } + setLoresPage(page: pageNo, lores: LoresPage) { + this._grs[page - 1] = lores; + } + + setHiresPage(page: pageNo, hires: HiresPage) { + this._hgrs[page - 1] = hires; + } + text(on: boolean) { - const old = textMode; - textMode = on; + const old = this.textMode; + this.textMode = on; if (old != on) { this._refresh(); @@ -709,27 +685,27 @@ export class VideoModesGL implements VideoModes { _80col(on: boolean) { if (!this.e) { return; } - const old = _80colMode; - _80colMode = on; + const old = this._80colMode; + this._80colMode = on; if (old != on) { this._refresh(); } } - altchar(on: boolean) { + altChar(on: boolean) { if (!this.e) { return; } - const old = altCharMode; - altCharMode = on; + const old = this.altCharMode; + this.altCharMode = on; if (old != on) { this._refresh(); } } hires(on: boolean) { - const old = hiresMode; - hiresMode = on; + const old = this.hiresMode; + this.hiresMode = on; if (old != on) { this._refresh(); @@ -739,8 +715,8 @@ export class VideoModesGL implements VideoModes { an3(on: boolean) { if (!this.e) { return; } - const old = an3; - an3 = on; + const old = this.an3State; + this.an3State = on; if (old != on) { this._refresh(); @@ -752,47 +728,47 @@ export class VideoModesGL implements VideoModes { } mixed(on: boolean) { - const old = mixedMode; - mixedMode = on; + const old = this.mixedMode; + this.mixedMode = on; if (old != on) { this._refresh(); } } page(pageNo: pageNo) { - const old = pageMode; - pageMode = pageNo; + const old = this.pageMode; + this.pageMode = pageNo; if (old != pageNo) { this._refresh(); } } isText() { - return textMode; + return this.textMode; } isMixed() { - return mixedMode; + return this.mixedMode; } isPage2() { - return pageMode == 2; + return this.pageMode == 2; } isHires() { - return hiresMode; + return this.hiresMode; } isDoubleHires() { - return doubleHiresMode; + return this.doubleHiresMode; } is80Col() { - return _80colMode; + return this._80colMode; } isAltChar() { - return altCharMode; + return this.altCharMode; } updateImage( @@ -803,7 +779,7 @@ export class VideoModesGL implements VideoModes { ) { let blitted = false; if (mainDirty.bottom !== -1 || (mixDirty && mixDirty.bottom !== -1)) { - const imageData = buildScreen(mainData, mixData); + const imageData = this.buildScreen(mainData, mixData); const imageInfo = new screenEmu.ImageInfo(imageData); this._sv.image = imageInfo; blitted = true; @@ -812,10 +788,24 @@ export class VideoModesGL implements VideoModes { return blitted; } + buildScreen(mainData: ImageData, mixData?: ImageData | null) { + const details = screenEmu.C.NTSC_DETAILS; + const { width, height } = details.imageSize; + const { x, y } = this._80colMode ? details.topLeft80Col : details.topLeft; + + if (mixData) { + this.context.putImageData(mainData, x, y, 0, 0, 560, 160); + this.context.putImageData(mixData, x, y, 0, 160, 560, 32); + } else { + this.context.putImageData(mainData, x, y); + } + return this.context.getImageData(0, 0, width, height); + } + blit(altData?: ImageData) { let blitted = false; - const hgr = this._hgrs[pageMode - 1]; - const gr = this._grs[pageMode - 1]; + const hgr = this._hgrs[this.pageMode - 1]; + const gr = this._grs[this.pageMode - 1]; if (this._refreshFlag) { hgr.refresh(); @@ -828,12 +818,12 @@ export class VideoModesGL implements VideoModes { altData, { top: 0, left: 0, right: 560, bottom: 192 } ); - } else if (hiresMode && !textMode) { + } else if (this.hiresMode && !this.textMode) { blitted = this.updateImage( hgr.imageData, hgr.dirty, - mixedMode ? gr.imageData : null, - mixedMode ? gr.dirty : null + this.mixedMode ? gr.imageData : null, + this.mixedMode ? gr.dirty : null ); } else { blitted = this.updateImage( @@ -850,24 +840,25 @@ export class VideoModesGL implements VideoModes { return { grs: [this._grs[0].getState(), this._grs[1].getState()], hgrs: [this._hgrs[0].getState(), this._hgrs[1].getState()], - textMode: textMode, - mixedMode: mixedMode, - hiresMode: hiresMode, - pageMode: pageMode, - _80colMode: _80colMode, - altCharMode: altCharMode, - an3: an3 + textMode: this.textMode, + mixedMode: this.mixedMode, + hiresMode: this.hiresMode, + pageMode: this.pageMode, + _80colMode: this._80colMode, + altCharMode: this.altCharMode, + an3State: this.an3State, + flag: 0 }; } setState(state: VideoModesState) { - textMode = state.textMode; - mixedMode = state.mixedMode; - hiresMode = state.hiresMode; - pageMode = state.pageMode; - _80colMode = state._80colMode; - altCharMode = state.altCharMode; - an3 = state.an3; + this.textMode = state.textMode; + this.mixedMode = state.mixedMode; + this.hiresMode = state.hiresMode; + this.pageMode = state.pageMode; + this._80colMode = state._80colMode; + this.altCharMode = state.altCharMode; + this.an3State = state.an3State; this._grs[0].setState(state.grs[0]); this._grs[1].setState(state.grs[1]); @@ -877,12 +868,7 @@ export class VideoModesGL implements VideoModes { } mono(on: boolean) { - this._grs[0].mono(on); - this._grs[1].mono(on); - this._hgrs[0].mono(on); - this._hgrs[1].mono(on); - - this._monoMode = on; + this.monoMode = on; this._displayConfig = on ? this.monitorII() : this.defaultMonitor(); this._refresh(); } @@ -893,6 +879,6 @@ export class VideoModesGL implements VideoModes { } getText() { - return this._grs[pageMode - 1].getText(); + return this._grs[this.pageMode - 1].getText(); } } diff --git a/js/mmu.ts b/js/mmu.ts index 5a70005..f0d1ed3 100644 --- a/js/mmu.ts +++ b/js/mmu.ts @@ -519,11 +519,11 @@ export default class MMU implements Memory, Restorable { break; case LOC.CLRALTCH: this._debug('Alt Char off'); - this.vm.altchar(false); + this.vm.altChar(false); break; case LOC.SETALTCH: this._debug('Alt Char on'); - this.vm.altchar(true); + this.vm.altChar(true); break; } this._updateBanks(); diff --git a/js/ui/options_modal.ts b/js/ui/options_modal.ts index 4e38ca5..9ec4768 100644 --- a/js/ui/options_modal.ts +++ b/js/ui/options_modal.ts @@ -38,10 +38,6 @@ export class OptionsModal { private handlers: Record = {} private sections: OptionSection[] = [] - construct() { - this.prefs = new Prefs(); - } - addOptions(handler: OptionHandler) { const sections = handler.getOptions(); for (const section of sections) { diff --git a/js/videomodes.ts b/js/videomodes.ts index b141aa8..883223d 100644 --- a/js/videomodes.ts +++ b/js/videomodes.ts @@ -1,4 +1,4 @@ -import { MemoryPages, Restorable, byte, memory } from './types'; +import { MemoryPages, Restorable, memory } from './types'; export type bank = 0 | 1; export type pageNo = 1 | 2; @@ -11,8 +11,6 @@ export interface Region { } export interface GraphicsState { - page: byte; - mono: boolean; buffer: memory[]; } @@ -25,7 +23,8 @@ export interface VideoModesState { pageMode: pageNo, _80colMode: boolean, altCharMode: boolean, - an3: boolean, + an3State: boolean, + flag: number, } export interface VideoPage extends MemoryPages, Restorable { @@ -35,7 +34,6 @@ export interface VideoPage extends MemoryPages, Restorable { bank0(): MemoryPages bank1(): MemoryPages - mono: (on: boolean) => void refresh: () => void } @@ -48,14 +46,32 @@ export interface HiresPage extends VideoPage { } export interface VideoModes extends Restorable { + textMode: boolean + mixedMode: boolean + hiresMode: boolean + pageMode: pageNo + _80colMode: boolean + altCharMode: boolean + an3State: boolean + doubleHiresMode: boolean + + flag: number + monoMode: boolean + + context: CanvasRenderingContext2D; + page(pageNo: number): void blit(altData?: ImageData): boolean reset(): void + setLoresPage(page: pageNo, lores: LoresPage): void + setHiresPage(page: pageNo, lores: HiresPage): void + _80col(on: boolean): void - altchar(on: boolean): void + altChar(on: boolean): void + an3(on: boolean): void doubleHires(on: boolean): void hires(on: boolean): void mixed(on: boolean): void diff --git a/package-lock.json b/package-lock.json index b1de460..3da9af4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -8,7 +8,7 @@ "version": "0.0.1", "license": "MIT", "dependencies": { - "apple2shader": "0.0.1", + "apple2shader": "0.0.3", "micromodal": "^0.4.2" }, "devDependencies": { @@ -17,14 +17,17 @@ "@testing-library/dom": "^7.30.3", "@testing-library/user-event": "^13.1.3", "@types/jest": "^26.0.14", + "@types/jest-image-snapshot": "^4.3.0", "@types/micromodal": "^0.3.2", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "ajv": "^6.12.0", "babel-jest": "^26.6.3", + "canvas": "^2.7.0", "eslint": "^7.22.0", "file-loader": "^6.0.0", "jest": "^26.6.3", + "jest-image-snapshot": "^4.5.0", "node-forge": "^0.10.0", "raw-loader": "^4.0.0", "ts-jest": "^26.5.0", @@ -2282,6 +2285,17 @@ "pretty-format": "^26.0.0" } }, + "node_modules/@types/jest-image-snapshot": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-4.3.0.tgz", + "integrity": "sha512-gb6zF1ICfvzBsQYMTq2qFhhiI46Cab/t5PtLK4Z3mpbyQoyKI2HgCFDi71iE7rjs6TrIPnsf2GXw+mnGvZSgrA==", + "dev": true, + "dependencies": { + "@types/jest": "*", + "@types/pixelmatch": "*", + "ssim.js": "^3.1.1" + } + }, "node_modules/@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -2306,6 +2320,15 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "node_modules/@types/pixelmatch": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.3.tgz", + "integrity": "sha512-p+nAQVYK/DUx7+s1Xyu9dqAg0gobf7VmJ+iDA4lljg1o4XRgQHr7R2h1NwFt3gdNOZiftxWB11+0TuZqXYf19w==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/prettier": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", @@ -2726,6 +2749,12 @@ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, + "node_modules/abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "node_modules/accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -2888,9 +2917,25 @@ } }, "node_modules/apple2shader": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.1.tgz", - "integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.3.tgz", + "integrity": "sha512-7nAffvWdGp4FTqpDxgjslN4XqaO9FOUCKnhJ4IPQXGdyiCWIZvZ+E5gXsb+BKt0L+Fs3E5Kcn+5vOamSUSU0xA==" + }, + "node_modules/aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "node_modules/are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "dependencies": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "node_modules/argparse": { "version": "1.0.10", @@ -3599,6 +3644,21 @@ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", "dev": true }, + "node_modules/canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "dev": true, + "hasInstallScript": true, + "dependencies": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -3661,6 +3721,12 @@ "fsevents": "~2.3.1" } }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "node_modules/chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -3744,6 +3810,15 @@ "node": ">= 0.12.0" } }, + "node_modules/code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -3874,6 +3949,12 @@ "node": ">=0.8" } }, + "node_modules/console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "node_modules/content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -4084,6 +4165,18 @@ "node": ">=0.10" } }, + "node_modules/decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "dependencies": { + "mimic-response": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -4101,6 +4194,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -4286,6 +4388,12 @@ "node": ">=0.4.0" } }, + "node_modules/delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, "node_modules/depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -4301,6 +4409,18 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "node_modules/detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true, + "bin": { + "detect-libc": "bin/detect-libc.js" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -5740,6 +5860,15 @@ "node": ">= 0.6" } }, + "node_modules/fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "dependencies": { + "minipass": "^2.6.0" + } + }, "node_modules/fs-monkey": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", @@ -5778,6 +5907,69 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "node_modules/gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "dependencies": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + } + }, + "node_modules/gauge/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "dependencies": { + "number-is-nan": "^1.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "dependencies": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/gauge/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -5819,6 +6011,15 @@ "node": ">=8.0.0" } }, + "node_modules/get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true, + "engines": { + "node": ">=0.12.0" + } + }, "node_modules/get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -5928,6 +6129,12 @@ "node": ">= 4" } }, + "node_modules/glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=", + "dev": true + }, "node_modules/graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -5982,6 +6189,27 @@ "node": ">= 0.4.0" } }, + "node_modules/has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/has-ansi/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -6012,6 +6240,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "node_modules/has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -6076,9 +6310,9 @@ } }, "node_modules/hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "node_modules/hpack.js": { @@ -6226,6 +6460,15 @@ "node": ">= 4" } }, + "node_modules/ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dev": true, + "dependencies": { + "minimatch": "^3.0.4" + } + }, "node_modules/import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -6301,6 +6544,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "node_modules/internal-ip": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", @@ -7408,6 +7657,96 @@ "fsevents": "^2.1.2" } }, + "node_modules/jest-image-snapshot": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-4.5.0.tgz", + "integrity": "sha512-9Q1xyjyUsepNgn6/DaMnT4maaCSi3yaDp/xq1bnsOTk/tR3utygOTLOFOwztNrrkWX7HIXcm5PcHC2Mc5iBwUw==", + "dev": true, + "dependencies": { + "chalk": "^1.1.3", + "get-stdin": "^5.0.1", + "glur": "^1.1.2", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "pixelmatch": "^5.1.0", + "pngjs": "^3.4.0", + "rimraf": "^2.6.2", + "ssim.js": "^3.1.1" + }, + "engines": { + "node": ">= 10.14.2" + }, + "peerDependencies": { + "jest": ">=20 <=26" + } + }, + "node_modules/jest-image-snapshot/node_modules/ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "dependencies": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/jest-image-snapshot/node_modules/strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "dependencies": { + "ansi-regex": "^2.0.0" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/jest-image-snapshot/node_modules/supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, "node_modules/jest-jasmine2": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", @@ -8984,6 +9323,18 @@ "node": ">=6" } }, + "node_modules/mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -9008,6 +9359,31 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "node_modules/minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "dependencies": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + } + }, + "node_modules/minipass/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, + "node_modules/minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "dependencies": { + "minipass": "^2.9.0" + } + }, "node_modules/mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -9070,6 +9446,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "node_modules/nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true + }, "node_modules/nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -9174,6 +9556,32 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "node_modules/needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dev": true, + "dependencies": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "bin": { + "needle": "bin/needle" + }, + "engines": { + "node": ">= 4.4.x" + } + }, + "node_modules/needle/node_modules/debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "dependencies": { + "ms": "^2.1.1" + } + }, "node_modules/negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -9266,12 +9674,68 @@ "node": ">= 8" } }, + "node_modules/node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "deprecated": "Please upgrade to @mapbox/node-pre-gyp: the non-scoped node-pre-gyp package is deprecated and only the @mapbox scoped package will recieve updates in the future", + "dev": true, + "dependencies": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "bin": { + "node-pre-gyp": "bin/node-pre-gyp" + } + }, + "node_modules/node-pre-gyp/node_modules/rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + } + }, + "node_modules/node-pre-gyp/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, "node_modules/node-releases": { "version": "1.1.71", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "node_modules/nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "dependencies": { + "abbrev": "1", + "osenv": "^0.1.4" + }, + "bin": { + "nopt": "bin/nopt.js" + } + }, "node_modules/normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -9302,6 +9766,32 @@ "node": ">=0.10.0" } }, + "node_modules/npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "dependencies": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "node_modules/npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "node_modules/npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "dependencies": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "node_modules/npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -9323,6 +9813,27 @@ "node": ">=8" } }, + "node_modules/npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "dependencies": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "node_modules/number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -9338,6 +9849,15 @@ "node": "*" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -9533,6 +10053,34 @@ "url-parse": "^1.4.3" } }, + "node_modules/os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "dependencies": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "node_modules/p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -9786,6 +10334,27 @@ "node": ">= 6" } }, + "node_modules/pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "dependencies": { + "pngjs": "^4.0.1" + }, + "bin": { + "pixelmatch": "bin/pixelmatch" + } + }, + "node_modules/pixelmatch/node_modules/pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true, + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -9798,6 +10367,15 @@ "node": ">=8" } }, + "node_modules/pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true, + "engines": { + "node": ">=4.0.0" + } + }, "node_modules/portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -10065,6 +10643,30 @@ "webpack": "^4.0.0 || ^5.0.0" } }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/rc/node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -10863,6 +11465,12 @@ "node": ">=0.10.0" } }, + "node_modules/sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "node_modules/saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -11119,6 +11727,37 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dev": true, + "dependencies": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "node_modules/sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -11546,6 +12185,12 @@ "node": ">=0.10.0" } }, + "node_modules/ssim.js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", + "integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==", + "dev": true + }, "node_modules/stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -11809,6 +12454,30 @@ "node": ">=6" } }, + "node_modules/tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "dependencies": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "engines": { + "node": ">=4.5" + } + }, + "node_modules/tar/node_modules/yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + }, "node_modules/terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -13181,6 +13850,58 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "dependencies": { + "string-width": "^1.0.2 || 2" + } + }, + "node_modules/wide-align/node_modules/ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "dependencies": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/wide-align/node_modules/strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "dependencies": { + "ansi-regex": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", @@ -15193,6 +15914,17 @@ "pretty-format": "^26.0.0" } }, + "@types/jest-image-snapshot": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/@types/jest-image-snapshot/-/jest-image-snapshot-4.3.0.tgz", + "integrity": "sha512-gb6zF1ICfvzBsQYMTq2qFhhiI46Cab/t5PtLK4Z3mpbyQoyKI2HgCFDi71iE7rjs6TrIPnsf2GXw+mnGvZSgrA==", + "dev": true, + "requires": { + "@types/jest": "*", + "@types/pixelmatch": "*", + "ssim.js": "^3.1.1" + } + }, "@types/json-schema": { "version": "7.0.7", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", @@ -15217,6 +15949,15 @@ "integrity": "sha512-f5j5b/Gf71L+dbqxIpQ4Z2WlmI/mPJ0fOkGGmFgtb6sAu97EPczzbS3/tJKxmcYDj55OX6ssqwDAWOHIYDRDGA==", "dev": true }, + "@types/pixelmatch": { + "version": "5.2.3", + "resolved": "https://registry.npmjs.org/@types/pixelmatch/-/pixelmatch-5.2.3.tgz", + "integrity": "sha512-p+nAQVYK/DUx7+s1Xyu9dqAg0gobf7VmJ+iDA4lljg1o4XRgQHr7R2h1NwFt3gdNOZiftxWB11+0TuZqXYf19w==", + "dev": true, + "requires": { + "@types/node": "*" + } + }, "@types/prettier": { "version": "2.2.3", "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.2.3.tgz", @@ -15542,6 +16283,12 @@ "integrity": "sha512-9IK9EadsbHo6jLWIpxpR6pL0sazTXV6+SQv25ZB+F7Bj9mJNaOc4nCRabwd5M/JwmUa8idz6Eci6eKfJryPs6Q==", "dev": true }, + "abbrev": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", + "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", + "dev": true + }, "accepts": { "version": "1.3.7", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.7.tgz", @@ -15657,9 +16404,25 @@ } }, "apple2shader": { - "version": "0.0.1", - "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.1.tgz", - "integrity": "sha512-RPexfw95Jk34UUHteg66rCG6GpNXySl2dD7P/WqyhJZ+7J5U9AJrIB/AxJMaBlHTyI8SZTlHvGX4r7tNCC6+qw==" + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/apple2shader/-/apple2shader-0.0.3.tgz", + "integrity": "sha512-7nAffvWdGp4FTqpDxgjslN4XqaO9FOUCKnhJ4IPQXGdyiCWIZvZ+E5gXsb+BKt0L+Fs3E5Kcn+5vOamSUSU0xA==" + }, + "aproba": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/aproba/-/aproba-1.2.0.tgz", + "integrity": "sha512-Y9J6ZjXtoYh8RnXVCMOU/ttDmk1aBjunq9vO0ta5x85WDQiQfUF9sIPBITdbiiIVcBo03Hi3jMxigBtsddlXRw==", + "dev": true + }, + "are-we-there-yet": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-1.1.5.tgz", + "integrity": "sha512-5hYdAkZlcG8tOLujVDTgCT+uPX0VnpAH28gWsLfzpXYm7wP6mp5Q/gYyR7YQ0cKVJcXJnl3j2kpBan13PtQf6w==", + "dev": true, + "requires": { + "delegates": "^1.0.0", + "readable-stream": "^2.0.6" + } }, "argparse": { "version": "1.0.10", @@ -16220,6 +16983,17 @@ "integrity": "sha512-JUdjWpcxfJ9IPamy2f5JaRDCaqJOxDzOSKtbdx4rH9VivMd1vIzoPumsJa9LoMIi4Fx2BV2KZOxWhNkBjaYivQ==", "dev": true }, + "canvas": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/canvas/-/canvas-2.7.0.tgz", + "integrity": "sha512-pzCxtkHb+5su5MQjTtepMDlIOtaXo277x0C0u3nMOxtkhTyQ+h2yNKhlROAaDllWgRyePAUitC08sXw26Eb6aw==", + "dev": true, + "requires": { + "nan": "^2.14.0", + "node-pre-gyp": "^0.15.0", + "simple-get": "^3.0.3" + } + }, "capture-exit": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/capture-exit/-/capture-exit-2.0.0.tgz", @@ -16268,6 +17042,12 @@ "readdirp": "~3.5.0" } }, + "chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "dev": true + }, "chrome-trace-event": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.2.tgz", @@ -16335,6 +17115,12 @@ "integrity": "sha1-bqa989hTrlTMuOR7+gvz+QMfsYQ=", "dev": true }, + "code-point-at": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/code-point-at/-/code-point-at-1.1.0.tgz", + "integrity": "sha1-DQcLTQQ6W+ozovGkDi7bPZpMz3c=", + "dev": true + }, "collect-v8-coverage": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/collect-v8-coverage/-/collect-v8-coverage-1.0.1.tgz", @@ -16452,6 +17238,12 @@ "integrity": "sha512-e54B99q/OUoH64zYYRf3HBP5z24G38h5D3qXu23JGRoigpX5Ss4r9ZnDk3g0Z8uQC2x2lPaJ+UlWBc1ZWBWdLg==", "dev": true }, + "console-control-strings": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz", + "integrity": "sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4=", + "dev": true + }, "content-disposition": { "version": "0.5.3", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.3.tgz", @@ -16615,6 +17407,15 @@ "integrity": "sha1-6zkTMzRYd1y4TNGh+uBiEGu4dUU=", "dev": true }, + "decompress-response": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz", + "integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==", + "dev": true, + "requires": { + "mimic-response": "^2.0.0" + } + }, "deep-equal": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-1.1.1.tgz", @@ -16629,6 +17430,12 @@ "regexp.prototype.flags": "^1.2.0" } }, + "deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "dev": true + }, "deep-is": { "version": "0.1.3", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.3.tgz", @@ -16762,6 +17569,12 @@ "integrity": "sha1-3zrhmayt+31ECqrgsp4icrJOxhk=", "dev": true }, + "delegates": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz", + "integrity": "sha1-hMbhWbgZBP3KWaDvRM2HDTElD5o=", + "dev": true + }, "depd": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", @@ -16774,6 +17587,12 @@ "integrity": "sha1-l4hXRCxEdJ5CBmE+N5RiBYJqvYA=", "dev": true }, + "detect-libc": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-1.0.3.tgz", + "integrity": "sha1-+hN8S9aY7fVc1c0CrFWfkaTEups=", + "dev": true + }, "detect-newline": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/detect-newline/-/detect-newline-3.1.0.tgz", @@ -17890,6 +18709,15 @@ "integrity": "sha1-PYyt2Q2XZWn6g1qx+OSyOhBWBac=", "dev": true }, + "fs-minipass": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-1.2.7.tgz", + "integrity": "sha512-GWSSJGFy4e9GUeCcbIkED+bgAoFyj7XF1mV8rma3QW4NIqX9Kyx79N/PF61H5udOV3aY1IaMLs6pGbH71nlCTA==", + "dev": true, + "requires": { + "minipass": "^2.6.0" + } + }, "fs-monkey": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/fs-monkey/-/fs-monkey-1.0.1.tgz", @@ -17921,6 +18749,59 @@ "integrity": "sha1-GwqzvVU7Kg1jmdKcDj6gslIHgyc=", "dev": true }, + "gauge": { + "version": "2.7.4", + "resolved": "https://registry.npmjs.org/gauge/-/gauge-2.7.4.tgz", + "integrity": "sha1-LANAXHU4w51+s3sxcCLjJfsBi/c=", + "dev": true, + "requires": { + "aproba": "^1.0.3", + "console-control-strings": "^1.0.0", + "has-unicode": "^2.0.0", + "object-assign": "^4.1.0", + "signal-exit": "^3.0.0", + "string-width": "^1.0.1", + "strip-ansi": "^3.0.1", + "wide-align": "^1.1.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-1.0.0.tgz", + "integrity": "sha1-754xOG8DGn8NZDr4L95QxFfvAMs=", + "dev": true, + "requires": { + "number-is-nan": "^1.0.0" + } + }, + "string-width": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-1.0.2.tgz", + "integrity": "sha1-EYvfW4zcUaKn5w0hHgfisLmxB9M=", + "dev": true, + "requires": { + "code-point-at": "^1.0.0", + "is-fullwidth-code-point": "^1.0.0", + "strip-ansi": "^3.0.0" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + } + } + }, "gensync": { "version": "1.0.0-beta.2", "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", @@ -17950,6 +18831,12 @@ "integrity": "sha512-pjzuKtY64GYfWizNAJ0fr9VqttZkNiK2iS430LtIHzjBEr6bX8Am2zm4sW4Ro5wjWW5cAlRL1qAMTcXbjNAO2Q==", "dev": true }, + "get-stdin": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-5.0.1.tgz", + "integrity": "sha1-Ei4WFZHiH/TFJTAwVpPyDmOTo5g=", + "dev": true + }, "get-stream": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz", @@ -18031,6 +18918,12 @@ } } }, + "glur": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/glur/-/glur-1.1.2.tgz", + "integrity": "sha1-8g6jbbEDv8KSNDkh8fkeg8NGdok=", + "dev": true + }, "graceful-fs": { "version": "4.2.6", "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", @@ -18075,6 +18968,23 @@ "function-bind": "^1.1.1" } }, + "has-ansi": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/has-ansi/-/has-ansi-2.0.0.tgz", + "integrity": "sha1-NPUEnOHs3ysGSa8+8k5F7TVBbZE=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + } + } + }, "has-bigints": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.1.tgz", @@ -18093,6 +19003,12 @@ "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", "dev": true }, + "has-unicode": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz", + "integrity": "sha1-4Ob+aijPUROIVeCG0Wkedx3iqLk=", + "dev": true + }, "has-value": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/has-value/-/has-value-1.0.0.tgz", @@ -18146,9 +19062,9 @@ } }, "hosted-git-info": { - "version": "2.8.8", - "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.8.tgz", - "integrity": "sha512-f/wzC2QaWBs7t9IYqB4T3sR1xviIViXJRJTWBlx2Gf3g0Xi5vI7Yy4koXQ1c9OYDGHN9sBy1DQ2AB8fqZBWhUg==", + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz", + "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==", "dev": true }, "hpack.js": { @@ -18273,6 +19189,15 @@ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==", "dev": true }, + "ignore-walk": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/ignore-walk/-/ignore-walk-3.0.4.tgz", + "integrity": "sha512-PY6Ii8o1jMRA1z4F2hRkH/xN59ox43DavKvD3oDpfurRlOJyAHpifIwpbdv1n4jt4ov0jSpw3kQ4GhJnpBL6WQ==", + "dev": true, + "requires": { + "minimatch": "^3.0.4" + } + }, "import-fresh": { "version": "3.3.0", "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", @@ -18329,6 +19254,12 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "dev": true }, + "ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "dev": true + }, "internal-ip": { "version": "6.2.0", "resolved": "https://registry.npmjs.org/internal-ip/-/internal-ip-6.2.0.tgz", @@ -19132,6 +20063,74 @@ "walker": "^1.0.7" } }, + "jest-image-snapshot": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/jest-image-snapshot/-/jest-image-snapshot-4.5.0.tgz", + "integrity": "sha512-9Q1xyjyUsepNgn6/DaMnT4maaCSi3yaDp/xq1bnsOTk/tR3utygOTLOFOwztNrrkWX7HIXcm5PcHC2Mc5iBwUw==", + "dev": true, + "requires": { + "chalk": "^1.1.3", + "get-stdin": "^5.0.1", + "glur": "^1.1.2", + "lodash": "^4.17.4", + "mkdirp": "^0.5.1", + "pixelmatch": "^5.1.0", + "pngjs": "^3.4.0", + "rimraf": "^2.6.2", + "ssim.js": "^3.1.1" + }, + "dependencies": { + "ansi-regex": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-2.1.1.tgz", + "integrity": "sha1-w7M6te42DYbg5ijwRorn7yfWVN8=", + "dev": true + }, + "ansi-styles": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-2.2.1.tgz", + "integrity": "sha1-tDLdM1i2NM914eRmQ2gkBTPB3b4=", + "dev": true + }, + "chalk": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-1.1.3.tgz", + "integrity": "sha1-qBFcVeSnAv5NFQq9OHKCKn4J/Jg=", + "dev": true, + "requires": { + "ansi-styles": "^2.2.1", + "escape-string-regexp": "^1.0.2", + "has-ansi": "^2.0.0", + "strip-ansi": "^3.0.0", + "supports-color": "^2.0.0" + } + }, + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "strip-ansi": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-3.0.1.tgz", + "integrity": "sha1-ajhfuIU9lS1f8F0Oiq+UJ43GPc8=", + "dev": true, + "requires": { + "ansi-regex": "^2.0.0" + } + }, + "supports-color": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-2.0.0.tgz", + "integrity": "sha1-U10EXOa2Nj+kARcIRimZXp3zJMc=", + "dev": true + } + } + }, "jest-jasmine2": { "version": "26.6.3", "resolved": "https://registry.npmjs.org/jest-jasmine2/-/jest-jasmine2-26.6.3.tgz", @@ -20323,6 +21322,12 @@ "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", "dev": true }, + "mimic-response": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz", + "integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==", + "dev": true + }, "minimalistic-assert": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", @@ -20344,6 +21349,33 @@ "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", "dev": true }, + "minipass": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/minipass/-/minipass-2.9.0.tgz", + "integrity": "sha512-wxfUjg9WebH+CUDX/CdbRlh5SmfZiy/hpkxaRI16Y9W56Pa75sWgd/rvFilSgrauD9NyFymP/+JFV3KwzIsJeg==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.2", + "yallist": "^3.0.0" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, + "minizlib": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-1.3.3.tgz", + "integrity": "sha512-6ZYMOEnmVsdCeTJVE0W9ZD+pVnE8h9Hma/iOwwRDsdQoePpoX56/8B6z3P9VNwppJuBKNRuFDRNRqRWexT9G9Q==", + "dev": true, + "requires": { + "minipass": "^2.9.0" + } + }, "mixin-deep": { "version": "1.3.2", "resolved": "https://registry.npmjs.org/mixin-deep/-/mixin-deep-1.3.2.tgz", @@ -20396,6 +21428,12 @@ "integrity": "sha1-iZ8R2WhuXgXLkbNdXw5jt3PPyQE=", "dev": true }, + "nan": { + "version": "2.14.2", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.14.2.tgz", + "integrity": "sha512-M2ufzIiINKCuDfBSAUr1vWQ+vuVcA9kqx8JJUsbQi6yf1uGRyb7HfpdfUr5qLXf3B/t8dPvcjhKMmlfnP47EzQ==", + "dev": true + }, "nanomatch": { "version": "1.2.13", "resolved": "https://registry.npmjs.org/nanomatch/-/nanomatch-1.2.13.tgz", @@ -20481,6 +21519,28 @@ "integrity": "sha1-Sr6/7tdUHywnrPspvbvRXI1bpPc=", "dev": true }, + "needle": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/needle/-/needle-2.6.0.tgz", + "integrity": "sha512-KKYdza4heMsEfSWD7VPUIz3zX2XDwOyX2d+geb4vrERZMT5RMU6ujjaD+I5Yr54uZxQ2w6XRTAhHBbSCyovZBg==", + "dev": true, + "requires": { + "debug": "^3.2.6", + "iconv-lite": "^0.4.4", + "sax": "^1.2.4" + }, + "dependencies": { + "debug": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/debug/-/debug-3.2.7.tgz", + "integrity": "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ==", + "dev": true, + "requires": { + "ms": "^2.1.1" + } + } + } + }, "negotiator": { "version": "0.6.2", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.2.tgz", @@ -20554,12 +21614,57 @@ } } }, + "node-pre-gyp": { + "version": "0.15.0", + "resolved": "https://registry.npmjs.org/node-pre-gyp/-/node-pre-gyp-0.15.0.tgz", + "integrity": "sha512-7QcZa8/fpaU/BKenjcaeFF9hLz2+7S9AqyXFhlH/rilsQ/hPZKK32RtR5EQHJElgu+q5RfbJ34KriI79UWaorA==", + "dev": true, + "requires": { + "detect-libc": "^1.0.2", + "mkdirp": "^0.5.3", + "needle": "^2.5.0", + "nopt": "^4.0.1", + "npm-packlist": "^1.1.6", + "npmlog": "^4.0.2", + "rc": "^1.2.7", + "rimraf": "^2.6.1", + "semver": "^5.3.0", + "tar": "^4.4.2" + }, + "dependencies": { + "rimraf": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz", + "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + } + } + }, "node-releases": { "version": "1.1.71", "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.71.tgz", "integrity": "sha512-zR6HoT6LrLCRBwukmrVbHv0EpEQjksO6GmFcZQQuCAy139BEsoVKPYnf3jongYW83fAa1torLGYwxxky/p28sg==", "dev": true }, + "nopt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/nopt/-/nopt-4.0.3.tgz", + "integrity": "sha512-CvaGwVMztSMJLOeXPrez7fyfObdZqNUK1cPAEzLHrTybIua9pMdmmPR5YwtfNftIOMv3DPUhFaxsZMNTQO20Kg==", + "dev": true, + "requires": { + "abbrev": "1", + "osenv": "^0.1.4" + } + }, "normalize-package-data": { "version": "2.5.0", "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz", @@ -20586,6 +21691,32 @@ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", "dev": true }, + "npm-bundled": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/npm-bundled/-/npm-bundled-1.1.2.tgz", + "integrity": "sha512-x5DHup0SuyQcmL3s7Rx/YQ8sbw/Hzg0rj48eN0dV7hf5cmQq5PXIeioroH3raV1QC1yh3uTYuMThvEQF3iKgGQ==", + "dev": true, + "requires": { + "npm-normalize-package-bin": "^1.0.1" + } + }, + "npm-normalize-package-bin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/npm-normalize-package-bin/-/npm-normalize-package-bin-1.0.1.tgz", + "integrity": "sha512-EPfafl6JL5/rU+ot6P3gRSCpPDW5VmIzX959Ob1+ySFUuuYHWHekXpwdUZcKP5C+DS4GEtdJluwBjnsNDl+fSA==", + "dev": true + }, + "npm-packlist": { + "version": "1.4.8", + "resolved": "https://registry.npmjs.org/npm-packlist/-/npm-packlist-1.4.8.tgz", + "integrity": "sha512-5+AZgwru5IevF5ZdnFglB5wNlHG1AOOuw28WhUq8/8emhBmLv6jX5by4WJCh7lW0uSYZYS6DXqIsyZVIXRZU9A==", + "dev": true, + "requires": { + "ignore-walk": "^3.0.1", + "npm-bundled": "^1.0.1", + "npm-normalize-package-bin": "^1.0.1" + } + }, "npm-run-path": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", @@ -20603,6 +21734,24 @@ } } }, + "npmlog": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-4.1.2.tgz", + "integrity": "sha512-2uUqazuKlTaSI/dC8AzicUck7+IrEaOnN/e0jd3Xtt1KcGpwx30v50mL7oPyr/h9bL3E4aZccVwpwP+5W9Vjkg==", + "dev": true, + "requires": { + "are-we-there-yet": "~1.1.2", + "console-control-strings": "~1.1.0", + "gauge": "~2.7.3", + "set-blocking": "~2.0.0" + } + }, + "number-is-nan": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/number-is-nan/-/number-is-nan-1.0.1.tgz", + "integrity": "sha1-CXtgK1NCKlIsGvuHkDGDNpQaAR0=", + "dev": true + }, "nwsapi": { "version": "2.2.0", "resolved": "https://registry.npmjs.org/nwsapi/-/nwsapi-2.2.0.tgz", @@ -20615,6 +21764,12 @@ "integrity": "sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==", "dev": true }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha1-IQmtx5ZYh8/AXLvUQsrIv7s2CGM=", + "dev": true + }, "object-copy": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/object-copy/-/object-copy-0.1.0.tgz", @@ -20761,6 +21916,28 @@ "url-parse": "^1.4.3" } }, + "os-homedir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-homedir/-/os-homedir-1.0.2.tgz", + "integrity": "sha1-/7xJiDNuDoM94MFox+8VISGqf7M=", + "dev": true + }, + "os-tmpdir": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/os-tmpdir/-/os-tmpdir-1.0.2.tgz", + "integrity": "sha1-u+Z0BseaqFxc/sdm/lc0VV36EnQ=", + "dev": true + }, + "osenv": { + "version": "0.1.5", + "resolved": "https://registry.npmjs.org/osenv/-/osenv-0.1.5.tgz", + "integrity": "sha512-0CWcCECdMVc2Rw3U5w9ZjqX6ga6ubk1xDVKxtBQPK7wis/0F2r9T6k4ydGYhecl7YUBxBVxhL5oisPsNxAPe2g==", + "dev": true, + "requires": { + "os-homedir": "^1.0.0", + "os-tmpdir": "^1.0.0" + } + }, "p-defer": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/p-defer/-/p-defer-1.0.0.tgz", @@ -20936,6 +22113,23 @@ "node-modules-regexp": "^1.0.0" } }, + "pixelmatch": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/pixelmatch/-/pixelmatch-5.2.1.tgz", + "integrity": "sha512-WjcAdYSnKrrdDdqTcVEY7aB7UhhwjYQKYhHiBXdJef0MOaQeYpUdQ+iVyBLa5YBKS8MPVPPMX7rpOByISLpeEQ==", + "dev": true, + "requires": { + "pngjs": "^4.0.1" + }, + "dependencies": { + "pngjs": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-4.0.1.tgz", + "integrity": "sha512-rf5+2/ioHeQxR6IxuYNYGFytUyG3lma/WW1nsmjeHlWwtb2aByla6dkVc8pmJ9nplzkTA0q2xx7mMWrOTqT4Gg==", + "dev": true + } + } + }, "pkg-dir": { "version": "4.2.0", "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", @@ -20945,6 +22139,12 @@ "find-up": "^4.0.0" } }, + "pngjs": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-3.4.0.tgz", + "integrity": "sha512-NCrCHhWmnQklfH4MtJMRjZ2a8c80qXeMlQMv2uVp9ISJMTt562SbGd6n2oq0PaPgKm7Z6pL9E2UlLIhC+SHL3w==", + "dev": true + }, "portfinder": { "version": "1.0.28", "resolved": "https://registry.npmjs.org/portfinder/-/portfinder-1.0.28.tgz", @@ -21146,6 +22346,26 @@ "schema-utils": "^3.0.0" } }, + "rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "dev": true, + "requires": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "dependencies": { + "strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", + "dev": true + } + } + }, "react-is": { "version": "17.0.2", "resolved": "https://registry.npmjs.org/react-is/-/react-is-17.0.2.tgz", @@ -21770,6 +22990,12 @@ } } }, + "sax": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/sax/-/sax-1.2.4.tgz", + "integrity": "sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==", + "dev": true + }, "saxes": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/saxes/-/saxes-5.0.1.tgz", @@ -21995,6 +23221,23 @@ "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", "dev": true }, + "simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "dev": true + }, + "simple-get": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.0.tgz", + "integrity": "sha512-bCR6cP+aTdScaQCnQKbPKtJOKDp/hj9EDLJo3Nw4y1QksqaovlW/bnptB6/c1e+qmNIDHRK+oXFDdEqBT8WzUA==", + "dev": true, + "requires": { + "decompress-response": "^4.2.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, "sisteransi": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/sisteransi/-/sisteransi-1.0.5.tgz", @@ -22366,6 +23609,12 @@ "tweetnacl": "~0.14.0" } }, + "ssim.js": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/ssim.js/-/ssim.js-3.5.0.tgz", + "integrity": "sha512-Aj6Jl2z6oDmgYFFbQqK7fght19bXdOxY7Tj03nF+03M9gCBAjeIiO8/PlEGMfKDwYpw4q6iBqVq2YuREorGg/g==", + "dev": true + }, "stack-utils": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.3.tgz", @@ -22568,6 +23817,29 @@ "integrity": "sha512-4WK/bYZmj8xLr+HUCODHGF1ZFzsYffasLUgEiMBY4fgtltdO6B4WJtlSbPaDTLpYTcGVwM2qLnFTICEcNxs3kA==", "dev": true }, + "tar": { + "version": "4.4.13", + "resolved": "https://registry.npmjs.org/tar/-/tar-4.4.13.tgz", + "integrity": "sha512-w2VwSrBoHa5BsSyH+KxEqeQBAllHhccyMFVHtGtdMpF4W7IRWfZjFiQceJPChOeTsSDVUpER2T8FA93pr0L+QA==", + "dev": true, + "requires": { + "chownr": "^1.1.1", + "fs-minipass": "^1.2.5", + "minipass": "^2.8.6", + "minizlib": "^1.2.1", + "mkdirp": "^0.5.0", + "safe-buffer": "^5.1.2", + "yallist": "^3.0.3" + }, + "dependencies": { + "yallist": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-3.1.1.tgz", + "integrity": "sha512-a4UGQaWPH59mOXUYnAG2ewncQS4i4F43Tv3JoAM+s2VDAmS9NsK8GpDMLrCHPksFT7h3K6TOoUNn2pb7RoXx4g==", + "dev": true + } + } + }, "terminal-link": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/terminal-link/-/terminal-link-2.1.1.tgz", @@ -23590,6 +24862,48 @@ "is-typed-array": "^1.1.3" } }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } + } + } + }, "wildcard": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", diff --git a/package.json b/package.json index 9420295..8e46bda 100644 --- a/package.json +++ b/package.json @@ -29,14 +29,17 @@ "@testing-library/dom": "^7.30.3", "@testing-library/user-event": "^13.1.3", "@types/jest": "^26.0.14", + "@types/jest-image-snapshot": "^4.3.0", "@types/micromodal": "^0.3.2", "@typescript-eslint/eslint-plugin": "^4.6.1", "@typescript-eslint/parser": "^4.6.1", "ajv": "^6.12.0", "babel-jest": "^26.6.3", + "canvas": "^2.7.0", "eslint": "^7.22.0", "file-loader": "^6.0.0", "jest": "^26.6.3", + "jest-image-snapshot": "^4.5.0", "node-forge": "^0.10.0", "raw-loader": "^4.0.0", "ts-jest": "^26.5.0", @@ -48,7 +51,7 @@ "y18n": "^4.0.1" }, "dependencies": { - "apple2shader": "0.0.1", + "apple2shader": "0.0.3", "micromodal": "^0.4.2" } } diff --git a/test/jest-setup.js b/test/jest-setup.js new file mode 100644 index 0000000..579dbe1 --- /dev/null +++ b/test/jest-setup.js @@ -0,0 +1,3 @@ +import { toMatchImageSnapshot } from 'jest-image-snapshot'; + +expect.extend({ toMatchImageSnapshot }); diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png new file mode 100644 index 0000000..f58ae4c Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png new file mode 100644 index 0000000..b58b8f4 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-double-lores-renders-mono-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-1-snap.png new file mode 100644 index 0000000..401d0ff Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png new file mode 100644 index 0000000..66d21dc Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-hires-page-hires-renders-mono-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png new file mode 100644 index 0000000..91ce95d Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png new file mode 100644 index 0000000..d8eb211 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png new file mode 100644 index 0000000..6ef12a0 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-double-lores-renders-mono-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-1-snap.png new file mode 100644 index 0000000..3f09821 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mixed-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mixed-1-snap.png new file mode 100644 index 0000000..d3a1a2b Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mixed-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mono-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mono-1-snap.png new file mode 100644 index 0000000..1bafb06 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-graphics-mode-lores-renders-mono-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png new file mode 100644 index 0000000..675ca35 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png new file mode 100644 index 0000000..b0f47a0 Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png new file mode 100644 index 0000000..456cabb Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png new file mode 100644 index 0000000..f907dca Binary files /dev/null and b/test/js/__image_snapshots__/canvas-test-ts-canvas-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-double-lores-renders-1-snap.png new file mode 100644 index 0000000..b58b8f4 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-double-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-hires-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-hires-renders-1-snap.png new file mode 100644 index 0000000..e56608a Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-hires-page-hires-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png new file mode 100644 index 0000000..6ef12a0 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png new file mode 100644 index 0000000..7110877 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-double-lores-renders-mixed-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-1-snap.png new file mode 100644 index 0000000..1bafb06 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png new file mode 100644 index 0000000..3d04f17 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-graphics-mode-lores-renders-mixed-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-1-snap.png new file mode 100644 index 0000000..675ca35 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png new file mode 100644 index 0000000..b0f47a0 Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-40-column-renders-alt-chars-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png new file mode 100644 index 0000000..456cabb Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-1-snap.png differ diff --git a/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png new file mode 100644 index 0000000..f907dca Binary files /dev/null and b/test/js/__image_snapshots__/gl-test-ts-gl-lores-page-text-mode-80-column-renders-alt-chars-1-snap.png differ diff --git a/test/js/__mocks__/apple2shader.js b/test/js/__mocks__/apple2shader.js new file mode 100644 index 0000000..5cbe040 --- /dev/null +++ b/test/js/__mocks__/apple2shader.js @@ -0,0 +1,18 @@ +export const screenEmu = (function () { + return { + C: { + NTSC_DETAILS: { + imageSize: { + width: 560, + height: 192, + }, + }, + }, + DisplayConfiguration: class {}, + Point: class {}, + ScreenView: class { + initOpenGL() {} + }, + Size: class{}, + }; +})(); diff --git a/test/js/canvas.test.ts b/test/js/canvas.test.ts new file mode 100644 index 0000000..0475cd6 --- /dev/null +++ b/test/js/canvas.test.ts @@ -0,0 +1,258 @@ +/** @fileoverview Test for canvas.ts. */ + +import { VideoPage } from 'js/videomodes'; +import { LoresPage2D, HiresPage2D, VideoModes2D } from 'js/canvas'; +import apple2enh_char from 'js/roms/apple2enh_char'; +import { createImageFromImageData } from 'test/util/image'; + +function checkImageData(page: VideoPage) { + page.refresh(); + const img = createImageFromImageData(page.imageData); + expect(img).toMatchImageSnapshot(); +} + +describe('canvas', () => { + + describe('LoresPage', () => { + let canvas: HTMLCanvasElement; + let lores1: LoresPage2D; + let vm: VideoModes2D; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModes2D(canvas, true); + lores1 = new LoresPage2D(vm, 1, apple2enh_char, true); + vm.reset(); + vm.hires(false); + }); + + describe('text mode', () => { + describe('40 column', () => { + it('renders', () => { + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('80 column', () => { + it('renders', () => { + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + + describe('graphics mode', () => { + describe('lores', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mono', () => { + vm.text(false); + vm.mono(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mono', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + vm.mono(true); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + }); + + describe('HiresPage', () => { + let canvas: HTMLCanvasElement; + let hires1: HiresPage2D; + let vm: VideoModes2D; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModes2D(canvas, true); + hires1 = new HiresPage2D(vm, 1); + vm.reset(); + vm.hires(true); + }); + + describe('hires', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + + it('renders mono', () => { + vm.text(false); + vm.mono(true); + + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + + it('renders mono', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + vm.mono(true); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + }); + }); +}); diff --git a/test/js/gl.test.ts b/test/js/gl.test.ts new file mode 100644 index 0000000..8724149 --- /dev/null +++ b/test/js/gl.test.ts @@ -0,0 +1,197 @@ +/** @fileoverview Test for canvas.ts. */ + +import { VideoPage } from 'js/videomodes'; +import { LoresPageGL, HiresPageGL, VideoModesGL } from 'js/gl'; +import apple2enh_char from 'js/roms/apple2enh_char'; +import { createImageFromImageData } from 'test/util/image'; + +function checkImageData(page: VideoPage) { + page.refresh(); + const img = createImageFromImageData(page.imageData); + expect(img).toMatchImageSnapshot(); +} + +describe('gl', () => { + + describe('LoresPage', () => { + let canvas: HTMLCanvasElement; + let lores1: LoresPageGL; + let vm: VideoModesGL; + + beforeEach(async () => { + canvas = document.createElement('canvas'); + vm = new VideoModesGL(canvas, true); + await vm.ready; + lores1 = new LoresPageGL(vm, 1, apple2enh_char, true); + vm.reset(); + vm.hires(false); + }); + + describe('text mode', () => { + describe('40 column', () => { + it('renders', () => { + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('80 column', () => { + it('renders', () => { + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders alt chars', () => { + vm.altChar(true); + vm._80col(true); + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + + describe('graphics mode', () => { + describe('lores', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + lores1.write(page, off, off); + } + } + + checkImageData(lores1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + + it('renders mixed', () => { + vm.text(false); + vm.mixed(true); + vm._80col(true); + vm.an3(false); + + const bank0 = lores1.bank0(); + const bank1 = lores1.bank1(); + for (let page = 0x4; page < 0x8; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(lores1); + }); + }); + }); + }); + + describe('HiresPage', () => { + let canvas: HTMLCanvasElement; + let hires1: HiresPageGL; + let vm: VideoModesGL; + + beforeEach(() => { + canvas = document.createElement('canvas'); + vm = new VideoModesGL(canvas, true); + hires1 = new HiresPageGL(vm, 1); + vm.reset(); + vm.hires(true); + }); + + describe('hires', () => { + it('renders', () => { + vm.text(false); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + hires1.write(page, off, off); + } + } + + checkImageData(hires1); + }); + }); + + describe('double lores', () => { + it('renders', () => { + vm.text(false); + vm._80col(true); + vm.an3(false); + + const bank0 = hires1.bank0(); + const bank1 = hires1.bank1(); + for (let page = 0x20; page < 0x40; page++) { + for (let off = 0; off < 0x100; off++) { + bank0.write(page, off, off); + bank1.write(page, off, 255 - off); + } + } + + checkImageData(hires1); + }); + }); + }); +}); diff --git a/test/util/image.ts b/test/util/image.ts new file mode 100644 index 0000000..761e6c2 --- /dev/null +++ b/test/util/image.ts @@ -0,0 +1,9 @@ +export const createImageFromImageData = (data: ImageData) => { + const canvas = document.createElement('canvas'); + canvas.width = data.width; + canvas.height = data.height; + const ctx = canvas.getContext('2d')!; + ctx.putImageData(data, 0, 0); + const url = canvas.toDataURL('image/png'); + return Buffer.from(url.split(',')[1], 'base64'); +}; diff --git a/tsconfig.json b/tsconfig.json index 96d79eb..d008dcf 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -21,8 +21,11 @@ ], "js/*": [ "js/*" + ], + "test/*": [ + "test/*" ] - }, + } }, "include": [ "js/**/*",