Skip to content

Commit

Permalink
fix(web): integrated test fixes, sync -> async for test sequences
Browse files Browse the repository at this point in the history
  • Loading branch information
jahorton committed Oct 9, 2023
1 parent 7d740a8 commit 28f1d25
Show file tree
Hide file tree
Showing 15 changed files with 168 additions and 100 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -151,6 +151,8 @@ export class MatcherSelector<Type> extends EventEmitter<EventMap<Type>> {
: (source.sources as GestureSourceSubview<Type>[]).map((source) => source.baseSource);

if(sourceNotYetStaged) {
// Cancellation before a first stage is possible; in this case, there's no sequence
// to trigger cleanup. We can do that here.
source.path.on('invalidated', () => {
this.cleanSourceIdsForSequence([source.identifier]);
})
Expand Down
4 changes: 2 additions & 2 deletions common/web/keyboard-processor/tests/node/basic-engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ describe('Engine - Basic Simulation', function() {
if(!proctor.compatibleWithSuite(testSuite)) {
it.skip(set.toTestName() + " - Cannot run this test suite on Node.");
} else {
it(set.toTestName(), function() {
it(set.toTestName(), async function() {
// Refresh the proctor instance at runtime.
let proctor = new NodeProctor(keyboardWithHarness, device, assert.equal);
set.test(proctor);
await set.test(proctor);
});
}
}
Expand Down
4 changes: 2 additions & 2 deletions common/web/keyboard-processor/tests/node/chirality.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ describe('Engine - Chirality', function() {
if(!proctor.compatibleWithSuite(testSuite)) {
it.skip(set.toTestName() + " - Cannot run this test suite on Node.");
} else if(set.constraint.target == 'hardware') {
it(set.toTestName(), function() {
it(set.toTestName(), async function() {
// Refresh the proctor instance at runtime.
let proctor = new NodeProctor(keyboardWithHarness, device, assert.equal);
set.test(proctor);
await set.test(proctor);
});
} else {
it.skip(set.toTestName() + " - modifier state simulation for OSK not yet supported in headless KeyboardProcessor");
Expand Down
4 changes: 2 additions & 2 deletions common/web/keyboard-processor/tests/node/deadkeys.js
Original file line number Diff line number Diff line change
Expand Up @@ -42,10 +42,10 @@ describe('Engine - Deadkeys', function() {
if(!proctor.compatibleWithSuite(testSuite)) {
it.skip(set.toTestName() + " - Cannot run this test suite on Node.");
} else {
it(set.toTestName(), function() {
it(set.toTestName(), async function() {
// Refresh the proctor instance at runtime.
let proctor = new NodeProctor(keyboardWithHarness, device, assert.equal);
set.test(proctor);
await set.test(proctor);
});
}
}
Expand Down
20 changes: 10 additions & 10 deletions common/web/recorder/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -212,15 +212,15 @@ export abstract class TestSequence<KeyRecord extends RecordedKeystroke | InputEv

abstract hasOSKInteraction(): boolean;

test(proctor: Proctor, target?: OutputTarget): {success: boolean, result: string} {
async test(proctor: Proctor, target?: OutputTarget): Promise<{success: boolean, result: string}> {
// Start with an empty OutputTarget and a fresh KeyboardProcessor.
if(!target) {
target = new Mock();
}

proctor.before();

let result = proctor.simulateSequence(this, target);
let result = await proctor.simulateSequence(this, target);
proctor.assertEquals(result, this.output, this.msg);

return {success: (result == this.output), result: result};
Expand Down Expand Up @@ -527,7 +527,7 @@ export interface TestSet<Sequence extends TestSequence<any>> {

addTest(seq: Sequence): void;
isValidForDevice(device: utils.DeviceSpec, usingOSK?: boolean): boolean;
test(proctor: Proctor): TestFailure[];
test(proctor: Proctor): Promise<TestFailure[]>;
}

/**
Expand Down Expand Up @@ -563,13 +563,13 @@ export class EventSpecTestSet implements TestSet<InputEventSpecSequence> {
}

// Validity should be checked before calling this method.
test(proctor: Proctor): TestFailure[] {
async test(proctor: Proctor): Promise<TestFailure[]> {
var failures: TestFailure[] = [];
let testSet = this.testSet;

for(var i=0; i < testSet.length; i++) {
var testSeq = this[i];
var simResult = testSet[i].test(proctor);
var simResult = await testSet[i].test(proctor);
if(!simResult.success) {
// Failed test!
failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
Expand Down Expand Up @@ -613,13 +613,13 @@ export class RecordedSequenceTestSet implements TestSet<RecordedKeystrokeSequenc
}

// Validity should be checked before calling this method.
test(proctor: Proctor): TestFailure[] {
async test(proctor: Proctor): Promise<TestFailure[]> {
var failures: TestFailure[] = [];
let testSet = this.testSet;

for(var i=0; i < testSet.length; i++) {
var testSeq = this[i];
var simResult = testSet[i].test(proctor);
var simResult = await testSet[i].test(proctor);
if(!simResult.success) {
// Failed test!
failures.push(new TestFailure(this.constraint, testSeq, simResult.result));
Expand Down Expand Up @@ -729,11 +729,11 @@ export class KeyboardTest {
newSet.addTest(seq);
}

test(proctor: Proctor) {
async test(proctor: Proctor) {
var setHasRun = false;
var failures: TestFailure[] = [];

proctor.beforeAll();
await proctor.beforeAll();

// The original test spec requires a browser environment and thus requires its own `.run` implementation.
if(!(proctor.compatibleWithSuite(this))) {
Expand All @@ -745,7 +745,7 @@ export class KeyboardTest {
var testSet = this.inputTestSets[i];

if(proctor.matchesTestSet(testSet)) {
var testFailures = testSet.test(proctor);
var testFailures = await testSet.test(proctor);
if(testFailures) {
failures = failures.concat(testFailures);
}
Expand Down
4 changes: 2 additions & 2 deletions common/web/recorder/src/nodeProctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ export default class NodeProctor extends Proctor {
this.keyboardWithHarness = kbdHarness;
}

beforeAll() {
async beforeAll() {
//
}

Expand All @@ -47,7 +47,7 @@ export default class NodeProctor extends Proctor {
return true;
}

simulateSequence(sequence: TestSequence<any>, target?: OutputTarget): string {
async simulateSequence(sequence: TestSequence<any>, target?: OutputTarget): Promise<string> {
// Start with an empty OutputTarget and a fresh KeyboardProcessor.
if(!target) {
target = new Mock();
Expand Down
4 changes: 2 additions & 2 deletions common/web/recorder/src/proctor.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@ export default abstract class Proctor {
}

// Performs global test prep.
abstract beforeAll();
abstract beforeAll(): Promise<void>;

// Performs per-test setup
abstract before();
Expand All @@ -49,5 +49,5 @@ export default abstract class Proctor {
* Simulates the specified test sequence for use in testing.
* @param sequence The recorded sequence, generally provided by a test set.
*/
abstract simulateSequence(sequence: TestSequence<any>, target?: OutputTarget);
abstract simulateSequence(sequence: TestSequence<any>, target?: OutputTarget): Promise<string>;
}
25 changes: 24 additions & 1 deletion web/src/engine/dom-utils/src/stylesheets.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { DeviceSpec } from '@keymanapp/web-utils';
import { DeviceSpec, ManagedPromise } from '@keymanapp/web-utils';
import { type InternalKeyboardFont as KeyboardFont } from '@keymanapp/keyboard-processor';

type FontFamilyStyleMap = {[family: string]: HTMLStyleElement};
Expand Down Expand Up @@ -28,6 +28,27 @@ export class StylesheetManager {
this.linkNode.appendChild(sheet);
}

/**
* Provides a `Promise` that resolves when all currently-linked stylesheets have loaded.
* Any change to the set of linked sheets after the initial call will be ignored.
*/
async allLoadedPromise() {
const promises: Promise<void>[] = [];

for(const sheetElem of this.linkedSheets) {
// Based on https://stackoverflow.com/a/21147238
if(sheetElem.sheet?.cssRules) {
promises.push(Promise.resolve());
} else {
const promise = new ManagedPromise<void>();
sheetElem.addEventListener('load', () => promise.resolve());
promises.push(promise.corePromise);
}
}

await Promise.all(promises);
}

/**
* Build a stylesheet with a font-face CSS descriptor for the embedded font appropriate
* for the browser being used
Expand Down Expand Up @@ -212,6 +233,8 @@ export class StylesheetManager {
sheet.parentNode.removeChild(sheet);
}
}

this.linkedSheets.splice(0, this.linkedSheets.length);
}
}

Expand Down
9 changes: 6 additions & 3 deletions web/src/engine/osk/src/views/oskView.ts
Original file line number Diff line number Diff line change
Expand Up @@ -698,14 +698,17 @@ export default abstract class OSKView extends EventEmitter<EventMap> implements
// Instantly resets the OSK container, erasing / delinking the previously-loaded keyboard.
this._Box.innerHTML = '';

// Since we cleared all inner HTML, that means we cleared the stylesheets, too.
this.uiStyleSheetManager.unlinkAll();
this.kbdStyleSheetManager.unlinkAll();

// Install the default OSK stylesheets - but don't have it managed by the keyboard-specific stylesheet manager.
// We wish to maintain kmwosk.css whenever keyboard-specific styles are reset/removed.
// Temp-hack: embedded products prefer their stylesheet, etc linkages without the /osk path component.
let subpath = 'osk/';
if(this.config.isEmbedded) {
subpath = '';
}

// Install the default OSK stylesheet - but don't have it managed by the keyboard-specific stylesheet manager.
// We wish to maintain kmwosk.css whenever keyboard-specific styles are reset/removed.
for(let sheetFile of OSKView.STYLESHEET_FILES) {
const sheetHref = `${this.config.pathConfig.resources}/${subpath}${sheetFile}`;
this.uiStyleSheetManager.linkExternalSheet(sheetHref);
Expand Down
18 changes: 9 additions & 9 deletions web/src/test/auto/integrated/cases/engine.js
Original file line number Diff line number Diff line change
Expand Up @@ -178,31 +178,31 @@ describe('Engine - Browser Interactions', function() {
assert.equal(inputElem.value, "ຫ");
});

it('Simple OSK click', 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);

let eventDriver = new KMWRecorder.BrowserDriver(inputElem);
eventDriver.simulateEvent(lao_s_event);
await eventDriver.simulateEvent(lao_s_event);

if(inputElem['base']) {
inputElem = inputElem['base'];
}
assert.equal(inputElem.value, ";");
});
})
});

describe('Sequence Simulation Checks', function() {
this.timeout(testconfig.timeouts.scriptLoad);

it('Keyboard simulation', function() {
return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad);
it('Keyboard simulation', async function() {
return await runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: false}, assert.equal, testconfig.timeouts.scriptLoad);
});

it('OSK simulation', function() {
return runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
it('OSK simulation', async function() {
return await runKeyboardTestFromJSON('/engine_tests/basic_lao_simulation.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
})
});
});
Expand Down Expand Up @@ -231,10 +231,10 @@ describe('Unmatched Final Groups', function() {
fixture.cleanup();
});

it('matches rule from early group AND performs default behavior', function() {
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 runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
return await runKeyboardTestFromJSON('/engine_tests/ghp_enter.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
});
});

Expand Down
8 changes: 4 additions & 4 deletions web/src/test/auto/integrated/cases/engine_chirality.js
Original file line number Diff line number Diff line change
Expand Up @@ -30,23 +30,23 @@ describe('Engine - Chirality', function() {
fixture.cleanup();
});

it('Keyboard + OSK simulation', function() {
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 runKeyboardTestFromJSON('/engine_tests/chirality.json',
return await runKeyboardTestFromJSON('/engine_tests/chirality.json',
{usingOSK: false},
assert.equal,
testconfig.timeouts.scriptLoad).then(() => {
testconfig.timeouts.scriptLoad).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 runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
return await runKeyboardTestFromJSON('/engine_tests/chirality.json', {usingOSK: true}, assert.equal, testconfig.timeouts.scriptLoad);
}
});
});
Expand Down
12 changes: 6 additions & 6 deletions web/src/test/auto/integrated/cases/events.js
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ describe('Event Management', function() {
assert.isNull(ele.onchange, '`onchange` handler was not called');
});

it('OSK-based onChange event generation', function() {
it('OSK-based onChange event generation', async function() {
var simple_A = {"type":"osk","keyID":"default-K_A"};
var event = new KMWRecorder.OSKInputEventSpec(simple_A);

Expand All @@ -62,7 +62,7 @@ describe('Event Management', function() {
keyman.setActiveElement(ele);

let eventDriver = new KMWRecorder.BrowserDriver(ele);
eventDriver.simulateEvent(event);
await eventDriver.simulateEvent(event);

let focusEvent = new FocusEvent('blur', {relatedTarget: ele});
ele.dispatchEvent(focusEvent);
Expand Down Expand Up @@ -94,7 +94,7 @@ describe('Event Management', function() {
assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
});

it('OSK-based onInput event generation', function() {
it('OSK-based onInput event generation', async function() {
var simple_A = {"type":"osk","keyID":"default-K_A"};
var event = new KMWRecorder.OSKInputEventSpec(simple_A);

Expand All @@ -109,9 +109,9 @@ describe('Event Management', function() {
});

let eventDriver = new KMWRecorder.BrowserDriver(ele);
eventDriver.simulateEvent(event);
eventDriver.simulateEvent(event);
eventDriver.simulateEvent(event);
await eventDriver.simulateEvent(event);
await eventDriver.simulateEvent(event);
await eventDriver.simulateEvent(event);

assert.equal(counterObj.i, fin, "Event handler not called the expected number of times");
});
Expand Down
15 changes: 4 additions & 11 deletions web/src/test/auto/integrated/test_utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -201,32 +201,25 @@ export async function loadKeyboardFromJSON(jsonPath, timeout, params) {
return loadKeyboardStub(stub, timeout, params);
}

function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) {
async function runLoadedKeyboardTest(testDef, device, usingOSK, assertCallback) {
var inputElem = document.getElementById('singleton');

let proctor = new KMWRecorder.BrowserProctor(inputElem, device, usingOSK, assertCallback);
testDef.test(proctor);
await testDef.test(proctor);
}

export function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) {
export async function runKeyboardTestFromJSON(jsonPath, params, assertCallback, timeout) {
var testSpec = new KMWRecorder.KeyboardTest(fixture.load(jsonPath, true));
let device = new Device();
device.detect();

return loadKeyboardStub(testSpec.keyboard, timeout).then(() => {
runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback);
return runLoadedKeyboardTest(testSpec, device.coreSpec, params.usingOSK, assertCallback);
}).finally(() => {
keyman.removeKeyboards(testSpec.keyboard.id);
});
}

// function retrieveAndReset(Pelem) {
// let val = Pelem.value;
// Pelem.value = "";

// return val;
// }

// Useful for tests related to strings with supplementary pairs.
export function toSupplementaryPairString(code) {
var H = Math.floor((code - 0x10000) / 0x400) + 0xD800;
Expand Down
Loading

0 comments on commit 28f1d25

Please sign in to comment.