summaryrefslogtreecommitdiffstats
path: root/tests/manual/wasm/qstdweb
diff options
context:
space:
mode:
Diffstat (limited to 'tests/manual/wasm/qstdweb')
-rw-r--r--tests/manual/wasm/qstdweb/CMakeLists.txt75
-rw-r--r--tests/manual/wasm/qstdweb/files_auto.html13
-rw-r--r--tests/manual/wasm/qstdweb/files_main.cpp471
-rw-r--r--tests/manual/wasm/qstdweb/iodevices_auto.html10
-rw-r--r--tests/manual/wasm/qstdweb/iodevices_main.cpp103
-rw-r--r--tests/manual/wasm/qstdweb/promise_auto.html2
-rw-r--r--tests/manual/wasm/qstdweb/promise_main.cpp2
-rw-r--r--tests/manual/wasm/qstdweb/qwasmcompositor_auto.html10
-rw-r--r--tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp172
9 files changed, 856 insertions, 2 deletions
diff --git a/tests/manual/wasm/qstdweb/CMakeLists.txt b/tests/manual/wasm/qstdweb/CMakeLists.txt
index ea034fad67..5242999ec4 100644
--- a/tests/manual/wasm/qstdweb/CMakeLists.txt
+++ b/tests/manual/wasm/qstdweb/CMakeLists.txt
@@ -20,3 +20,78 @@ add_custom_command(
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
+
+qt_internal_add_manual_test(files_auto
+ SOURCES
+ files_main.cpp
+ ../qtwasmtestlib/qtwasmtestlib.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::CorePrivate
+ Qt::GuiPrivate
+)
+
+include_directories(../qtwasmtestlib/)
+
+add_custom_command(
+ TARGET files_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/files_auto.html
+ ${CMAKE_CURRENT_BINARY_DIR}/files_auto.html)
+
+add_custom_command(
+ TARGET files_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js
+ ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
+
+qt_internal_add_manual_test(qwasmcompositor_auto
+ SOURCES
+ qwasmcompositor_main.cpp
+ ../qtwasmtestlib/qtwasmtestlib.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::CorePrivate
+ Qt::GuiPrivate
+)
+
+include_directories(../qtwasmtestlib/)
+
+add_custom_command(
+ TARGET qwasmcompositor_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/qwasmcompositor_auto.html
+ ${CMAKE_CURRENT_BINARY_DIR}/qwasmcompositor_auto.html)
+
+add_custom_command(
+ TARGET qwasmcompositor_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js
+ ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
+
+target_link_options(qwasmcompositor_auto PRIVATE -sASYNCIFY -Os)
+
+qt_internal_add_manual_test(iodevices_auto
+ SOURCES
+ iodevices_main.cpp
+ ../qtwasmtestlib/qtwasmtestlib.cpp
+ LIBRARIES
+ Qt::Core
+ Qt::CorePrivate
+ Qt::GuiPrivate
+)
+
+add_custom_command(
+ TARGET iodevices_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/iodevices_auto.html
+ ${CMAKE_CURRENT_BINARY_DIR}/iodevices_auto.html)
+
+add_custom_command(
+ TARGET iodevices_auto POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/../qtwasmtestlib/qtwasmtestlib.js
+ ${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
+
+target_link_options(iodevices_auto PRIVATE -sASYNCIFY -Os)
+
diff --git a/tests/manual/wasm/qstdweb/files_auto.html b/tests/manual/wasm/qstdweb/files_auto.html
new file mode 100644
index 0000000000..9027fdc660
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/files_auto.html
@@ -0,0 +1,13 @@
+<!doctype html>
+<script type="text/javascript" src="https://sinonjs.org/releases/sinon-14.0.0.js"
+ integrity="sha384-z8J4N1s2hPDn6ClmFXDQkKD/e738VOWcR8JmhztPRa+PgezxQupgZu3LzoBO4Jw8"
+ crossorigin="anonymous"></script>
+<script type="text/javascript" src="qtwasmtestlib.js"></script>
+<script type="text/javascript" src="files_auto.js"></script>
+<script>
+ window.onload = () => {
+ runTestCase(files_auto_entry, document.getElementById("log"));
+ };
+</script>
+<p>Running files auto test.</p>
+<div id="log"></div>
diff --git a/tests/manual/wasm/qstdweb/files_main.cpp b/tests/manual/wasm/qstdweb/files_main.cpp
new file mode 100644
index 0000000000..45939feb10
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/files_main.cpp
@@ -0,0 +1,471 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QCoreApplication>
+#include <QtCore/QEvent>
+#include <QtCore/QMutex>
+#include <QtCore/QObject>
+#include <QtCore/QTimer>
+#include <QtGui/private/qwasmlocalfileaccess_p.h>
+
+#include <qtwasmtestlib.h>
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#include <string_view>
+
+using namespace emscripten;
+
+class FilesTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ FilesTest() : m_window(val::global("window")), m_testSupport(val::object()) {}
+
+ ~FilesTest() noexcept {
+ for (auto& cleanup: m_cleanup) {
+ cleanup();
+ }
+ }
+
+private:
+ void init() {
+ EM_ASM({
+ window.testSupport = {};
+
+ window.showOpenFilePicker = sinon.stub();
+
+ window.mockOpenFileDialog = (files) => {
+ window.showOpenFilePicker.withArgs(sinon.match.any).callsFake(
+ (options) => Promise.resolve(files.map(file => {
+ const getFile = sinon.stub();
+ getFile.callsFake(() => Promise.resolve({
+ name: file.name,
+ size: file.content.length,
+ slice: () => new Blob([new TextEncoder().encode(file.content)]),
+ }));
+ return {
+ kind: 'file',
+ name: file.name,
+ getFile
+ };
+ }))
+ );
+ };
+
+ window.showSaveFilePicker = sinon.stub();
+
+ window.mockSaveFilePicker = (file) => {
+ window.showSaveFilePicker.withArgs(sinon.match.any).callsFake(
+ (options) => {
+ const createWritable = sinon.stub();
+ createWritable.callsFake(() => {
+ const write = file.writeFn ?? (() => {
+ const write = sinon.stub();
+ write.callsFake((stuff) => {
+ if (file.content !== new TextDecoder().decode(stuff)) {
+ const message = `Bad file content ${file.content} !== ${new TextDecoder().decode(stuff)}`;
+ Module.qtWasmFail(message);
+ return Promise.reject(message);
+ }
+
+ return Promise.resolve();
+ });
+ return write;
+ })();
+
+ window.testSupport.write = write;
+
+ const close = file.closeFn ?? (() => {
+ const close = sinon.stub();
+ close.callsFake(() => Promise.resolve());
+ return close;
+ })();
+
+ window.testSupport.close = close;
+
+ return Promise.resolve({
+ write,
+ close
+ });
+ });
+ return Promise.resolve({
+ kind: 'file',
+ name: file.name,
+ createWritable
+ });
+ }
+ );
+ };
+ });
+ }
+
+ template <class T>
+ T* Own(T* plainPtr) {
+ m_cleanup.emplace_back([plainPtr]() mutable {
+ delete plainPtr;
+ });
+ return plainPtr;
+ }
+
+ val m_window;
+ val m_testSupport;
+
+ std::vector<std::function<void()>> m_cleanup;
+
+private slots:
+ void selectOneFileWithFileDialog();
+ void selectMultipleFilesWithFileDialog();
+ void cancelFileDialog();
+ void rejectFile();
+ void saveFileWithFileDialog();
+};
+
+class BarrierCallback {
+public:
+ BarrierCallback(int number, std::function<void()> onDone)
+ : m_remaining(number), m_onDone(std::move(onDone)) {}
+
+ void operator()() {
+ if (!--m_remaining) {
+ m_onDone();
+ }
+ }
+
+private:
+ int m_remaining;
+ std::function<void()> m_onDone;
+};
+
+
+template <class Arg>
+std::string argToString(std::add_lvalue_reference_t<std::add_const_t<Arg>> arg) {
+ return std::to_string(arg);
+}
+
+template <>
+std::string argToString<bool>(const bool& value) {
+ return value ? "true" : "false";
+}
+
+template <>
+std::string argToString<std::string>(const std::string& arg) {
+ return arg;
+}
+
+template <>
+std::string argToString<const std::string&>(const std::string& arg) {
+ return arg;
+}
+
+template<class Type>
+struct Matcher {
+ virtual ~Matcher() = default;
+
+ virtual bool matches(std::string* explanation, const Type& actual) const = 0;
+};
+
+template<class Type>
+struct AnyMatcher : public Matcher<Type> {
+ bool matches(std::string* explanation, const Type& actual) const final {
+ Q_UNUSED(explanation);
+ Q_UNUSED(actual);
+ return true;
+ }
+
+ Type m_value;
+};
+
+template<class Type>
+struct EqualsMatcher : public Matcher<Type> {
+ EqualsMatcher(Type value) : m_value(std::forward<Type>(value)) {}
+
+ bool matches(std::string* explanation, const Type& actual) const final {
+ const bool ret = actual == m_value;
+ if (!ret)
+ *explanation += argToString<Type>(actual) + " != " + argToString<Type>(m_value);
+ return actual == m_value;
+ }
+
+ // It is crucial to hold a copy, otherwise we lose const refs.
+ std::remove_reference_t<Type> m_value;
+};
+
+template<class Type>
+std::unique_ptr<EqualsMatcher<Type>> equals(Type value) {
+ return std::make_unique<EqualsMatcher<Type>>(value);
+}
+
+template<class Type>
+std::unique_ptr<AnyMatcher<Type>> any(Type value) {
+ return std::make_unique<AnyMatcher<Type>>(value);
+}
+
+template <class ...Types>
+struct Expectation {
+ std::tuple<std::unique_ptr<Matcher<Types>>...> m_argMatchers;
+ int m_callCount = 0;
+ int m_expectedCalls = 1;
+
+ template<std::size_t... Indices>
+ bool match(std::string* explanation, const std::tuple<Types...>& tuple, std::index_sequence<Indices...>) const {
+ return ( ... && (std::get<Indices>(m_argMatchers)->matches(explanation, std::get<Indices>(tuple))));
+ }
+
+ bool matches(std::string* explanation, Types... args) const {
+ if (m_callCount >= m_expectedCalls) {
+ *explanation += "Too many calls\n";
+ return false;
+ }
+ return match(explanation, std::make_tuple(args...), std::make_index_sequence<std::tuple_size_v<std::tuple<Types...>>>());
+ }
+};
+
+template <class R, class ...Types>
+struct Behavior {
+ std::function<R(Types...)> m_callback;
+
+ void call(std::function<R(Types...)> callback) {
+ m_callback = std::move(callback);
+ }
+};
+
+template<class... Args>
+std::string argsToString(Args... args) {
+ return (... + (", " + argToString<Args>(args)));
+}
+
+template<>
+std::string argsToString<>() {
+ return "";
+}
+
+template<class R, class ...Types>
+struct ExpectationToBehaviorMapping {
+ Expectation<Types...> expectation;
+ Behavior<R, Types...> behavior;
+};
+
+template<class R, class... Args>
+class MockCallback {
+public:
+ std::function<R(Args...)> get() {
+ return [this](Args... result) -> R {
+ return processCall(std::forward<Args>(result)...);
+ };
+ }
+
+ Behavior<R, Args...>& expectCallWith(std::unique_ptr<Matcher<Args>>... matcherArgs) {
+ auto matchers = std::make_tuple(std::move(matcherArgs)...);
+ m_behaviorByExpectation.push_back({Expectation<Args...> {std::move(matchers)}, Behavior<R, Args...> {}});
+ return m_behaviorByExpectation.back().behavior;
+ }
+
+ Behavior<R, Args...>& expectRepeatedCallWith(int times, std::unique_ptr<Matcher<Args>>... matcherArgs) {
+ auto matchers = std::make_tuple(std::move(matcherArgs)...);
+ m_behaviorByExpectation.push_back({Expectation<Args...> {std::move(matchers), 0, times}, Behavior<R, Args...> {}});
+ return m_behaviorByExpectation.back().behavior;
+ }
+
+private:
+ R processCall(Args... args) {
+ std::string argsAsString = argsToString(args...);
+ std::string triedExpectations;
+ auto it = std::find_if(m_behaviorByExpectation.begin(), m_behaviorByExpectation.end(),
+ [&](const ExpectationToBehaviorMapping<R, Args...>& behavior) {
+ return behavior.expectation.matches(&triedExpectations, std::forward<Args>(args)...);
+ });
+ if (it != m_behaviorByExpectation.end()) {
+ ++it->expectation.m_callCount;
+ return it->behavior.m_callback(args...);
+ } else {
+ QWASMFAIL("Unexpected call with " + argsAsString + ". Tried: " + triedExpectations);
+ }
+ return R();
+ }
+
+ std::vector<ExpectationToBehaviorMapping<R, Args...>> m_behaviorByExpectation;
+};
+
+void FilesTest::selectOneFileWithFileDialog()
+{
+ init();
+
+ static constexpr std::string_view testFileContent = "This is a happy case.";
+
+ EM_ASM({
+ mockOpenFileDialog([{
+ name: 'file1.jpg',
+ content: UTF8ToString($0)
+ }]);
+ }, testFileContent.data());
+
+ auto* fileSelectedCallback = Own(new MockCallback<void, bool>());
+ fileSelectedCallback->expectCallWith(equals(true)).call([](bool) mutable {});
+
+ auto* fileBuffer = Own(new QByteArray());
+
+ auto* acceptFileCallback = Own(new MockCallback<char*, uint64_t, const std::string&>());
+ acceptFileCallback->expectCallWith(equals<uint64_t>(testFileContent.size()), equals<const std::string&>("file1.jpg"))
+ .call([fileBuffer](uint64_t, std::string) mutable -> char* {
+ fileBuffer->resize(testFileContent.size());
+ return fileBuffer->data();
+ });
+
+ auto* fileDataReadyCallback = Own(new MockCallback<void>());
+ fileDataReadyCallback->expectCallWith().call([fileBuffer]() mutable {
+ QWASMCOMPARE(fileBuffer->data(), std::string(testFileContent));
+ QWASMSUCCESS();
+ });
+
+ QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
+ fileDataReadyCallback->get());
+}
+
+void FilesTest::selectMultipleFilesWithFileDialog()
+{
+ static constexpr std::array<std::string_view, 3> testFileContent =
+ { "Cont 1", "2s content", "What is hiding in 3?"};
+
+ init();
+
+ EM_ASM({
+ mockOpenFileDialog([{
+ name: 'file1.jpg',
+ content: UTF8ToString($0)
+ }, {
+ name: 'file2.jpg',
+ content: UTF8ToString($1)
+ }, {
+ name: 'file3.jpg',
+ content: UTF8ToString($2)
+ }]);
+ }, testFileContent[0].data(), testFileContent[1].data(), testFileContent[2].data());
+
+ auto* fileSelectedCallback = Own(new MockCallback<void, int>());
+ fileSelectedCallback->expectCallWith(equals(3)).call([](int) mutable {});
+
+ auto fileBuffer = std::make_shared<QByteArray>();
+
+ auto* acceptFileCallback = Own(new MockCallback<char*, uint64_t, const std::string&>());
+ acceptFileCallback->expectCallWith(equals<uint64_t>(testFileContent[0].size()), equals<const std::string&>("file1.jpg"))
+ .call([fileBuffer](uint64_t, std::string) mutable -> char* {
+ fileBuffer->resize(testFileContent[0].size());
+ return fileBuffer->data();
+ });
+ acceptFileCallback->expectCallWith(equals<uint64_t>(testFileContent[1].size()), equals<const std::string&>("file2.jpg"))
+ .call([fileBuffer](uint64_t, std::string) mutable -> char* {
+ fileBuffer->resize(testFileContent[1].size());
+ return fileBuffer->data();
+ });
+ acceptFileCallback->expectCallWith(equals<uint64_t>(testFileContent[2].size()), equals<const std::string&>("file3.jpg"))
+ .call([fileBuffer](uint64_t, std::string) mutable -> char* {
+ fileBuffer->resize(testFileContent[2].size());
+ return fileBuffer->data();
+ });
+
+ auto* fileDataReadyCallback = Own(new MockCallback<void>());
+ fileDataReadyCallback->expectRepeatedCallWith(3).call([fileBuffer]() mutable {
+ static int callCount = 0;
+ QWASMCOMPARE(fileBuffer->data(), std::string(testFileContent[callCount]));
+
+ callCount++;
+ if (callCount == 3) {
+ QWASMSUCCESS();
+ }
+ });
+
+ QWasmLocalFileAccess::openFiles("*", QWasmLocalFileAccess::FileSelectMode::MultipleFiles,
+ fileSelectedCallback->get(), acceptFileCallback->get(),
+ fileDataReadyCallback->get());
+}
+
+void FilesTest::cancelFileDialog()
+{
+ init();
+
+ EM_ASM({
+ window.showOpenFilePicker.withArgs(sinon.match.any).returns(Promise.reject("The user cancelled the dialog"));
+ });
+
+ auto* fileSelectedCallback = Own(new MockCallback<void, bool>());
+ fileSelectedCallback->expectCallWith(equals(false)).call([](bool) mutable {
+ QWASMSUCCESS();
+ });
+
+ auto* acceptFileCallback = Own(new MockCallback<char*, uint64_t, const std::string&>());
+ auto* fileDataReadyCallback = Own(new MockCallback<void>());
+
+ QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
+ fileDataReadyCallback->get());
+}
+
+void FilesTest::rejectFile()
+{
+ init();
+
+ static constexpr std::string_view testFileContent = "We don't want this file.";
+
+ EM_ASM({
+ mockOpenFileDialog([{
+ name: 'dontwant.dat',
+ content: UTF8ToString($0)
+ }]);
+ }, testFileContent.data());
+
+ auto* fileSelectedCallback = Own(new MockCallback<void, bool>());
+ fileSelectedCallback->expectCallWith(equals(true)).call([](bool) mutable {});
+
+ auto* fileDataReadyCallback = Own(new MockCallback<void>());
+
+ auto* acceptFileCallback = Own(new MockCallback<char*, uint64_t, const std::string&>());
+ acceptFileCallback->expectCallWith(equals<uint64_t>(std::string_view(testFileContent).size()), equals<const std::string&>("dontwant.dat"))
+ .call([](uint64_t, const std::string) {
+ QTimer::singleShot(0, []() {
+ // No calls to fileDataReadyCallback
+ QWASMSUCCESS();
+ });
+ return nullptr;
+ });
+
+ QWasmLocalFileAccess::openFile("*", fileSelectedCallback->get(), acceptFileCallback->get(),
+ fileDataReadyCallback->get());
+}
+
+void FilesTest::saveFileWithFileDialog()
+{
+ init();
+
+ static constexpr std::string_view testFileContent = "Save this important content";
+
+ EM_ASM({
+ mockSaveFilePicker({
+ name: 'somename',
+ content: UTF8ToString($0),
+ closeFn: (() => {
+ const close = sinon.stub();
+ close.callsFake(() =>
+ new Promise(resolve => {
+ resolve();
+ Module.qtWasmPass();
+ }));
+ return close;
+ })()
+ });
+ }, testFileContent.data());
+
+ QByteArray data;
+ data.prepend(testFileContent);
+ QWasmLocalFileAccess::saveFile(data, "hintie");
+}
+
+int main(int argc, char **argv)
+{
+ auto testObject = std::make_shared<FilesTest>();
+ QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
+ return 0;
+}
+
+#include "files_main.moc"
diff --git a/tests/manual/wasm/qstdweb/iodevices_auto.html b/tests/manual/wasm/qstdweb/iodevices_auto.html
new file mode 100644
index 0000000000..7937b8a483
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/iodevices_auto.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<script type="text/javascript" src="qtwasmtestlib.js"></script>
+<script type="text/javascript" src="iodevices_auto.js"></script>
+<script>
+ window.onload = () => {
+ runTestCase(iodevices_auto_entry, document.getElementById("log"));
+ };
+</script>
+<p>Running qstdweb iodevices auto test.</p>
+<div id="log"></div>
diff --git a/tests/manual/wasm/qstdweb/iodevices_main.cpp b/tests/manual/wasm/qstdweb/iodevices_main.cpp
new file mode 100644
index 0000000000..0dbdd0084e
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/iodevices_main.cpp
@@ -0,0 +1,103 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QtCore>
+#include <QtCore/private/qstdweb_p.h>
+
+#include <qtwasmtestlib.h>
+
+#include "emscripten.h"
+
+using qstdweb::ArrayBuffer;
+using qstdweb::Uint8Array;
+using qstdweb::Blob;
+using qstdweb::BlobIODevice;
+using qstdweb::Uint8ArrayIODevice;
+
+class WasmIoDevicesTest: public QObject
+{
+ Q_OBJECT
+
+private slots:
+ void blobIODevice();
+ void uint8ArrayIODevice();
+};
+
+// Creates a test arraybuffer with byte values [0..size] % 256 * 2
+char testByteValue(int i) { return (i % 256) * 2; }
+ArrayBuffer createTestArrayBuffer(int size)
+{
+ ArrayBuffer buffer(size);
+ Uint8Array array(buffer);
+ for (int i = 0; i < size; ++i)
+ array.val().set(i, testByteValue(i));
+ return buffer;
+}
+
+void WasmIoDevicesTest::blobIODevice()
+{
+ if (!qstdweb::canBlockCallingThread()) {
+ QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
+ return;
+ }
+
+ // Create test buffer and BlobIODevice
+ const int bufferSize = 16;
+ BlobIODevice blobDevice(Blob::fromArrayBuffer(createTestArrayBuffer(bufferSize)));
+
+ // Read back byte for byte from the device
+ QWASMVERIFY(blobDevice.open(QIODevice::ReadOnly));
+ for (int i = 0; i < bufferSize; ++i) {
+ char byte;
+ blobDevice.seek(i);
+ blobDevice.read(&byte, 1);
+ QWASMCOMPARE(byte, testByteValue(i));
+ }
+
+ blobDevice.close();
+ QWASMVERIFY(!blobDevice.open(QIODevice::WriteOnly));
+ QWASMSUCCESS();
+}
+
+void WasmIoDevicesTest::uint8ArrayIODevice()
+{
+ // Create test buffer and Uint8ArrayIODevice
+ const int bufferSize = 1024;
+ Uint8Array array(createTestArrayBuffer(bufferSize));
+ Uint8ArrayIODevice arrayDevice(array);
+
+ // Read back byte for byte from the device
+ QWASMVERIFY(arrayDevice.open(QIODevice::ReadWrite));
+ for (int i = 0; i < bufferSize; ++i) {
+ char byte;
+ arrayDevice.seek(i);
+ arrayDevice.read(&byte, 1);
+ QWASMCOMPARE(byte, testByteValue(i));
+ }
+
+ // Write a different set of bytes
+ QWASMCOMPARE(arrayDevice.seek(0), true);
+ for (int i = 0; i < bufferSize; ++i) {
+ char byte = testByteValue(i + 1);
+ arrayDevice.seek(i);
+ QWASMCOMPARE(arrayDevice.write(&byte, 1), 1);
+ }
+
+ // Verify that the original array was updated
+ QByteArray copy = QByteArray::fromEcmaUint8Array(array.val());
+ for (int i = 0; i < bufferSize; ++i)
+ QWASMCOMPARE(copy.at(i), testByteValue(i + 1));
+
+ arrayDevice.close();
+ QWASMSUCCESS();
+}
+
+int main(int argc, char **argv)
+{
+ auto testObject = std::make_shared<WasmIoDevicesTest>();
+ QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
+ return 0;
+}
+
+#include "iodevices_main.moc"
+
diff --git a/tests/manual/wasm/qstdweb/promise_auto.html b/tests/manual/wasm/qstdweb/promise_auto.html
index 786145419f..94a8dbb88a 100644
--- a/tests/manual/wasm/qstdweb/promise_auto.html
+++ b/tests/manual/wasm/qstdweb/promise_auto.html
@@ -3,7 +3,7 @@
<script type="text/javascript" src="promise_auto.js"></script>
<script>
window.onload = () => {
- runTestCase(document.getElementById("log"));
+ runTestCase(promise_auto_entry, document.getElementById("log"));
};
</script>
<p>Running promise auto test.</p>
diff --git a/tests/manual/wasm/qstdweb/promise_main.cpp b/tests/manual/wasm/qstdweb/promise_main.cpp
index 351f06c91d..c5f6f7f412 100644
--- a/tests/manual/wasm/qstdweb/promise_main.cpp
+++ b/tests/manual/wasm/qstdweb/promise_main.cpp
@@ -1,5 +1,5 @@
// Copyright (C) 2022 The Qt Company Ltd.
-// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
#include <QtCore/QCoreApplication>
#include <QtCore/QEvent>
diff --git a/tests/manual/wasm/qstdweb/qwasmcompositor_auto.html b/tests/manual/wasm/qstdweb/qwasmcompositor_auto.html
new file mode 100644
index 0000000000..f33aab0b9c
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/qwasmcompositor_auto.html
@@ -0,0 +1,10 @@
+<!doctype html>
+<script type="text/javascript" src="qtwasmtestlib.js"></script>
+<script type="text/javascript" src="qwasmcompositor_auto.js"></script>
+<script>
+ window.onload = () => {
+ runTestCase(qwasmcompositor_auto_entry, document.getElementById("log"));
+ };
+</script>
+<p>Running files auto test.</p>
+<div id="log"></div>
diff --git a/tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp b/tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp
new file mode 100644
index 0000000000..e1a9cf604d
--- /dev/null
+++ b/tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp
@@ -0,0 +1,172 @@
+// Copyright (C) 2022 The Qt Company Ltd.
+// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+#include <QtCore/QEvent>
+#include <QtCore/QObject>
+#include <QtGui/qwindow.h>
+#include <QtGui/qguiapplication.h>
+#include <QtGui/qoffscreensurface.h>
+#include <QtGui/qpa/qwindowsysteminterface.h>
+#include <QtGui/rhi/qrhi.h>
+
+#include <qtwasmtestlib.h>
+
+#include <emscripten.h>
+#include <emscripten/val.h>
+
+#include <functional>
+#include <memory>
+#include <vector>
+
+namespace tst_qwasmcompositor_internal {
+namespace {
+class Window : public QWindow
+{
+ Q_OBJECT
+public:
+ Window();
+ ~Window() override { qDebug() << "dtor Window"; }
+
+ void keyPressEvent(QKeyEvent *) final;
+
+signals:
+ void exposed();
+ void keyEventReceived();
+ void initFailed();
+
+protected:
+private:
+ void init();
+
+ void exposeEvent(QExposeEvent *) override;
+ bool m_exposedOnce = false;
+
+ std::unique_ptr<QOffscreenSurface> m_fallbackSurface;
+ std::unique_ptr<QRhi> m_rhi;
+};
+
+Window::Window()
+{
+ setSurfaceType(OpenGLSurface);
+}
+
+void Window::exposeEvent(QExposeEvent *)
+{
+ if (isExposed() && !m_exposedOnce) {
+ m_exposedOnce = true;
+ init();
+ emit exposed();
+ }
+}
+
+void Window::keyPressEvent(QKeyEvent *)
+{
+ emit keyEventReceived();
+}
+
+void Window::init()
+{
+ QRhi::Flags rhiFlags = QRhi::EnableDebugMarkers;
+
+ m_fallbackSurface.reset(QRhiGles2InitParams::newFallbackSurface());
+ QRhiGles2InitParams params;
+ params.fallbackSurface = m_fallbackSurface.get();
+ params.window = this;
+
+ // Double init of RHI causes the OpenGL context to be destroyed, which causes a bug with input.
+ m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params, rhiFlags));
+ m_rhi.reset(QRhi::create(QRhi::OpenGLES2, &params, rhiFlags));
+
+ if (!m_rhi)
+ emit initFailed();
+}
+
+} // namespace
+} // namespace tst_qwasmcompositor_internal
+
+using namespace emscripten;
+
+class QWasmCompositorTest : public QObject
+{
+ Q_OBJECT
+
+public:
+ QWasmCompositorTest() : m_window(val::global("window")), m_testSupport(val::object())
+ {
+ m_window.set("testSupport", m_testSupport);
+ m_testSupport.set("qtSetContainerElements",
+ emscripten::val::module_property("qtSetContainerElements"));
+ }
+
+ ~QWasmCompositorTest() noexcept
+ {
+ qDebug() << this << "In dtor";
+ for (auto it = m_cleanup.rbegin(); it != m_cleanup.rend(); ++it)
+ (*it)();
+ m_window.set("testSupport", emscripten::val::undefined());
+ }
+
+private:
+ void init()
+ {
+ EM_ASM({
+ testSupport.screenElement = document.createElement("div");
+ testSupport.screenElement.id = "test-canvas-qwasmcompositor";
+ document.body.appendChild(testSupport.screenElement);
+ });
+ m_cleanup.emplace_back([]() mutable {
+ EM_ASM({
+ testSupport.qtSetContainerElements([]);
+ testSupport.screenElement.parentElement.removeChild(testSupport.screenElement);
+ });
+ });
+
+ EM_ASM({ testSupport.qtSetContainerElements([testSupport.screenElement]); });
+ }
+
+ template<class T>
+ T *Own(T *plainPtr)
+ {
+ m_cleanup.emplace_back([plainPtr]() mutable { delete plainPtr; });
+ return plainPtr;
+ }
+
+ val m_window;
+ val m_testSupport;
+
+ std::vector<std::function<void()>> m_cleanup;
+
+private slots:
+ void testReceivingKeyboardEventsAfterOpenGLContextReset();
+};
+
+void QWasmCompositorTest::testReceivingKeyboardEventsAfterOpenGLContextReset()
+{
+ init();
+
+ using Window = tst_qwasmcompositor_internal::Window;
+ Window *window = Own(new Window);
+ window->show();
+ window->requestActivate();
+
+ QWindowSystemInterface::flushWindowSystemEvents();
+
+ QObject::connect(window, &Window::keyEventReceived, []() { QWASMSUCCESS(); });
+ QObject::connect(window, &Window::initFailed,
+ []() { QWASMFAIL("Cannot initialize test window"); });
+ QObject::connect(window, &Window::exposed, []() {
+ EM_ASM({
+ testSupport.screenElement.shadowRoot.querySelector('.qt-window')
+ .dispatchEvent(new KeyboardEvent('keydown', { key : 'a' }));
+ });
+ });
+}
+
+int main(int argc, char **argv)
+{
+ auto testObject = std::make_shared<QWasmCompositorTest>();
+ QtWasmTest::initTestCase<QGuiApplication>(argc, argv, testObject);
+ return 0;
+}
+
+#include "qwasmcompositor_main.moc"