446 lines
15 KiB
JavaScript
446 lines
15 KiB
JavaScript
|
// Copyright (C) 2023 The Qt Company Ltd.
|
||
|
// SPDXLicenseIdentifier: LicenseRefQtCommercial OR GPL3.0only
|
||
|
|
||
|
import { Mock, assert, TestRunner } from './testrunner.js';
|
||
|
|
||
|
export class QtLoaderIntegrationTests
|
||
|
{
|
||
|
#testScreenContainers = []
|
||
|
|
||
|
async beforeEach()
|
||
|
{
|
||
|
this.#addScreenContainer('screen-container-0', { width: '200px', height: '300px' });
|
||
|
}
|
||
|
|
||
|
async afterEach()
|
||
|
{
|
||
|
this.#testScreenContainers.forEach(screenContainer =>
|
||
|
{
|
||
|
document.body.removeChild(screenContainer);
|
||
|
});
|
||
|
this.#testScreenContainers = [];
|
||
|
}
|
||
|
|
||
|
async missingConfig()
|
||
|
{
|
||
|
let caughtException;
|
||
|
try {
|
||
|
await qtLoad();
|
||
|
} catch (e) {
|
||
|
caughtException = e;
|
||
|
}
|
||
|
|
||
|
assert.isNotUndefined(caughtException);
|
||
|
assert.equal('config is required, expected an object', caughtException.message);
|
||
|
}
|
||
|
|
||
|
async missingQtSection()
|
||
|
{
|
||
|
let caughtException;
|
||
|
try {
|
||
|
await qtLoad({});
|
||
|
} catch (e) {
|
||
|
caughtException = e;
|
||
|
}
|
||
|
|
||
|
assert.isNotUndefined(caughtException);
|
||
|
assert.equal(
|
||
|
'config.qt is required, expected an object', caughtException.message);
|
||
|
}
|
||
|
|
||
|
async useDefaultOnMissingEntryFunction()
|
||
|
{
|
||
|
const instance = await qtLoad({ arguments: ['--no-gui'], qt: {}});
|
||
|
assert.isNotUndefined(instance);
|
||
|
}
|
||
|
|
||
|
async environmentVariables()
|
||
|
{
|
||
|
const instance = await qtLoad({
|
||
|
qt: {
|
||
|
environment: {
|
||
|
variable1: 'value1',
|
||
|
variable2: 'value2'
|
||
|
},
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: [this.#testScreenContainers[0]]
|
||
|
}
|
||
|
});
|
||
|
assert.isTrue(instance.getEnvironmentVariable('variable1') === 'value1');
|
||
|
assert.isTrue(instance.getEnvironmentVariable('variable2') === 'value2');
|
||
|
}
|
||
|
|
||
|
async screenContainerManipulations()
|
||
|
{
|
||
|
// ... (do other things), then call addContainerElement() to add a new container/screen.
|
||
|
// This can happen either before or after load() is called - loader will route the
|
||
|
// call to instance when it's ready.
|
||
|
this.#addScreenContainer('appcontainer1', { width: '100px', height: '100px' })
|
||
|
|
||
|
const instance = await qtLoad({
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: this.#testScreenContainers
|
||
|
}
|
||
|
});
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instance);
|
||
|
|
||
|
assert.equal(2, screenInformation.length);
|
||
|
assert.equal(200, screenInformation[0].width);
|
||
|
assert.equal(300, screenInformation[0].height);
|
||
|
assert.equal(100, screenInformation[1].width);
|
||
|
assert.equal(100, screenInformation[1].height);
|
||
|
}
|
||
|
|
||
|
this.#addScreenContainer('appcontainer2', { width: '234px', height: '99px' })
|
||
|
instance.qtSetContainerElements(this.#testScreenContainers);
|
||
|
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instance);
|
||
|
|
||
|
assert.equal(3, screenInformation.length);
|
||
|
assert.equal(200, screenInformation[0].width);
|
||
|
assert.equal(300, screenInformation[0].height);
|
||
|
assert.equal(100, screenInformation[1].width);
|
||
|
assert.equal(100, screenInformation[1].height);
|
||
|
assert.equal(234, screenInformation[2].width);
|
||
|
assert.equal(99, screenInformation[2].height);
|
||
|
}
|
||
|
|
||
|
document.body.removeChild(this.#testScreenContainers.splice(2, 1)[0]);
|
||
|
instance.qtSetContainerElements(this.#testScreenContainers);
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instance);
|
||
|
|
||
|
assert.equal(2, screenInformation.length);
|
||
|
assert.equal(200, screenInformation[0].width);
|
||
|
assert.equal(300, screenInformation[0].height);
|
||
|
assert.equal(100, screenInformation[1].width);
|
||
|
assert.equal(100, screenInformation[1].height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async primaryScreenIsAlwaysFirst()
|
||
|
{
|
||
|
const instance = await qtLoad({
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: this.#testScreenContainers,
|
||
|
}
|
||
|
});
|
||
|
this.#addScreenContainer(
|
||
|
'appcontainer3', { width: '12px', height: '24px' },
|
||
|
container => this.#testScreenContainers.splice(0, 0, container));
|
||
|
this.#addScreenContainer(
|
||
|
'appcontainer4', { width: '34px', height: '68px' },
|
||
|
container => this.#testScreenContainers.splice(1, 0, container));
|
||
|
|
||
|
instance.qtSetContainerElements(this.#testScreenContainers);
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instance);
|
||
|
|
||
|
assert.equal(3, screenInformation.length);
|
||
|
// The primary screen (at position 0) is always at 0
|
||
|
assert.equal(12, screenInformation[0].width);
|
||
|
assert.equal(24, screenInformation[0].height);
|
||
|
// Other screens are pushed at the back
|
||
|
assert.equal(200, screenInformation[1].width);
|
||
|
assert.equal(300, screenInformation[1].height);
|
||
|
assert.equal(34, screenInformation[2].width);
|
||
|
assert.equal(68, screenInformation[2].height);
|
||
|
}
|
||
|
|
||
|
this.#testScreenContainers.forEach(screenContainer =>
|
||
|
{
|
||
|
document.body.removeChild(screenContainer);
|
||
|
});
|
||
|
this.#testScreenContainers = [
|
||
|
this.#addScreenContainer('appcontainer5', { width: '11px', height: '12px' }),
|
||
|
this.#addScreenContainer('appcontainer6', { width: '13px', height: '14px' }),
|
||
|
];
|
||
|
|
||
|
instance.qtSetContainerElements(this.#testScreenContainers);
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instance);
|
||
|
|
||
|
assert.equal(2, screenInformation.length);
|
||
|
assert.equal(11, screenInformation[0].width);
|
||
|
assert.equal(12, screenInformation[0].height);
|
||
|
assert.equal(13, screenInformation[1].width);
|
||
|
assert.equal(14, screenInformation[1].height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async multipleInstances()
|
||
|
{
|
||
|
// Fetch/Compile the module once; reuse for each instance. This is also if the page wants to
|
||
|
// initiate the .wasm file download fetch as early as possible, before the browser has
|
||
|
// finished fetching and parsing testapp.js and qtloader.js
|
||
|
const modulePromise = WebAssembly.compileStreaming(fetch('tst_qtloader_integration.wasm'));
|
||
|
|
||
|
const instances = await Promise.all([1, 2, 3].map(i => qtLoad({
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: [this.#addScreenContainer(`screen-container-${i}`, {
|
||
|
width: `${i * 10}px`,
|
||
|
height: `${i * 10}px`,
|
||
|
})],
|
||
|
modulePromise,
|
||
|
}
|
||
|
})));
|
||
|
// Confirm the identity of instances by querying their screen widths and heights
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instances[0]);
|
||
|
console.log();
|
||
|
assert.equal(1, screenInformation.length);
|
||
|
assert.equal(10, screenInformation[0].width);
|
||
|
assert.equal(10, screenInformation[0].height);
|
||
|
}
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instances[1]);
|
||
|
assert.equal(1, screenInformation.length);
|
||
|
assert.equal(20, screenInformation[0].width);
|
||
|
assert.equal(20, screenInformation[0].height);
|
||
|
}
|
||
|
{
|
||
|
const screenInformation = this.#getScreenInformation(instances[2]);
|
||
|
assert.equal(1, screenInformation.length);
|
||
|
assert.equal(30, screenInformation[0].width);
|
||
|
assert.equal(30, screenInformation[0].height);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
async consoleMode()
|
||
|
{
|
||
|
// 'Console mode' for autotesting type scenarios
|
||
|
let accumulatedStdout = '';
|
||
|
const instance = await qtLoad({
|
||
|
arguments: ['--no-gui'],
|
||
|
print: output =>
|
||
|
{
|
||
|
accumulatedStdout += output;
|
||
|
},
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
|
||
|
this.#callTestInstanceApi(instance, 'produceOutput');
|
||
|
assert.equal('Sample output!', accumulatedStdout);
|
||
|
}
|
||
|
|
||
|
async moduleProvided()
|
||
|
{
|
||
|
await qtLoad({
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: [this.#testScreenContainers[0]],
|
||
|
modulePromise: WebAssembly.compileStreaming(
|
||
|
await fetch('tst_qtloader_integration.wasm'))
|
||
|
}
|
||
|
});
|
||
|
}
|
||
|
|
||
|
async arguments()
|
||
|
{
|
||
|
const instance = await qtLoad({
|
||
|
arguments: ['--no-gui', 'arg1', 'other', 'yetanotherarg'],
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
const args = this.#callTestInstanceApi(instance, 'retrieveArguments');
|
||
|
assert.equal(5, args.length);
|
||
|
assert.isTrue('arg1' === args[2]);
|
||
|
assert.equal('other', args[3]);
|
||
|
assert.equal('yetanotherarg', args[4]);
|
||
|
}
|
||
|
|
||
|
async moduleProvided_exceptionThrownInFactory()
|
||
|
{
|
||
|
let caughtException;
|
||
|
try {
|
||
|
await qtLoad({
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
containerElements: [this.#testScreenContainers[0]],
|
||
|
modulePromise: Promise.reject(new Error('Failed to load')),
|
||
|
}
|
||
|
});
|
||
|
} catch (e) {
|
||
|
caughtException = e;
|
||
|
}
|
||
|
assert.isTrue(caughtException !== undefined);
|
||
|
assert.equal('Failed to load', caughtException.message);
|
||
|
}
|
||
|
|
||
|
async abort()
|
||
|
{
|
||
|
const onExitMock = new Mock();
|
||
|
const instance = await qtLoad({
|
||
|
arguments: ['--no-gui'],
|
||
|
qt: {
|
||
|
onExit: onExitMock,
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
try {
|
||
|
instance.crash();
|
||
|
} catch { }
|
||
|
assert.equal(1, onExitMock.calls.length);
|
||
|
const exitStatus = onExitMock.calls[0][0];
|
||
|
assert.isTrue(exitStatus.crashed);
|
||
|
assert.isUndefined(exitStatus.code);
|
||
|
assert.isNotUndefined(exitStatus.text);
|
||
|
}
|
||
|
|
||
|
async abortImmediately()
|
||
|
{
|
||
|
const onExitMock = new Mock();
|
||
|
let caughtException;
|
||
|
try {
|
||
|
await qtLoad({
|
||
|
arguments: ['--no-gui', '--crash-immediately'],
|
||
|
qt: {
|
||
|
onExit: onExitMock,
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
} catch (e) {
|
||
|
caughtException = e;
|
||
|
}
|
||
|
|
||
|
// An exception should have been thrown from load()
|
||
|
assert.equal('RuntimeError', caughtException.name);
|
||
|
|
||
|
assert.equal(1, onExitMock.calls.length);
|
||
|
const exitStatus = onExitMock.calls[0][0];
|
||
|
assert.isTrue(exitStatus.crashed);
|
||
|
assert.isUndefined(exitStatus.code);
|
||
|
assert.isNotUndefined(exitStatus.text);
|
||
|
}
|
||
|
|
||
|
async userAbortCallbackCalled()
|
||
|
{
|
||
|
const onAbortMock = new Mock();
|
||
|
let instance = await qtLoad({
|
||
|
arguments: ['--no-gui'],
|
||
|
onAbort: onAbortMock,
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
try {
|
||
|
instance.crash();
|
||
|
} catch (e) {
|
||
|
// emscripten throws an 'Aborted' error here, which we ignore for the sake of the test
|
||
|
}
|
||
|
assert.equal(1, onAbortMock.calls.length);
|
||
|
}
|
||
|
|
||
|
async exit()
|
||
|
{
|
||
|
const onExitMock = new Mock();
|
||
|
let instance = await qtLoad({
|
||
|
arguments: ['--no-gui'],
|
||
|
qt: {
|
||
|
onExit: onExitMock,
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
// The module is running. onExit should not have been called.
|
||
|
assert.equal(0, onExitMock.calls.length);
|
||
|
try {
|
||
|
instance.exitApp();
|
||
|
} catch (e) {
|
||
|
// emscripten throws a 'Runtime error: unreachable' error here. We ignore it for the
|
||
|
// sake of the test.
|
||
|
}
|
||
|
assert.equal(1, onExitMock.calls.length);
|
||
|
const exitStatus = onExitMock.calls[0][0];
|
||
|
assert.isFalse(exitStatus.crashed);
|
||
|
assert.equal(instance.EXIT_VALUE_FROM_EXIT_APP, exitStatus.code);
|
||
|
assert.isNotUndefined(exitStatus.text);
|
||
|
}
|
||
|
|
||
|
async exitImmediately()
|
||
|
{
|
||
|
const onExitMock = new Mock();
|
||
|
const instance = await qtLoad({
|
||
|
arguments: ['--no-gui', '--exit-immediately'],
|
||
|
qt: {
|
||
|
onExit: onExitMock,
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
assert.equal(1, onExitMock.calls.length);
|
||
|
|
||
|
const exitStatusFromOnExit = onExitMock.calls[0][0];
|
||
|
|
||
|
assert.isFalse(exitStatusFromOnExit.crashed);
|
||
|
assert.equal(instance.EXIT_VALUE_IMMEDIATE_RETURN, exitStatusFromOnExit.code);
|
||
|
assert.isNotUndefined(exitStatusFromOnExit.text);
|
||
|
}
|
||
|
|
||
|
async userQuitCallbackCalled()
|
||
|
{
|
||
|
const quitMock = new Mock();
|
||
|
let instance = await qtLoad({
|
||
|
arguments: ['--no-gui'],
|
||
|
quit: quitMock,
|
||
|
qt: {
|
||
|
entryFunction: createQtAppInstance,
|
||
|
}
|
||
|
});
|
||
|
try {
|
||
|
instance.exitApp();
|
||
|
} catch (e) {
|
||
|
// emscripten throws a 'Runtime error: unreachable' error here. We ignore it for the
|
||
|
// sake of the test.
|
||
|
}
|
||
|
assert.equal(1, quitMock.calls.length);
|
||
|
const [exitCode, exception] = quitMock.calls[0];
|
||
|
assert.equal(instance.EXIT_VALUE_FROM_EXIT_APP, exitCode);
|
||
|
assert.equal('ExitStatus', exception.name);
|
||
|
}
|
||
|
|
||
|
#callTestInstanceApi(instance, apiName)
|
||
|
{
|
||
|
return eval(instance[apiName]());
|
||
|
}
|
||
|
|
||
|
#getScreenInformation(instance)
|
||
|
{
|
||
|
return this.#callTestInstanceApi(instance, 'screenInformation').map(elem => ({
|
||
|
x: elem[0],
|
||
|
y: elem[1],
|
||
|
width: elem[2],
|
||
|
height: elem[3],
|
||
|
}));
|
||
|
}
|
||
|
|
||
|
#addScreenContainer(id, style, inserter)
|
||
|
{
|
||
|
const container = (() =>
|
||
|
{
|
||
|
const container = document.createElement('div');
|
||
|
container.id = id;
|
||
|
container.style.width = style.width;
|
||
|
container.style.height = style.height;
|
||
|
document.body.appendChild(container);
|
||
|
return container;
|
||
|
})();
|
||
|
inserter ? inserter(container) : this.#testScreenContainers.push(container);
|
||
|
return container;
|
||
|
}
|
||
|
}
|
||
|
|
||
|
(async () =>
|
||
|
{
|
||
|
const runner = new TestRunner(new QtLoaderIntegrationTests(), {
|
||
|
timeoutSeconds: 10
|
||
|
});
|
||
|
await runner.runAll();
|
||
|
})();
|