feat(tests): move from karma to vite

This commit is contained in:
Néfix Estrada 2025-11-08 21:59:00 +01:00
parent d44f7e04fc
commit e0326c834a
31 changed files with 2926 additions and 2857 deletions

View File

@ -258,15 +258,6 @@ export default class Websock {
attach(rawChannel) {
this.init();
// Must get object and class methods to be compatible with the tests.
const channelProps = [...Object.keys(rawChannel), ...Object.getOwnPropertyNames(Object.getPrototypeOf(rawChannel))];
for (let i = 0; i < rawChannelProps.length; i++) {
const prop = rawChannelProps[i];
if (channelProps.indexOf(prop) < 0) {
throw new Error('Raw channel missing property: ' + prop);
}
}
this._websocket = rawChannel;
this._websocket.binaryType = "arraybuffer";
this._websocket.onmessage = this._recvMessage.bind(this);

View File

@ -1,92 +0,0 @@
// Karma configuration
// The Safari launcher is broken, so construct our own
function SafariBrowser(id, baseBrowserDecorator, args) {
baseBrowserDecorator(this);
this._start = function(url) {
this._execCommand('/usr/bin/open', ['-W', '-n', '-a', 'Safari', url]);
}
}
SafariBrowser.prototype = {
name: 'Safari'
}
module.exports = (config) => {
let browsers = [];
if (process.env.TEST_BROWSER_NAME) {
browsers = process.env.TEST_BROWSER_NAME.split(',');
}
const my_conf = {
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: '',
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['mocha'],
// list of files / patterns to load in the browser
files: [
// node modules
{ pattern: 'node_modules/chai/**', included: false },
{ pattern: 'node_modules/sinon/**', included: false },
{ pattern: 'node_modules/sinon-chai/**', included: false },
// modules to test
{ pattern: 'app/localization.js', included: false, type: 'module' },
{ pattern: 'app/webutil.js', included: false, type: 'module' },
{ pattern: 'core/**/*.js', included: false, type: 'module' },
{ pattern: 'vendor/pako/**/*.js', included: false, type: 'module' },
// tests
{ pattern: 'tests/test.*.js', type: 'module' },
// test support files
{ pattern: 'tests/fake.*.js', included: false, type: 'module' },
{ pattern: 'tests/assertions.js', type: 'module' },
],
client: {
mocha: {
// replace Karma debug page with mocha display
'reporter': 'html',
'ui': 'bdd'
}
},
// list of files to exclude
exclude: [
],
plugins: [
'karma-*',
'@chiragrupani/karma-chromium-edge-launcher',
{ 'launcher:Safari': [ 'type', SafariBrowser ] },
],
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: browsers,
// test results reporter to use
// possible values: 'dots', 'progress'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha'],
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true,
};
config.set(my_conf);
};

View File

@ -15,7 +15,7 @@
"exports": "./core/rfb.js",
"scripts": {
"lint": "eslint app core po/po2js po/xgettext-html tests utils",
"test": "karma start karma.conf.cjs"
"test": "vitest run"
},
"repository": {
"type": "git",
@ -34,29 +34,17 @@
"devDependencies": {
"@babel/core": "latest",
"@babel/preset-env": "latest",
"@vitest/browser-playwright": "^4.0.8",
"babel-plugin-import-redirect": "latest",
"browserify": "latest",
"chai": "latest",
"commander": "latest",
"eslint": "latest",
"fs-extra": "latest",
"globals": "latest",
"jsdom": "latest",
"karma": "latest",
"karma-mocha": "latest",
"karma-chrome-launcher": "latest",
"@chiragrupani/karma-chromium-edge-launcher": "latest",
"karma-firefox-launcher": "latest",
"karma-ie-launcher": "latest",
"karma-mocha-reporter": "latest",
"karma-safari-launcher": "latest",
"karma-script-launcher": "latest",
"mocha": "latest",
"pofile": "latest",
"sinon": "latest",
"sinon-chai": "latest"
"vitest": "^4.0.8"
},
"dependencies": {},
"keywords": [
"vnc",
"rfb",

View File

@ -1,11 +1,4 @@
import * as chai from '../node_modules/chai/index.js';
import sinon from '../node_modules/sinon/pkg/sinon-esm.js';
import sinonChai from '../node_modules/sinon-chai/lib/sinon-chai.js';
window.expect = chai.expect;
window.sinon = sinon;
chai.use(sinonChai);
import { chai } from "vitest";
// noVNC specific assertions
chai.use(function (_chai, utils) {

View File

@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest';
import Base64 from '../core/base64.js';
describe('Base64 tools', function () {

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import { isMac, isWindows, isIOS, isAndroid, isChromeOS,
isSafari, isFirefox, isChrome, isChromium, isOpera, isEdge,
isGecko, isWebKit, isBlink,
@ -7,52 +8,55 @@ describe('Async clipboard', function () {
"use strict";
beforeEach(function () {
sinon.stub(navigator, "clipboard").value({
writeText: sinon.stub(),
readText: sinon.stub(),
});
sinon.stub(navigator, "permissions").value({
query: sinon.stub().resolves({ state: "granted" })
vi.stubGlobal('navigator', {
...navigator,
clipboard: {
writeText: vi.fn(),
readText: vi.fn(),
},
permissions: {
query: vi.fn().mockResolvedValue({ state: "granted" })
}
});
});
afterEach(function () {
sinon.restore();
vi.restoreAllMocks();
});
it("queries permissions with correct parameters", async function () {
const queryStub = navigator.permissions.query;
await browserAsyncClipboardSupport();
expect(queryStub.firstCall).to.have.been.calledWithExactly({
expect(queryStub).toHaveBeenNthCalledWith(1, {
name: "clipboard-write",
allowWithoutGesture: true
});
expect(queryStub.secondCall).to.have.been.calledWithExactly({
expect(queryStub).toHaveBeenNthCalledWith(2, {
name: "clipboard-read",
allowWithoutGesture: false
});
});
it("is available when API present and permissions granted", async function () {
navigator.permissions.query.resolves({ state: "granted" });
navigator.permissions.query.mockResolvedValue({ state: "granted" });
const result = await browserAsyncClipboardSupport();
expect(result).to.equal('available');
});
it("is available when API present and permissions yield 'prompt'", async function () {
navigator.permissions.query.resolves({ state: "prompt" });
navigator.permissions.query.mockResolvedValue({ state: "prompt" });
const result = await browserAsyncClipboardSupport();
expect(result).to.equal('available');
});
it("is unavailable when permissions denied", async function () {
navigator.permissions.query.resolves({ state: "denied" });
navigator.permissions.query.mockResolvedValue({ state: "denied" });
const result = await browserAsyncClipboardSupport();
expect(result).to.equal('denied');
});
it("is unavailable when permissions API fails", async function () {
navigator.permissions.query.rejects(new Error("fail"));
navigator.permissions.query.mockRejectedValue(new Error("fail"));
const result = await browserAsyncClipboardSupport();
expect(result).to.equal('unsupported');
});

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import AsyncClipboard from '../core/clipboard.js';
describe('Async Clipboard', function () {
@ -7,13 +8,15 @@ describe('Async Clipboard', function () {
let clipboard;
beforeEach(function () {
sinon.stub(navigator, "clipboard").value({
writeText: sinon.stub().resolves(),
readText: sinon.stub().resolves(),
});
sinon.stub(navigator, "permissions").value({
query: sinon.stub(),
vi.stubGlobal('navigator', {
...navigator,
clipboard: {
writeText: vi.fn().mockResolvedValue(),
readText: vi.fn().mockResolvedValue(),
},
permissions: {
query: vi.fn(),
}
});
targetMock = document.createElement("canvas");
@ -21,18 +24,15 @@ describe('Async Clipboard', function () {
});
afterEach(function () {
sinon.restore();
vi.restoreAllMocks();
targetMock = null;
clipboard = null;
});
function stubClipboardPermissions(state) {
navigator.permissions.query
.withArgs({ name: 'clipboard-write', allowWithoutGesture: true })
.resolves({ state: state });
navigator.permissions.query
.withArgs({ name: 'clipboard-read', allowWithoutGesture: false })
.resolves({ state: state });
navigator.permissions.query.mockImplementation(args =>
Promise.resolve({ state: state })
);
}
function nextTick() {
@ -42,30 +42,30 @@ describe('Async Clipboard', function () {
it('grab() adds listener if permissions granted', async function () {
stubClipboardPermissions('granted');
const addListenerSpy = sinon.spy(targetMock, 'addEventListener');
const addListenerSpy = vi.spyOn(targetMock, 'addEventListener');
clipboard.grab();
await nextTick();
expect(addListenerSpy.calledWith('focus')).to.be.true;
expect(addListenerSpy).toHaveBeenCalledWith('focus', expect.any(Function));
});
it('grab() does not add listener if permissions denied', async function () {
stubClipboardPermissions('denied');
const addListenerSpy = sinon.spy(targetMock, 'addEventListener');
const addListenerSpy = vi.spyOn(targetMock, 'addEventListener');
clipboard.grab();
await nextTick();
expect(addListenerSpy.calledWith('focus')).to.be.false;
expect(addListenerSpy).not.toHaveBeenCalledWith('focus', expect.any(Function));
});
it('focus event triggers onpaste() if permissions granted', async function () {
stubClipboardPermissions('granted');
const text = 'hello clipboard world';
navigator.clipboard.readText.resolves(text);
navigator.clipboard.readText.mockResolvedValue(text);
const spyPromise = new Promise(resolve => clipboard.onpaste = resolve);
@ -83,9 +83,9 @@ describe('Async Clipboard', function () {
stubClipboardPermissions('denied');
const text = 'should not read';
navigator.clipboard.readText.resolves(text);
navigator.clipboard.readText.mockResolvedValue(text);
clipboard.onpaste = sinon.spy();
clipboard.onpaste = vi.fn();
clipboard.grab();
@ -93,7 +93,7 @@ describe('Async Clipboard', function () {
targetMock.dispatchEvent(new Event('focus'));
expect(clipboard.onpaste.called).to.be.false;
expect(clipboard.onpaste).not.toHaveBeenCalled();
});
it('writeClipboard() calls navigator.clipboard.writeText() if permissions granted', async function () {
@ -103,8 +103,8 @@ describe('Async Clipboard', function () {
const text = 'writing to clipboard';
const result = clipboard.writeClipboard(text);
expect(navigator.clipboard.writeText.calledWith(text)).to.be.true;
expect(result).to.be.true;
expect(navigator.clipboard.writeText).toHaveBeenCalledWith(text);
expect(result).toBe(true);
});
it('writeClipboard() does not call navigator.clipboard.writeText() if permissions denied', async function () {
@ -114,8 +114,8 @@ describe('Async Clipboard', function () {
const text = 'should not write';
const result = clipboard.writeClipboard(text);
expect(navigator.clipboard.writeText.called).to.be.false;
expect(result).to.be.false;
expect(navigator.clipboard.writeText).not.toHaveBeenCalled();
expect(result).toBe(false);
});
});

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('CopyRect decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new CopyRectDecoder();

View File

@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest';
import { inflateInit, inflate } from "../vendor/pako/lib/zlib/inflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
import Deflator from "../core/deflator.js";

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import Base64 from '../core/base64.js';
import Display from '../core/display.js';
@ -71,15 +72,15 @@ describe('Display/Canvas helper', function () {
});
it('should redraw when moving the viewport', function () {
display.flip = sinon.spy();
display.flip = vi.fn();
display.viewportChangePos(-1, 1);
expect(display.flip).to.have.been.calledOnce;
expect(display.flip).toHaveBeenCalledOnce();
});
it('should redraw when resizing the viewport', function () {
display.flip = sinon.spy();
display.flip = vi.fn();
display.viewportChangeSize(2, 2);
expect(display.flip).to.have.been.calledOnce;
expect(display.flip).toHaveBeenCalledOnce();
});
it('should show the entire framebuffer when disabling the viewport', function () {
@ -322,37 +323,37 @@ describe('Display/Canvas helper', function () {
beforeEach(function () {
display = new Display(document.createElement('canvas'));
display.resize(4, 4);
sinon.spy(display, '_scanRenderQ');
vi.spyOn(display, '_scanRenderQ');
});
it('should try to process an item when it is pushed on, if nothing else is on the queue', function () {
display._renderQPush({ type: 'noop' }); // does nothing
expect(display._scanRenderQ).to.have.been.calledOnce;
expect(display._scanRenderQ).toHaveBeenCalledOnce();
});
it('should not try to process an item when it is pushed on if we are waiting for other items', function () {
display._renderQ.length = 2;
display._renderQPush({ type: 'noop' });
expect(display._scanRenderQ).to.not.have.been.called;
expect(display._scanRenderQ).not.toHaveBeenCalled();
});
it('should wait until an image is loaded to attempt to draw it and the rest of the queue', function () {
const img = { complete: false, width: 4, height: 4, addEventListener: sinon.spy() };
const img = { complete: false, width: 4, height: 4, addEventListener: vi.fn() };
display._renderQ = [{ type: 'img', x: 3, y: 4, width: 4, height: 4, img: img },
{ type: 'fill', x: 1, y: 2, width: 3, height: 4, color: 5 }];
display.drawImage = sinon.spy();
display.fillRect = sinon.spy();
display.drawImage = vi.fn();
display.fillRect = vi.fn();
display._scanRenderQ();
expect(display.drawImage).to.not.have.been.called;
expect(display.fillRect).to.not.have.been.called;
expect(img.addEventListener).to.have.been.calledOnce;
expect(display.drawImage).not.toHaveBeenCalled();
expect(display.fillRect).not.toHaveBeenCalled();
expect(img.addEventListener).toHaveBeenCalledOnce();
display._renderQ[0].img.complete = true;
display._scanRenderQ();
expect(display.drawImage).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledOnce;
expect(img.addEventListener).to.have.been.calledOnce;
expect(display.drawImage).toHaveBeenCalledOnce();
expect(display.fillRect).toHaveBeenCalledOnce();
expect(img.addEventListener).toHaveBeenCalledOnce();
});
it('should resolve promise when queue is flushed', async function () {
@ -363,32 +364,32 @@ describe('Display/Canvas helper', function () {
});
it('should draw a blit image on type "blit"', function () {
display.blitImage = sinon.spy();
display.blitImage = vi.fn();
display._renderQPush({ type: 'blit', x: 3, y: 4, width: 5, height: 6, data: [7, 8, 9] });
expect(display.blitImage).to.have.been.calledOnce;
expect(display.blitImage).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9], 0);
expect(display.blitImage).toHaveBeenCalledOnce();
expect(display.blitImage).toHaveBeenCalledWith(3, 4, 5, 6, [7, 8, 9], 0, true);
});
it('should copy a region on type "copy"', function () {
display.copyImage = sinon.spy();
display.copyImage = vi.fn();
display._renderQPush({ type: 'copy', x: 3, y: 4, width: 5, height: 6, oldX: 7, oldY: 8 });
expect(display.copyImage).to.have.been.calledOnce;
expect(display.copyImage).to.have.been.calledWith(7, 8, 3, 4, 5, 6);
expect(display.copyImage).toHaveBeenCalledOnce();
expect(display.copyImage).toHaveBeenCalledWith(7, 8, 3, 4, 5, 6, true);
});
it('should fill a rect with a given color on type "fill"', function () {
display.fillRect = sinon.spy();
display.fillRect = vi.fn();
display._renderQPush({ type: 'fill', x: 3, y: 4, width: 5, height: 6, color: [7, 8, 9]});
expect(display.fillRect).to.have.been.calledOnce;
expect(display.fillRect).to.have.been.calledWith(3, 4, 5, 6, [7, 8, 9]);
expect(display.fillRect).toHaveBeenCalledOnce();
expect(display.fillRect).toHaveBeenCalledWith(3, 4, 5, 6, [7, 8, 9], true);
});
it('should draw an image from an image object on type "img" (if complete)', function () {
const img = { complete: true };
display.drawImage = sinon.spy();
display.drawImage = vi.fn();
display._renderQPush({ type: 'img', x: 3, y: 4, img: img });
expect(display.drawImage).to.have.been.calledOnce;
expect(display.drawImage).to.have.been.calledWith(img, 3, 4);
expect(display.drawImage).toHaveBeenCalledOnce();
expect(display.drawImage).toHaveBeenCalledWith(img, 3, 4);
});
});
});

1103
tests/gesturehandler.test.js Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -133,9 +134,9 @@ describe('H.264 parser', function () {
describe('H.264 decoder unit test', function () {
let decoder;
beforeEach(function () {
beforeEach(function ({ skip }) {
if (!supportsWebCodecsH264Decode) {
this.skip();
skip();
return;
}
decoder = new H264Decoder();
@ -185,12 +186,12 @@ describe('H.264 decoder functional test', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
beforeEach(function ({ skip }) {
if (!supportsWebCodecsH264Decode) {
this.skip();
skip();
return;
}
decoder = new H264Decoder();

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeEach, afterEach } from 'vitest';
import keysyms from '../core/input/keysymdef.js';
import * as KeyboardUtil from "../core/input/util.js";
@ -140,8 +141,8 @@ describe('Helpers', function () {
it('should handle Windows key with incorrect location', function () {
expect(KeyboardUtil.getKeysym({key: 'Meta', location: 0})).to.be.equal(0xFFEC);
});
it('should handle Clear/NumLock key with incorrect location', function () {
this.skip(); // Broken because of Clear/NumLock override
it('should handle Clear/NumLock key with incorrect location', function ({ skip }) {
skip(); // Broken because of Clear/NumLock override
expect(KeyboardUtil.getKeysym({key: 'Clear', code: 'NumLock', location: 3})).to.be.equal(0xFF0B);
});
it('should handle Meta/Windows distinction', function () {

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -40,8 +41,8 @@ describe('Hextile decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new HextileDecoder();

View File

@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest';
import { deflateInit, deflate, Z_FULL_FLUSH } from "../vendor/pako/lib/zlib/deflate.js";
import ZStream from "../vendor/pako/lib/zlib/zstream.js";
import Inflator from "../core/inflator.js";

View File

@ -1,3 +1,4 @@
import { describe, it, expect } from 'vitest';
import { toUnsigned32bit, toSigned32bit } from '../core/util/int.js';
describe('Integer casting', function () {

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('JPEG decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new JPEGDecoder();

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import Keyboard from '../core/input/keyboard.js';
describe('Key event handling', function () {
@ -10,8 +11,8 @@ describe('Key event handling', function () {
for (let key in KeyboardEventInit) {
e[key] = KeyboardEventInit[key];
}
e.stopPropagation = sinon.spy();
e.preventDefault = sinon.spy();
e.stopPropagation = vi.fn();
e.preventDefault = vi.fn();
e.getModifierState = function (key) {
return e[key];
};
@ -20,7 +21,8 @@ describe('Key event handling', function () {
}
describe('Decode keyboard events', function () {
it('should decode keydown events', function (done) {
it('should decode keydown events', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
@ -30,7 +32,9 @@ describe('Key event handling', function () {
};
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
});
it('should decode keyup events', function (done) {
});
it('should decode keyup events', function () {
return new Promise((done) => {
let calls = 0;
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@ -45,9 +49,11 @@ describe('Key event handling', function () {
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
});
});
});
describe('Fake keyup', function () {
it('should fake keyup events for virtual keyboards', function (done) {
it('should fake keyup events for virtual keyboards', function () {
return new Promise((done) => {
let count = 0;
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@ -67,9 +73,11 @@ describe('Key event handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'Unidentified', key: 'a'}));
});
});
});
describe('Track key state', function () {
it('should send release using the same keysym as the press', function (done) {
it('should send release using the same keysym as the press', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
@ -81,6 +89,7 @@ describe('Key event handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'b'}));
});
});
it('should send the same keysym for multiple presses', function () {
let count = 0;
const kbd = new Keyboard(document);
@ -96,13 +105,14 @@ describe('Key event handling', function () {
});
it('should do nothing on keyup events if no keys are down', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
expect(kbd.onkeyevent).to.not.have.been.called;
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
describe('Legacy events', function () {
it('should track keys using keyCode if no code', function (done) {
it('should track keys using keyCode if no code', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
@ -114,6 +124,7 @@ describe('Key event handling', function () {
kbd._handleKeyDown(keyevent('keydown', {keyCode: 65, key: 'a'}));
kbd._handleKeyUp(keyevent('keyup', {keyCode: 65, key: 'b'}));
});
});
it('should ignore compositing code', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
@ -122,7 +133,8 @@ describe('Key event handling', function () {
};
kbd._handleKeyDown(keyevent('keydown', {keyCode: 229, key: 'a'}));
});
it('should track keys using keyIdentifier if no code', function (done) {
it('should track keys using keyIdentifier if no code', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0x61);
@ -136,6 +148,7 @@ describe('Key event handling', function () {
});
});
});
});
describe('Shuffle modifiers on macOS', function () {
let origNavigator;
@ -171,7 +184,8 @@ describe('Key event handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
expect(count).to.be.equal(2);
});
it('should change left Super to Alt', function (done) {
it('should change left Super to Alt', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0xFFE9);
@ -180,7 +194,9 @@ describe('Key event handling', function () {
};
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaLeft', key: 'Meta', location: 1}));
});
it('should change right Super to left Super', function (done) {
});
it('should change right Super to left Super', function () {
return new Promise((done) => {
const kbd = new Keyboard(document);
kbd.onkeyevent = (keysym, code, down) => {
expect(keysym).to.be.equal(0xFFEB);
@ -190,10 +206,11 @@ describe('Key event handling', function () {
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2}));
});
});
});
describe('Meta key combination on iOS and macOS', function () {
let origNavigator;
beforeEach(function () {
beforeEach(function ({ skip }) {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
// tests.
@ -203,7 +220,7 @@ describe('Key event handling', function () {
if (window.navigator.platform !== undefined) {
// Object.defineProperty() doesn't work properly in old
// versions of Chrome
this.skip();
skip();
}
});
@ -216,39 +233,39 @@ describe('Key event handling', function () {
it('should send keyup when meta key is pressed on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
kbd.onkeyevent.mockClear();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0x61, "KeyA", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0x61, "KeyA", false, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
});
it('should send keyup when meta key is pressed on macOS', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
kbd.onkeyevent.mockClear();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a', metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).to.have.been.calledWith(0x61, "KeyA", false);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0x61, "KeyA", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0x61, "KeyA", false, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyUp(keyevent('keyup', {code: 'MetaRight', key: 'Meta', location: 2, metaKey: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
});
});
@ -270,45 +287,45 @@ describe('Key event handling', function () {
it('should toggle caps lock on key press on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xFFE5, "CapsLock", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xFFE5, "CapsLock", false, null, null);
});
it('should toggle caps lock on key press on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'CapsLock', key: 'CapsLock'}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xFFE5, "CapsLock", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xFFE5, "CapsLock", false, null, null);
});
it('should toggle caps lock on key release on iOS', function () {
window.navigator.platform = "iPad";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xFFE5, "CapsLock", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xFFE5, "CapsLock", false, null, null);
});
it('should toggle caps lock on key release on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyUp(keyevent('keyup', {code: 'CapsLock', key: 'CapsLock'}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xFFE5, "CapsLock", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xFFE5, "CapsLock", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xFFE5, "CapsLock", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xFFE5, "CapsLock", false, null, null);
});
});
@ -329,30 +346,30 @@ describe('Key event handling', function () {
it('should provide caps lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, false, true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0x41, "KeyA", true, false, true);
});
it('should provide num lock state', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: true, CapsLock: false}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, true, false);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0x41, "KeyA", true, true, false);
});
it('should have no num lock state on mac', function () {
window.navigator.platform = "Mac";
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'A', NumLock: false, CapsLock: true}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x41, "KeyA", true, null, true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0x41, "KeyA", true, null, true);
});
});
@ -379,18 +396,20 @@ describe('Key event handling', function () {
for (let [key, keysym] of Object.entries(keys)) {
it(`should fake key release for ${key} on Windows`, function () {
let kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'FakeIM', key: key}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(keysym, "FakeIM", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(keysym, "FakeIM", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, keysym, "FakeIM", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, keysym, "FakeIM", false, null, null);
});
}
});
describe('Escape AltGraph on Windows', function () {
let origNavigator;
let clock;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
@ -400,181 +419,183 @@ describe('Key event handling', function () {
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
clock = vi.useFakeTimers();
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
if (clock !== undefined) {
clock.restoreAllMocks();
}
});
it('should supress ControlLeft until it knows if it is AltGr', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.not.have.been.called;
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should not trigger on repeating ControlLeft', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffe3, "ControlLeft", true, null, null);
});
it('should not supress ControlRight', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlRight', key: 'Control', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe4, "ControlRight", true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe4, "ControlRight", true, null, null);
});
it('should release ControlLeft after 100 ms', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.not.have.been.called;
this.clock.tick(100);
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe3, "ControlLeft", true, null, null);
});
it('should release ControlLeft on other key press', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.not.have.been.called;
expect(kbd.onkeyevent).not.toHaveBeenCalled();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0x61, "KeyA", true, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should release ControlLeft on other key release', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'KeyA', key: 'a'}));
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0x61, "KeyA", true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0x61, "KeyA", true, null, null);
kbd._handleKeyUp(keyevent('keyup', {code: 'KeyA', key: 'a'}));
expect(kbd.onkeyevent).to.have.been.calledThrice;
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.thirdCall).to.have.been.calledWith(0x61, "KeyA", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(3);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(3, 0x61, "KeyA", false, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should release ControlLeft on blur', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1}));
expect(kbd.onkeyevent).to.not.have.been.called;
expect(kbd.onkeyevent).not.toHaveBeenCalled();
kbd._allKeysUp();
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffe3, "ControlLeft", false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffe3, "ControlLeft", false, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should generate AltGraph for quick Ctrl+Alt sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(20);
clock.advanceTimersByTime(20);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xfe03, 'AltRight', true, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should generate Ctrl, Alt for slow Ctrl+Alt sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(60);
clock.advanceTimersByTime(60);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xffea, "AltRight", true);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffea, "AltRight", true, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should generate AltGraph for quick Ctrl+AltGraph sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(20);
clock.advanceTimersByTime(20);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xfe03, 'AltRight', true, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should generate Ctrl, AltGraph for slow Ctrl+AltGraph sequence', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ControlLeft', key: 'Control', location: 1, timeStamp: Date.now()}));
this.clock.tick(60);
clock.advanceTimersByTime(60);
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2, timeStamp: Date.now()}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent.firstCall).to.have.been.calledWith(0xffe3, "ControlLeft", true);
expect(kbd.onkeyevent.secondCall).to.have.been.calledWith(0xfe03, "AltRight", true);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe3, "ControlLeft", true, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xfe03, "AltRight", true, null, null);
// Check that the timer is properly dead
kbd.onkeyevent.resetHistory();
this.clock.tick(100);
expect(kbd.onkeyevent).to.not.have.been.called;
kbd.onkeyevent.mockClear();
clock.advanceTimersByTime(100);
expect(kbd.onkeyevent).not.toHaveBeenCalled();
});
it('should pass through single Alt', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'Alt', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffea, 'AltRight', true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffea, 'AltRight', true, null, null);
});
it('should pass through single AltGr', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'AltRight', key: 'AltGraph', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xfe03, 'AltRight', true);
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xfe03, 'AltRight', true, null, null);
});
});
describe('Missing Shift keyup on Windows', function () {
let origNavigator;
let clock;
beforeEach(function () {
// window.navigator is a protected read-only property in many
// environments, so we need to redefine it whilst running these
@ -584,53 +605,53 @@ describe('Key event handling', function () {
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.platform = "Windows x86_64";
this.clock = sinon.useFakeTimers();
clock = vi.useFakeTimers();
});
afterEach(function () {
Object.defineProperty(window, "navigator", origNavigator);
if (this.clock !== undefined) {
this.clock.restore();
if (clock !== undefined) {
clock.restoreAllMocks();
}
});
it('should fake a left Shift keyup', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe1, 'ShiftLeft', true, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe2, 'ShiftRight', true, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftLeft', key: 'Shift', location: 1}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe1, 'ShiftLeft', false, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffe2, 'ShiftRight', false, null, null);
});
it('should fake a right Shift keyup', function () {
const kbd = new Keyboard(document);
kbd.onkeyevent = sinon.spy();
kbd.onkeyevent = vi.fn();
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftLeft', key: 'Shift', location: 1}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', true);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe1, 'ShiftLeft', true, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyDown(keyevent('keydown', {code: 'ShiftRight', key: 'Shift', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledOnce;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', true);
kbd.onkeyevent.resetHistory();
expect(kbd.onkeyevent).toHaveBeenCalledOnce();
expect(kbd.onkeyevent).toHaveBeenCalledWith(0xffe2, 'ShiftRight', true, null, null);
kbd.onkeyevent.mockClear();
kbd._handleKeyUp(keyevent('keyup', {code: 'ShiftRight', key: 'Shift', location: 2}));
expect(kbd.onkeyevent).to.have.been.calledTwice;
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe2, 'ShiftRight', false);
expect(kbd.onkeyevent).to.have.been.calledWith(0xffe1, 'ShiftLeft', false);
expect(kbd.onkeyevent).toHaveBeenCalledTimes(2);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(1, 0xffe2, 'ShiftRight', false, null, null);
expect(kbd.onkeyevent).toHaveBeenNthCalledWith(2, 0xffe1, 'ShiftLeft', false, null, null);
});
});
});

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
import _, { Localizer, l10n } from '../app/localization.js';
describe('Localization', function () {
@ -15,11 +16,11 @@ describe('Localization', function () {
Object.defineProperty(window, "navigator", {value: {}});
window.navigator.languages = [];
fetch = sinon.stub(window, "fetch");
fetch.resolves(new Response("{}"));
fetch = vi.spyOn(window, "fetch");
fetch.mockResolvedValue(new Response("{}"));
});
afterEach(function () {
fetch.restore();
vi.restoreAllMocks();
Object.defineProperty(window, "navigator", origNavigator);
});
@ -31,7 +32,7 @@ describe('Localization', function () {
it('should export a singleton translation function', async function () {
// FIXME: Can we use some spy instead?
window.navigator.languages = ["de"];
fetch.resolves(new Response(JSON.stringify({ "Foobar": "gazonk" })));
fetch.mockResolvedValue(new Response(JSON.stringify({ "Foobar": "gazonk" })));
await l10n.setup(["de"]);
expect(_("Foobar")).to.equal("gazonk");
});
@ -103,35 +104,38 @@ describe('Localization', function () {
window.navigator.languages = [];
let lclz = new Localizer();
await lclz.setup([]);
expect(fetch).to.not.have.been.called;
expect(fetch).not.toHaveBeenCalled();
});
it('should fetch dictionary relative base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
fetch.mockResolvedValue(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path/");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(fetch).toHaveBeenCalledOnce();
expect(fetch).toHaveBeenCalledWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle base URL without trailing slash', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
fetch.mockResolvedValue(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"], "/some/path");
expect(fetch).to.have.been.calledOnceWith("/some/path/fr.json");
expect(fetch).toHaveBeenCalledOnce();
expect(fetch).toHaveBeenCalledWith("/some/path/fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should handle current base URL', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{ "Foobar": "gazonk" }'));
fetch.mockResolvedValue(new Response('{ "Foobar": "gazonk" }'));
let lclz = new Localizer();
await lclz.setup(["ru", "fr"]);
expect(fetch).to.have.been.calledOnceWith("fr.json");
expect(fetch).toHaveBeenCalledOnce();
expect(fetch).toHaveBeenCalledWith("fr.json");
expect(lclz.get("Foobar")).to.equal("gazonk");
});
it('should fail if dictionary cannot be found', async function () {
window.navigator.languages = ["de", "fr"];
fetch.resolves(new Response('{}', { status: 404 }));
fetch.mockResolvedValue(new Response('{}', { status: 404 }));
let lclz = new Localizer();
let ok = false;
try {

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('Raw decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RawDecoder();

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -45,8 +46,8 @@ describe('RRE decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new RREDecoder();

File diff suppressed because it is too large Load Diff

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('Tight decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightDecoder();

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('TightPng decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new TightPngDecoder();

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeEach, afterEach } from 'vitest';
/* eslint-disable no-console */
import * as Log from '../core/util/logging.js';
import { encodeUTF8, decodeUTF8 } from '../core/util/strings.js';
@ -7,19 +8,19 @@ describe('Utils', function () {
describe('logging functions', function () {
beforeEach(function () {
sinon.spy(console, 'log');
sinon.spy(console, 'debug');
sinon.spy(console, 'warn');
sinon.spy(console, 'error');
sinon.spy(console, 'info');
vi.spyOn(console, 'log');
vi.spyOn(console, 'debug');
vi.spyOn(console, 'warn');
vi.spyOn(console, 'error');
vi.spyOn(console, 'info');
});
afterEach(function () {
console.log.restore();
console.debug.restore();
console.warn.restore();
console.error.restore();
console.info.restore();
console.log.mockRestore();
console.debug.mockRestore();
console.warn.mockRestore();
console.error.mockRestore();
console.info.mockRestore();
Log.initLogging();
});
@ -27,33 +28,33 @@ describe('Utils', function () {
Log.initLogging('warn');
Log.Debug('hi');
Log.Info('hello');
expect(console.log).to.not.have.been.called;
expect(console.log).not.toHaveBeenCalled();
});
it('should use console.debug for Debug', function () {
Log.initLogging('debug');
Log.Debug('dbg');
expect(console.debug).to.have.been.calledWith('dbg');
expect(console.debug).toHaveBeenCalledWith('dbg');
});
it('should use console.info for Info', function () {
Log.initLogging('debug');
Log.Info('inf');
expect(console.info).to.have.been.calledWith('inf');
expect(console.info).toHaveBeenCalledWith('inf');
});
it('should use console.warn for Warn', function () {
Log.initLogging('warn');
Log.Warn('wrn');
expect(console.warn).to.have.been.called;
expect(console.warn).to.have.been.calledWith('wrn');
expect(console.warn).toHaveBeenCalled();
expect(console.warn).toHaveBeenCalledWith('wrn');
});
it('should use console.error for Error', function () {
Log.initLogging('error');
Log.Error('err');
expect(console.error).to.have.been.called;
expect(console.error).to.have.been.calledWith('err');
expect(console.error).toHaveBeenCalled();
expect(console.error).toHaveBeenCalledWith('err');
});
});

View File

@ -1,3 +1,4 @@
import { vi, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import Websock from '../core/websock.js';
import FakeWebSocket from './fake.websocket.js';
@ -352,7 +353,7 @@ describe('Websock', function () {
describe('lifecycle methods', function () {
let oldWS;
before(function () {
beforeAll(function () {
oldWS = WebSocket;
});
@ -360,7 +361,11 @@ describe('Websock', function () {
beforeEach(function () {
sock = new Websock();
// eslint-disable-next-line no-global-assign
WebSocket = sinon.spy(FakeWebSocket);
WebSocket = vi.fn(FakeWebSocket);
});
afterEach(function () {
vi.restoreAllMocks();
});
describe('opening', function () {
@ -370,7 +375,7 @@ describe('Websock', function () {
it('should open the actual websocket', function () {
sock.open('ws://localhost:8675', 'binary');
expect(WebSocket).to.have.been.calledWith('ws://localhost:8675', 'binary');
expect(WebSocket).toHaveBeenCalledWith('ws://localhost:8675', 'binary');
});
// it('should initialize the event handlers')?
@ -380,79 +385,79 @@ describe('Websock', function () {
it('should attach to an existing websocket', function () {
let ws = new FakeWebSocket('ws://localhost:8675');
sock.attach(ws);
expect(WebSocket).to.not.have.been.called;
expect(WebSocket).not.toHaveBeenCalled();
});
});
describe('closing', function () {
beforeEach(function () {
sock.open('ws://localhost');
sock._websocket.close = sinon.spy();
sock._websocket.close = vi.fn();
});
it('should close the actual websocket if it is open', function () {
sock._websocket.readyState = WebSocket.OPEN;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
expect(sock._websocket.close).toHaveBeenCalledOnce();
});
it('should close the actual websocket if it is connecting', function () {
sock._websocket.readyState = WebSocket.CONNECTING;
sock.close();
expect(sock._websocket.close).to.have.been.calledOnce;
expect(sock._websocket.close).toHaveBeenCalledOnce();
});
it('should not try to close the actual websocket if closing', function () {
sock._websocket.readyState = WebSocket.CLOSING;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
expect(sock._websocket.close).not.toHaveBeenCalled();
});
it('should not try to close the actual websocket if closed', function () {
sock._websocket.readyState = WebSocket.CLOSED;
sock.close();
expect(sock._websocket.close).not.to.have.been.called;
expect(sock._websocket.close).not.toHaveBeenCalled();
});
it('should reset onmessage to not call _recvMessage', function () {
sinon.spy(sock, '_recvMessage');
vi.spyOn(sock, '_recvMessage');
sock.close();
sock._websocket.onmessage(null);
try {
expect(sock._recvMessage).not.to.have.been.called;
expect(sock._recvMessage).not.toHaveBeenCalled();
} finally {
sock._recvMessage.restore();
sock._recvMessage.mockRestore();
}
});
});
describe('event handlers', function () {
beforeEach(function () {
sock._recvMessage = sinon.spy();
sock.on('open', sinon.spy());
sock.on('close', sinon.spy());
sock.on('error', sinon.spy());
sock._recvMessage = vi.fn();
sock.on('open', vi.fn());
sock.on('close', vi.fn());
sock.on('error', vi.fn());
sock.open('ws://localhost');
});
it('should call _recvMessage on a message', function () {
sock._websocket.onmessage(null);
expect(sock._recvMessage).to.have.been.calledOnce;
expect(sock._recvMessage).toHaveBeenCalledOnce();
});
it('should call the open event handler on opening', function () {
sock._websocket.onopen();
expect(sock._eventHandlers.open).to.have.been.calledOnce;
expect(sock._eventHandlers.open).toHaveBeenCalledOnce();
});
it('should call the close event handler on closing', function () {
sock._websocket.onclose();
expect(sock._eventHandlers.close).to.have.been.calledOnce;
expect(sock._eventHandlers.close).toHaveBeenCalledOnce();
});
it('should call the error event handler on error', function () {
sock._websocket.onerror();
expect(sock._eventHandlers.error).to.have.been.calledOnce;
expect(sock._eventHandlers.error).toHaveBeenCalledOnce();
});
});
@ -543,7 +548,7 @@ describe('Websock', function () {
});
});
after(function () {
afterAll(function () {
// eslint-disable-next-line no-global-assign
WebSocket = oldWS;
});
@ -563,19 +568,19 @@ describe('Websock', function () {
});
it('should call the message event handler if present', function () {
sock._eventHandlers.message = sinon.spy();
sock._eventHandlers.message = vi.fn();
const msg = { data: new Uint8Array([1, 2, 3]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._eventHandlers.message).to.have.been.calledOnce;
expect(sock._eventHandlers.message).toHaveBeenCalledOnce();
});
it('should not call the message event handler if there is nothing in the receive queue', function () {
sock._eventHandlers.message = sinon.spy();
sock._eventHandlers.message = vi.fn();
const msg = { data: new Uint8Array([]).buffer };
sock._mode = 'binary';
sock._recvMessage(msg);
expect(sock._eventHandlers.message).not.to.have.been.called;
expect(sock._eventHandlers.message).not.toHaveBeenCalled();
});
it('should compact the receive queue when fully read', function () {

View File

@ -1,5 +1,6 @@
/* jshint expr: true */
import { vi, describe, it, expect, beforeAll, afterAll, beforeEach, afterEach } from 'vitest';
import * as WebUtil from '../app/webutil.js';
describe('WebUtil', function () {
@ -52,19 +53,17 @@ describe('WebUtil', function () {
});
});
describe('cookies', function () {
// TODO
});
describe.todo('cookies');
describe('settings', function () {
describe('localStorage', function () {
let chrome = window.chrome;
before(function () {
beforeAll(function () {
chrome = window.chrome;
window.chrome = null;
});
after(function () {
afterAll(function () {
window.chrome = chrome;
});
@ -74,9 +73,9 @@ describe('WebUtil', function () {
Object.defineProperty(window, "localStorage", {value: {}});
window.localStorage.setItem = sinon.stub();
window.localStorage.getItem = sinon.stub();
window.localStorage.removeItem = sinon.stub();
window.localStorage.setItem = vi.fn();
window.localStorage.getItem = vi.fn();
window.localStorage.removeItem = vi.fn();
return WebUtil.initSettings();
});
@ -87,12 +86,12 @@ describe('WebUtil', function () {
describe('writeSetting', function () {
it('should save the setting value to local storage', function () {
WebUtil.writeSetting('test', 'value');
expect(window.localStorage.setItem).to.have.been.calledWithExactly('test', 'value');
expect(window.localStorage.setItem).toHaveBeenCalledWith('test', 'value');
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should not crash when local storage save fails', function () {
localStorage.setItem.throws(new DOMException());
localStorage.setItem.mockImplementation(() => { throw new DOMException(); });
expect(WebUtil.writeSetting('test', 'value')).to.not.throw;
});
});
@ -100,14 +99,14 @@ describe('WebUtil', function () {
describe('setSetting', function () {
it('should update the setting but not save to local storage', function () {
WebUtil.setSetting('test', 'value');
expect(window.localStorage.setItem).to.not.have.been.called;
expect(window.localStorage.setItem).not.toHaveBeenCalled();
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
describe('readSetting', function () {
it('should read the setting value from local storage', function () {
localStorage.getItem.returns('value');
localStorage.getItem.mockReturnValue('value');
expect(WebUtil.readSetting('test')).to.equal('value');
});
@ -116,33 +115,33 @@ describe('WebUtil', function () {
});
it('should return the cached value even if local storage changed', function () {
localStorage.getItem.returns('value');
localStorage.getItem.mockReturnValue('value');
expect(WebUtil.readSetting('test')).to.equal('value');
localStorage.getItem.returns('something else');
localStorage.getItem.mockReturnValue('something else');
expect(WebUtil.readSetting('test')).to.equal('value');
});
it('should cache the value even if it is not initially in local storage', function () {
expect(WebUtil.readSetting('test')).to.be.null;
localStorage.getItem.returns('value');
localStorage.getItem.mockReturnValue('value');
expect(WebUtil.readSetting('test')).to.be.null;
});
it('should return the default value always if the first read was not in local storage', function () {
expect(WebUtil.readSetting('test', 'default')).to.equal('default');
localStorage.getItem.returns('value');
localStorage.getItem.mockReturnValue('value');
expect(WebUtil.readSetting('test', 'another default')).to.equal('another default');
});
it('should return the last local written value', function () {
localStorage.getItem.returns('value');
localStorage.getItem.mockReturnValue('value');
expect(WebUtil.readSetting('test')).to.equal('value');
WebUtil.writeSetting('test', 'something else');
expect(WebUtil.readSetting('test')).to.equal('something else');
});
it('should not crash when local storage read fails', function () {
localStorage.getItem.throws(new DOMException());
localStorage.getItem.mockImplementation(() => { throw new DOMException(); });
expect(WebUtil.readSetting('test')).to.not.throw;
});
});
@ -151,11 +150,11 @@ describe('WebUtil', function () {
describe('eraseSetting', function () {
it('should remove the setting from local storage', function () {
WebUtil.eraseSetting('test');
expect(window.localStorage.removeItem).to.have.been.calledWithExactly('test');
expect(window.localStorage.removeItem).toHaveBeenCalledWith('test');
});
it('should not crash when local storage remove fails', function () {
localStorage.removeItem.throws(new DOMException());
localStorage.removeItem.mockImplementation(() => { throw new DOMException(); });
expect(WebUtil.eraseSetting('test')).to.not.throw;
});
});
@ -164,7 +163,7 @@ describe('WebUtil', function () {
describe('chrome.storage', function () {
let chrome = window.chrome;
let settings = {};
before(function () {
beforeAll(function () {
chrome = window.chrome;
window.chrome = {
storage: {
@ -176,25 +175,25 @@ describe('WebUtil', function () {
}
};
});
after(function () {
afterAll(function () {
window.chrome = chrome;
});
beforeEach(function () {
settings = {};
sinon.spy(window.chrome.storage.sync, 'set');
sinon.spy(window.chrome.storage.sync, 'remove');
vi.spyOn(window.chrome.storage.sync, 'set');
vi.spyOn(window.chrome.storage.sync, 'remove');
return WebUtil.initSettings();
});
afterEach(function () {
window.chrome.storage.sync.set.restore();
window.chrome.storage.sync.remove.restore();
window.chrome.storage.sync.set.mockRestore();
window.chrome.storage.sync.remove.mockRestore();
});
describe('writeSetting', function () {
it('should save the setting value to chrome storage', function () {
WebUtil.writeSetting('test', 'value');
expect(window.chrome.storage.sync.set).to.have.been.calledWithExactly(sinon.match({ test: 'value' }));
expect(window.chrome.storage.sync.set).toHaveBeenCalledWith(expect.objectContaining({ test: 'value' }));
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
@ -202,7 +201,7 @@ describe('WebUtil', function () {
describe('setSetting', function () {
it('should update the setting but not save to chrome storage', function () {
WebUtil.setSetting('test', 'value');
expect(window.chrome.storage.sync.set).to.not.have.been.called;
expect(window.chrome.storage.sync.set).not.toHaveBeenCalled();
expect(WebUtil.readSetting('test')).to.equal('value');
});
});
@ -229,7 +228,7 @@ describe('WebUtil', function () {
describe('eraseSetting', function () {
it('should remove the setting from chrome storage', function () {
WebUtil.eraseSetting('test');
expect(window.chrome.storage.sync.remove).to.have.been.calledWithExactly('test');
expect(window.chrome.storage.sync.remove).toHaveBeenCalledWith('test');
});
});
});

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('Zlib decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new ZlibDecoder();

View File

@ -1,3 +1,4 @@
import { describe, it, expect, beforeAll, afterAll, beforeEach } from 'vitest';
import Websock from '../core/websock.js';
import Display from '../core/display.js';
@ -33,8 +34,8 @@ describe('ZRLE decoder', function () {
let decoder;
let display;
before(FakeWebSocket.replace);
after(FakeWebSocket.restore);
beforeAll(FakeWebSocket.replace);
afterAll(FakeWebSocket.restore);
beforeEach(function () {
decoder = new ZRLEDecoder();

18
vitest.config.js Normal file
View File

@ -0,0 +1,18 @@
import { defineConfig } from 'vitest/config';
import { playwright } from '@vitest/browser-playwright';
export default defineConfig({
test: {
setupFiles: "./tests/assertions.js",
browser: {
enabled: true,
provider: playwright(),
// https://vitest.dev/guide/browser/playwright
instances: [
{ browser: 'chromium' },
{ browser: 'firefox' },
{ browser: 'webkit' },
],
},
},
});