mirror of
https://github.com/crystalidea/qt6windows7.git
synced 2025-07-01 23:02:23 +08:00
qt 6.5.1 original
This commit is contained in:
12
tests/manual/wasm/CMakeLists.txt
Normal file
12
tests/manual/wasm/CMakeLists.txt
Normal 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()
|
15
tests/manual/wasm/README.md
Normal file
15
tests/manual/wasm/README.md
Normal 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
|
3
tests/manual/wasm/a11y/CMakeLists.txt
Normal file
3
tests/manual/wasm/a11y/CMakeLists.txt
Normal file
@ -0,0 +1,3 @@
|
||||
if(QT_FEATURE_widgets)
|
||||
add_subdirectory(basic_widgets)
|
||||
endif()
|
21
tests/manual/wasm/a11y/basic_widgets/CMakeLists.txt
Normal file
21
tests/manual/wasm/a11y/basic_widgets/CMakeLists.txt
Normal 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
|
||||
)
|
24
tests/manual/wasm/a11y/basic_widgets/basic_widgets.html
Normal file
24
tests/manual/wasm/a11y/basic_widgets/basic_widgets.html
Normal 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>
|
114
tests/manual/wasm/a11y/basic_widgets/basica11ywidget.cpp
Normal file
114
tests/manual/wasm/a11y/basic_widgets/basica11ywidget.cpp
Normal 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();
|
||||
}
|
41
tests/manual/wasm/a11y/basic_widgets/basica11ywidget.h
Normal file
41
tests/manual/wasm/a11y/basic_widgets/basica11ywidget.h
Normal 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();
|
||||
|
||||
};
|
17
tests/manual/wasm/a11y/basic_widgets/main.cpp
Normal file
17
tests/manual/wasm/a11y/basic_widgets/main.cpp
Normal 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();
|
||||
}
|
||||
|
63
tests/manual/wasm/a11y/basic_widgets/tabswidget.cpp
Normal file
63
tests/manual/wasm/a11y/basic_widgets/tabswidget.cpp
Normal 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 );
|
||||
}
|
34
tests/manual/wasm/a11y/basic_widgets/tabswidget.h
Normal file
34
tests/manual/wasm/a11y/basic_widgets/tabswidget.h
Normal 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
|
38
tests/manual/wasm/clipboard/CMakeLists.txt
Normal file
38
tests/manual/wasm/clipboard/CMakeLists.txt
Normal 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
|
||||
)
|
2
tests/manual/wasm/clipboard/README
Normal file
2
tests/manual/wasm/clipboard/README
Normal 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.
|
27
tests/manual/wasm/clipboard/clipboard.pro
Normal file
27
tests/manual/wasm/clipboard/clipboard.pro
Normal 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
|
5
tests/manual/wasm/clipboard/data.qrc
Normal file
5
tests/manual/wasm/clipboard/data.qrc
Normal file
@ -0,0 +1,5 @@
|
||||
<RCC>
|
||||
<qresource prefix="/">
|
||||
<file>data/qticon64.png</file>
|
||||
</qresource>
|
||||
</RCC>
|
BIN
tests/manual/wasm/clipboard/data/qticon64.png
Normal file
BIN
tests/manual/wasm/clipboard/data/qticon64.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 6.3 KiB |
14
tests/manual/wasm/clipboard/main.cpp
Normal file
14
tests/manual/wasm/clipboard/main.cpp
Normal 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();
|
||||
}
|
311
tests/manual/wasm/clipboard/mainwindow.cpp
Normal file
311
tests/manual/wasm/clipboard/mainwindow.cpp
Normal 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();
|
||||
}
|
52
tests/manual/wasm/clipboard/mainwindow.h
Normal file
52
tests/manual/wasm/clipboard/mainwindow.h
Normal 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
|
222
tests/manual/wasm/clipboard/mainwindow.ui
Normal file
222
tests/manual/wasm/clipboard/mainwindow.ui
Normal 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><!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
|
||||
<html><head><meta name="qrichtext" content="1" /><meta charset="utf-8" /><style type="text/css">
|
||||
p, li { white-space: pre-wrap; }
|
||||
</style></head><body style=" font-family:'.AppleSystemUIFont'; font-size:13pt; font-weight:400; font-style:normal;">
|
||||
<p style=" margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><img src=":/data/qticon64.png" /></p>
|
||||
<p style=" margin-top:12px; margin-bottom:12px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><a name="tw-target"></a><span style=" font-family:'monospace'; font-size:9pt; font-weight:600;">L</span><span style=" font-family:'monospace'; font-size:9pt; font-weight:600;">orem</span><span style=" font-family:'monospace'; font-size:9pt;"> </span><span style=" font-family:'monospace'; font-size:9pt; font-style:italic;">ipsum</span><span style=" font-family:'monospace'; font-size:9pt;"> </span><span style=" font-family:'monospace'; font-size:9pt; text-decoration: underline;">dolor</span><span style=" font-family:'monospace'; font-size:9pt;"> </span><span style=" font-family:'monospace'; font-size:9pt; vertical-align:super;">sit</span><span style=" font-family:'monospace'; font-size:9pt;"> </span><span style=" font-family:'monospace'; font-size:9pt; vertical-align:sub;">amet</span><span style=" font-family:'monospace'; font-size:9pt;">, </span><a href="http://localhost"><span style=" font-family:'Sans Serif'; font-size:9pt; text-decoration: underline; color:#0000ff;">consectetur</span></a><span style=" font-family:'monospace'; font-size:9pt;"> </span><span style=" font-family:'monospace'; font-size:9pt; color:#7320a4;">adipiscing</span><span style=" font-family:'monospace'; font-size:9pt;"> 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.</span></p>
|
||||
<p style="-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;"><br /></p>
|
||||
<p style="-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;"><br /></p></body></html></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>
|
15
tests/manual/wasm/cursors/CMakeLists.txt
Normal file
15
tests/manual/wasm/cursors/CMakeLists.txt
Normal 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
|
||||
)
|
29
tests/manual/wasm/cursors/MainWindow.cpp
Normal file
29
tests/manual/wasm/cursors/MainWindow.cpp
Normal 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;
|
||||
}
|
23
tests/manual/wasm/cursors/MainWindow.h
Normal file
23
tests/manual/wasm/cursors/MainWindow.h
Normal 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;
|
||||
};
|
53
tests/manual/wasm/cursors/MainWindow.ui
Normal file
53
tests/manual/wasm/cursors/MainWindow.ui
Normal 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>
|
13
tests/manual/wasm/cursors/cursors.pro
Normal file
13
tests/manual/wasm/cursors/cursors.pro
Normal file
@ -0,0 +1,13 @@
|
||||
QT += core gui widgets
|
||||
|
||||
SOURCES += \
|
||||
main.cpp \
|
||||
MainWindow.cpp
|
||||
|
||||
HEADERS += \
|
||||
MainWindow.h
|
||||
|
||||
FORMS += \
|
||||
MainWindow.ui
|
||||
|
||||
LIBS += -lidbfs.js
|
13
tests/manual/wasm/cursors/main.cpp
Normal file
13
tests/manual/wasm/cursors/main.cpp
Normal 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();
|
||||
}
|
11
tests/manual/wasm/eventloop/CMakeLists.txt
Normal file
11
tests/manual/wasm/eventloop/CMakeLists.txt
Normal 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()
|
15
tests/manual/wasm/eventloop/README.md
Normal file
15
tests/manual/wasm/eventloop/README.md
Normal 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)
|
12
tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt
Normal file
12
tests/manual/wasm/eventloop/asyncify_exec/CMakeLists.txt
Normal 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)
|
25
tests/manual/wasm/eventloop/asyncify_exec/main.cpp
Normal file
25
tests/manual/wasm/eventloop/asyncify_exec/main.cpp
Normal 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();
|
||||
}
|
12
tests/manual/wasm/eventloop/dialog_exec/CMakeLists.txt
Normal file
12
tests/manual/wasm/eventloop/dialog_exec/CMakeLists.txt
Normal 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
|
||||
)
|
48
tests/manual/wasm/eventloop/dialog_exec/main.cpp
Normal file
48
tests/manual/wasm/eventloop/dialog_exec/main.cpp
Normal 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();
|
||||
}
|
43
tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt
Normal file
43
tests/manual/wasm/eventloop/eventloop_auto/CMakeLists.txt
Normal 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)
|
||||
|
||||
|
@ -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>
|
@ -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>
|
327
tests/manual/wasm/eventloop/eventloop_auto/main.cpp
Normal file
327
tests/manual/wasm/eventloop/eventloop_auto/main.cpp
Normal 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"
|
11
tests/manual/wasm/eventloop/main_exec/CMakeLists.txt
Normal file
11
tests/manual/wasm/eventloop/main_exec/CMakeLists.txt
Normal 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
|
||||
)
|
67
tests/manual/wasm/eventloop/main_exec/main.cpp
Normal file
67
tests/manual/wasm/eventloop/main_exec/main.cpp
Normal 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;
|
11
tests/manual/wasm/eventloop/main_noexec/CMakeLists.txt
Normal file
11
tests/manual/wasm/eventloop/main_noexec/CMakeLists.txt
Normal 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
|
||||
)
|
66
tests/manual/wasm/eventloop/main_noexec/main.cpp
Normal file
66
tests/manual/wasm/eventloop/main_noexec/main.cpp
Normal 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;
|
||||
|
||||
|
||||
|
11
tests/manual/wasm/eventloop/thread_exec/CMakeLists.txt
Normal file
11
tests/manual/wasm/eventloop/thread_exec/CMakeLists.txt
Normal 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
|
||||
)
|
75
tests/manual/wasm/eventloop/thread_exec/main.cpp
Normal file
75
tests/manual/wasm/eventloop/thread_exec/main.cpp
Normal 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"
|
12
tests/manual/wasm/localfiles/CMakeLists.txt
Normal file
12
tests/manual/wasm/localfiles/CMakeLists.txt
Normal 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
|
||||
)
|
8
tests/manual/wasm/localfiles/localfiles.pro
Normal file
8
tests/manual/wasm/localfiles/localfiles.pro
Normal file
@ -0,0 +1,8 @@
|
||||
TEMPLATE = app
|
||||
TARGET = localfiles
|
||||
QT += core gui widgets
|
||||
|
||||
OBJECTS_DIR = .obj
|
||||
MOC_DIR = .moc
|
||||
|
||||
SOURCES += main.cpp
|
137
tests/manual/wasm/localfiles/main.cpp
Normal file
137
tests/manual/wasm/localfiles/main.cpp
Normal 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"
|
0
tests/manual/wasm/network/CMakeLists.txt
Normal file
0
tests/manual/wasm/network/CMakeLists.txt
Normal file
@ -0,0 +1,8 @@
|
||||
qt_internal_add_manual_test(echo_client_mainthread
|
||||
GUI
|
||||
SOURCES
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
)
|
52
tests/manual/wasm/network/echo_client_mainthread/main.cpp
Normal file
52
tests/manual/wasm/network/echo_client_mainthread/main.cpp
Normal 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();
|
||||
}
|
@ -0,0 +1,8 @@
|
||||
qt_internal_add_manual_test(echo_client_secondarythread
|
||||
GUI
|
||||
SOURCES
|
||||
main.cpp
|
||||
LIBRARIES
|
||||
Qt::Core
|
||||
Qt::Network
|
||||
)
|
@ -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();
|
||||
}
|
14
tests/manual/wasm/network/echo_server/CMakeLists.txt
Normal file
14
tests/manual/wasm/network/echo_server/CMakeLists.txt
Normal 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
|
||||
)
|
80
tests/manual/wasm/network/echo_server/main.cpp
Normal file
80
tests/manual/wasm/network/echo_server/main.cpp
Normal 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();
|
||||
}
|
||||
|
@ -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)
|
318
tests/manual/wasm/network/sockify_sockets_auto/main.cpp
Normal file
318
tests/manual/wasm/network/sockify_sockets_auto/main.cpp
Normal 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"
|
@ -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>
|
72
tests/manual/wasm/qstdweb/CMakeLists.txt
Normal file
72
tests/manual/wasm/qstdweb/CMakeLists.txt
Normal 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)
|
13
tests/manual/wasm/qstdweb/files_auto.html
Normal file
13
tests/manual/wasm/qstdweb/files_auto.html
Normal 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>
|
471
tests/manual/wasm/qstdweb/files_main.cpp
Normal file
471
tests/manual/wasm/qstdweb/files_main.cpp
Normal 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"
|
10
tests/manual/wasm/qstdweb/promise_auto.html
Normal file
10
tests/manual/wasm/qstdweb/promise_auto.html
Normal 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>
|
486
tests/manual/wasm/qstdweb/promise_main.cpp
Normal file
486
tests/manual/wasm/qstdweb/promise_main.cpp
Normal 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"
|
10
tests/manual/wasm/qstdweb/qwasmcompositor_auto.html
Normal file
10
tests/manual/wasm/qstdweb/qwasmcompositor_auto.html
Normal 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>
|
174
tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp
Normal file
174
tests/manual/wasm/qstdweb/qwasmcompositor_main.cpp
Normal 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, ¶ms, rhiFlags));
|
||||
m_rhi.reset(QRhi::create(QRhi::OpenGLES2, ¶ms, 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"
|
19
tests/manual/wasm/qtloader/tst_qtloader.html
Normal file
19
tests/manual/wasm/qtloader/tst_qtloader.html
Normal 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>
|
42
tests/manual/wasm/qtloader/tst_qtloader.js
Normal file
42
tests/manual/wasm/qtloader/tst_qtloader.js
Normal 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();
|
||||
})();
|
75
tests/manual/wasm/qtwasmtestlib/README.md
Normal file
75
tests/manual/wasm/qtwasmtestlib/README.md
Normal 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>
|
175
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
Normal file
175
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.cpp
Normal 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
|
||||
|
73
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h
Normal file
73
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.h
Normal 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
|
135
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.js
Normal file
135
tests/manual/wasm/qtwasmtestlib/qtwasmtestlib.js
Normal 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
|
||||
}
|
||||
}
|
11
tests/manual/wasm/rasterwindow/CMakeLists.txt
Normal file
11
tests/manual/wasm/rasterwindow/CMakeLists.txt
Normal 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
|
||||
)
|
14
tests/manual/wasm/rasterwindow/main.cpp
Normal file
14
tests/manual/wasm/rasterwindow/main.cpp
Normal 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();
|
||||
}
|
185
tests/manual/wasm/rasterwindow/rasterwindow.cpp
Normal file
185
tests/manual/wasm/rasterwindow/rasterwindow.cpp
Normal 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();
|
||||
}
|
43
tests/manual/wasm/rasterwindow/rasterwindow.h
Normal file
43
tests/manual/wasm/rasterwindow/rasterwindow.h
Normal 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
|
30
tests/manual/wasm/shared/run.sh
Normal file
30
tests/manual/wasm/shared/run.sh
Normal 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
|
78
tests/manual/wasm/shared/testrunner.js
Normal file
78
tests/manual/wasm/shared/testrunner.js
Normal 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);
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user