qt 6.5.1 original

This commit is contained in:
kleuter
2023-10-29 23:33:08 +01:00
parent 71d22ab6b0
commit 85d238dfda
21202 changed files with 5499099 additions and 0 deletions

View File

@ -0,0 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(eventloop)
add_subdirectory(rasterwindow)
add_subdirectory(a11y)
if(QT_FEATURE_widgets)
add_subdirectory(cursors)
add_subdirectory(localfiles)
add_subdirectory(qstdweb)
add_subdirectory(clipboard)
endif()

View File

@ -0,0 +1,15 @@
Manual tests and examples for Qt on WebAssembly
===============================================
These examples demonstrates various technical aspects of
the Qt for WebAssambly platform port, and can be used
as a reference when writing application code.
Content
-------
cursors Cursor handling
eventloop Event loops, application startup, dialog exec()
localfiles Local file download and upload
rasterwindow Basic GUI app, event handling
qtwasmtestlib native auto test framework

View File

@ -0,0 +1,3 @@
if(QT_FEATURE_widgets)
add_subdirectory(basic_widgets)
endif()

View File

@ -0,0 +1,21 @@
qt_internal_add_manual_test(a11y_basic_widgets
GUI
SOURCES
tabswidget.cpp
tabswidget.h
basica11ywidget.h
basica11ywidget.cpp
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
)
add_custom_command(
TARGET a11y_basic_widgets PRE_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/basic_widgets.html
${CMAKE_CURRENT_BINARY_DIR}/basic_widgets.html
DEPENDS ${CMAKE_CURRENT_SOURCE_DIR}/basic_widgets.html
)

View File

@ -0,0 +1,24 @@
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<script src="a11y_basic_widgets.js" async></script>
<script>
window.onload = async () => {
let qt_instance = await createQtAppInstance({
qtContainerElements: [document.getElementById("qt_container")],
});
}
</script>
</head>
<body>
<h1>Qt Accessibility Tester</H1>
<div id="qt_container" style="width:640px; height:640px"></div>
</body>
</html>

View File

@ -0,0 +1,114 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "basica11ywidget.h"
BasicA11yWidget::BasicA11yWidget() :
m_toolBar (new QToolBar()),
m_layout(new QVBoxLayout),
m_tabWidget(new QTabWidget)
{
createActions();
createMenus();
createToolBar();
m_lblDateTime =new QLabel("Select Chrono Menu for todays date and time.");
m_layout->addWidget(m_lblDateTime);
m_tabWidget->addTab(new GeneralTab(), ("General Widget"));
m_editView =new EditViewTab();
m_tabWidget->addTab(m_editView, ("Edit Widget"));
m_layout->addWidget(m_tabWidget);
m_layout->addStretch();
connect(m_editView, &EditViewTab::connectToToolBar, this,&BasicA11yWidget::connectToolBar);
setLayout(m_layout);
}
void BasicA11yWidget::handleButton() {
QDialog *asmSmplDlg = new QDialog(this);
QVBoxLayout *vlayout = new QVBoxLayout(asmSmplDlg);
asmSmplDlg->setWindowTitle("WebAssembly Dialog box ");
QLabel *label = new QLabel("Accessibility Demo sample application developed in Qt.");
QAbstractButton *bExit = new QPushButton("Exit");
vlayout->addWidget(label);
vlayout->addWidget(bExit);
asmSmplDlg->setLayout(vlayout);
auto p = asmSmplDlg->palette();
p.setColor( asmSmplDlg->backgroundRole(), Qt::gray);
asmSmplDlg->setPalette(p);
asmSmplDlg->show();
asmSmplDlg->connect(bExit, SIGNAL(clicked()), asmSmplDlg, SLOT(close()));
}
void BasicA11yWidget::createToolBar()
{
m_copyAct = new QAction(tr("&Copy"), this);
m_copyAct->setShortcuts(QKeySequence::Copy);
m_pasteAct = new QAction(tr("&Paste"), this);
m_pasteAct->setStatusTip(tr("To paste selected text"));
m_pasteAct->setShortcuts(QKeySequence::Paste);
m_cutAct = new QAction(tr("C&ut"), this);
m_cutAct->setShortcuts(QKeySequence::Cut);
m_toolBar->addAction(m_copyAct);
m_toolBar->addAction(m_cutAct);
m_toolBar->addAction(m_pasteAct);
m_layout->addWidget(m_toolBar);
}
void BasicA11yWidget::connectToolBar()
{
connect(m_copyAct, &QAction::triggered, m_editView->getTextEdit(), &QPlainTextEdit::copy);
connect(m_pasteAct, &QAction::triggered, m_editView->getTextEdit(), &QPlainTextEdit::paste);
connect(m_cutAct, &QAction::triggered, m_editView->getTextEdit(), &QPlainTextEdit::cut);
}
void BasicA11yWidget::createActions()
{
m_DateAct = new QAction( tr("&Date"), this);
m_DateAct->setStatusTip(tr("To tell you todays date."));
connect(m_DateAct, &QAction::triggered, this, &BasicA11yWidget::todaysDate);
m_TimeAct = new QAction(tr("&Time"), this);
m_TimeAct->setStatusTip(tr("To tell you current time."));
connect(m_TimeAct, &QAction::triggered, this, &BasicA11yWidget::currentTime);
}
void BasicA11yWidget::createMenus()
{
m_menuBar = new QMenuBar();
m_TodayMenu = m_menuBar->addMenu(tr("&Chrono"));
m_TodayMenu->addAction(m_DateAct);
m_TodayMenu->addAction(m_TimeAct);
m_aboutAct = new QAction(tr("&About"), this);
m_aboutAct->setStatusTip(tr("Show the application's About box"));
connect(m_aboutAct, &QAction::triggered, this, &BasicA11yWidget::about);
m_helpMenu = m_menuBar->addMenu(tr("&Help"));
m_helpMenu->addAction(m_aboutAct);
m_layout->setMenuBar(m_menuBar);
}
void BasicA11yWidget::todaysDate()
{
QDateTime dt=QDateTime::currentDateTime();
QString str = "Today's Date:"+ dt.date().toString();
m_lblDateTime->setText(str);
}
void BasicA11yWidget::currentTime()
{
QDateTime dt=QDateTime::currentDateTime();
QString str = "Current Time:"+ dt.time().toString();
m_lblDateTime->setText(str);
}
void BasicA11yWidget::about()
{
handleButton();
}

View File

@ -0,0 +1,41 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets>
#include "tabswidget.h"
class BasicA11yWidget: public QWidget
{
Q_OBJECT
private:
QMenu* m_helpMenu = nullptr;
QMenu* m_TodayMenu = nullptr;
QMenuBar* m_menuBar = nullptr;
QToolBar* m_toolBar = nullptr;
QLabel* m_lblDateTime = nullptr;
QVBoxLayout* m_layout = nullptr ;
QTabWidget* m_tabWidget = nullptr;
EditViewTab *m_editView = nullptr;
QAction* m_DateAct = nullptr;
QAction* m_TimeAct = nullptr;
QAction* m_aboutAct = nullptr;
QAction* m_copyAct = nullptr;
QAction* m_pasteAct = nullptr;
QAction* m_cutAct = nullptr;
public slots:
void connectToolBar();
public:
BasicA11yWidget() ;
void createActions();
void createMenus();
void createToolBar();
void todaysDate();
void currentTime();
void about();
QToolBar* getToolbar(){return m_toolBar;}
void handleButton();
};

View File

@ -0,0 +1,17 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QApplication>
#include <QtWidgets>
#include "basica11ywidget.h"
int main(int argc, char **argv)
{
QApplication app(argc, argv);
BasicA11yWidget a11yWidget;
a11yWidget.show();
return app.exec();
}

View File

@ -0,0 +1,63 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "tabswidget.h"
GeneralTab::GeneralTab(QWidget *parent)
: QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout();
layout->setSizeConstraint(QLayout::SetMaximumSize);
layout->addWidget(new QLabel("This is a text label"));
QPushButton *btn = new QPushButton("This is a push button");
layout->addWidget(btn);
connect(btn, &QPushButton::released, this, [=] () {
btn->setText("You clicked me");
});
layout->addWidget(new QCheckBox("This is a check box"));
layout->addWidget(new QRadioButton("Radio 1"));
layout->addWidget(new QRadioButton("Radio 2"));
QSlider *slider = new QSlider(Qt::Horizontal);
slider->setTickInterval(10);
slider->setTickPosition(QSlider::TicksAbove);
layout->addWidget(slider);
QSpinBox *spin = new QSpinBox();
spin->setValue(10);
spin->setSingleStep(1);
layout->addWidget(spin);
layout->addStretch();
QScrollBar *scrollBar = new QScrollBar(Qt::Horizontal);
scrollBar->setFocusPolicy(Qt::StrongFocus);
layout->addWidget(scrollBar);
setLayout(layout);
}
EditViewTab::EditViewTab(QWidget *parent) :
QWidget(parent)
{
QVBoxLayout *layout = new QVBoxLayout();
layout->setSizeConstraint(QLayout::SetMaximumSize);
textEdit = new QPlainTextEdit();
textEdit->setPlaceholderText("Enter Text here");
layout->addWidget(textEdit);
setLayout(layout);
}
void EditViewTab::showEvent( QShowEvent* event ) {
if (!b_connected)
{
emit connectToToolBar();
b_connected=true;
}
QWidget::showEvent( event );
}

View File

@ -0,0 +1,34 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef TABDIALOG_H
#define TABDIALOG_H
#include <QTabWidget>
#include <QtWidgets>
class GeneralTab : public QWidget
{
Q_OBJECT
public:
explicit GeneralTab(QWidget *parent = nullptr);
};
class EditViewTab : public QWidget
{
Q_OBJECT
private:
bool b_connected = false;
QPlainTextEdit* textEdit =nullptr;
QToolBar* m_toolbar= nullptr;
public:
void showEvent( QShowEvent* event ) ;
QPlainTextEdit* getTextEdit(){return textEdit;}
explicit EditViewTab( QWidget *parent = nullptr);
signals:
void connectToToolBar();
};
#endif

View File

@ -0,0 +1,38 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
#####################################################################
## clipboard Binary:
#####################################################################
qt_internal_add_manual_test(clipboard
GUI
SOURCES
main.cpp
mainwindow.cpp mainwindow.h mainwindow.ui
LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
ENABLE_AUTOGEN_TOOLS
uic
)
# Resources:
set(data_resource_files
"data/qticon64.png"
)
qt_internal_add_resource(clipboard "data"
PREFIX
"/"
FILES
${data_resource_files}
)
## Scopes:
#####################################################################
qt_internal_extend_target(clipboard CONDITION (QT_MAJOR_VERSION GREATER 4)
LIBRARIES
Qt::Widgets
)

View File

@ -0,0 +1,2 @@
The Clipboard manual test app can be used both on desktop and in the browser
using WebAssembly to test clipboard use between WebAssembly app and the desktop.

View File

@ -0,0 +1,27 @@
QT += core gui
greaterThan(QT_MAJOR_VERSION, 4): QT += widgets
CONFIG += c++11
# You can make your code fail to compile if it uses deprecated APIs.
# In order to do so, uncomment the following line.
#DEFINES += QT_DISABLE_DEPRECATED_UP_TO=0x060000 # disables all APIs deprecated in Qt 6.0.0 and earlier
SOURCES += \
main.cpp \
mainwindow.cpp
HEADERS += \
mainwindow.h
FORMS += \
mainwindow.ui
RESOURCES += \
data.qrc
# Default rules for deployment.
qnx: target.path = /tmp/$${TARGET}/bin
else: unix:!android: target.path = /opt/$${TARGET}/bin
!isEmpty(target.path): INSTALLS += target

View File

@ -0,0 +1,5 @@
<RCC>
<qresource prefix="/">
<file>data/qticon64.png</file>
</qresource>
</RCC>

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.3 KiB

View File

@ -0,0 +1,14 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.show();
return a.exec();
}

View File

@ -0,0 +1,311 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "mainwindow.h"
#include "ui_mainwindow.h"
#include <QClipboard>
#include <QMimeData>
#include <QImageReader>
#include <QBuffer>
#include <QRandomGenerator>
#include <QPainter>
#include <QKeyEvent>
#include <QMimeDatabase>
#include <QFileInfo>
#ifdef Q_OS_WASM
#include <emscripten.h>
#include <emscripten/html5.h>
#include <emscripten/val.h>
#include <emscripten/bind.h>
using namespace emscripten;
#endif
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
ui->imageLabel->installEventFilter(this);
ui->imageLabel->setBackgroundRole(QPalette::Base);
ui->imageLabel->setSizePolicy(QSizePolicy::Ignored, QSizePolicy::Ignored);
ui->imageLabel->setScaledContents(true);
setAcceptDrops(true);
clipboard = QGuiApplication::clipboard();
connect(
clipboard, &QClipboard::dataChanged,
[=]() {
ui->textEdit_2->insertHtml("<b>Clipboard data changed:</b><br>");
const QMimeData *mimeData = clipboard->mimeData();
QByteArray ba;
for (auto mimetype : mimeData->formats()) {
qDebug() << Q_FUNC_INFO << mimetype;
ba = mimeData->data(mimetype);
}
QString sizeStr;
if (mimeData->hasImage()) {
qsizetype imageSize = qvariant_cast<QImage>(mimeData->imageData()).sizeInBytes();
sizeStr.setNum(imageSize);
ui->textEdit_2->insertHtml("has Image data: " + sizeStr + "<br>");
}
if (mimeData->hasHtml()) {
int size = mimeData->html().length();
sizeStr.setNum(size);
ui->textEdit_2->insertHtml("has html data: " + sizeStr + "<br>");
}
if (mimeData->hasText()) {
int size = mimeData->text().length();
sizeStr.setNum(size);
ui->textEdit_2->insertHtml("has text data: " + sizeStr + "<br>");
}
ui->textEdit_2->insertHtml(mimeData->formats().join(" | ")+ "<br>");
ui->textEdit_2->ensureCursorVisible();
const QString message = tr("Clipboard changed, %1 ")
.arg(mimeData->formats().join(' '));
statusBar()->showMessage(message + sizeStr);
}
);
#ifdef Q_OS_WASM
val clipboard = val::global("navigator")["clipboard"];
bool hasClipboardApi = (!clipboard.isUndefined() && !clipboard["readText"].isUndefined());
QString messageApi;
if (hasClipboardApi)
messageApi = QStringLiteral("Using Clipboard API");
else
messageApi = QStringLiteral("Using Clipboard events");
ui->label->setText(messageApi);
#else
ui->label->setText("desktop clipboard");
#endif
}
MainWindow::~MainWindow()
{
delete ui;
}
void MainWindow::on_setTextButton_clicked()
{
QGuiApplication::clipboard()->setText(ui->textEdit->textCursor().selectedText());
}
static QImage clipboardImage()
{
if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData()) {
if (mimeData->hasImage()) {
const QImage image = qvariant_cast<QImage>(mimeData->imageData());
if (!image.isNull())
return image;
}
}
return QImage();
}
static QByteArray clipboardBinary()
{
if (const QMimeData *mimeData = QGuiApplication::clipboard()->mimeData()) {
if (mimeData->formats().contains("application/octet-stream")) {
const QByteArray ba = qvariant_cast<QByteArray>(mimeData->data("application/octet-stream"));
qDebug() << Q_FUNC_INFO << ba;
if (!ba.isNull())
return ba;
}
}
return QByteArray();
}
void MainWindow::on_pasteImageButton_clicked()
{
const QImage newImage = clipboardImage();
if (newImage.isNull()) {
qDebug() << "No image in clipboard";
const QString message = tr("No image in clipboard")
.arg(newImage.width()).arg(newImage.height()).arg(newImage.depth());
statusBar()->showMessage(message);
} else {
setImage(newImage);
setWindowFilePath(QString());
const QString message = tr("Obtained image from clipboard, %1x%2, Depth: %3")
.arg(newImage.width()).arg(newImage.height()).arg(newImage.depth());
statusBar()->showMessage(message);
}
}
void MainWindow::setImage(const QImage &newImage)
{
image = newImage;
ui->imageLabel->setPixmap(QPixmap::fromImage(image));
}
void MainWindow::on_pasteTextButton_clicked()
{
ui->textEdit->insertPlainText(QGuiApplication::clipboard()->text());
}
void MainWindow::on_copyBinaryButton_clicked()
{
QByteArray ba;
ba.resize(10);
ba[0] = 0x3c;
ba[1] = 0xb8;
ba[2] = 0x64;
ba[3] = 0x18;
ba[4] = 0xca;
ba[5] = 0xca;
ba[6] = 0x18;
ba[7] = 0x64;
ba[8] = 0xb8;
ba[9] = 0x3c;
QMimeData *mimeData = new QMimeData();
mimeData->setData("application/octet-stream", ba);
QGuiApplication::clipboard()->setMimeData(mimeData);
const QString message = tr("Copied binary to clipboard: " + ba + " 10 bytes");
statusBar()->showMessage(message);
}
void MainWindow::on_pasteBinaryButton_clicked()
{
const QByteArray ba = clipboardBinary();
if (ba.isNull()) {
qDebug() << "No binary in clipboard";
const QString message = tr("No binary in clipboard");
statusBar()->showMessage(message);
} else {
setWindowFilePath(QString());
const QString message = tr("Obtained binary from clipboard: " + ba);
statusBar()->showMessage(message);
}
}
void MainWindow::on_comboBox_textActivated(const QString &arg1)
{
QImage image(QSize(150,100), QImage::Format_RGB32);
QPainter painter(&image);
painter.fillRect(QRectF(0,0,150,100),generateRandomColor());
painter.fillRect(QRectF(20,30,130,40),generateRandomColor());
painter.setPen(QPen(generateRandomColor()));
painter.drawText(QRect(25,30,130,40),"Qt WebAssembly");
QByteArray ba;
QBuffer buffer(&ba);
buffer.open(QIODevice::WriteOnly);
image.save(&buffer, arg1.toLocal8Bit());
qDebug() << ba.mid(0,10) << ba.length();
qDebug() << Q_FUNC_INFO << image.sizeInBytes();
QGuiApplication::clipboard()->setImage(image);
}
QColor MainWindow::generateRandomColor()
{
return QColor::fromRgb(QRandomGenerator::global()->generate());
}
bool MainWindow::eventFilter(QObject *obj, QEvent *event)
{
if (event->type() == QEvent::KeyPress) {
QKeyEvent *ke = static_cast<QKeyEvent *>(event);
if (ke->key() == Qt::Key_V && ke->modifiers().testFlag(Qt::ControlModifier)) {
if (obj == ui->imageLabel) {
setImage(clipboardImage());
return true;
}
}
}
// standard event processing
return QObject::eventFilter(obj, event);
}
void MainWindow::on_pasteHtmlButton_clicked()
{
ui->textEdit->insertHtml(QGuiApplication::clipboard()->mimeData()->html());
}
void MainWindow::on_clearButton_clicked()
{
ui->textEdit_2->clear();
ui->imageLabel->clear();
ui->imageLabel->setText("Paste or drop image here");
}
void MainWindow::dragEnterEvent(QDragEnterEvent* e)
{
e->acceptProposedAction();
}
void MainWindow::dropEvent(QDropEvent* e)
{
QString sizeStr;
ui->textEdit_2->insertPlainText("New Drop has mime formats: " + e->mimeData()->formats().join(", ") + "\n");
QString urlMessage = QString(" Drop contains %1 urls\n").arg(e->mimeData()->urls().count());
ui->textEdit_2->insertPlainText(urlMessage);
foreach (const QUrl &url, e->mimeData()->urls()) {
QString urlStr = url.toDisplayString();
int size = urlStr.length();
sizeStr.setNum(size);
ui->textEdit_2->insertPlainText(" Drop has url data length: " + sizeStr + "\n");
ui->textEdit_2->insertPlainText(urlStr + "\n");
QString fname = url.toLocalFile();
QFileInfo info(fname);
if (info.exists()) { // this is a file
QMimeDatabase db;
QMimeType mt = db.mimeTypeForFile(info);
if (mt.name().contains("image")) {
QImage image = QImage(fname);
setImage(image);
}
}
}
if (e->mimeData()->hasImage()) {
qsizetype imageSize = qvariant_cast<QImage>(e->mimeData()->imageData()).sizeInBytes();
sizeStr.setNum(imageSize);
ui->textEdit_2->insertPlainText(" Drop has Image data length: " + sizeStr + "\n");
QImage image = qvariant_cast<QImage>(e->mimeData()->imageData());
setImage(image);
const QString message = tr("Obtained image from drop, %1x%2, Depth: %3")
.arg(image.width()).arg(image.height()).arg(image.depth());
statusBar()->showMessage(message);
}
if (e->mimeData()->hasHtml()) {
int size = e->mimeData()->html().length();
sizeStr.setNum(size);
ui->textEdit_2->insertPlainText(" Drop has html data length: " + sizeStr + "\n");
ui->textEdit_2->insertPlainText(e->mimeData()->html()+"\n");
ui->textEdit->insertHtml(e->mimeData()->html()+"<br>");
}
if (e->mimeData()->hasText()) {
int size = e->mimeData()->text().length();
sizeStr.setNum(size);
ui->textEdit_2->insertPlainText(" Drop has text data length: " + sizeStr + "\n");
ui->textEdit_2->insertPlainText(e->mimeData()->text());
}
const QString message = tr(" Drop accepted, %1 ")
.arg(e->mimeData()->formats().join(' '));
statusBar()->showMessage(message + sizeStr);
e->acceptProposedAction();
}

View File

@ -0,0 +1,52 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef MAINWINDOW_H
#define MAINWINDOW_H
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui { class MainWindow; }
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private slots:
void on_setTextButton_clicked();
void on_pasteImageButton_clicked();
void setImage(const QImage &newImage);
void on_pasteTextButton_clicked();
void on_copyBinaryButton_clicked();
void on_pasteBinaryButton_clicked();
void on_comboBox_textActivated(const QString &arg1);
void on_pasteHtmlButton_clicked();
void on_clearButton_clicked();
private:
Ui::MainWindow *ui;
QImage image;
QClipboard *clipboard;
bool eventFilter(QObject *obj, QEvent *event) override;
QColor generateRandomColor();
protected:
void dragEnterEvent(QDragEnterEvent *e) override;
void dropEvent(QDropEvent *e) override;
};
#endif // MAINWINDOW_H

View File

@ -0,0 +1,222 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1222</width>
<height>1011</height>
</rect>
</property>
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="Preferred">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QGridLayout" name="gridLayout_2">
<item row="0" column="0">
<layout class="QVBoxLayout" name="verticalLayout">
<item>
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QPushButton" name="setTextButton">
<property name="text">
<string>setText()</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteTextButton">
<property name="text">
<string>paste text</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteHtmlButton">
<property name="text">
<string>paste html</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_2">
<item>
<widget class="QComboBox" name="comboBox">
<property name="currentText">
<string>PNG</string>
</property>
<item>
<property name="text">
<string>PNG</string>
</property>
</item>
<item>
<property name="text">
<string>JPG</string>
</property>
</item>
<item>
<property name="text">
<string>BMP</string>
</property>
</item>
<item>
<property name="text">
<string>NAN</string>
</property>
</item>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteImageButton">
<property name="text">
<string>paste image</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<layout class="QHBoxLayout" name="horizontalLayout_3">
<item>
<widget class="QPushButton" name="copyBinaryButton">
<property name="text">
<string>setData</string>
</property>
</widget>
</item>
<item>
<widget class="QPushButton" name="pasteBinaryButton">
<property name="text">
<string>paste data</string>
</property>
</widget>
</item>
</layout>
</item>
<item>
<widget class="QPushButton" name="clearButton">
<property name="text">
<string>clear</string>
</property>
</widget>
</item>
<item>
<widget class="QTextEdit" name="textEdit_2">
<property name="sizePolicy">
<sizepolicy hsizetype="Minimum" vsizetype="MinimumExpanding">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
</widget>
</item>
</layout>
</item>
<item row="0" column="1">
<layout class="QGridLayout" name="gridLayout">
<item row="2" column="0">
<widget class="QTextBrowser" name="textEdit">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="minimumSize">
<size>
<width>500</width>
<height>400</height>
</size>
</property>
<property name="readOnly">
<bool>false</bool>
</property>
<property name="html">
<string>&lt;!DOCTYPE HTML PUBLIC &quot;-//W3C//DTD HTML 4.0//EN&quot; &quot;http://www.w3.org/TR/REC-html40/strict.dtd&quot;&gt;
&lt;html&gt;&lt;head&gt;&lt;meta name=&quot;qrichtext&quot; content=&quot;1&quot; /&gt;&lt;meta charset=&quot;utf-8&quot; /&gt;&lt;style type=&quot;text/css&quot;&gt;
p, li { white-space: pre-wrap; }
&lt;/style&gt;&lt;/head&gt;&lt;body style=&quot; font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;&quot;&gt;
&lt;p style=&quot; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;img src=&quot;:/data/qticon64.png&quot; /&gt;&lt;/p&gt;
&lt;p style=&quot; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;&quot;&gt;&lt;a name=&quot;tw-target&quot;&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; font-weight:600;&quot;&gt;L&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; font-weight:600;&quot;&gt;orem&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; font-style:italic;&quot;&gt;ipsum&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; text-decoration: underline;&quot;&gt;dolor&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; vertical-align:super;&quot;&gt;sit&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; vertical-align:sub;&quot;&gt;amet&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt;, &lt;/span&gt;&lt;a href=&quot;http://localhost&quot;&gt;&lt;span style=&quot; font-family:'Sans Serif'; font-size:9pt; text-decoration: underline; color:#0000ff;&quot;&gt;consectetur&lt;/span&gt;&lt;/a&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; &lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt; color:#7320a4;&quot;&gt;adipiscing&lt;/span&gt;&lt;span style=&quot; font-family:'monospace'; font-size:9pt;&quot;&gt; elit. Som medlemmer av byrået ønsker imidlertid en eiendomsmegler. Ullamcorper største lekseforfatter. Dolor et consectetuer litt ernæring. Maecenas smile jord sitter Vulputate medlemmer og, basketball ethvert problem. Reservert lever nå propaganda. På makroen investere laoreet kan, av enhver latter. Jasmine som en TV -tegneserie.&lt;/span&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'Sans Serif'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;
&lt;p style=&quot;-qt-paragraph-type:empty; margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px; font-family:'monospace'; font-size:9pt;&quot;&gt;&lt;br /&gt;&lt;/p&gt;&lt;/body&gt;&lt;/html&gt;</string>
</property>
</widget>
</item>
<item row="3" column="0">
<widget class="QLabel" name="imageLabel">
<property name="mouseTracking">
<bool>true</bool>
</property>
<property name="focusPolicy">
<enum>Qt::StrongFocus</enum>
</property>
<property name="acceptDrops">
<bool>true</bool>
</property>
<property name="frameShape">
<enum>QFrame::StyledPanel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Raised</enum>
</property>
<property name="text">
<string>Paste or drop content here</string>
</property>
<property name="scaledContents">
<bool>true</bool>
</property>
<property name="textInteractionFlags">
<set>Qt::LinksAccessibleByMouse|Qt::TextSelectableByMouse</set>
</property>
</widget>
</item>
<item row="1" column="0">
<widget class="QLabel" name="label">
<property name="sizePolicy">
<sizepolicy hsizetype="Preferred" vsizetype="Fixed">
<horstretch>0</horstretch>
<verstretch>0</verstretch>
</sizepolicy>
</property>
<property name="frameShape">
<enum>QFrame::Panel</enum>
</property>
<property name="frameShadow">
<enum>QFrame::Sunken</enum>
</property>
<property name="text">
<string>TextLabel</string>
</property>
</widget>
</item>
</layout>
</item>
</layout>
</widget>
<widget class="QMenuBar" name="menubar">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>1222</width>
<height>24</height>
</rect>
</property>
</widget>
<widget class="QStatusBar" name="statusbar"/>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,15 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(cursors
GUI
SOURCES
MainWindow.cpp MainWindow.h MainWindow.ui
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
ENABLE_AUTOGEN_TOOLS
uic
)

View File

@ -0,0 +1,29 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "MainWindow.h"
#include "ui_MainWindow.h"
#include <QPushButton>
#include <QVariant>
#include <QApplication>
MainWindow::MainWindow(QWidget *parent)
: QMainWindow(parent)
, ui(new Ui::MainWindow)
{
ui->setupUi(this);
for (int i = 0; i <= Qt::LastCursor; i++) {
auto shape = Qt::CursorShape(i);
auto button =
new QPushButton(QVariant::fromValue(shape).toString(), this);
ui->buttonsLayout->addWidget(button);
QObject::connect(button, &QPushButton::clicked,
[this, shape]() { ui->cursorWidget->setCursor(shape); });
}
}
MainWindow::~MainWindow()
{
delete ui;
}

View File

@ -0,0 +1,23 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#pragma once
#include <QMainWindow>
QT_BEGIN_NAMESPACE
namespace Ui {
class MainWindow;
}
QT_END_NAMESPACE
class MainWindow : public QMainWindow
{
Q_OBJECT
public:
MainWindow(QWidget *parent = nullptr);
~MainWindow();
private:
Ui::MainWindow *ui;
};

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="UTF-8"?>
<ui version="4.0">
<class>MainWindow</class>
<widget class="QMainWindow" name="MainWindow">
<property name="geometry">
<rect>
<x>0</x>
<y>0</y>
<width>700</width>
<height>520</height>
</rect>
</property>
<property name="windowTitle">
<string>MainWindow</string>
</property>
<widget class="QWidget" name="centralwidget">
<layout class="QHBoxLayout" name="horizontalLayout">
<item>
<widget class="QWidget" name="buttons" native="true">
<property name="maximumSize">
<size>
<width>200</width>
<height>16777215</height>
</size>
</property>
<layout class="QVBoxLayout" name="buttonsLayout"/>
</widget>
</item>
<item>
<widget class="QWidget" name="cursorWidget" native="true">
<layout class="QGridLayout" name="gridLayout">
<item row="0" column="0">
<widget class="QLabel" name="label">
<property name="text">
<string>Push the button to activate cursor and move mouse th this area.</string>
</property>
<property name="alignment">
<set>Qt::AlignCenter</set>
</property>
<property name="textInteractionFlags">
<set>Qt::NoTextInteraction</set>
</property>
</widget>
</item>
</layout>
</widget>
</item>
</layout>
</widget>
</widget>
<resources/>
<connections/>
</ui>

View File

@ -0,0 +1,13 @@
QT += core gui widgets
SOURCES += \
main.cpp \
MainWindow.cpp
HEADERS += \
MainWindow.h
FORMS += \
MainWindow.ui
LIBS += -lidbfs.js

View File

@ -0,0 +1,13 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "MainWindow.h"
#include <QApplication>
int main(int argc, char *argv[])
{
QApplication a(argc, argv);
MainWindow w;
w.showFullScreen();
return a.exec();
}

View File

@ -0,0 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
add_subdirectory(asyncify_exec)
add_subdirectory(eventloop_auto)
add_subdirectory(main_exec)
add_subdirectory(main_noexec)
add_subdirectory(thread_exec)
if(QT_FEATURE_widgets)
add_subdirectory(dialog_exec)
endif()

View File

@ -0,0 +1,15 @@
Event loop exec() and main() on Qt for WebAssembly
==================================================
These examples demonstrate how QEventLoop::exec() works on
Qt for WebAssembly, and also shows how to implement main()
without calling QApplication::exec().
Contents
========
main_exec Standard Qt main(), where QApplication::exec() does not return
main_noexec Qt main() without QApplication::exec()
dialog_exec Shows how QDialog::exec() also does not return
thread_exec Shows how to use QThread::exec()
eventloop_auto Event loop autotest (manually run)

View File

@ -0,0 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(asyncify_exec
SOURCES
main.cpp
LIBRARIES
Qt::Core
)
# Enable asyncify for this test. Also enable optimizations in order to reduce the binary size.
target_link_options(asyncify_exec PUBLIC -sASYNCIFY -Os)

View File

@ -0,0 +1,25 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
// This test shows how to use asyncify to enable blocking the main
// thread on QEventLoop::exec(), while event processing continues.
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QTimer::singleShot(1000, []() {
QEventLoop loop;
QTimer::singleShot(2000, [&loop]() {
qDebug() << "Calling QEventLoop::quit()";
loop.quit();
});
qDebug() << "Calling QEventLoop::exec()";
loop.exec();
qDebug() << "Returned from QEventLoop::exec()";
});
app.exec();
}

View File

@ -0,0 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(dialog_exec
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
)

View File

@ -0,0 +1,48 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui>
#include <QtWidgets>
// This example show how calling QDialog::exec() shows the dialog,
// but does not return.
class ClickWindow: public QRasterWindow
{
public:
ClickWindow() {
qDebug() << "ClickWindow constructor";
}
~ClickWindow() {
qDebug() << "ClickWindow destructor";
}
void paintEvent(QPaintEvent *ev) override {
QPainter p(this);
p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue);
p.drawText(50, 100, "Application has started. See the developer tools console for debug output");
}
void mousePressEvent(QMouseEvent *) override {
qDebug() << "mousePressEvent(): calling QMessageBox::exec()";
QMessageBox messageBox;
messageBox.setText("Hello! This is a message box.");
connect(&messageBox, &QMessageBox::buttonClicked, [](QAbstractButton *button) {
qDebug() << "Button Clicked" << button;
});
messageBox.exec(); // <-- does not return
qDebug() << "mousePressEvent(): done"; // <--- will not be printed
}
};
int main(int argc, char **argv)
{
QApplication app(argc, argv);
ClickWindow window;
window.show();
return app.exec();
}

View File

@ -0,0 +1,43 @@
include_directories(../../qtwasmtestlib/)
# default buid
qt_internal_add_manual_test(eventloop_auto
SOURCES
main.cpp
../../qtwasmtestlib/qtwasmtestlib.cpp
LIBRARIES
Qt::Core
Qt::CorePrivate
)
add_custom_command(
TARGET eventloop_auto POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/eventloop_auto.html
${CMAKE_CURRENT_BINARY_DIR}/eventloop_auto.html)
add_custom_command(
TARGET eventloop_auto POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../../qtwasmtestlib/qtwasmtestlib.js
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)
# asyncify enabled build
qt_internal_add_manual_test(eventloop_auto_asyncify
SOURCES
main.cpp
../../qtwasmtestlib/qtwasmtestlib.cpp
LIBRARIES
Qt::Core
Qt::CorePrivate
)
target_link_options(eventloop_auto_asyncify PRIVATE -sASYNCIFY -Os)
add_custom_command(
TARGET eventloop_auto_asyncify POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/eventloop_auto_asyncify.html
${CMAKE_CURRENT_BINARY_DIR}/eventloop_auto_asyncify.html)

View File

@ -0,0 +1,10 @@
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="eventloop_auto.js"></script>
<script>
window.onload = () => {
runTestCase(document.getElementById("log"));
};
</script>
<p>Running event dispatcher auto test.</p>
<div id="log"></div>

View File

@ -0,0 +1,10 @@
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="eventloop_auto_asyncify.js"></script>
<script>
window.onload = () => {
runTestCase(document.getElementById("log"));
};
</script>
<p>Running event dispatcher auto test.</p>
<div id="log"></div>

View File

@ -0,0 +1,327 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QCoreApplication>
#include <QtCore/QEvent>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/QThread>
#include <QtCore/QTimer>
#include <QtCore/private/qstdweb_p.h>
#include <qtwasmtestlib.h>
#include "emscripten.h"
const int timerTimeout = 10;
class WasmEventDispatcherTest: public QObject
{
Q_OBJECT
private slots:
void postEventMainThread();
void timerMainThread();
void timerMainThreadMultiple();
#if QT_CONFIG(thread)
void postEventSecondaryThread();
void postEventSecondaryThreads();
void postEventToSecondaryThread();
void timerSecondaryThread();
#endif
void postEventAsyncify();
void timerAsyncify();
void postEventAsyncifyLoop();
private:
// Disabled test function: Asyncify wait on pthread_join is not supported,
// see https://github.com/emscripten-core/emscripten/issues/9910
#if QT_CONFIG(thread)
void threadAsyncifyWait();
#endif
};
class EventTarget : public QObject
{
Q_OBJECT
public:
static EventTarget *create(std::function<void()> callback)
{
return new EventTarget(callback);
}
static QEvent *createEvent()
{
return new QEvent(QEvent::User);
}
protected:
EventTarget(std::function<void()> callback)
: m_callback(callback) { }
bool event(QEvent *evt)
{
if (evt->type() == QEvent::User) {
m_callback();
deleteLater();
return true;
}
return QObject::event(evt);
}
private:
std::function<void()> m_callback;
};
class CompleteTestFunctionRefGuard {
public:
CompleteTestFunctionRefGuard(CompleteTestFunctionRefGuard const&) = delete;
CompleteTestFunctionRefGuard& operator=(CompleteTestFunctionRefGuard const&) = delete;
static CompleteTestFunctionRefGuard *create() {
return new CompleteTestFunctionRefGuard();
}
void ref() {
QMutexLocker lock(&mutex);
++m_counter;
}
void deref() {
const bool finalDeref = [this] {
QMutexLocker lock(&mutex);
return --m_counter == 0;
}();
if (finalDeref)
QtWasmTest::completeTestFunction();
}
private:
CompleteTestFunctionRefGuard() { };
QMutex mutex;
int m_counter = 0;
};
#if QT_CONFIG(thread)
class TestThread : public QThread
{
public:
static QThread *create(std::function<void()> started, std::function<void()> finished)
{
TestThread *thread = new TestThread();
connect(thread, &QThread::started, [started]() {
started();
});
connect(thread, &QThread::finished, [thread, finished]() {
finished();
thread->deleteLater();
});
thread->start();
return thread;
}
};
#endif
// Post event to the main thread and verify that it is processed.
void WasmEventDispatcherTest::postEventMainThread()
{
QCoreApplication::postEvent(EventTarget::create([](){
QtWasmTest::completeTestFunction();
}), EventTarget::createEvent());
}
// Create a timer on the main thread and verify that it fires
void WasmEventDispatcherTest::timerMainThread()
{
QTimer::singleShot(timerTimeout, [](){
QtWasmTest::completeTestFunction();
});
}
void WasmEventDispatcherTest::timerMainThreadMultiple()
{
CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create();
int timers = 10;
for (int i = 0; i < timers; ++i) {
completeGuard->ref();
QTimer::singleShot(timerTimeout * i, [completeGuard](){
completeGuard->deref();
});
}
}
#if QT_CONFIG(thread)
// Post event on a secondary thread and verify that it is processed.
void WasmEventDispatcherTest::postEventSecondaryThread()
{
auto started = [](){
QCoreApplication::postEvent(EventTarget::create([](){
QThread::currentThread()->quit();
}), EventTarget::createEvent());
};
auto finished = [](){
QtWasmTest::completeTestFunction();
};
TestThread::create(started, finished);
}
// Post event _to_ a secondary thread and verify that it is processed.
void WasmEventDispatcherTest::postEventToSecondaryThread()
{
auto started = [](){};
auto finished = [](){
QtWasmTest::completeTestFunction();
};
QThread *t = TestThread::create(started, finished);
EventTarget *target = EventTarget::create([](){
QThread::currentThread()->quit();
});
target->moveToThread(t);
QCoreApplication::postEvent(target, EventTarget::createEvent());
}
// Post events to many secondary threads and verify that they are processed.
void WasmEventDispatcherTest::postEventSecondaryThreads()
{
// This test completes afer all threads has finished
CompleteTestFunctionRefGuard *completeGuard = CompleteTestFunctionRefGuard::create();
completeGuard->ref(); // including this thread
auto started = [](){
QCoreApplication::postEvent(EventTarget::create([](){
QThread::currentThread()->quit();
}), EventTarget::createEvent());
};
auto finished = [completeGuard](){
completeGuard->deref();
};
// Start a nymber of threads in parallel, keeping in mind that the browser
// has some max number of concurrent web workers (maybe 20), and that starting
// a new web worker requires completing a GET network request for the worker's JS.
const int numThreads = 10;
for (int i = 0; i < numThreads; ++i) {
completeGuard->ref();
TestThread::create(started, finished);
}
completeGuard->deref();
}
// Create a timer a secondary thread and verify that it fires
void WasmEventDispatcherTest::timerSecondaryThread()
{
auto started = [](){
QTimer::singleShot(timerTimeout, [](){
QThread::currentThread()->quit();
});
};
auto finished = [](){
QtWasmTest::completeTestFunction();
};
TestThread::create(started, finished);
}
#endif
// Post an event to the main thread and asyncify wait for it
void WasmEventDispatcherTest::postEventAsyncify()
{
if (!qstdweb::haveAsyncify()) {
QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
return;
}
QEventLoop loop;
QCoreApplication::postEvent(EventTarget::create([&loop](){
loop.quit();
}), EventTarget::createEvent());
loop.exec();
QtWasmTest::completeTestFunction();
}
// Create a timer on the main thread and asyncify wait for it
void WasmEventDispatcherTest::timerAsyncify()
{
if (!qstdweb::haveAsyncify()) {
QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
return;
}
QEventLoop loop;
QTimer::singleShot(timerTimeout, [&loop](){
loop.quit();
});
loop.exec();
QtWasmTest::completeTestFunction();
}
// Asyncify wait in a loop
void WasmEventDispatcherTest::postEventAsyncifyLoop()
{
if (!qstdweb::haveAsyncify()) {
QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
return;
}
for (int i = 0; i < 10; ++i) {
QEventLoop loop;
QCoreApplication::postEvent(EventTarget::create([&loop]() {
loop.quit();
}), EventTarget::createEvent());
loop.exec();
}
QtWasmTest::completeTestFunction();
}
#if QT_CONFIG(thread)
// Asyncify wait for QThread::wait() / pthread_join()
void WasmEventDispatcherTest::threadAsyncifyWait()
{
if (!qstdweb::haveAsyncify())
QtWasmTest::completeTestFunction(QtWasmTest::TestResult::Skip, "requires asyncify");
const int threadCount = 15;
QVector<QThread *> threads;
threads.reserve(threadCount);
for (int i = 0; i < threadCount; ++i) {
QThread *thread = new QThread();
threads.push_back(thread);
thread->start();
}
for (int i = 0; i < threadCount; ++i) {
QThread *thread = threads[i];
thread->wait();
delete thread;
}
QtWasmTest::completeTestFunction();
}
#endif
int main(int argc, char **argv)
{
auto testObject = std::make_shared<WasmEventDispatcherTest>();
QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
return 0;
}
#include "main.moc"

View File

@ -0,0 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(main_exec
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
)

View File

@ -0,0 +1,67 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui>
// This example demonstrates how the standard Qt main()
// pattern works on Emscripten/WebAssambly, where exec()
// does not return.
class ClickWindow: public QRasterWindow
{
public:
ClickWindow() {
qDebug() << "ClickWindow constructor";
}
~ClickWindow() {
qDebug() << "ClickWindow destructor";
}
void paintEvent(QPaintEvent *ev) override {
QPainter p(this);
p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue);
p.drawText(50, 100, "Application has started. See the developer tools console for debug output");
}
void mousePressEvent(QMouseEvent *) override {
qDebug() << "mousePressEvent(): calling QGuiApplication::quit()";
QGuiApplication::quit();
}
};
int main(int argc, char **argv)
{
qDebug() << "main(): Creating QGuiApplication object";
QGuiApplication app(argc, argv);
QObject::connect(&app, &QCoreApplication::aboutToQuit, [](){
qDebug() << "QCoreApplication::aboutToQuit";
});
ClickWindow window;
window.show();
qDebug() << "main(): calling exec()";
app.exec();
// The exec() call above never returns; instead, a JavaScript exception
// is thrown such that control returns to the browser while preserving
// the C++ stack.
// This means that the window object above is not destroyed, and that
// shutdown code after exec() does not run.
qDebug() << "main(): after exit"; // <- will not be printed
}
// Global variables are created before main() as usual, but not destroyed
class Global
{
public:
Global() {
qDebug() << "Global constructor";
}
~Global() {
qDebug() << "Global destructor"; // <- will not be printed
}
};
Global global;

View File

@ -0,0 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(main_noexec
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
)

View File

@ -0,0 +1,66 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui>
// This example demonstrates how to create QGuiApplication
// without calling exec(), and then exiting main() without
// shutting down the Qt event loop.
class ClickWindow: public QRasterWindow
{
public:
ClickWindow() {
qDebug() << "ClickWindow constructor";
}
~ClickWindow() {
qDebug() << "ClickWindow destructor";
}
void paintEvent(QPaintEvent *ev) override {
QPainter p(this);
p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue);
p.drawText(50, 100, "Application has started. See the developer tools console for debug output");
}
void mousePressEvent(QMouseEvent *) override {
qDebug() << "mousePressEvent(): calling QGuiApplication::quit()";
QGuiApplication::quit();
}
};
int main(int argc, char **argv)
{
qDebug() << "main(): Creating QGuiApplication object";
QGuiApplication *app = new QGuiApplication(argc, argv);
QObject::connect(app, &QCoreApplication::aboutToQuit, [](){
qDebug() << "QCoreApplication::aboutToQuit";
});
qDebug() << "main(): Creating ClickWindow object";
ClickWindow *window = new ClickWindow();
window->show();
// We can exit main; the Qt event loop and the emscripten runtime
// will keep running, as long as Emscriptens EXIT_RUNTIME option
// has not been enabled.
qDebug() << "main(): exit";
}
// Global variables are created before main() as usual, but not destroyed
class Global
{
public:
Global() {
qDebug() << "Global constructor";
}
~Global() {
qDebug() << "Global destructor"; // <- will not be printed
}
};
Global global;

View File

@ -0,0 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(thread_exec
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
)

View File

@ -0,0 +1,75 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui>
class EventTarget : public QObject
{
Q_OBJECT
protected:
bool event(QEvent *evt)
{
if (evt->type() == QEvent::User) {
qDebug() << "User event on thread" << QThread::currentThread();
return true;
}
return QObject::event(evt);
}
};
class EventPosterWindow: public QRasterWindow
{
public:
EventPosterWindow(EventTarget *target)
:m_target(target)
{ }
void paintEvent(QPaintEvent *ev) override {
QPainter p(this);
p.fillRect(ev->rect(), QColorConstants::Svg::deepskyblue);
p.drawText(50, 100, "Application has started. Click to post events.\n See the developer tools console for debug output");
}
void mousePressEvent(QMouseEvent *) override {
qDebug() << "Posting events from thread" << QThread::currentThread();
QGuiApplication::postEvent(m_target, new QEvent(QEvent::User));
QTimer::singleShot(500, m_target, []() {
qDebug() << "Timer event on secondary thread" << QThread::currentThread();
});
}
public:
EventTarget *m_target;
};
class SecondaryThread : public QThread
{
public:
void run() override {
qDebug() << "exec on secondary thread" << QThread::currentThread();
exec();
}
};
// This example demonstrates how to start a secondary thread event loop
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
EventTarget eventTarget;
EventPosterWindow window(&eventTarget);
window.show();
SecondaryThread thread;
eventTarget.moveToThread(&thread);
#if QT_CONFIG(thread)
thread.start();
#else
qDebug() << "Warning: This test requires a multithreaded build of Qt for WebAssembly";
#endif
return app.exec();
}
#include "main.moc"

View File

@ -0,0 +1,12 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(localfiles
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Gui
Qt::Widgets
)

View File

@ -0,0 +1,8 @@
TEMPLATE = app
TARGET = localfiles
QT += core gui widgets
OBJECTS_DIR = .obj
MOC_DIR = .moc
SOURCES += main.cpp

View File

@ -0,0 +1,137 @@
// Copyright (C) 2019 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtWidgets/QtWidgets>
#include <emscripten/val.h>
#include <emscripten.h>
class AppWindow : public QObject
{
Q_OBJECT
public:
AppWindow() : m_layout(new QVBoxLayout(&m_loadFileUi)),
m_window(emscripten::val::global("window")),
m_showOpenFilePickerFunction(m_window["showOpenFilePicker"]),
m_showSaveFilePickerFunction(m_window["showSaveFilePicker"])
{
addWidget<QLabel>("Filename filter");
const bool localFileApiAvailable =
!m_showOpenFilePickerFunction.isUndefined() && !m_showSaveFilePickerFunction.isUndefined();
m_useLocalFileApisCheckbox = addWidget<QCheckBox>("Use the window.showXFilePicker APIs");
m_useLocalFileApisCheckbox->setEnabled(localFileApiAvailable);
m_useLocalFileApisCheckbox->setChecked(localFileApiAvailable);
m_filterEdit = addWidget<QLineEdit>("Images (*.png *.jpg);;PDF (*.pdf);;*.txt");
auto* loadFile = addWidget<QPushButton>("Load File");
m_fileInfo = addWidget<QLabel>("Opened file:");
m_fileInfo->setTextInteractionFlags(Qt::TextSelectableByMouse);
m_fileHash = addWidget<QLabel>("Sha256:");
m_fileHash->setTextInteractionFlags(Qt::TextSelectableByMouse);
addWidget<QLabel>("Saved file name");
m_savedFileNameEdit = addWidget<QLineEdit>("qttestresult");
m_saveFile = addWidget<QPushButton>("Save File");
m_saveFile->setEnabled(false);
m_layout->addStretch();
m_loadFileUi.setLayout(m_layout);
QObject::connect(m_useLocalFileApisCheckbox, &QCheckBox::toggled, std::bind(&AppWindow::onUseLocalFileApisCheckboxToggled, this));
QObject::connect(loadFile, &QPushButton::clicked, this, &AppWindow::onLoadClicked);
QObject::connect(m_saveFile, &QPushButton::clicked, std::bind(&AppWindow::onSaveClicked, this));
}
void show() {
m_loadFileUi.show();
}
~AppWindow() = default;
private Q_SLOTS:
void onUseLocalFileApisCheckboxToggled()
{
m_window.set("showOpenFilePicker",
m_useLocalFileApisCheckbox->isChecked() ?
m_showOpenFilePickerFunction : emscripten::val::undefined());
m_window.set("showSaveFilePicker",
m_useLocalFileApisCheckbox->isChecked() ?
m_showSaveFilePickerFunction : emscripten::val::undefined());
}
void onFileContentReady(const QString &fileName, const QByteArray &fileContents)
{
m_fileContent = fileContents;
m_fileInfo->setText(QString("Opened file: %1 size: %2").arg(fileName).arg(fileContents.size()));
m_saveFile->setEnabled(true);
QTimer::singleShot(100, this, &AppWindow::computeAndDisplayFileHash); // update UI before computing hash
}
void computeAndDisplayFileHash()
{
QByteArray hash = QCryptographicHash::hash(m_fileContent, QCryptographicHash::Sha256);
m_fileHash->setText(QString("Sha256: %1").arg(QString(hash.toHex())));
}
void onFileSaved(bool success)
{
m_fileInfo->setText(QString("File save result: %1").arg(success ? "success" : "failed"));
}
void onLoadClicked()
{
QFileDialog::getOpenFileContent(
m_filterEdit->text(),
std::bind(&AppWindow::onFileContentReady, this, std::placeholders::_1, std::placeholders::_2));
}
void onSaveClicked()
{
m_fileInfo->setText("Saving file... (no result information with current API)");
QFileDialog::saveFileContent(m_fileContent, m_savedFileNameEdit->text());
}
private:
template <class T, class... Args>
T* addWidget(Args... args)
{
T* widget = new T(std::forward<Args>(args)..., &m_loadFileUi);
m_layout->addWidget(widget);
return widget;
}
QWidget m_loadFileUi;
QCheckBox* m_useLocalFileApisCheckbox;
QLineEdit* m_filterEdit;
QVBoxLayout *m_layout;
QLabel* m_fileInfo;
QLabel* m_fileHash;
QLineEdit* m_savedFileNameEdit;
QPushButton* m_saveFile;
emscripten::val m_window;
emscripten::val m_showOpenFilePickerFunction;
emscripten::val m_showSaveFilePickerFunction;
QByteArray m_fileContent;
};
int main(int argc, char **argv)
{
QApplication application(argc, argv);
AppWindow window;
window.show();
return application.exec();
}
#include "main.moc"

View File

View File

@ -0,0 +1,8 @@
qt_internal_add_manual_test(echo_client_mainthread
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Network
)

View File

@ -0,0 +1,52 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
#include <QtNetwork>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
// This example connects to localhost, but note that the host can
// be any host reachable from the client using webscokets, at any port.
QString hostName = "localhost";
int port = 1515;
qDebug() << "This example connects to a server at" << hostName << "port" << port << ","
<< "where it expects to find a WebSockify server, which forwards to the fortune server.";
auto echo = [hostName, port]() {
QTcpSocket *socket = new QTcpSocket();
QObject::connect(socket, &QAbstractSocket::connected, [socket]() {
qDebug() << "Connected";
socket->write("Hello, echo server!");
socket->flush();
});
QObject::connect(socket, &QIODevice::readyRead, [socket]() {
QByteArray data = socket->readAll();
qDebug() << "Ready Read, got echo:" << data;
socket->disconnectFromHost();
socket->deleteLater();
});
QObject::connect(socket, &QAbstractSocket::errorOccurred, [socket]() {
qDebug() << "Error Occurred" << socket->error();
});
QObject::connect(socket, &QAbstractSocket::disconnected, [socket]() {
qDebug() << "Disconnected";
socket->deleteLater();
});
qDebug() << "Connect to host" << hostName << port;
socket->connectToHost(hostName, port);
};
QTimer::singleShot(500, [echo](){
echo();
});
return app.exec();
}

View File

@ -0,0 +1,8 @@
qt_internal_add_manual_test(echo_client_secondarythread
GUI
SOURCES
main.cpp
LIBRARIES
Qt::Core
Qt::Network
)

View File

@ -0,0 +1,50 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
#include <QtNetwork>
int main(int argc, char **argv) {
QCoreApplication app(argc, argv);
// This example connects to localhost, but note that the host can
// be any host reachable from the client using webscokets, at any port.
QString hostName = "localhost";
int port = 1515;
qDebug() << "## This example connects to a server at" << hostName << "port" << port << ","
<< "where it expects to find a WebSockify server, which forwards to the fortune server.";
auto echo = [hostName, port]() {
qDebug() << "Connecting to" << hostName << port;
QTcpSocket socket;
socket.connectToHost(hostName, port);
bool connected = socket.waitForConnected(3000);
if (!connected) {
qDebug() << "connect failure";
return;
}
qDebug() << "Connected";
socket.write("echo:Hello, echo server!;");
socket.flush();
qDebug() << "Calling waitForReadyRead()";
socket.waitForReadyRead(20000);
QByteArray data = socket.readAll();
qDebug() << "Got echo:" << data;
socket.disconnectFromHost();
socket.deleteLater();
qDebug() << "Disconnected";
};
QThread thread;
QObject::connect(&thread, &QThread::started, [echo](){
echo();
});
thread.start();
app.exec();
}

View File

@ -0,0 +1,14 @@
project(echo_server)
cmake_minimum_required(VERSION 3.19)
find_package(Qt6 COMPONENTS Core)
find_package(Qt6 COMPONENTS network)
qt_add_executable(echo_server
main.cpp
)
target_link_libraries(echo_server PUBLIC
Qt::Core
Qt::Network
)

View File

@ -0,0 +1,80 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtCore>
#include <QtNetwork>
const int timeout = 60 * 1000;
int main(int argc, char **argv)
{
QCoreApplication app(argc, argv);
QTcpServer server;
QObject::connect(&server, &QTcpServer::newConnection, [&server](){
qDebug() << "new connection";
QByteArray *receiveBuffer = new QByteArray();
QTcpSocket *socket = server.nextPendingConnection();
QObject::connect(socket, &QIODevice::readyRead, [socket, receiveBuffer](){
// This implements a very simple command protocol, where the server
// processes a stream of commands delimited by ';', and then performs
// an action in reply. The supported commands with actions are:
//
// echo:<message>; writes the received <message> back
// close; closes the socket
//
// We might receive multiple or partial commands; read all available data
// and then scan the buffer for complete commands.
QByteArray newData = socket->readAll();
*receiveBuffer += newData;
int pos = receiveBuffer->indexOf(";");
while (pos != -1) {
QByteArray command = receiveBuffer->left(pos);
receiveBuffer->remove(0, pos + 1);
pos = receiveBuffer->indexOf(";");
if (command.startsWith("echo")) {
// Echo expects echo:<message>
QList<QByteArray> parts = command.split(':');
QByteArray reply = parts.last() + ';';
qDebug() << "Command: echo:" << parts.last();
socket->write(reply);
socket->flush();
} else if (command.startsWith("close")) {
qDebug() << "Command: close";
socket->write("bye!;");
socket->flush();
socket->close();
break;
} else {
qDebug() << "Unknown command:" << command;
}
}
});
QObject::connect(socket, &QAbstractSocket::disconnected, [socket, receiveBuffer](){
delete receiveBuffer;
socket->deleteLater();
});
});
// This is example is intended to be used together with WebSockify on
// the server and acts as a counterpart to the client examples which
// run in the browser. (This example does not run in the browser).
qDebug() << "\nStarting echo server at port 1516. You should now start the"
<< "\nWebSockify forwarding server, and then connect from one of"
<< "\nthe client examples."
<< "\n websockify 1515 localhost:1516";
server.listen(QHostAddress::Any, 1516);
return app.exec();
}

View File

@ -0,0 +1,22 @@
qt_internal_add_manual_test(sockify_sockets_auto
SOURCES
main.cpp
../../qtwasmtestlib/qtwasmtestlib.cpp
LIBRARIES
Qt::Core
Qt::Network
)
include_directories(../../qtwasmtestlib/)
add_custom_command(
TARGET sockify_sockets_auto POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/sockify_sockets_auto.html
${CMAKE_CURRENT_BINARY_DIR}/sockify_sockets_auto.html)
add_custom_command(
TARGET sockify_sockets_auto POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/../../qtwasmtestlib/qtwasmtestlib.js
${CMAKE_CURRENT_BINARY_DIR}/qtwasmtestlib.js)

View File

@ -0,0 +1,318 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <qtwasmtestlib.h>
#include <QtCore>
#include <QtNetwork>
const int socketWait = 1000;
const QString hostName = "localhost";
const int port = 1515;
class SockifySocketsTest: public QObject
{
Q_OBJECT
private slots:
void echo();
void echoMultipleMessages();
void echoMultipleSockets();
void remoteClose();
#if QT_CONFIG(thread)
void thread_echo();
void thread_remoteClose();
void thread_echoMultipleSockets();
#endif
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
void asyncify_echo();
void asyncify_remoteClose();
#endif
};
class CompleteTestFunctionRefGuard {
public:
CompleteTestFunctionRefGuard(CompleteTestFunctionRefGuard const&) = delete;
CompleteTestFunctionRefGuard& operator=(CompleteTestFunctionRefGuard const&) = delete;
static CompleteTestFunctionRefGuard *create() {
return new CompleteTestFunctionRefGuard();
}
void ref() {
QMutexLocker lock(&mutex);
++counter;
}
void deref() {
bool itsTheFinalDeref = [this] {
QMutexLocker lock(&mutex);
return --counter == 0;
}();
if (itsTheFinalDeref) {
delete this;
QtWasmTest::completeTestFunction();
}
}
private:
CompleteTestFunctionRefGuard() { };
QMutex mutex;
int counter = 0;
};
#if QT_CONFIG(thread)
class TestThread : public QThread
{
public:
static QThread *create(std::function<void()> started, std::function<void()> finished)
{
TestThread *thread = new TestThread();
connect(thread, &QThread::started, [started]() {
started();
});
connect(thread, &QThread::finished, [thread, finished]() {
finished();
thread->deleteLater();
});
thread->start();
return thread;
}
};
#endif
void blockingEchoTest()
{
QTcpSocket socket;
socket.connectToHost(hostName, port);
if (!socket.waitForConnected(socketWait))
qFatal("socket connect error");
QByteArray message = "Hello, echo server!";
QByteArray command = "echo:" + message + ';';
socket.write(command);
socket.flush();
socket.waitForReadyRead(socketWait);
QByteArray expectedReply = message + ';';
QByteArray reply = socket.readAll();
if (reply != expectedReply)
qFatal("echo_multiple received incorrect reply");
socket.disconnectFromHost();
}
void blockingRemoteClose()
{
QTcpSocket socket;
qDebug() << "## connectToHost";
socket.connectToHost(hostName, port);
qDebug() << "## waitForConnected";
socket.waitForConnected(socketWait);
socket.write("close;");
socket.flush();
qDebug() << "## waitForBytesWritten";
socket.waitForBytesWritten(socketWait);
qDebug() << "## waitForReadyRead";
socket.waitForReadyRead(200);
qDebug() << "## waitForDisconnected";
socket.waitForDisconnected(socketWait);
qDebug() << "## done";
}
// Verify that sending one echo command and receiving the reply works
void SockifySocketsTest::echo()
{
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost(hostName, port);
QByteArray message = "Hello, echo server!";
QObject::connect(socket, &QAbstractSocket::connected, [socket, message]() {
QByteArray command = "echo:" + message + ';';
socket->write(command);
socket->flush();
});
QByteArray *reply = new QByteArray();
QObject::connect(socket, &QIODevice::readyRead, [socket, reply, message]() {
*reply += socket->readAll();
if (reply->contains(';')) {
bool match = (*reply == message + ';');
socket->disconnectFromHost();
socket->deleteLater();
delete reply;
QtWasmTest::completeTestFunction(match ? QtWasmTest::TestResult::Pass : QtWasmTest::TestResult::Fail, std::string());
}
});
}
void SockifySocketsTest::echoMultipleMessages()
{
const int count = 20;
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost(hostName, port);
QByteArray message = "Hello, echo server!";
QObject::connect(socket, &QAbstractSocket::connected, [socket, message]() {
QByteArray command = "echo:" + message + ';';
for (int i = 0; i < count; ++i) {
quint64 written = socket->write(command);
if (written != quint64(command.size()))
qFatal("Unable to write to socket");
}
socket->flush();
});
QByteArray expectedReply;
for (int i = 0; i < count; ++i)
expectedReply += (message + ';');
QByteArray *receivedReply = new QByteArray;
QObject::connect(socket, &QIODevice::readyRead, [socket, receivedReply, expectedReply]() {
QByteArray reply = socket->readAll();
*receivedReply += reply;
if (*receivedReply == expectedReply) {
socket->disconnectFromHost();
socket->deleteLater();
delete receivedReply;
QtWasmTest::completeTestFunction();
}
});
}
void SockifySocketsTest::echoMultipleSockets()
{
const int connections = 5;
auto guard = CompleteTestFunctionRefGuard::create();
QByteArray message = "Hello, echo server!";
for (int i = 0; i < connections; ++i) {
guard->ref();
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost(hostName, port);
QObject::connect(socket, &QAbstractSocket::connected, [socket, message]() {
QByteArray command = "echo:" + message + ';';
socket->write(command);
socket->flush();
});
QObject::connect(socket, &QIODevice::readyRead, [guard, socket, message]() {
QByteArray reply = socket->readAll();
socket->disconnectFromHost();
socket->deleteLater();
if (reply != (message + ';'))
qFatal("echo_multiple received incorrect reply");
guard->deref();
});
}
}
void SockifySocketsTest::remoteClose()
{
QTcpSocket *socket = new QTcpSocket();
socket->connectToHost(hostName, port);
QObject::connect(socket, &QAbstractSocket::connected, [socket]() {
socket->write("close;");
socket->flush();
});
QObject::connect(socket, &QAbstractSocket::disconnected, [socket]() {
qDebug() << "disconnected";
socket->deleteLater();
QtWasmTest::completeTestFunction();
});
}
#if QT_CONFIG(thread)
void SockifySocketsTest::thread_echo()
{
auto started = []() {
blockingEchoTest();
QThread::currentThread()->quit();
};
auto finished = [](){
QtWasmTest::completeTestFunction();
};
TestThread::create(started, finished);
}
void SockifySocketsTest::thread_echoMultipleSockets()
{
const int connections = 2; // TODO: test more threads
auto guard = CompleteTestFunctionRefGuard::create();
guard->ref();
for (int i = 0; i < connections; ++i) {
guard->ref();
auto started = [](){
blockingEchoTest();
QThread::currentThread()->quit();
};
auto finished = [guard](){
guard->deref();
};
TestThread::create(started, finished);
}
guard->deref();
}
void SockifySocketsTest::thread_remoteClose()
{
auto started = [](){
blockingRemoteClose();
QThread::currentThread()->quit();
};
auto finished = [](){
QtWasmTest::completeTestFunction();
};
TestThread::create(started, finished);
}
#endif
#ifdef QT_HAVE_EMSCRIPTEN_ASYNCIFY
// Post an event to the main thread and asyncify wait for it
void SockifySocketsTest::asyncify_echo()
{
blockingEchoTest();
QtWasmTest::completeTestFunction();
}
void SockifySocketsTest::asyncify_remoteClose()
{
blockingRemoteClose();
QtWasmTest::completeTestFunction();
}
#endif
int main(int argc, char **argv)
{
auto testObject = std::make_shared<SockifySocketsTest>();
QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
return 0;
}
#include "main.moc"

View File

@ -0,0 +1,17 @@
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="sockify_sockets_auto.js"></script>
<script>
window.onload = async () => {
runTestCase(document.getElementById("log"));
};
</script>
<p> Sockify tunneled sockets auto test.
<p>This test requires running echo_server and <a href=https://github.com/novnc/websockify>websockify</a> (or equivalent) on the host:
<pre>
/path/to/qtbase/tests/manual/wasm/network/echo_server/echo_server
websockify 1515 localhost:1516
</pre>
<div id="log"></div>

View File

@ -0,0 +1,72 @@
qt_internal_add_manual_test(promise_auto
SOURCES
promise_main.cpp
../qtwasmtestlib/qtwasmtestlib.cpp
LIBRARIES
Qt::Core
Qt::CorePrivate
)
include_directories(../qtwasmtestlib/)
add_custom_command(
TARGET promise_auto POST_BUILD
COMMAND ${CMAKE_COMMAND} -E copy
${CMAKE_CURRENT_SOURCE_DIR}/promise_auto.html
${CMAKE_CURRENT_BINARY_DIR}/promise_auto.html)
add_custom_command(
TARGET promise_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(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)

View File

@ -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(document.getElementById("log"));
};
</script>
<p>Running files auto test.</p>
<div id="log"></div>

View File

@ -0,0 +1,471 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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(
{QStringLiteral("*")}, 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(
{QStringLiteral("*")}, 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(
{QStringLiteral("*")}, 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(
{QStringLiteral("*")}, 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"

View File

@ -0,0 +1,10 @@
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="promise_auto.js"></script>
<script>
window.onload = () => {
runTestCase(document.getElementById("log"));
};
</script>
<p>Running promise auto test.</p>
<div id="log"></div>

View File

@ -0,0 +1,486 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include <QtCore/QCoreApplication>
#include <QtCore/QEvent>
#include <QtCore/QMutex>
#include <QtCore/QObject>
#include <QtCore/private/qstdweb_p.h>
#include <qtwasmtestlib.h>
#include <emscripten.h>
using namespace emscripten;
class WasmPromiseTest : public QObject
{
Q_OBJECT
public:
WasmPromiseTest() : m_window(val::global("window")), m_testSupport(val::object()) {}
~WasmPromiseTest() noexcept = default;
private:
void init() {
m_testSupport = val::object();
m_window.set("testSupport", m_testSupport);
EM_ASM({
testSupport.resolve = {};
testSupport.reject = {};
testSupport.promises = {};
testSupport.waitConditionPromise = new Promise((resolve, reject) => {
testSupport.finishWaiting = resolve;
});
testSupport.makeTestPromise = (param) => {
testSupport.promises[param] = new Promise((resolve, reject) => {
testSupport.resolve[param] = resolve;
testSupport.reject[param] = reject;
});
return testSupport.promises[param];
};
});
}
val m_window;
val m_testSupport;
private slots:
void simpleResolve();
void multipleResolve();
void simpleReject();
void multipleReject();
void throwInThen();
void bareFinally();
void finallyWithThen();
void finallyWithThrow();
void finallyWithThrowInThen();
void nested();
void all();
void allWithThrow();
void allWithFinally();
void allWithFinallyAndThrow();
};
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;
};
// Post event to the main thread and verify that it is processed.
void WasmPromiseTest::simpleResolve()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE("Some lovely data", result.as<std::string>());
QWASMSUCCESS();
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, std::string("simpleResolve"));
EM_ASM({
testSupport.resolve["simpleResolve"]("Some lovely data");
});
}
void WasmPromiseTest::multipleResolve()
{
init();
static constexpr int promiseCount = 1000;
auto onThen = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS();
});
for (int i = 0; i < promiseCount; ++i) {
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [=](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
(*onThen)();
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, (QStringLiteral("test") + QString::number(i)).toStdString());
}
EM_ASM({
for (let i = $0 - 1; i >= 0; --i) {
testSupport.resolve['test' + i](`${i}`);
}
}, promiseCount);
}
void WasmPromiseTest::simpleReject()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
QWASMFAIL("Unexpected then");
},
.catchFunc = [](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE("Evil error", result.as<std::string>());
QWASMSUCCESS();
}
}, std::string("simpleReject"));
EM_ASM({
testSupport.reject["simpleReject"]("Evil error");
});
}
void WasmPromiseTest::multipleReject()
{
static constexpr int promiseCount = 1000;
auto onCatch = std::make_shared<BarrierCallback>(promiseCount, []() {
QWASMSUCCESS();
});
for (int i = 0; i < promiseCount; ++i) {
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [=](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE(QString::number(i).toStdString(), result.as<std::string>());
(*onCatch)();
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, (QStringLiteral("test") + QString::number(i)).toStdString());
}
EM_ASM({
for (let i = $0 - 1; i >= 0; --i) {
testSupport.resolve['test' + i](`${i}`);
}
}, promiseCount);
}
void WasmPromiseTest::throwInThen()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "Expected error";
});
},
.catchFunc = [](val error) {
QWASMCOMPARE("Expected error", error.as<std::string>());
QWASMSUCCESS();
}
}, std::string("throwInThen"));
EM_ASM({
testSupport.resolve["throwInThen"]();
});
}
void WasmPromiseTest::bareFinally()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.finallyFunc = []() {
QWASMSUCCESS();
}
}, std::string("bareFinally"));
EM_ASM({
testSupport.resolve["bareFinally"]();
});
}
void WasmPromiseTest::finallyWithThen()
{
init();
auto thenCalled = std::make_shared<bool>();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [thenCalled] (val result) {
Q_UNUSED(result);
*thenCalled = true;
},
.finallyFunc = [thenCalled]() {
QWASMVERIFY(*thenCalled);
QWASMSUCCESS();
}
}, std::string("finallyWithThen"));
EM_ASM({
testSupport.resolve["finallyWithThen"]();
});
}
void WasmPromiseTest::finallyWithThrow()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.catchFunc = [](val error) {
Q_UNUSED(error);
},
.finallyFunc = []() {
QWASMSUCCESS();
}
}, std::string("finallyWithThrow"));
EM_ASM({
testSupport.reject["finallyWithThrow"]();
});
}
void WasmPromiseTest::finallyWithThrowInThen()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "Expected error";
});
},
.catchFunc = [](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE("Expected error", result.as<std::string>());
},
.finallyFunc = []() {
QWASMSUCCESS();
}
}, std::string("bareFinallyWithThen"));
EM_ASM({
testSupport.resolve["bareFinallyWithThen"]();
});
}
void WasmPromiseTest::nested()
{
init();
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [this](val result) {
QWASMVERIFY(result.isString());
QWASMCOMPARE("Outer data", result.as<std::string>());
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [this](val innerResult) {
QWASMVERIFY(innerResult.isString());
QWASMCOMPARE("Inner data", innerResult.as<std::string>());
qstdweb::Promise::make(m_testSupport, "makeTestPromise", {
.thenFunc = [](val innerResult) {
QWASMVERIFY(innerResult.isString());
QWASMCOMPARE("Innermost data", innerResult.as<std::string>());
QWASMSUCCESS();
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, std::string("innermost"));
EM_ASM({
testSupport.resolve["innermost"]("Innermost data");
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, std::string("inner"));
EM_ASM({
testSupport.resolve["inner"]("Inner data");
});
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
}, std::string("outer"));
EM_ASM({
testSupport.resolve["outer"]("Outer data");
});
}
void WasmPromiseTest::all()
{
init();
static constexpr int promiseCount = 1000;
auto thenCalledOnce = std::shared_ptr<bool>();
*thenCalledOnce = true;
std::vector<val> promises;
promises.reserve(promiseCount);
for (int i = 0; i < promiseCount; ++i) {
promises.push_back(m_testSupport.call<val>("makeTestPromise", val(("all" + QString::number(i)).toStdString())));
}
qstdweb::Promise::all(std::move(promises), {
.thenFunc = [=](val result) {
QWASMVERIFY(*thenCalledOnce);
*thenCalledOnce = false;
QWASMVERIFY(result.isArray());
QWASMCOMPARE(promiseCount, result["length"].as<int>());
for (int i = 0; i < promiseCount; ++i) {
QWASMCOMPARE(QStringLiteral("Data %1").arg(i).toStdString(), result[i].as<std::string>());
}
QWASMSUCCESS();
},
.catchFunc = [](val error) {
Q_UNUSED(error);
QWASMFAIL("Unexpected catch");
}
});
EM_ASM({
console.log('Resolving');
for (let i = $0 - 1; i >= 0; --i) {
testSupport.resolve['all' + i](`Data ${i}`);
}
}, promiseCount);
}
void WasmPromiseTest::allWithThrow()
{
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto catchCalledOnce = std::shared_ptr<bool>();
*catchCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
QWASMFAIL("Unexpected then");
},
.catchFunc = [catchCalledOnce](val result) {
QWASMVERIFY(*catchCalledOnce);
*catchCalledOnce = false;
QWASMVERIFY(result.isString());
QWASMCOMPARE("Error 2", result.as<std::string>());
QWASMSUCCESS();
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.reject["promise2"]("Error 2");
});
}
void WasmPromiseTest::allWithFinally()
{
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto finallyCalledOnce = std::shared_ptr<bool>();
*finallyCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
},
.finallyFunc = [finallyCalledOnce]() {
QWASMVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false;
QWASMSUCCESS();
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.resolve["promise2"]("Data 2");
});
}
void WasmPromiseTest::allWithFinallyAndThrow()
{
init();
val promise1 = m_testSupport.call<val>("makeTestPromise", val("promise1"));
val promise2 = m_testSupport.call<val>("makeTestPromise", val("promise2"));
val promise3 = m_testSupport.call<val>("makeTestPromise", val("promise3"));
auto finallyCalledOnce = std::shared_ptr<bool>();
*finallyCalledOnce = true;
qstdweb::Promise::all({promise1, promise2, promise3}, {
.thenFunc = [](val result) {
Q_UNUSED(result);
EM_ASM({
throw "This breaks it all!!!";
});
},
.finallyFunc = [finallyCalledOnce]() {
QWASMVERIFY(*finallyCalledOnce);
*finallyCalledOnce = false;
QWASMSUCCESS();
}
});
EM_ASM({
testSupport.resolve["promise3"]("Data 3");
testSupport.resolve["promise1"]("Data 1");
testSupport.resolve["promise2"]("Data 2");
});
}
int main(int argc, char **argv)
{
auto testObject = std::make_shared<WasmPromiseTest>();
QtWasmTest::initTestCase<QCoreApplication>(argc, argv, testObject);
return 0;
}
#include "promise_main.moc"

View File

@ -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(document.getElementById("log"));
};
</script>
<p>Running files auto test.</p>
<div id="log"></div>

View File

@ -0,0 +1,174 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#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/private/qrhigles2_p.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 | QRhi::EnableProfiling;
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("qtAddContainerElement",
emscripten::val::module_property("qtAddContainerElement"));
m_testSupport.set("qtRemoveContainerElement",
emscripten::val::module_property("qtRemoveContainerElement"));
}
~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.qtRemoveContainerElement(testSupport.screenElement);
testSupport.screenElement.parentElement.removeChild(testSupport.screenElement);
});
});
EM_ASM({ testSupport.qtAddContainerElement(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"

View File

@ -0,0 +1,19 @@
<!--
Copyright (C) 2022 The Qt Company Ltd.
SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
-->
<!doctype html>
<html>
<head>
<meta charset="utf-8">
<title>Qt Loader tests</title>
<script type="text/javascript" src="https://sinonjs.org/releases/sinon-14.0.0.js"
integrity="sha384-z8J4N1s2hPDn6ClmFXDQkKD/e738VOWcR8JmhztPRa+PgezxQupgZu3LzoBO4Jw8"
crossorigin="anonymous"></script>
<script src="/src/plugins/platforms/wasm/qtloader.js"></script>
<script src="tst_qtloader.js" type="module" defer></script>
</head>
<body></body>
</html>

View File

@ -0,0 +1,42 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
import { TestRunner } from '../shared/testrunner.js';
class QtLoaderTests
{
async beforeEach() { sinon.stub(window, 'alert'); }
async afterEach() { sinon.restore(); }
async sampleTestCase()
{
await new Promise(resolve =>
{
window.alert();
sinon.assert.calledOnce(window.alert);
window.setTimeout(resolve, 4000);
});
}
async sampleTestCase2()
{
await new Promise(resolve =>
{
window.alert();
sinon.assert.calledOnce(window.alert);
window.setTimeout(resolve, 1000);
});
}
async constructQtLoader()
{
new QtLoader({});
}
}
(async () =>
{
const runner = new TestRunner(new QtLoaderTests());
await runner.runAll();
})();

View File

@ -0,0 +1,75 @@
QtWasmTestLib - async auto tests for WebAssembly
================================================
QtWasmTestLib supports auto-test cases in the web browser. Like QTestLib, each
test case is defined by a QObject subclass with one or more test functions. The
test functions may be asynchronous, where they return early and then complete
at some later point.
The test lib is implemented as a C++ and JavaScript library, where the test is written
using C++ and a hosting html page calls JavaScript API to run the test.
Implementing a basic test case
------------------------------
In the test cpp file, define the test functions as private slots. All test
functions must call completeTestFunction() exactly once, or will time out
otherwise. Subsequent calls to completeTestFunction will be disregarded.
It is advised to use QWASMSUCCESS/QWASMFAIL for reporting the test execution
status and QWASMCOMPARE/QWASMVERIFY to assert on test conditions. The call can
be made after the test function itself has returned.
class TestTest: public QObject
{
Q_OBJECT
private slots:
void timerTest() {
QTimer::singleShot(timeout, [](){
completeTestFunction();
});
}
};
Then define a main() function which calls initTestCase(). The main()
function is async too, as per Emscripten default. Build the .cpp file
as a normal Qt for WebAssembly app.
int main(int argc, char **argv)
{
auto testObject = std::make_shared<TestTest>();
initTestCase<QCoreApplication>(argc, argv, testObject);
return 0;
}
Finally provide an html file which hosts the test runner and calls runTestCase()
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="test_case.js"></script>
<script>
window.onload = async () => {
runTestCase(document.getElementById("log"));
};
</script>
<p>Running Foo auto test.</p>
<div id="log"></div>
Implementing a GUI test case
----------------------------
This is similar to implementing a basic test case, with the difference that the hosting
html file provides container elements which becomes QScreens for the test code.
<!doctype html>
<script type="text/javascript" src="qtwasmtestlib.js"></script>
<script type="text/javascript" src="test_case.js"></script>
<script>
window.onload = async () => {
let log = document.getElementById("log")
let containers = [document.getElementById("container")];
runTestCase(log, containers);
};
</script>
<p>Running Foo auto test.</p>
<div id="container"></div>
<div id="log"></div>

View File

@ -0,0 +1,175 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#include "qtwasmtestlib.h"
#include <QtCore/qmetaobject.h>
#include <emscripten/bind.h>
#include <emscripten.h>
#include <emscripten/threading.h>
namespace QtWasmTest {
namespace {
QObject *g_testObject = nullptr;
std::string g_currentTestName;
std::function<void ()> g_cleanup;
}
void runOnMainThread(std::function<void(void)> fn);
static bool isValidSlot(const QMetaMethod &sl);
//
// Public API
//
// Initializes the test case with a test object and cleanup function. The
// cleanup function is called when all test functions have completed.
void initTestCase(QObject *testObject, std::function<void ()> cleanup)
{
g_testObject = testObject;
g_cleanup = cleanup;
}
void verify(bool condition, std::string_view conditionString, std::string_view file, int line)
{
if (!condition) {
completeTestFunction(
TestResult::Fail,
formatMessage(file, line, "Condition failed: " + std::string(conditionString)));
}
}
// Completes the currently running test function with a result. This function is
// thread-safe and call be called from any thread.
void completeTestFunction(TestResult result, std::string message)
{
auto resultString = [](TestResult result) {
switch (result) {
case TestResult::Pass:
return "PASS";
break;
case TestResult::Fail:
return "FAIL";
break;
case TestResult::Skip:
return "SKIP";
break;
}
};
// Report test result to JavaScript test runner, on the main thread
runOnMainThread([resultString = resultString(result), message](){
EM_ASM({
completeTestFunction(UTF8ToString($0), UTF8ToString($1), UTF8ToString($2));
}, g_currentTestName.c_str(), resultString, message.c_str());
});
}
// Completes the currently running test function with a Pass result.
void completeTestFunction()
{
completeTestFunction(TestResult::Pass, std::string());
}
//
// Private API for the Javascript test runnner
//
std::string formatMessage(std::string_view file, int line, std::string_view message)
{
return "[" + std::string(file) + ":" + QString::number(line).toStdString() + "] " + std::string(message);
}
void cleanupTestCase()
{
g_testObject = nullptr;
g_cleanup();
}
std::string getTestFunctions()
{
std::string testFunctions;
// Duplicate qPrintTestSlots (private QTestLib function) logic.
for (int i = 0; i < g_testObject->metaObject()->methodCount(); ++i) {
QMetaMethod sl = g_testObject->metaObject()->method(i);
if (!isValidSlot(sl))
continue;
QByteArray signature = sl.methodSignature();
Q_ASSERT(signature.endsWith("()"));
signature.chop(2);
if (!testFunctions.empty())
testFunctions += " ";
testFunctions += std::string(signature.constData());
}
return testFunctions;
}
void runTestFunction(std::string name)
{
g_currentTestName = name;
QMetaObject::invokeMethod(g_testObject, name.c_str());
}
void failTest(std::string message)
{
completeTestFunction(QtWasmTest::Fail, std::move(message));
}
void passTest()
{
completeTestFunction(QtWasmTest::Pass, "");
}
EMSCRIPTEN_BINDINGS(qtwebtestrunner) {
emscripten::function("cleanupTestCase", &cleanupTestCase);
emscripten::function("getTestFunctions", &getTestFunctions);
emscripten::function("runTestFunction", &runTestFunction);
emscripten::function("qtWasmFail", &failTest);
emscripten::function("qtWasmPass", &passTest);
}
//
// Test lib implementation
//
static bool isValidSlot(const QMetaMethod &sl)
{
if (sl.access() != QMetaMethod::Private || sl.parameterCount() != 0
|| sl.returnType() != QMetaType::Void || sl.methodType() != QMetaMethod::Slot)
return false;
const QByteArray name = sl.name();
return !(name.isEmpty() || name.endsWith("_data")
|| name == "initTestCase" || name == "cleanupTestCase"
|| name == "init" || name == "cleanup");
}
void trampoline(void *context)
{
Q_ASSERT(emscripten_is_main_runtime_thread());
emscripten_async_call([](void *context) {
std::function<void(void)> *fn = reinterpret_cast<std::function<void(void)> *>(context);
(*fn)();
delete fn;
}, context, 0);
}
// Runs the given function on the main thread, asynchronously
void runOnMainThread(std::function<void(void)> fn)
{
void *context = new std::function<void(void)>(fn);
if (emscripten_is_main_runtime_thread()) {
trampoline(context);
} else {
#if QT_CONFIG(thread)
emscripten_async_run_in_main_runtime_thread_(EM_FUNC_SIG_VI, reinterpret_cast<void *>(trampoline), context);
#endif
}
}
} // namespace QtWasmTest

View File

@ -0,0 +1,73 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
#ifndef QT_WASM_TESTRUNNER_H
#define QT_WASM_TESTRUNNER_H
#include <QtCore/qobject.h>
#include <functional>
namespace QtWasmTest {
enum TestResult {
Pass,
Fail,
Skip,
};
std::string formatMessage(std::string_view file,
int line,
std::string_view message);
void completeTestFunction(TestResult result, std::string message);
void completeTestFunction();
void initTestCase(QObject *testObject, std::function<void ()> cleanup);
template <typename App>
void initTestCase(int argc,
char **argv,
std::shared_ptr<QObject> testObject)
{
auto app = std::make_shared<App>(argc, argv);
auto cleanup = [testObject, app]() mutable {
// C++ lambda capture destruction order is unspecified;
// delete test before app by calling reset().
testObject.reset();
app.reset();
};
initTestCase(testObject.get(), cleanup);
}
void verify(bool condition,
std::string_view conditionString,
std::string_view file,
int line);
template<class L, class R>
void compare(const L& lhs,
const R& rhs,
std::string_view lhsString,
std::string_view rhsString,
std::string_view file,
int line) {
if (lhs != rhs) {
completeTestFunction(
TestResult::Fail,
formatMessage(file, line, "Comparison failed: " + std::string(lhsString) + " == " + std::string(rhsString)));
}
}
} // namespace QtWasmTest
#define QWASMVERIFY(condition) \
QtWasmTest::verify((condition), #condition, __FILE__, __LINE__);
#define QWASMCOMPARE(left, right) \
QtWasmTest::compare((left), (right), #left, #right, __FILE__, __LINE__);
#define QWASMSUCCESS() \
QtWasmTest::completeTestFunction(QtWasmTest::Pass, "")
#define QWASMFAIL(message) \
QtWasmTest::completeTestFunction(QtWasmTest::Fail, QtWasmTest::formatMessage(__FILE__, __LINE__, message))
#endif

View File

@ -0,0 +1,135 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
// A minimal async test runner for Qt async auto tests.
//
// Usage: Call runTest(name, testFunctionCompleted), where "name" is the name of the app
// (the .wasm file name), and testFunctionCompleted is a test-function-complete
// callback. The test runner will then instantiate the app and run tests.
//
// The test runner expects that the app instance defines the following
// functions:
//
// void cleanupTestCase()
// string getTestFunctions()
// runTestFunction(string)
//
// Further, the test runner expects that the app instance calls
// completeTestFunction() (below - note that both the instance and this
// file have a function with that name) when a test function finishes. This
// can be done during runTestFunction(), or after it has returned (this
// is the part which enables async testing). Test functions which fail
// to call completeTestFunction() will time out after 2000ms.
//
const g_maxTime = 2000;
class TestFunction {
constructor(instance, name) {
this.instance = instance;
this.name = name;
this.resolve = undefined;
this.reject = undefined;
this.timeoutId = undefined;
}
complete(result, details) {
// Reset timeout
clearTimeout(this.timeoutId);
this.timeoutId = undefined;
const callback = result.startsWith('FAIL') ? this.reject : this.resolve;
callback(`${result}${details ? ': ' + details : ''}`);
}
run() {
// Set timer which will catch test functions
// which fail to call completeTestFunction()
this.timeoutId = setTimeout(() => {
completeTestFunction(this.name, 'FAIL', `Timeout after ${g_maxTime} ms`)
}, g_maxTime);
return new Promise((resolve, reject) => {
this.resolve = resolve;
this.reject = reject;
this.instance.runTestFunction(this.name);
});
}
};
function completeTestFunction(testFunctionName, result, details) {
if (!window.currentTestFunction || testFunctionName !== window.currentTestFunction.name)
return;
window.currentTestFunction.complete(result, details);
}
async function runTestFunction(instance, name) {
if (window.currentTestFunction) {
throw new Error(`While trying to run ${name}: Last function hasn't yet finished`);
}
window.currentTestFunction = new TestFunction(instance, name);
try {
const result = await window.currentTestFunction.run();
return result;
} finally {
delete window.currentTestFunction;
}
}
async function runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers) {
// Create test case instance
const config = {
qtContainerElements: qtContainers || []
}
const instance = await createQtAppInstance(config);
// Run all test functions
const functionsString = instance.getTestFunctions();
const functions = functionsString.split(" ").filter(Boolean);
for (const name of functions) {
testFunctionStarted(name);
try {
const result = await runTestFunction(instance, name);
testFunctionCompleted(result);
} catch (err) {
testFunctionCompleted(err.message ?? err);
}
}
// Cleanup
instance.cleanupTestCase();
}
var g_htmlLogElement = undefined;
function testFunctionStarted(name) {
let line = name + ": ";
g_htmlLogElement.innerHTML += line;
}
function testFunctionCompleted(status) {
const color = (status) => {
if (status.startsWith("PASS"))
return "green";
if (status.startsWith("FAIL"))
return "red";
if (status.startsWith("SKIP"))
return "tan";
return "black";
};
const line = `<span style='color: ${color(status)};'>${status}</text><br>`;
g_htmlLogElement.innerHTML += line;
}
async function runTestCase(htmlLogElement, qtContainers) {
g_htmlLogElement = htmlLogElement;
try {
await runTestCaseImpl(testFunctionStarted, testFunctionCompleted, qtContainers);
g_htmlLogElement.innerHTML += "<br> DONE"
} catch (err) {
g_htmlLogElement.innerHTML += err
}
}

View File

@ -0,0 +1,11 @@
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: BSD-3-Clause
qt_internal_add_manual_test(rasterwindow
GUI
SOURCES
main.cpp rasterwindow.cpp
LIBRARIES
Qt::Core
Qt::Gui
)

View File

@ -0,0 +1,14 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include <QtGui>
#include "rasterwindow.h"
int main(int argc, char **argv)
{
QGuiApplication app(argc, argv);
RasterWindow window;
window.show();
return app.exec();
}

View File

@ -0,0 +1,185 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#include "rasterwindow.h"
RasterWindow::RasterWindow()
:m_eventCount(0)
,m_timeoutCount(0)
,m_frameCount(0)
,m_fps(0)
,m_pressed(false)
{
qDebug() << "RasterWindow()";
// disable alpha; saves filling the window with transparent pixels
QSurfaceFormat format;
format.setAlphaBufferSize(0);
setFormat(format);
QTimer *timer = new QTimer(this);
connect(timer, &QTimer::timeout, [this](){
++m_timeoutCount;
m_fps = m_frameCount;
m_frameCount = 0;
update();
});
timer->start(1000);
}
void RasterWindow::paintEvent(QPaintEvent * event)
{
QRect r = event->rect();
qDebug() << "RasterWindow::paintEvent" << r;
++m_frameCount;
QPainter p(this);
QColor fillColor(0, 102, 153);
QColor fillColor2(0, 85, 123);
int tileSize = 40;
for (int i = -tileSize * 2; i < r.width() + tileSize * 2; i += tileSize) {
for (int j = -tileSize * 2; j < r.height() + tileSize * 2; j += tileSize) {
QRect rect(i + (m_offset.x() % tileSize * 2), j + (m_offset.y() % tileSize * 2), tileSize, tileSize);
int colorIndex = abs((i/tileSize - j/tileSize) % 2);
p.fillRect(rect, colorIndex == 0 ? fillColor : fillColor2);
}
}
QRect g = geometry();
QRect sg = this->screen()->geometry();
QString text;
text += QString("Window Geometry: %1 %2 %3 %4\n").arg(g.x()).arg(g.y()).arg(g.width()).arg(g.height());
text += QString("Window devicePixelRatio: %1\n").arg(devicePixelRatio());
text += QString("Screen Geometry: %1 %2 %3 %4\n").arg(sg.x()).arg(sg.y()).arg(sg.width()).arg(sg.height());
text += QString("Received Events: %1\n").arg(m_eventCount);
text += QString("Received Timers: %1\n").arg(m_timeoutCount);
text += QString("Frames Per Second: %1\n").arg(m_fps);
p.drawText(QRectF(0, 0, width(), height()), Qt::AlignCenter, text);
}
void RasterWindow::exposeEvent(QExposeEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::exposeEvent(ev);
incrementEventCount();
}
void RasterWindow::focusInEvent(QFocusEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::focusInEvent(ev);
incrementEventCount();
}
void RasterWindow::focusOutEvent(QFocusEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::focusOutEvent(ev);
incrementEventCount();
}
void RasterWindow::hideEvent(QHideEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::hideEvent(ev);
incrementEventCount();
}
void RasterWindow::keyPressEvent(QKeyEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::keyPressEvent(ev);
incrementEventCount();
}
void RasterWindow::keyReleaseEvent(QKeyEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::keyReleaseEvent(ev);
incrementEventCount();
}
void RasterWindow::mouseDoubleClickEvent(QMouseEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::mouseDoubleClickEvent(ev);
incrementEventCount();
}
void RasterWindow::mouseMoveEvent(QMouseEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::mouseMoveEvent(ev);
incrementEventCount();
if (m_pressed)
m_offset += ev->position().toPoint() - m_lastPos;
m_lastPos = ev->position().toPoint();
}
void RasterWindow::mousePressEvent(QMouseEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::mousePressEvent(ev);
incrementEventCount();
m_pressed = true;
}
void RasterWindow::mouseReleaseEvent(QMouseEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::mouseReleaseEvent(ev);
incrementEventCount();
m_pressed = false;
}
void RasterWindow::moveEvent(QMoveEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::moveEvent(ev);
incrementEventCount();
}
void RasterWindow::resizeEvent(QResizeEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::resizeEvent(ev);
incrementEventCount();
}
void RasterWindow::showEvent(QShowEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::showEvent(ev);
incrementEventCount();
}
void RasterWindow::tabletEvent(QTabletEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::tabletEvent(ev);
incrementEventCount();
}
void RasterWindow::touchEvent(QTouchEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::touchEvent(ev);
incrementEventCount();
}
void RasterWindow::wheelEvent(QWheelEvent * ev)
{
qDebug() << __PRETTY_FUNCTION__;
QRasterWindow::wheelEvent(ev);
incrementEventCount();
m_offset += ev->pixelDelta();
}
void RasterWindow::incrementEventCount()
{
++m_eventCount;
update();
}

View File

@ -0,0 +1,43 @@
// Copyright (C) 2021 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR BSD-3-Clause
#ifndef RASTERWINDOW_H
#define RASTERWINDOW_H
#include <QtGui>
class RasterWindow : public QRasterWindow
{
public:
RasterWindow();
virtual void paintEvent(QPaintEvent * event);
virtual void exposeEvent(QExposeEvent * ev);
virtual void focusInEvent(QFocusEvent * ev);
virtual void focusOutEvent(QFocusEvent * ev);
virtual void hideEvent(QHideEvent * ev);
virtual void keyPressEvent(QKeyEvent * ev);
virtual void keyReleaseEvent(QKeyEvent * ev);
virtual void mouseDoubleClickEvent(QMouseEvent * ev);
virtual void mouseMoveEvent(QMouseEvent * ev);
virtual void mousePressEvent(QMouseEvent * ev);
virtual void mouseReleaseEvent(QMouseEvent * ev);
virtual void moveEvent(QMoveEvent * ev);
// virtual bool nativeEvent(const QByteArray & eventType, void * message, long * result);
virtual void resizeEvent(QResizeEvent * ev);
virtual void showEvent(QShowEvent * ev);
virtual void tabletEvent(QTabletEvent * ev);
virtual void touchEvent(QTouchEvent * ev);
virtual void wheelEvent(QWheelEvent * ev);
private:
void incrementEventCount();
int m_eventCount;
int m_timeoutCount;
int m_frameCount;
int m_fps;
QPoint m_offset;
QPoint m_lastPos;
bool m_pressed;
};
#endif // RASTERWINDOW_H

View File

@ -0,0 +1,30 @@
#! /bin/bash
# Copyright (C) 2022 The Qt Company Ltd.
# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
set -m
function removeServer()
{
kill $cleanupPid
}
if [ -z "$1"]
then
echo "Usage: $0 testname, where testname is a test in the tests/manual/wasm directory" >&2
exit 1
fi
trap removeServer EXIT
script_dir=`dirname ${BASH_SOURCE[0]}`
cd "$script_dir/../../../../"
python3 -m http.server 8001 &
cleanupPid=$!
cd -
python3 -m webbrowser "http://localhost:8001/tests/manual/wasm/$1/tst_$1.html"
echo 'Press any key to continue...' >&2
read -n 1

View File

@ -0,0 +1,78 @@
// Copyright (C) 2022 The Qt Company Ltd.
// SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
function output(message)
{
const outputLine = document.createElement('div');
outputLine.style.fontFamily = 'monospace';
outputLine.innerText = message;
document.body.appendChild(outputLine);
console.log(message);
}
export class TestRunner
{
#testClassInstance
constructor(testClassInstance)
{
this.#testClassInstance = testClassInstance;
}
async run(testCase)
{
const prototype = Object.getPrototypeOf(this.#testClassInstance);
try {
output(`Running ${testCase}`);
if (!prototype.hasOwnProperty(testCase))
throw new Error(`No such testcase ${testCase}`);
if (prototype.beforeEach) {
await prototype.beforeEach.apply(this.#testClassInstance);
}
await new Promise((resolve, reject) =>
{
let rejected = false;
const timeout = window.setTimeout(() =>
{
rejected = true;
reject(new Error('Timeout after 2 seconds'));
}, 2000);
prototype[testCase].apply(this.#testClassInstance).then(() =>
{
if (!rejected) {
window.clearTimeout(timeout);
output(`✅ Test passed ${testCase}`);
resolve();
}
}).catch(reject);
});
} catch (e) {
output(`❌ Failed ${testCase}: exception ${e} ${e.stack}`);
} finally {
if (prototype.afterEach) {
await prototype.afterEach.apply(this.#testClassInstance);
}
}
}
async runAll()
{
const SPECIAL_FUNCTIONS =
['beforeEach', 'afterEach', 'beforeAll', 'afterAll', 'constructor'];
const prototype = Object.getPrototypeOf(this.#testClassInstance);
const testFunctions =
Object.getOwnPropertyNames(prototype).filter(
entry => SPECIAL_FUNCTIONS.indexOf(entry) === -1);
if (prototype.beforeAll)
await prototype.beforeAll.apply(this.#testClassInstance);
for (const fn of testFunctions)
await this.run(fn);
if (prototype.afterAll)
await prototype.afterAll.apply(this.#testClassInstance);
}
}