From 55f1ea2b8d9d5447d482d102382e853801c6aecb Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 3 May 2024 14:22:57 +0700 Subject: [PATCH 1/7] chore(web): converts web end-to-end style tests --- web/package.json | 3 + web/src/test/auto/integrated/cases/basics.js | 158 ---------------- .../auto/integrated/cases/basics.spec.mjs | 172 ++++++++++++++++++ .../cases/{engine.js => engine.spec.mjs} | 124 +++++++------ ...chirality.js => engine_chirality.spec.mjs} | 34 ++-- .../cases/{events.js => events.spec.mjs} | 45 +++-- .../test/auto/integrated/cases/test_config.js | 29 --- ...t_selection.js => text_selection.spec.mjs} | 37 ++-- web/src/test/auto/integrated/test_utils.js | 36 ++-- .../integrated/web-test-runner.config.mjs | 71 ++++++++ web/test.sh | 6 +- 11 files changed, 403 insertions(+), 312 deletions(-) delete mode 100644 web/src/test/auto/integrated/cases/basics.js create mode 100644 web/src/test/auto/integrated/cases/basics.spec.mjs rename web/src/test/auto/integrated/cases/{engine.js => engine.spec.mjs} (68%) rename web/src/test/auto/integrated/cases/{engine_chirality.js => engine_chirality.spec.mjs} (55%) rename web/src/test/auto/integrated/cases/{events.js => events.spec.mjs} (77%) delete mode 100644 web/src/test/auto/integrated/cases/test_config.js rename web/src/test/auto/integrated/cases/{text_selection.js => text_selection.spec.mjs} (96%) create mode 100644 web/src/test/auto/integrated/web-test-runner.config.mjs diff --git a/web/package.json b/web/package.json index 357126aae4d..79c54619ef7 100644 --- a/web/package.json +++ b/web/package.json @@ -68,6 +68,9 @@ "import": "./build/engine/osk/obj/test-index.js" } }, + "imports": { + "#recorder": "./build/tools/testing/recorder/obj/index.js" + }, "repository": { "type": "git", "url": "git+https://github.com/keymanapp/keyman.git" diff --git a/web/src/test/auto/integrated/cases/basics.js b/web/src/test/auto/integrated/cases/basics.js deleted file mode 100644 index 8d2aa73bd51..00000000000 --- a/web/src/test/auto/integrated/cases/basics.js +++ /dev/null @@ -1,158 +0,0 @@ -import { assert } from '/node_modules/chai/chai.js'; - -import { DEVICE_DETECT_FAILURE, setupKMW, teardownKMW } from "../test_utils.js"; - -describe('Basic KeymanWeb', function() { - this.timeout(testconfig.timeouts.standard); - - before(function() { - // These tests require use of KMW's device-detection functionality. - assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); - }) - - beforeEach(function() { - this.timeout(testconfig.timeouts.scriptLoad); - - fixture.setBase('fixtures'); - fixture.load("singleInput.html"); - return setupKMW(null, testconfig.timeouts.scriptLoad); - }); - - afterEach(function() { - fixture.cleanup(); - teardownKMW(); - }); - - describe('Initialization', function() { - it('KMW should attach to the input element.', function() { - var singleton = document.getElementById('singleton'); - assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!"); - }); - it('KMW\'s initialization variable should indicate completion.', function() { - assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!'); - }); - }); -}); - -Modernizr.on('touchevents', function(result) { - // Warning: desktop Chrome gives `result == true`! - // It _does_ at least prevent a problem on mobile-app platforms, though. - if(!result) { - describe('Basic Toggle UI', function() { - this.timeout(testconfig.timeouts.scriptLoad); - - beforeEach(function() { - this.timeout(testconfig.timeouts.uiLoad); - fixture.setBase('fixtures'); - fixture.load('singleInput.html'); - - // Loads two scripts in parallel, but just in case, 2x timeout. - return setupKMW('toggle', testconfig.timeouts.uiLoad); - }); - - afterEach(function() { - fixture.cleanup(); - teardownKMW(); - }); - - it('The Toggle UI initializes correctly.', function() { - assert(keyman.ui.initialized, 'Initialization flag is set to false!'); - - assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!'); - - var divs = document.getElementsByTagName("div"); - var match = false; - - for(var i=0; i < divs.length; i++) { - if(divs[i] == keyman.ui.controller) { - match = true; - } - } - - assert(match, 'Controller element has not been added to the page!'); - }) - }); - - describe('Basic Button UI', function() { - - beforeEach(function() { - this.timeout(testconfig.timeouts.uiLoad); - fixture.setBase('fixtures'); - fixture.load('singleInput.html'); - - // Loads two scripts in parallel, but just in case, 2x timeout. - return setupKMW('button', testconfig.timeouts.uiLoad); - }); - - afterEach(function() { - fixture.cleanup(); - teardownKMW(); - }); - - it('The Button UI initializes correctly.', function() { - assert(keyman.ui.init, 'Initialization flag is set to false!'); - }) - }); - - describe('Basic Float UI', function() { - - beforeEach(function() { - this.timeout(testconfig.timeouts.uiLoad); - fixture.setBase('fixtures'); - fixture.load('singleInput.html'); - - // Loads two scripts in parallel, but just in case, 2x timeout. - return setupKMW('float', testconfig.timeouts.uiLoad); - }); - - afterEach(function() { - fixture.cleanup(); - teardownKMW(); - }); - - it('The Float UI initializes correctly.', function() { - assert(keyman.ui.initialized, 'Initialization flag is set to false!'); - - assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); - - var divs = document.getElementsByTagName("div"); - var match = false; - - for(var i=0; i < divs.length; i++) { - if(divs[i] == keyman.ui.outerDiv) { - match = true; - } - } - - assert(match, 'Floating controller element has not been added to the page!'); - }) - }); - - describe('Basic Toolbar UI', function() { - - beforeEach(function() { - this.timeout(testconfig.timeouts.uiLoad); - fixture.setBase('fixtures'); - fixture.load('singleInput.html'); - - // Loads two scripts in parallel, but just in case, 2x timeout. - return setupKMW('toolbar', testconfig.timeouts.uiLoad); - }); - - afterEach(function() { - fixture.cleanup(); - teardownKMW(); - }); - - it('The Toolbar UI initializes correctly.', function() { - assert(keyman.ui.init, 'Initialization flag is set to false!'); - - var kwc = document.getElementById('KeymanWebControl'); - assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!'); - - var toolbar = document.getElementById('kmw_controls'); - assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!'); - }) - }); - } -}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/basics.spec.mjs b/web/src/test/auto/integrated/cases/basics.spec.mjs new file mode 100644 index 00000000000..4c9267a05bc --- /dev/null +++ b/web/src/test/auto/integrated/cases/basics.spec.mjs @@ -0,0 +1,172 @@ +import { assert } from 'chai'; + +import { DEVICE_DETECT_FAILURE, setupKMW, teardownKMW } from "../test_utils.js"; +import { Device } from "keyman/engine/device-detect"; + +const baseTimeout = 5000; + +const host = document.createElement('div'); +document.body.appendChild(host); + +describe('Basic KeymanWeb', function() { + this.timeout(baseTimeout); + + before(function() { + // These tests require use of KMW's device-detection functionality. + assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); + }) + + beforeEach(function() { + this.timeout(baseTimeout); + + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); + + return setupKMW(null, baseTimeout); + }); + + afterEach(function() { + host.innerHTML = ''; + teardownKMW(); + }); + + describe('Initialization', function() { + it('KMW should attach to the input element.', function() { + var singleton = document.getElementById('singleton'); + assert.isTrue(keyman.isAttached(singleton), "KeymanWeb did not automatically attach to the element!"); + }); + it('KMW\'s initialization variable should indicate completion.', function() { + assert(keyman.initialized == 2, 'Keyman indicates incomplete initialization!'); + }); + }); +}); + +const device = new Device(); +device.detect(); +if(!device.touchable) { + describe('Basic Toggle UI', function() { + this.timeout(baseTimeout); + + beforeEach(async function() { + this.timeout(baseTimeout); + + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('toggle', baseTimeout); + }); + + afterEach(function() { + host.innerHTML = ''; + teardownKMW(); + }); + + it('The Toggle UI initializes correctly.', function() { + assert(keyman.ui.initialized, 'Initialization flag is set to false!'); + + assert.isNotNull(keyman.ui.controller, 'Failed to create the controller element!'); + + var divs = document.getElementsByTagName("div"); + var match = false; + + for(var i=0; i < divs.length; i++) { + if(divs[i] == keyman.ui.controller) { + match = true; + } + } + + assert(match, 'Controller element has not been added to the page!'); + }) + }); + + describe('Basic Button UI', function() { + + beforeEach(async function() { + this.timeout(baseTimeout); + + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('button', baseTimeout); + }); + + afterEach(function() { + host.innerHTML = ''; + teardownKMW(); + }); + + it('The Button UI initializes correctly.', function() { + assert(keyman.ui.init, 'Initialization flag is set to false!'); + }) + }); + + describe('Basic Float UI', function() { + + beforeEach(async function() { + this.timeout(baseTimeout); + + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('float', baseTimeout); + }); + + afterEach(function() { + host.innerHTML = ''; + teardownKMW(); + }); + + it('The Float UI initializes correctly.', function() { + assert(keyman.ui.initialized, 'Initialization flag is set to false!'); + + assert.isNotNull(keyman.ui.outerDiv, 'Failed to create the floating controller element!'); + + var divs = document.getElementsByTagName("div"); + var match = false; + + for(var i=0; i < divs.length; i++) { + if(divs[i] == keyman.ui.outerDiv) { + match = true; + } + } + + assert(match, 'Floating controller element has not been added to the page!'); + }) + }); + + describe('Basic Toolbar UI', function() { + + beforeEach(async function() { + this.timeout(baseTimeout); + + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); + + // Loads two scripts in parallel, but just in case, 2x timeout. + return setupKMW('toolbar', baseTimeout); + }); + + afterEach(function() { + host.innerHTML = ''; + teardownKMW(); + }); + + it('The Toolbar UI initializes correctly.', function() { + assert(keyman.ui.init, 'Initialization flag is set to false!'); + + var kwc = document.getElementById('KeymanWebControl'); + assert.isNotNull(kwc, 'Toolbar DIV was not added to the page!'); + + var toolbar = document.getElementById('kmw_controls'); + assert.isNotNull(toolbar, 'The main toolbar element was not added to the page!'); + }) + }); +} \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/engine.js b/web/src/test/auto/integrated/cases/engine.spec.mjs similarity index 68% rename from web/src/test/auto/integrated/cases/engine.js rename to web/src/test/auto/integrated/cases/engine.spec.mjs index ba66d645a6c..20dfca64955 100644 --- a/web/src/test/auto/integrated/cases/engine.js +++ b/web/src/test/auto/integrated/cases/engine.spec.mjs @@ -1,4 +1,4 @@ -import { assert } from '/node_modules/chai/chai.js'; +import { assert } from 'chai'; import { loadKeyboardFromJSON, @@ -6,22 +6,33 @@ import { setupKMW, teardownKMW } from "../test_utils.js"; -import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; +import * as KMWRecorder from '#recorder'; + +/** @type {HTMLInputElement} */ +let inputElem; + +// The OSK is appended to the body; we need the ability to clear things out without nullifying the OSK. +const host = document.createElement('div'); +document.body.appendChild(host); + +const baseTimeout = 5000; +const eventTimeout = 500; describe('Engine - Browser Interactions', function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); before(function() { - fixture.setBase('fixtures'); - return setupKMW(null, testconfig.timeouts.scriptLoad); + return setupKMW(null, baseTimeout); }); beforeEach(function(done) { - fixture.load("singleInput.html"); + inputElem = document.createElement('input'); + inputElem.id = 'singleton'; + host.appendChild(inputElem); window.setTimeout(function() { done() - }, testconfig.timeouts.eventDelay); + }, eventTimeout); }); after(function() { @@ -29,23 +40,17 @@ describe('Engine - Browser Interactions', function() { }); afterEach(function() { - fixture.cleanup(); + host.innerHTML = ''; }); describe('RegisterStub', function() { it('RegisterStub on same keyboard twice', async function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); - var test_callback = async function() { - assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); - assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); - keyman.removeKeyboards('lao_2008_basic'); - await Promise.resolve(); // so that the keyboard can reset. - assert.equal(keyman.getActiveKeyboard(), '', "Keyboard not removed correctly!"); - } + await loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", baseTimeout); - let finalPromise = loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad) - .then(test_callback); + assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); + assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); var stub = { 'KI': 'Keyboard_lao_2008_basic', @@ -58,21 +63,18 @@ describe('Engine - Browser Interactions', function() { await Promise.resolve(); assert.equal(KeymanWeb.registerStub(stub), 1, "Registering existing keyboard should return 1!"); - return finalPromise; }); - }); describe('Variable Stores', function() { - this.timeout(testconfig.timeouts.scriptLoad + testconfig.timeouts.standard); + this.timeout(baseTimeout + baseTimeout); beforeEach(function() { - return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); + return loadKeyboardFromJSON("resources/json/keyboards/options_with_save.json", baseTimeout); }); after(function() { keyman.removeKeyboards('options_with_save'); - fixture.cleanup(); }); it('Backing up and restoring (loadStore/saveStore)', function() { @@ -94,7 +96,7 @@ describe('Engine - Browser Interactions', function() { return Promise.resolve(); }).then(() => { - return loadKeyboardFromJSON("/keyboards/options_with_save.json", testconfig.timeouts.scriptLoad); + return loadKeyboardFromJSON("resources/json/keyboards/options_with_save.json", baseTimeout); }).then(() => { return keyman.setActiveKeyboard(keyboardID, 'en'); }).then(() => { @@ -107,7 +109,7 @@ describe('Engine - Browser Interactions', function() { }); it("Multiple-sequence check", function() { - this.timeout(testconfig.timeouts.standard + testconfig.timeouts.scriptLoad * 3); + this.timeout(baseTimeout + baseTimeout * 3); var keyboardID = "options_with_save"; var storeName = "foo"; @@ -118,10 +120,10 @@ describe('Engine - Browser Interactions', function() { await Promise.resolve(); }).then(() => { // First test: expects option to be "on" from cookie-init setting, emitting "foo.", then turning option "off". - return runKeyboardTestFromJSON('/engine_tests/options_with_save_1.json', + return runKeyboardTestFromJSON('resources/json/engine_tests/options_with_save_1.json', {usingOSK: false}, assert.equal, - testconfig.timeouts.scriptLoad) + baseTimeout) }).then(async () => { // Reset the keyboard... again. keyman.removeKeyboards(keyboardID); @@ -129,10 +131,10 @@ describe('Engine - Browser Interactions', function() { await Promise.resolve(); // Second test: expects option to still be "off" b/c cookies. - return runKeyboardTestFromJSON('/engine_tests/options_with_save_2.json', + return runKeyboardTestFromJSON('resources/json/engine_tests/options_with_save_2.json', {usingOSK: false}, assert.equal, - testconfig.timeouts.scriptLoad); + baseTimeout); }); }); }); @@ -140,22 +142,20 @@ describe('Engine - Browser Interactions', function() { // Performs basic processing system checks/tests to ensure the sequence testing // is based on correct assumptions about the code. describe('Integrated Simulation Checks', function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(baseTimeout); before(function() { - this.timeout = testconfig.timeouts.scriptLoad; - return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", testconfig.timeouts.scriptLoad); + this.timeout = baseTimeout; + return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", baseTimeout); }); beforeEach(function() { - var inputElem = document.getElementById('singleton'); keyman.setActiveElement(inputElem); inputElem.value = ""; }); after(function() { keyman.removeKeyboards('lao_2008_basic'); - fixture.cleanup(); }); /** @@ -165,8 +165,6 @@ describe('Engine - Browser Interactions', function() { * See #8830. */ it('Simple Keypress', function() { - var inputElem = document.getElementById('singleton'); - var lao_s_key_json = {"type": "key", "key":"s", "code":"KeyS","keyCode":83,"modifierSet":0,"location":0}; var lao_s_event = new KMWRecorder.PhysicalInputEventSpec(lao_s_key_json); @@ -180,8 +178,6 @@ describe('Engine - Browser Interactions', function() { }); it('Simple OSK click', async function() { - var inputElem = document.getElementById('singleton'); - var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'}; var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json); @@ -196,32 +192,35 @@ describe('Engine - Browser Interactions', function() { }); describe('Sequence Simulation Checks', function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); it('Keyboard simulation', async function() { - return await runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad); + return await runKeyboardTestFromJSON('resources/json/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, baseTimeout); }); it('OSK simulation', async function() { - return await runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); + return await runKeyboardTestFromJSON('resources/json/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, baseTimeout); }) }); }); describe('Unmatched Final Groups', function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); before(function() { - fixture.setBase('fixtures'); - return setupKMW(null, testconfig.timeouts.scriptLoad + testconfig.timeouts.eventDelay); + return setupKMW(null, baseTimeout + eventTimeout); }); beforeEach(function(done) { - fixture.load("singleTextArea.html"); + // Do NOT use an here, as that has special return-char handling that + // complicates matters. + const textArea = document.createElement('textarea'); + textArea.id = 'singleton'; + host.appendChild(textArea); window.setTimeout(function() { done() - }, testconfig.timeouts.eventDelay); + }, eventTimeout); }); after(function() { @@ -229,40 +228,39 @@ describe('Unmatched Final Groups', function() { }); afterEach(function() { - fixture.cleanup(); + host.innerHTML = ''; }); it('matches rule from early group AND performs default behavior', async function() { // While a TAB-oriented version would be nice, it's much harder to write the test // to detect change in last input element. - return await runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); + return await runKeyboardTestFromJSON('resources/json/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, baseTimeout); }); }); // Kept separate to maintain an extra-clean setup for this test. describe('Engine - Browser Interactions', function() { - this.timeout(testconfig.timeouts.scriptLoad); - - before(function() { - fixture.setBase('fixtures'); - }); + this.timeout(baseTimeout); beforeEach(function() { - fixture.load("singleInput.html"); - return setupKMW(null, testconfig.timeouts.scriptLoad); + inputElem = document.createElement('input'); + inputElem.id = 'singleton'; + host.appendChild(inputElem); + + return setupKMW(null, baseTimeout); }); afterEach(function() { - fixture.cleanup(); + host.innerHTML = ''; teardownKMW(); }); describe('Keyboard Loading', function() { it('Local', function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); - return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", - testconfig.timeouts.scriptLoad).then(async function() { + return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", + baseTimeout).then(async function() { assert.isNotNull(keyman.getKeyboard("lao_2008_basic", "lo"), "Keyboard stub was not registered!"); assert.equal(keyman.getActiveKeyboard(), "Keyboard_lao_2008_basic", "Keyboard not set automatically!"); keyman.removeKeyboards('lao_2008_basic'); @@ -273,10 +271,10 @@ describe('Engine - Browser Interactions', function() { }); it('Automatically sets first available keyboard', function() { - this.timeout(2 * testconfig.timeouts.scriptLoad); + this.timeout(2 * baseTimeout); - return loadKeyboardFromJSON("/keyboards/lao_2008_basic.json", - testconfig.timeouts.scriptLoad, + return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", + baseTimeout, {passive: true}).then(() => { // Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called // on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's @@ -292,7 +290,7 @@ describe('Engine - Browser Interactions', function() { } window.clearTimeout(intervalTimer); - }, testconfig.timeouts.scriptLoad); + }, baseTimeout); let intervalTimer = window.setInterval(() => { if(keyman.getActiveKeyboard() != '') { diff --git a/web/src/test/auto/integrated/cases/engine_chirality.js b/web/src/test/auto/integrated/cases/engine_chirality.spec.mjs similarity index 55% rename from web/src/test/auto/integrated/cases/engine_chirality.js rename to web/src/test/auto/integrated/cases/engine_chirality.spec.mjs index 43e664f7e5f..b074c80d95e 100644 --- a/web/src/test/auto/integrated/cases/engine_chirality.js +++ b/web/src/test/auto/integrated/cases/engine_chirality.spec.mjs @@ -1,4 +1,4 @@ -import { assert } from '/node_modules/chai/chai.js'; +import { assert } from 'chai'; import { loadKeyboardFromJSON, @@ -7,20 +7,23 @@ import { teardownKMW } from "../test_utils.js"; +const baseTimeout = 5000; +const eventTimeout = 500; + +const host = document.createElement('div'); +document.body.appendChild(host); + describe('Engine - Chirality', function() { - this.timeout(testconfig.timeouts.scriptLoad); + this.timeout(baseTimeout); before(function() { - fixture.setBase('fixtures'); - return setupKMW(null, testconfig.timeouts.scriptLoad); + return setupKMW(null, baseTimeout); }); - beforeEach(function(done) { - fixture.load("singleInput.html"); - - window.setTimeout(function() { - done() - }, testconfig.timeouts.eventDelay); + beforeEach(async function() { + const singleton = document.createElement('input'); + singleton.id = 'singleton'; + host.appendChild(singleton); }); after(function() { @@ -28,27 +31,24 @@ describe('Engine - Chirality', function() { }); afterEach(function() { - fixture.cleanup(); + host.innerHTML = ''; }); it('Keyboard + OSK simulation', async function() { - this.timeout(testconfig.timeouts.scriptLoad * (testconfig.mobile ? 1 : 2)); /* Interestingly, this still works on iOS, probably because we're able to force-set * the 'location' property in the simulated event on mobile devices, even when iOS neglects to * set it for real events. */ - return await runKeyboardTestFromJSON('/engine_tests/chirality.json', + return await runKeyboardTestFromJSON('resources/json/engine_tests/chirality.json', {usingOSK: false}, assert.equal, - testconfig.timeouts.scriptLoad).then(async () => { + baseTimeout).then(async () => { /* We only really care to test the 'desktop' OSK because of how it directly models the modifier keys. * * The 'phone' and 'layout' versions take shortcuts that bypass any tricky chiral logic; * a better test for those would be to ensure the touch OSK is constructed properly. */ - if(!testconfig.mobile) { - return await runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad); - } + return await runKeyboardTestFromJSON('resources/json/engine_tests/chirality.json', {usingOSK: true}, assert.equal, baseTimeout); }); }); }); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/events.js b/web/src/test/auto/integrated/cases/events.spec.mjs similarity index 77% rename from web/src/test/auto/integrated/cases/events.js rename to web/src/test/auto/integrated/cases/events.spec.mjs index 41eb42bcfe6..d18d9fba4df 100644 --- a/web/src/test/auto/integrated/cases/events.js +++ b/web/src/test/auto/integrated/cases/events.spec.mjs @@ -1,4 +1,4 @@ -import { assert } from '/node_modules/chai/chai.js'; +import { assert } from 'chai'; import { loadKeyboardFromJSON, @@ -6,23 +6,42 @@ import { setupKMW, teardownKMW } from "../test_utils.js"; -import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; +import * as KMWRecorder from '#recorder'; +import { timedPromise } from '@keymanapp/web-utils'; + +const host = document.createElement('div'); +document.body.appendChild(host); + +const attachmentTimeout = 500; describe('Event Management', function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(5000); before(function() { - this.timeout(testconfig.timeouts.scriptLoad * 2); - fixture.setBase('fixtures'); - fixture.load("eventTestConfig.html"); - - return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { + return setupKMW(null, 5000).then(() => { // We use this keyboard since we only need minimal input functionality for these tests. // Smaller is better when dealing with net latency. - return loadKeyboardFromJSON("/keyboards/test_simple_deadkeys.json", testconfig.timeouts.scriptLoad); + return loadKeyboardFromJSON("resources/json/keyboards/test_simple_deadkeys.json", 5000); }); }); + // Async, to give the mutation observers a chance to fire first. + beforeEach(async function() { + const input = document.createElement('input'); + input.id = 'input'; + const textarea = document.createElement('textarea'); + textarea.id = 'textarea'; + + host.appendChild(input); + host.appendChild(textarea); + + await timedPromise(attachmentTimeout); + }); + + afterEach(function() { + host.innerHTML = '' + }); + after(function() { teardownKMW(); }); @@ -31,7 +50,7 @@ describe('Event Management', function() { var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); - var ele = document.getElementById("input"); + var ele = document.getElementById('input'); ele.onchange = function() { ele.onchange = null; @@ -54,7 +73,7 @@ describe('Event Management', function() { var simple_A = {"type":"osk","keyID":"default-K_A"}; var event = new KMWRecorder.OSKInputEventSpec(simple_A); - var ele = document.getElementById("input"); + var ele = document.getElementById('input'); ele.onchange = function() { ele.onchange = null; @@ -82,7 +101,7 @@ describe('Event Management', function() { var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); - var ele = document.getElementById("input"); + var ele = document.getElementById('input'); keyman.setActiveElement(ele); var counterObj = {i:0}; @@ -104,7 +123,7 @@ describe('Event Management', function() { var simple_A = {"type":"osk","keyID":"default-K_A"}; var event = new KMWRecorder.OSKInputEventSpec(simple_A); - var ele = document.getElementById("input"); + var ele = document.getElementById('input'); keyman.setActiveElement(ele); var counterObj = {i:0}; diff --git a/web/src/test/auto/integrated/cases/test_config.js b/web/src/test/auto/integrated/cases/test_config.js deleted file mode 100644 index 18b95c672a7..00000000000 --- a/web/src/test/auto/integrated/cases/test_config.js +++ /dev/null @@ -1,29 +0,0 @@ -import { assert } from '/node_modules/chai/chai.js'; - -/* - * A canary test to ensure our timeout configurations are working correctly for our CI. - */ -describe('Test configuration', function () { - this.timeout(testconfig.timeouts.standard); - - it('Correctly retrieves timeout configurations', function() { - let configArgs = __karma__.config.args; - assert.isNotNull(configArgs, "Cannot find the custom arguments object from our Karma config."); - - var timeoutConfig; - - for(var i=0; i < configArgs.length; i++) { - if(configArgs[i].type == 'timeouts') { - timeoutConfig = configArgs[i]; - } - } - - assert.isNotNull(timeoutConfig, "Cannot find the timeout configuration object."); - - // Now, were they processed correctly? - assert.isNotNull(window['testconfig'], "Test global 'config' is missing!"); - let timeouts = testconfig.timeouts; - // May be longer if an extra factor (such as a mobile device multiplier) is added. - assert.isAtLeast(timeouts.standard, timeoutConfig.standard, "Timeouts were not parsed properly for test configuration."); - }) -}); \ No newline at end of file diff --git a/web/src/test/auto/integrated/cases/text_selection.js b/web/src/test/auto/integrated/cases/text_selection.spec.mjs similarity index 96% rename from web/src/test/auto/integrated/cases/text_selection.js rename to web/src/test/auto/integrated/cases/text_selection.spec.mjs index 377bb359167..df674035a69 100644 --- a/web/src/test/auto/integrated/cases/text_selection.js +++ b/web/src/test/auto/integrated/cases/text_selection.spec.mjs @@ -1,4 +1,4 @@ -import { assert } from '/node_modules/chai/chai.js'; +import { assert } from 'chai'; import { DEVICE_DETECT_FAILURE, @@ -6,10 +6,16 @@ import { setupKMW, teardownKMW } from "../test_utils.js"; -import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; +import * as KMWRecorder from '#recorder'; +import { timedPromise } from '@keymanapp/web-utils'; + +const baseTimeout = 5000; +const attachmentTimeout = 500; +const host = document.createElement('div'); +document.body.appendChild(host); describe('Text Selection', function() { - this.timeout(testconfig.timeouts.standard); + this.timeout(baseTimeout); /* Utility functions */ @@ -55,28 +61,31 @@ describe('Text Selection', function() { before(function() { // These tests require use of KMW's device-detection functionality. assert.isFalse(DEVICE_DETECT_FAILURE, "Cannot run due to device detection failure."); - fixture.setBase('fixtures'); - fixture.load("single"+inputType+".html"); - this.timeout(testconfig.timeouts.scriptLoad*2); - return setupKMW(null, testconfig.timeouts.scriptLoad).then(() => { - return loadKeyboardFromJSON("/keyboards/web_context_tests.json", testconfig.timeouts.scriptLoad).then(() => { + return setupKMW(null, baseTimeout).then(() => { + return loadKeyboardFromJSON("resources/json/keyboards/web_context_tests.json", baseTimeout).then(() => { return keyman.setActiveKeyboard("web_context_tests"); }); }); }); + beforeEach(async function() { + const ele = document.createElement(inputType); + ele.id = 'singleton'; + host.appendChild(ele); + + await timedPromise(attachmentTimeout); + }); + after(function() { - fixture.cleanup(); window.keyman?.removeKeyboards('web_context_tests'); teardownKMW(); }); - afterEach(function(done) { - fixture.load("single"+inputType+".html"); - window.setTimeout(function(){ - done(); - }, testconfig.timeouts.eventDelay); + afterEach(async function() { + host.innerHTML = ''; + + Promise.resolve(); }); /* diff --git a/web/src/test/auto/integrated/test_utils.js b/web/src/test/auto/integrated/test_utils.js index 9d0227bae52..4151b698baf 100644 --- a/web/src/test/auto/integrated/test_utils.js +++ b/web/src/test/auto/integrated/test_utils.js @@ -1,10 +1,15 @@ // // KeymanWeb test suite - processing of the Karma configuration's client.args parameter. -import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs'; -import * as KMWRecorder from '/@keymanapp/keyman/build/tools/testing/recorder/lib/index.mjs'; +import Device from 'keyman/engine/device-detect'; +import * as KMWRecorder from '#recorder'; export let DEVICE_DETECT_FAILURE = false; +const loc = document.location; +// config.testFile generally starts with a '/', with the path resembling the actual full local +// filesystem for the drive. +const domain = `${loc.protocol}/${loc.host}` + // If we've set things up to support Device dection without loading KMW... try { let device = new Device(); @@ -25,7 +30,8 @@ export function setupKMW(kmwOptions, timeout) { var kmwOptions = { attachType:'auto', root:'/', - resources:'/build/app/resources' + // up from 'browser/debug' + resources:'../../resources' }; if(ui) { @@ -33,23 +39,23 @@ export function setupKMW(kmwOptions, timeout) { } } - const kmwPromise = setupScript('build/app/browser/keymanweb.js', timeout); + const kmwPromise = setupScript('web/build/app/browser/debug/keymanweb.js', timeout); ui = kmwOptions.ui; kmwOptions.attachType = kmwOptions.attachType ? kmwOptions.attachType : 'auto'; if(!kmwOptions.root) { - kmwOptions.root = '/build/app/browser'; + kmwOptions.root = 'web/build/app/browser/debug'; } if(!kmwOptions.resources) { - kmwOptions.resources = '/build/resources'; + kmwOptions.resources = 'web/build/app/resources'; } let uiPromise; if(ui) { - uiPromise = setupScript('build/app/ui/kmwui' + ui + '.js', timeout); + uiPromise = setupScript('web/build/app/ui/debug/kmwui' + ui + '.js', timeout); kmwOptions.ui=ui; } @@ -140,7 +146,7 @@ function setupScriptInternal(src, timeout, attemptCount, existingTimer) { } Lscript.src = src; - fixture.el.appendChild(Lscript); + document.body.appendChild(Lscript); }); return promise; @@ -196,7 +202,8 @@ export async function loadKeyboardStub(stub, timeout, params) { } export async function loadKeyboardFromJSON(jsonPath, timeout, params) { - var stub = fixture.load(jsonPath, true); + const jsonResponse = await fetch(new URL(`${domain}/${jsonPath}`)); + const stub = await jsonResponse.json(); return loadKeyboardStub(stub, timeout, params); } @@ -209,7 +216,10 @@ async function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) } export async function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) { - var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true)); + const jsonResponse = await fetch(new URL(`${domain}/${jsonPath}`)); + const testJSON = await jsonResponse.json(); + + var testSpec = new KMWRecorder.KeyboardTest(testJSON); let device = new Device(); device.detect(); @@ -288,7 +298,7 @@ if(typeof(DynamicElements) == 'undefined') { if(loadCallback) { frame.addEventListener('load', function() { // Give KMW's attachment events a chance to run first. - window.setTimeout(loadCallback, Math.max(100, testconfig.timeouts.scriptLoad)); + window.setTimeout(loadCallback, Math.max(100, 5000)); }); } frame.setAttribute("src", "resources/html/iframe.html"); @@ -338,7 +348,7 @@ if(typeof(DynamicElements) == 'undefined') { window.setTimeout(function() { assertion(); done(); - }, testconfig.timeouts.eventDelay); + }, 5000); } else { assertion(); } @@ -352,7 +362,7 @@ if(typeof(DynamicElements) == 'undefined') { window.setTimeout(function() { assertion(); done(); - }, testconfig.timeouts.eventDelay); + }, 5000); } else { assertion(); } diff --git a/web/src/test/auto/integrated/web-test-runner.config.mjs b/web/src/test/auto/integrated/web-test-runner.config.mjs new file mode 100644 index 00000000000..5a526aa0a1f --- /dev/null +++ b/web/src/test/auto/integrated/web-test-runner.config.mjs @@ -0,0 +1,71 @@ +// @ts-check +import { devices, playwrightLauncher } from '@web/test-runner-playwright'; +import { summaryReporter } from '@web/test-runner'; +import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; +import { importMapsPlugin } from '@web/dev-server-import-maps'; +import { dirname, resolve } from 'path'; +import { fileURLToPath } from 'url'; + +const dir = dirname(fileURLToPath(import.meta.url)); +const KEYMAN_ROOT = resolve(dir, '../../../../../'); + +/** @type {import('@web/test-runner').TestRunnerConfig} */ +export default { + browsers: [ + // These are the same type - and probably the same _instances_ - as are visible within the reporter! + // Probably a helpful fact to resolve name disambiguation. + playwrightLauncher({ product: 'chromium' }), + playwrightLauncher({ product: 'firefox' }), + playwrightLauncher({ product: 'webkit' }) + ], + // Setting it higher makes things faster... but Webkit experiences stability + // issues for some of the tests if this is set higher than 1. Notably, + // engine.spec.mjs, events.spec.mjs, and text_selection.spec.mjs. All the + // text-simulation ones. + concurrency: 1, + nodeResolve: true, + files: [ + 'src/test/auto/integrated/**/*.spec.mjs', + // '**/*.spec.html' + ], + middleware: [ + // Rewrites short-hand paths for test resources, making them fully relative to the repo root. + function rewriteResourcePath(context, next) { + if(context.url.startsWith('/resources/')) { + context.url = '/common/test' + context.url; + } + + return next(); + } + ], + plugins: [ + importMapsPlugin({ + inject: { + importMap: { + // Redirects `eventemitter3` imports to the bundled ESM library. The standard import is an + // ESM wrapper around the CommonJS implementation, and WTR fails when it hits the CommonJS. + imports: { + 'eventemitter3': '/node_modules/eventemitter3/dist/eventemitter3.esm.js' + } + } + } + }) + ], + reporters: [ + summaryReporter({}), /* local-dev mocha-style */ + teamcityReporter() /* custom-written, for CI-friendly reports */ + ], + /* + Un-comment the next two lines for easy interactive debugging; it'll launch the + test page in your preferred browser. + + WARNING: https://github.com/modernweb-dev/web/issues/2721 may cause issues when + using manual mode. Changing rootDir to the drive root (or similar) may provide + a decent workaround; it appears that Web Test Runner can do a little searching + for node_modules if and when necessary. + */ + // debug: true, + // open: true, + // manual: true, + rootDir: KEYMAN_ROOT +} \ No newline at end of file diff --git a/web/test.sh b/web/test.sh index cbf5c0c0962..b7365f26700 100755 --- a/web/test.sh +++ b/web/test.sh @@ -85,10 +85,6 @@ if builder_start_action test:integrated; then KARMA_EXT_FLAGS="${BROWSERS}" fi - # Build modernizr module - modernizr -c src/test/auto/integrated/modernizr.config.json -d src/test/auto/integrated/modernizr.js - # shellcheck disable=SC2086 - karma start ${KARMA_FLAGS} ${KARMA_EXT_FLAGS} "${KEYMAN_ROOT}/web/src/test/auto/integrated/${CONFIG}" - + web-test-runner --config src/test/auto/integrated/web-test-runner.config.mjs builder_finish_action success test:integrated fi From 14d42de3a90acec10ee3c65d12f1e9bceb298389 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 7 May 2024 10:39:31 +0700 Subject: [PATCH 2/7] chore(web): drops global concurency restriction in favor of WebKit-specific restriction --- web/src/test/auto/integrated/web-test-runner.config.mjs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/web/src/test/auto/integrated/web-test-runner.config.mjs b/web/src/test/auto/integrated/web-test-runner.config.mjs index 5a526aa0a1f..08936a6a47e 100644 --- a/web/src/test/auto/integrated/web-test-runner.config.mjs +++ b/web/src/test/auto/integrated/web-test-runner.config.mjs @@ -16,13 +16,13 @@ export default { // Probably a helpful fact to resolve name disambiguation. playwrightLauncher({ product: 'chromium' }), playwrightLauncher({ product: 'firefox' }), - playwrightLauncher({ product: 'webkit' }) + playwrightLauncher({ product: 'webkit', concurrency: 1 }) ], // Setting it higher makes things faster... but Webkit experiences stability // issues for some of the tests if this is set higher than 1. Notably, // engine.spec.mjs, events.spec.mjs, and text_selection.spec.mjs. All the // text-simulation ones. - concurrency: 1, + concurrency: 10, nodeResolve: true, files: [ 'src/test/auto/integrated/**/*.spec.mjs', From 40d01c25f2f9f12f6a12064bed98676fa6692156 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Tue, 7 May 2024 13:28:13 +0700 Subject: [PATCH 3/7] chore(web): main + child project Web tests - local vs CI reporting --- web/src/test/auto/integrated/CI.conf.cjs | 4 - web/src/test/auto/integrated/base.conf.cjs | 126 ------------------ web/src/test/auto/integrated/manual.conf.cjs | 26 ---- .../integrated/web-test-runner.CI.config.mjs | 11 ++ .../integrated/web-test-runner.config.mjs | 2 - web/test.sh | 20 +-- 6 files changed, 12 insertions(+), 177 deletions(-) delete mode 100644 web/src/test/auto/integrated/CI.conf.cjs delete mode 100644 web/src/test/auto/integrated/base.conf.cjs delete mode 100644 web/src/test/auto/integrated/manual.conf.cjs create mode 100644 web/src/test/auto/integrated/web-test-runner.CI.config.mjs diff --git a/web/src/test/auto/integrated/CI.conf.cjs b/web/src/test/auto/integrated/CI.conf.cjs deleted file mode 100644 index 94e94a0fc80..00000000000 --- a/web/src/test/auto/integrated/CI.conf.cjs +++ /dev/null @@ -1,4 +0,0 @@ -var BASE_CONFIG = require("./base.conf.cjs"); -var ci_config_adapter = require("../../../../../common/test/resources/karma-browserstack-config.cjs"); - -module.exports = ci_config_adapter(BASE_CONFIG, "full"); \ No newline at end of file diff --git a/web/src/test/auto/integrated/base.conf.cjs b/web/src/test/auto/integrated/base.conf.cjs deleted file mode 100644 index 1f2790f2b9e..00000000000 --- a/web/src/test/auto/integrated/base.conf.cjs +++ /dev/null @@ -1,126 +0,0 @@ -// Karma configuration -// Generated on Mon Jan 29 2018 11:58:53 GMT+0700 (SE Asia Standard Time) - -// Super-useful! http://www.bradoncode.com/blog/2015/02/27/karma-tutorial/#handling-html-fixtures - -module.exports = { - // base path that will be used to resolve all patterns (eg. files, exclude) - // set to Keyman repo base. - basePath: '../../../../..', - - // Safeguards against disconnecting after starting tests. - browserNoActivityTimeout: 60000, - - client: { - /* `client.args` here is passed to the test runner page as `__karma__.config.args`. - * - * Karma doc type spec says "array", so we use an array. It also gives us room to add alternate - * configuration details later if we need to, though on a CI vs local basis only. - * - * Timeouts below are in milliseconds - */ - args: [{ - type: "timeouts", // This base is designed for local machine testing. - eventDelay: 60, // Designed for small delays to allow time for event handling to occur before proceeding. - // Make sure this stays under 1/4 of 'standard', as multiple eventDelays may occur within a test. - standard: 5000, - scriptLoad: 8000, - uiLoad: 30000, // Loads two scripts + includes internal setup/timeout time requirements. - // At this time of writing this, UI script loading is one of the longest checks. - mobileFactor: 1 // An extra timeout modifier to be applied when running on a mobile device. - }] - }, - - // frameworks to use - // available frameworks: https://npmjs.org/browse/keyword/karma-adapter - frameworks: ['mocha', 'fixture'], - - // list of files / patterns to load in the browser - files: [ - { pattern: 'node_modules/chai/chai.js', watched: true, served: true, included: false, type: 'module'}, - 'web/src/test/auto/integrated/modernizr.js', // A dependency-managed utility script that helps with browser feature detection. - {pattern: 'web/src/test/auto/integrated/test_init_check.js', type: 'module'}, // Ensures that tests will initialize properly - 'common/test/resources/timeout-adapter.js', // Handles configuration timeout setup at runtime. - {pattern: 'web/src/test/auto/integrated/test_utils.js', type: 'module'}, // A basic utility script useful for constructing tests - - {pattern: 'web/src/test/auto/integrated/cases/**/*.js', type: 'module'}, // Where the tests actually reside. - 'common/test/resources/json/**/*.json', // Where pre-loaded JSON resides. - - {pattern: 'web/build/**/*.js', watched: true, served: true, included: false}, // Includes all top-level KMW products - {pattern: 'web/build/**/*.js.map', watched: true, served: true, included: false}, // and their sourcemaps. - {pattern: 'web/build/**/*.mjs', watched: true, served: true, included: false}, // Includes all top-level KMW products - {pattern: 'web/build/**/*.mjs.map', watched: true, served: true, included: false}, // and their sourcemaps. - - { pattern: 'common/predictive-text/build/obj/**/*.*', watched: true, served: true, included: false }, - { pattern: 'common/predictive-text/build/obj/**/*.js.map', watched: true, served: true, included: false }, - // { pattern: 'common/web/lm-worker/build/lib/*.js', watched: true, served: true, included: false}, - // { pattern: 'common/web/lm-worker/build/lib/*.js.map', watched: true, served: true, included: false} - - {pattern: 'common/test/resources/fixtures/**/*.html', watched: true}, // HTML structures useful for testing. - {pattern: 'common/test/resources/**/*.*', watched: true, served: true, included: false}, // General testing resources. - {pattern: 'web/build/app/resources/**/*.css', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/resources/**/*.gif', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/resources/**/*.png', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/resources/**/*.ttf', watched: false, served: true, included: false}, // OSK resources - {pattern: 'web/build/app/web/debug/*.js', watched: true, served: true, included: false}, // The actual KMW code. - {pattern: 'web/build/app/web/debug/*.map', watched: true, served: true, included: false}, // + sourcemaps. - {pattern: 'web/build/app/ui/debug/*.js', watched: true, served: true, included: false}, // The actual KMW UI code. - {pattern: 'web/build/app/ui/debug/*.map', watched: true, served: true, included: false}, // + sourcemaps. - {pattern: 'web/build/tools/testing/recorder/obj/index.js.map', watched: true, served: true, included: false}, - {pattern: 'web/build/engine/element-wrappers/obj/index.bundled.js.map', watched: true, served: true, included: false}, - {pattern: 'web/build/engine/device-detect/obj/index.bundled.js.map', watched: true, served: true, included: false}, - {pattern: 'common/web/*/build/lib/*.mjs', watched: true, served: true, included: false}, - {pattern: 'common/web/*/build/lib/*.mjs.map', watched: true, served: true, included: false} - ], - - proxies: { - "/build/app/browser/": "/base/web/build/app/browser/debug/", - "/build/app/resources/": "/base/web/build/app/resources/", - "/build/app/ui/": "/base/web/build/app/ui/debug/", - "/resources/": "/base/common/test/resources/", - "/source/recorder_InputEvents.js.map": "/base/common/tests/recorder_InputEvents.js.map", - "/node_modules/": "/base/node_modules/", - "/@keymanapp/web-utils/": "/base/common/web/utils/", - "/@keymanapp/keyman/": "/base/web/", - "/@keymanapp/keyboard-processor/": "/base/common/web/keyboard-processor/" - }, - - - // list of files / patterns to exclude - exclude: [ - ], - - // preprocess matching files before serving them to the browser - // available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor - preprocessors: { - 'common/test/resources/fixtures/**/*.html' : ['html2js'], - 'common/test/resources/json/**/*.json' : ['json_fixtures'] - }, - - // The fixture preprocessor assumes that all fixtures will rely under a common directory, - // only accepting a single 'stripPrefix' string match, etc. - // Reference: https://github.com/karma-runner/karma-html2js-preprocessor/blob/master/lib/html2js.js - html2JsPreprocessor: { - stripPrefix: 'common/test/resources/' - }, - - // Settings to properly configure how JSON fixtures are automatically loaded by Karma. - jsonFixturesPreprocessor: { - stripPrefix: 'common/test/resources/json', - variableName: '__json__' - }, - - // web server port - port: 9876, - - // enable / disable colors in the output (reporters and logs) - colors: true, - - // enable / disable watching file and executing tests whenever any file changes - autoWatch: true, - - // Continuous Integration mode - // if true, Karma captures browsers, runs the tests and exits - // if false, it generates the pages and acts as a persistent server. - singleRun: true, -} diff --git a/web/src/test/auto/integrated/manual.conf.cjs b/web/src/test/auto/integrated/manual.conf.cjs deleted file mode 100644 index 1969c400503..00000000000 --- a/web/src/test/auto/integrated/manual.conf.cjs +++ /dev/null @@ -1,26 +0,0 @@ -module.exports = function(config) { - var base = require("./base.conf.cjs"); - - var specifics = { - // test results reporter to use - // possible values: 'dots', 'progress' - // available reporters: https://npmjs.org/browse/keyword/karma-reporter - reporters: ['mocha'], - - // start these browsers - // available browser launchers: https://npmjs.org/browse/keyword/karma-launcher - browsers: ['Firefox', 'Chrome'], // Can be specified at run-time instead! - // Future note for us: https://www.npmjs.com/package/karma-browserstack-launcher - - // Concurrency level - // how many browser should be started simultaneous - concurrency: Infinity, - - // level of logging - // possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG - // Can't set in base.conf.cjs b/c of constant's definition style. - logLevel: config.LOG_INFO - }; - - config.set(Object.assign(specifics, base)); -} diff --git a/web/src/test/auto/integrated/web-test-runner.CI.config.mjs b/web/src/test/auto/integrated/web-test-runner.CI.config.mjs new file mode 100644 index 00000000000..aa6568b3e56 --- /dev/null +++ b/web/src/test/auto/integrated/web-test-runner.CI.config.mjs @@ -0,0 +1,11 @@ +// @ts-check +import BASE_CONFIG from './web-test-runner.config.mjs'; +import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; + +/** @type {import('@web/test-runner').TestRunnerConfig} */ +export default { + ...BASE_CONFIG, + reporters: [ + teamcityReporter() /* custom-written, for CI-friendly reports */ + ] +} \ No newline at end of file diff --git a/web/src/test/auto/integrated/web-test-runner.config.mjs b/web/src/test/auto/integrated/web-test-runner.config.mjs index 08936a6a47e..52ecfae4dfc 100644 --- a/web/src/test/auto/integrated/web-test-runner.config.mjs +++ b/web/src/test/auto/integrated/web-test-runner.config.mjs @@ -1,7 +1,6 @@ // @ts-check import { devices, playwrightLauncher } from '@web/test-runner-playwright'; import { summaryReporter } from '@web/test-runner'; -import teamcityReporter from '@keymanapp/common-test-resources/test-runner-TC-reporter.mjs'; import { importMapsPlugin } from '@web/dev-server-import-maps'; import { dirname, resolve } from 'path'; import { fileURLToPath } from 'url'; @@ -53,7 +52,6 @@ export default { ], reporters: [ summaryReporter({}), /* local-dev mocha-style */ - teamcityReporter() /* custom-written, for CI-friendly reports */ ], /* Un-comment the next two lines for easy interactive debugging; it'll launch the diff --git a/web/test.sh b/web/test.sh index b7365f26700..b1dbc01263d 100755 --- a/web/test.sh +++ b/web/test.sh @@ -69,22 +69,4 @@ fi builder_run_action test:dom web-test-runner --config "src/test/auto/dom/web-test-runner${WTR_CONFIG}.config.mjs" "${WTR_DEBUG}" -# The multi-browser test suite, which uses BrowserStack when run by our CI. -if builder_start_action test:integrated; then - if builder_has_option --ci && builder_is_debug_build; then - builder_die "Options --ci and --debug are incompatible." - fi - - # Auto-select browsers if not specified as an option - if ! builder_has_option --browsers; then - get_default_browser_set - fi - - KARMA_EXT_FLAGS= - if ! builder_has_option --ci; then - KARMA_EXT_FLAGS="${BROWSERS}" - fi - - web-test-runner --config src/test/auto/integrated/web-test-runner.config.mjs - builder_finish_action success test:integrated -fi +builder_run_action test:integrated web-test-runner --config "src/test/auto/integrated/web-test-runner${WTR_CONFIG}.config.mjs" "${WTR_DEBUG}" From a31fa36ec0ff7d5329031a1aabfb6ef9292241e7 Mon Sep 17 00:00:00 2001 From: "Joshua A. Horton" Date: Fri, 10 May 2024 13:44:52 +0700 Subject: [PATCH 4/7] chore(web): converts 'integrated' (e2e-ish) Web auto-tests to TS --- web/src/app/browser/src/contextManager.ts | 2 +- web/src/app/browser/src/keymanEngine.ts | 2 +- web/src/app/browser/src/test-index.ts | 3 +- .../cases/{basics.spec.mjs => basics.spec.ts} | 39 ++++-- .../cases/{engine.spec.mjs => engine.spec.ts} | 43 ++++-- ...lity.spec.mjs => engine_chirality.spec.ts} | 1 - .../cases/{events.spec.mjs => events.spec.ts} | 12 +- ...ection.spec.mjs => text_selection.spec.ts} | 39 +++--- ..._init_check.js => test_init_check.spec.ts} | 6 +- .../{test_utils.js => test_utils.ts} | 127 ++++++++++-------- web/src/test/auto/integrated/tsconfig.json | 9 ++ .../integrated/web-test-runner.CI.config.mjs | 5 +- .../integrated/web-test-runner.config.mjs | 25 ++-- 13 files changed, 196 insertions(+), 117 deletions(-) rename web/src/test/auto/integrated/cases/{basics.spec.mjs => basics.spec.ts} (76%) rename web/src/test/auto/integrated/cases/{engine.spec.mjs => engine.spec.ts} (87%) rename web/src/test/auto/integrated/cases/{engine_chirality.spec.mjs => engine_chirality.spec.ts} (98%) rename web/src/test/auto/integrated/cases/{events.spec.mjs => events.spec.ts} (89%) rename web/src/test/auto/integrated/cases/{text_selection.spec.mjs => text_selection.spec.ts} (91%) rename web/src/test/auto/integrated/{test_init_check.js => test_init_check.spec.ts} (87%) rename web/src/test/auto/integrated/{test_utils.js => test_utils.ts} (76%) create mode 100644 web/src/test/auto/integrated/tsconfig.json diff --git a/web/src/app/browser/src/contextManager.ts b/web/src/app/browser/src/contextManager.ts index 435c8d68dfc..b553bb50b7d 100644 --- a/web/src/app/browser/src/contextManager.ts +++ b/web/src/app/browser/src/contextManager.ts @@ -227,7 +227,7 @@ export default class ContextManager extends ContextManagerBase, sendEvents: boolean) { + public setActiveTarget(target: OutputTarget, sendEvents?: boolean) { const previousTarget = this.mostRecentTarget; const originalTarget = this.activeTarget; // may differ, depending on focus state. diff --git a/web/src/app/browser/src/keymanEngine.ts b/web/src/app/browser/src/keymanEngine.ts index 189541dd53e..9555fea2f7b 100644 --- a/web/src/app/browser/src/keymanEngine.ts +++ b/web/src/app/browser/src/keymanEngine.ts @@ -546,7 +546,7 @@ export default class KeymanEngine extends KeymanEngineBase { // This requires proper storage to a cookie, as we'll be on a new instance of the same keyboard. - var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, 0); - assert.equal(value, 1, "Did not properly save and reload variable store setting"); + var value = KeymanWeb.loadStore(prefixedKeyboardID, storeName, '0'); + assert.equal(value, '1', "Did not properly save and reload variable store setting"); }).finally(() => { - KeymanWeb.saveStore(storeName, 0); + KeymanWeb.saveStore(storeName, '0'); }); }); it("Multiple-sequence check", function() { this.timeout(baseTimeout + baseTimeout * 3); + const keyman: KeymanEngine = window['keyman']; + const KeymanWeb: KeyboardInterface = window['KeymanWeb']; + var keyboardID = "options_with_save"; var storeName = "foo"; return keyman.setActiveKeyboard(keyboardID, 'en').then(async function() { - KeymanWeb.saveStore(storeName, 1); + KeymanWeb.saveStore(storeName, '1'); keyman.removeKeyboards(keyboardID); await Promise.resolve(); @@ -145,16 +158,18 @@ describe('Engine - Browser Interactions', function() { this.timeout(baseTimeout); before(function() { - this.timeout = baseTimeout; + this.timeout(baseTimeout); return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", baseTimeout); }); beforeEach(function() { + const keyman: KeymanEngine = window['keyman']; keyman.setActiveElement(inputElem); inputElem.value = ""; }); after(function() { + const keyman: KeymanEngine = window['keyman']; keyman.removeKeyboards('lao_2008_basic'); }); @@ -179,7 +194,7 @@ describe('Engine - Browser Interactions', function() { it('Simple OSK click', async function() { var lao_s_osk_json = {"type": "osk", "keyID": 'shift-K_S'}; - var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json); + var lao_s_event = new KMWRecorder.OSKInputEventSpec(lao_s_osk_json as OSKInputEventSpec); let eventDriver = new KMWRecorder.BrowserDriver(inputElem); await eventDriver.simulateEvent(lao_s_event); @@ -258,6 +273,8 @@ describe('Engine - Browser Interactions', function() { describe('Keyboard Loading', function() { it('Local', function() { this.timeout(baseTimeout); + const keyman: KeymanEngine = window['keyman']; + const KeymanWeb: KeyboardInterface = window['KeymanWeb']; return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", baseTimeout).then(async function() { @@ -272,6 +289,8 @@ describe('Engine - Browser Interactions', function() { it('Automatically sets first available keyboard', function() { this.timeout(2 * baseTimeout); + const keyman: KeymanEngine = window['keyman']; + const KeymanWeb: KeyboardInterface = window['KeymanWeb']; return loadKeyboardFromJSON("resources/json/keyboards/lao_2008_basic.json", baseTimeout, @@ -279,7 +298,7 @@ describe('Engine - Browser Interactions', function() { // Because we're loading the keyboard 'passively', KMW's setActiveKeyboard function is auto-called // on the stub-add. That specific call (for first keyboard auto-activation) is outside of KMW's // current Promise chain, so we can't _directly_ rely on a KMW Promise to test it. - return new Promise((resolve) => { + return new Promise((resolve) => { let hasResolved = false; // So, we give KMW the time needed for auto-activation to happen, polling a bit actively so that we don't // wait unnecessarily long after it occurs. diff --git a/web/src/test/auto/integrated/cases/engine_chirality.spec.mjs b/web/src/test/auto/integrated/cases/engine_chirality.spec.ts similarity index 98% rename from web/src/test/auto/integrated/cases/engine_chirality.spec.mjs rename to web/src/test/auto/integrated/cases/engine_chirality.spec.ts index b074c80d95e..2969fe5d3e0 100644 --- a/web/src/test/auto/integrated/cases/engine_chirality.spec.mjs +++ b/web/src/test/auto/integrated/cases/engine_chirality.spec.ts @@ -1,7 +1,6 @@ import { assert } from 'chai'; import { - loadKeyboardFromJSON, runKeyboardTestFromJSON, setupKMW, teardownKMW diff --git a/web/src/test/auto/integrated/cases/events.spec.mjs b/web/src/test/auto/integrated/cases/events.spec.ts similarity index 89% rename from web/src/test/auto/integrated/cases/events.spec.mjs rename to web/src/test/auto/integrated/cases/events.spec.ts index d18d9fba4df..f4f5566ce66 100644 --- a/web/src/test/auto/integrated/cases/events.spec.mjs +++ b/web/src/test/auto/integrated/cases/events.spec.ts @@ -7,7 +7,10 @@ import { teardownKMW } from "../test_utils.js"; import * as KMWRecorder from '#recorder'; +import OSKInputEventSpec = KMWRecorder.OSKInputEventSpec; + import { timedPromise } from '@keymanapp/web-utils'; +import { type KeymanEngine } from 'keyman/app/browser'; const host = document.createElement('div'); document.body.appendChild(host); @@ -47,6 +50,8 @@ describe('Event Management', function() { }); it('Keystroke-based onChange event generation', async function() { + const keyman: KeymanEngine = window['keyman']; + var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); @@ -70,8 +75,9 @@ describe('Event Management', function() { }); it('OSK-based onChange event generation', async function() { + const keyman: KeymanEngine = window['keyman']; var simple_A = {"type":"osk","keyID":"default-K_A"}; - var event = new KMWRecorder.OSKInputEventSpec(simple_A); + var event = new KMWRecorder.OSKInputEventSpec(simple_A as OSKInputEventSpec); var ele = document.getElementById('input'); @@ -98,6 +104,7 @@ describe('Event Management', function() { }); it('Keystroke-based onInput event generation', async function() { + const keyman: KeymanEngine = window['keyman']; var simple_A = {"type":"key","key":"a","code":"KeyA","keyCode":65,"modifierSet":0,"location":0}; var event = new KMWRecorder.PhysicalInputEventSpec(simple_A); @@ -120,8 +127,9 @@ describe('Event Management', function() { }); it('OSK-based onInput event generation', async function() { + const keyman: KeymanEngine = window['keyman']; var simple_A = {"type":"osk","keyID":"default-K_A"}; - var event = new KMWRecorder.OSKInputEventSpec(simple_A); + var event = new KMWRecorder.OSKInputEventSpec(simple_A as OSKInputEventSpec); var ele = document.getElementById('input'); keyman.setActiveElement(ele); diff --git a/web/src/test/auto/integrated/cases/text_selection.spec.mjs b/web/src/test/auto/integrated/cases/text_selection.spec.ts similarity index 91% rename from web/src/test/auto/integrated/cases/text_selection.spec.mjs rename to web/src/test/auto/integrated/cases/text_selection.spec.ts index df674035a69..2b641a04249 100644 --- a/web/src/test/auto/integrated/cases/text_selection.spec.mjs +++ b/web/src/test/auto/integrated/cases/text_selection.spec.ts @@ -8,6 +8,7 @@ import { } from "../test_utils.js"; import * as KMWRecorder from '#recorder'; import { timedPromise } from '@keymanapp/web-utils'; +import { type KeymanEngine } from 'keyman/app/browser'; const baseTimeout = 5000; const attachmentTimeout = 500; @@ -20,12 +21,13 @@ describe('Text Selection', function() { /* Utility functions */ function setupElement(ele) { + const keyman: KeymanEngine = window['keyman']; keyman.setActiveElement(ele); return ele; } function assertInputSteps(ele, count, setup) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject) => { var i = 0; var listener = function() { i++; @@ -45,7 +47,7 @@ describe('Text Selection', function() { /* Define key event specs */ - var keys = {}; + let keys: {[id: string]: KMWRecorder.PhysicalInputEventSpec} = {}; for (var i = 0; i < 26; i++) { var simple = {"type":"key","key":String.fromCharCode(i+97),"code":"Key"+String.fromCharCode(i+65),"keyCode":i+65,"modifierSet":0,"location":0}; var key = new KMWRecorder.PhysicalInputEventSpec(simple); @@ -64,6 +66,7 @@ describe('Text Selection', function() { return setupKMW(null, baseTimeout).then(() => { return loadKeyboardFromJSON("resources/json/keyboards/web_context_tests.json", baseTimeout).then(() => { + const keyman: KeymanEngine = window['keyman']; return keyman.setActiveKeyboard("web_context_tests"); }); }); @@ -78,7 +81,8 @@ describe('Text Selection', function() { }); after(function() { - window.keyman?.removeKeyboards('web_context_tests'); + const keyman: KeymanEngine = window['keyman']; + keyman?.removeKeyboards('web_context_tests'); teardownKMW(); }); @@ -103,7 +107,7 @@ describe('Text Selection', function() { * Using the web_context_tests keyboard, type abcd. The output after the final key should be '!'. */ it('Should do a basic transform without selection involved', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); await assertInputSteps(ele, 5, () => { @@ -119,7 +123,7 @@ describe('Text Selection', function() { }); it('Should do a basic transform without selection involved - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); await assertInputSteps(ele, 5, () => { @@ -134,7 +138,8 @@ describe('Text Selection', function() { assert.deepEqual([ele.selectionStart, ele.selectionEnd], [4,4], "Expected caret to be at end of text"); }); - for(var direction of ['forward', 'backward']) { + // `as const` allows TS to lock in on the specific strings, rather than just saying 'string' as the type. + for(let direction of ['forward', 'backward'] as const) { /** * TEST_SELECTION @@ -143,7 +148,7 @@ describe('Text Selection', function() { * The output after the final key should be aqx. */ it('Should do a basic selection replacement, in '+direction+' direction', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -173,7 +178,7 @@ describe('Text Selection', function() { }); it('Should do a basic selection replacement, with a matching rule, in '+direction+' direction', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -203,7 +208,7 @@ describe('Text Selection', function() { }); it('Should do a basic selection replacement, in '+direction+' direction - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'fghj' @@ -239,7 +244,7 @@ describe('Text Selection', function() { * The output after the final key should be 'abcd'. */ it('Should ignore context when a selection, in '+direction+' direction, is made', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -269,7 +274,7 @@ describe('Text Selection', function() { }); it('Should ignore context when a selection, in '+direction+' direction, is made - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'fghj' @@ -305,7 +310,7 @@ describe('Text Selection', function() { * delete it with Backspace. Type d. The output after the final key should be !. */ it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -345,7 +350,7 @@ describe('Text Selection', function() { }); it('Should correctly delete selection, in '+direction+' direction, with backspace and not lose sync - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -391,7 +396,7 @@ describe('Text Selection', function() { * Press Backspace, and type d. The output after the final key should be !. */ it('Should correctly replace selection, in '+direction+' direction, and not lose sync', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -439,7 +444,7 @@ describe('Text Selection', function() { }); it('Should correctly replace selection, in '+direction+' direction, and not lose sync - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'abcx' @@ -493,7 +498,7 @@ describe('Text Selection', function() { * Select the abc characters, and type d. The output after the final key should be xdx. */ it('Should not treat the selection, in '+direction+' direction, as context', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: 'xabcx' @@ -524,7 +529,7 @@ describe('Text Selection', function() { }); it('Should not treat the selection, in '+direction+' direction, as context - SMP', async () => { - var ele = document.getElementById("singleton"); + var ele = document.getElementById("singleton") as HTMLInputElement | HTMLTextAreaElement; var eventDriver = instantiateBrowserDriver(ele); // Step 1: [k][f][g][h][k] diff --git a/web/src/test/auto/integrated/test_init_check.js b/web/src/test/auto/integrated/test_init_check.spec.ts similarity index 87% rename from web/src/test/auto/integrated/test_init_check.js rename to web/src/test/auto/integrated/test_init_check.spec.ts index 7eb95c1e21b..f51a280f560 100644 --- a/web/src/test/auto/integrated/test_init_check.js +++ b/web/src/test/auto/integrated/test_init_check.spec.ts @@ -1,8 +1,8 @@ // A simple "does device detection work properly" unit test. // We'll let things break after this reports, since this will likely signal a LOT of other failures. -import { assert } from '/node_modules/chai/chai.js'; +import { assert } from 'chai'; -import Device from '/@keymanapp/keyman/build/engine/device-detect/lib/index.mjs'; +import Device from 'keyman/engine/device-detect'; /* Note - we still have to prevent errors setting up test resources; * Karma will fail to report errors for affected browsers otherwise. @@ -16,7 +16,7 @@ describe('Test Initialization', function() { this.timeout(15000); it("Detects device without JS errors", function() { - var device; + let device: Device; try { device = new Device(); device.detect(); diff --git a/web/src/test/auto/integrated/test_utils.js b/web/src/test/auto/integrated/test_utils.ts similarity index 76% rename from web/src/test/auto/integrated/test_utils.js rename to web/src/test/auto/integrated/test_utils.ts index 4151b698baf..dabb206e701 100644 --- a/web/src/test/auto/integrated/test_utils.js +++ b/web/src/test/auto/integrated/test_utils.ts @@ -2,6 +2,8 @@ import Device from 'keyman/engine/device-detect'; import * as KMWRecorder from '#recorder'; +import { type BrowserInitOptionSpec, type KeymanEngine } from 'keyman/app/browser'; +import { ErrorStub, type KeyboardAPISpec, type KeyboardStub } from 'keyman/engine/package-cache'; export let DEVICE_DETECT_FAILURE = false; @@ -21,13 +23,13 @@ try { // // Keyman test suite utility methods -export function setupKMW(kmwOptions, timeout) { - let ui; +export function setupKMW(kmwOptions: BrowserInitOptionSpec | string, timeout: number) { + let ui: string; if(typeof(kmwOptions) == 'string' || typeof(kmwOptions) == 'undefined' || kmwOptions == null) { - ui = kmwOptions; + ui = kmwOptions as string; - var kmwOptions = { + kmwOptions = { attachType:'auto', root:'/', // up from 'browser/debug' @@ -59,7 +61,7 @@ export function setupKMW(kmwOptions, timeout) { kmwOptions.ui=ui; } - let compositePromise = kmwPromise; + let compositePromise: Promise = kmwPromise; if(uiPromise) { compositePromise = Promise.all([kmwPromise, uiPromise]); } @@ -82,17 +84,17 @@ export function setupKMW(kmwOptions, timeout) { * @param {*} timeout * @returns */ -export function setupScript(src, timeout) { +export function setupScript(src: string, timeout: number) { return setupScriptInternal(src, timeout); } -function setupScriptInternal(src, timeout, attemptCount, existingTimer) { +function setupScriptInternal(src: string, timeout: number, attemptCount?: number, existingTimer?: number) { attemptCount = attemptCount || 1; if(attemptCount > 1) { console.log("Re-attempting load of script '" + src + "': retry #" + attemptCount); } - let promise = new Promise((resolve, reject) => { + let promise = new Promise((resolve, reject) => { const Lscript = document.createElement('script'); let hasResolved = false; Lscript.charset="UTF-8"; // KMEW-89 @@ -106,9 +108,11 @@ function setupScriptInternal(src, timeout, attemptCount, existingTimer) { reject(new Error("Script load attempt for '" + src + "' timed out.")); }, timeout); + // @ts-ignore // TS does not recognize that