summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMikolaj Boc <[email protected]>2022-12-06 13:17:30 +0100
committerMikolaj Boc <[email protected]>2023-02-14 17:48:09 +0100
commitd846ea423d93c4575c3973f98b536ad7dec273af (patch)
tree42ae8b44f4d553c7f60cbbac4e270d9ee8f21843
parent21453410713650a97a074a8f3d78afbb4c70e6b3 (diff)
Add a manual test for wasm window resizing/moving
This tests resizing and moving in various setups. The test driver communicates with the actual modules to assert various postconditions. It's semi-automated so that minimum interaction is required. Change-Id: I745d689c6ffa6aa6d478b795dd433f5b067241f1 Reviewed-by: Lorn Potter <[email protected]>
-rw-r--r--tests/manual/wasm/qwasmwindow/CMakeLists.txt36
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow.py256
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp151
-rw-r--r--tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html65
-rwxr-xr-xtests/manual/wasm/qwasmwindow/run.sh26
-rwxr-xr-xtests/manual/wasm/shared/run.sh2
6 files changed, 535 insertions, 1 deletions
diff --git a/tests/manual/wasm/qwasmwindow/CMakeLists.txt b/tests/manual/wasm/qwasmwindow/CMakeLists.txt
new file mode 100644
index 00000000000..73e267331e1
--- /dev/null
+++ b/tests/manual/wasm/qwasmwindow/CMakeLists.txt
@@ -0,0 +1,36 @@
+qt_internal_add_manual_test(qwasmwindow_harness
+ SOURCES
+ qwasmwindow_harness.cpp
+ PUBLIC_LIBRARIES
+ Qt::Core
+ Qt::CorePrivate
+ Qt::GuiPrivate
+)
+
+add_custom_command(
+ TARGET qwasmwindow_harness POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/qwasmwindow_harness.html
+ ${CMAKE_CURRENT_BINARY_DIR}/qwasmwindow_harness.html
+)
+
+add_custom_command(
+ TARGET qwasmwindow_harness POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/qwasmwindow.py
+ ${CMAKE_CURRENT_BINARY_DIR}/qwasmwindow.py
+)
+
+add_custom_command(
+ TARGET qwasmwindow_harness POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/run.sh
+ ${CMAKE_CURRENT_BINARY_DIR}/run.sh
+)
+
+add_custom_command(
+ TARGET qwasmwindow_harness POST_BUILD
+ COMMAND ${CMAKE_COMMAND} -E copy
+ ${CMAKE_CURRENT_SOURCE_DIR}/../../../../util/wasm/qtwasmserver/qtwasmserver.py
+ ${CMAKE_CURRENT_BINARY_DIR}/qtwasmserver.py
+)
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow.py b/tests/manual/wasm/qwasmwindow/qwasmwindow.py
new file mode 100644
index 00000000000..efe7ffd0a02
--- /dev/null
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow.py
@@ -0,0 +1,256 @@
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0
+
+from selenium.webdriver import Chrome
+from selenium.webdriver.common.by import By
+from selenium.webdriver.support.expected_conditions import presence_of_element_located
+from selenium.webdriver.support.ui import WebDriverWait
+from selenium.webdriver.common.action_chains import ActionChains
+
+import unittest
+class WidgetTestCase(unittest.TestCase):
+ def setUp(self):
+ self._driver = Chrome()
+ self._driver.get(
+ 'http://localhost:8001/qwasmwindow_harness.html')
+ self._test_sandbox_element = WebDriverWait(self._driver, 30).until(
+ presence_of_element_located((By.ID, 'test-sandbox'))
+ )
+
+ def _make_geometry(self, x, y, width, height):
+ return { 'x': x, 'y': y, 'width': width, 'height': height }
+
+ def test_window_resizing(self):
+ screen = self._create_screen_with_fixed_position(0, 0, 600, 600)
+ screen_information = self._screen_information()
+ self.assertEqual(len(screen_information), 1)
+ screen_information = screen_information[-1]
+ window = self._create_window(100, 100, 200, 200, screen, screen_information["name"], 'title')
+
+ window_information = self._window_information()[0]
+ self.assertEqual(window_information["geometry"], self._make_geometry(100, 100, 200, 200))
+
+ self._drag_window(window, window_information, [0, 0], [-10, -10])
+ window_information = self._window_information()[0]
+ self.assertEqual(window_information["geometry"], self._make_geometry(90, 90, 210, 210))
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2, 0], [-100, 10])
+ window_information = self._window_information()[0]
+ self.assertEqual(window_information["geometry"], self._make_geometry(90, 100, 210, 200))
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'], 0], [-5, -5])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(window_information["geometry"], self._make_geometry(90, 95, 205, 205))
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'],
+ window_information['frameGeometry']['height'] / 2], [5, 100])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(window_information["geometry"], self._make_geometry(90, 95, 210, 205))
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'],
+ window_information['frameGeometry']['height']], [-10, -5])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(90, 95, 200, 200))
+
+ self._drag_window(window, window_information, [
+ window_information['frameGeometry']['width'] / 2,
+ window_information['frameGeometry']['height']], [-100, 20])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(90, 95, 200, 220))
+
+ self._drag_window(window, window_information, [
+ 0, window_information['frameGeometry']['height']], [-10, 10])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(80, 95, 210, 230))
+
+ self._drag_window(window, window_information, [
+ 0, window_information['frameGeometry']['height'] / 2], [-5, 343])
+ window_information = self._window_information()[0]
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(75, 95, 215, 230))
+
+ def test_cannot_resize_over_screen_top_edge(self):
+ screen = self._create_screen_with_fixed_position(200, 200, 300, 300)
+ screen_information = self._screen_information()[-1]
+
+ window = self._create_window(
+ 300, 300, 100, 100, screen, screen_information['name'], 'title')
+
+ window_information = self._window_information()[0]
+ frame_geometry_before_resize = window_information["frameGeometry"]
+ self.assertEqual(window_information["geometry"], self._make_geometry(300, 300, 100, 100))
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2, 0], [0, -200])
+
+ geometry = self._window_information()[0]["geometry"]
+ frame_geometry = self._window_information()[0]["frameGeometry"]
+ self.assertEqual(geometry['x'], 300)
+ self.assertEqual(frame_geometry['y'], screen_information['geometry']['y'])
+ self.assertEqual(geometry['width'], 100)
+ self.assertEqual(frame_geometry['y'] + frame_geometry['height'],
+ frame_geometry_before_resize['y'] + frame_geometry_before_resize['height'])
+
+ def test_window_move(self):
+ screen = self._create_screen_with_fixed_position(200, 200, 300, 300)
+ screen_information = self._screen_information()[-1]
+
+ window = self._create_window(
+ 300, 300, 100, 100, screen, screen_information['name'], 'title')
+
+ self.assertEqual(
+ self._window_information()[0]["geometry"], self._make_geometry(300, 300, 100, 100))
+
+ window_information = self._window_information()[0]
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2, 6], [0, -30])
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(300, 270, 100, 100))
+
+ window_information = self._window_information()[0]
+ window_frame_top = window_information['frameGeometry']['y']
+ window_client_area_top = window_information['geometry']['y']
+
+ top_frame_height = window_client_area_top - window_frame_top + 1
+
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2,
+ top_frame_height], [0, -30])
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(300, 270, 100, 100))
+
+ def test_screen_limits_window_moves(self):
+ screen = self._create_screen_with_relative_position(200, 200, 300, 300)
+ screen_information = self._screen_information()[-1]
+
+ window = self._create_window(
+ 300, 300, 100, 100, screen, screen_information['name'], 'title')
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(300, 300, 100, 100))
+
+ window_information = self._window_information()[0]
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2, 6], [-300, 0])
+ self.assertEqual(
+ self._window_information()[0]["frameGeometry"]['x'],
+ screen_information['geometry']['x'] - window_information['frameGeometry']['width'] / 2)
+
+ def test_screen_in_scroll_container_limits_window_moves(self):
+ _, screen = self._create_screen_in_scroll_container(500, 7000, 200, 2000, 300, 300)
+ screen_information = self._screen_information()[-1]
+
+ ActionChains(self._driver).scroll_to_element(screen).perform()
+
+ window = self._create_window(
+ 300, 2100, 100, 100, screen, screen_information['name'], 'title')
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(300, 2100, 100, 100))
+
+ window_information = self._window_information()[0]
+ self._drag_window(window, window_information,
+ [window_information['frameGeometry']['width'] / 2, 6], [-300, 0])
+ self.assertEqual(
+ self._window_information()[0]["frameGeometry"]['x'],
+ screen_information['geometry']['x'] - window_information['frameGeometry']['width'] / 2)
+
+ def test_maximize(self):
+ screen = self._create_screen_with_relative_position(200, 200, 300, 300)
+ screen_information = self._screen_information()[-1]
+
+ window = self._create_window(
+ 300, 300, 100, 100, screen, screen_information['name'], 'Maximize')
+
+ self.assertEqual(self._window_information()[0]["geometry"],
+ self._make_geometry(300, 300, 100, 100))
+
+ maximize_button = window.find_element(
+ By.CSS_SELECTOR, f'.title-bar :nth-child(6)')
+ maximize_button.click()
+
+ self.assertEqual(
+ self._window_information()[0]["frameGeometry"],
+ self._make_geometry(200, 200, 300, 300))
+
+ def tearDown(self):
+ self._driver.quit()
+
+ def _drag_window(self, window_element, window_information, origin, offset):
+ ActionChains(self._driver).move_to_element(
+ window_element).move_by_offset(
+ -window_information['frameGeometry']['width'] / 2 + origin[0],
+ -window_information['frameGeometry']['height'] / 2 + origin[1]).click_and_hold(
+ ).move_by_offset(*offset).release().perform()
+
+ def _call_instance_function(self, name):
+ return self._driver.execute_script(
+ f'''let result;
+ window.{name}Callback = data => result = data;
+ instance.{name}();
+ return eval(result);''')
+
+ def _window_information(self):
+ return self._call_instance_function('windowInformation')
+
+ def _screen_information(self):
+ return self._call_instance_function('screenInformation')
+
+ def _get_resize_location(self, screen, window_info, which):
+ frame_geometry = window_info['frameGeometry']
+ frame_x_in_screen = frame_geometry['x'] - screen['geometry']['x']
+ frame_y_in_screen = frame_geometry['y'] - screen['geometry']['y']
+
+ return [frame_x_in_screen, frame_y_in_screen]
+
+ def _get_title_bar(self, window):
+ return window.find_element(
+ By.CSS_SELECTOR, f'.window-name')
+
+ def _create_screen_with_fixed_position(self, x, y, w, h):
+ return self._driver.execute_script(
+ f'''
+ return testSupport.initializeScreenWithFixedPosition({x}, {y}, {w}, {h});
+ '''
+ )
+
+ def _create_screen_with_relative_position(self, x, y, w, h):
+ return self._driver.execute_script(
+ f'''
+ return testSupport.initializeScreenWithRelativePosition({x}, {y}, {w}, {h});
+ '''
+ )
+
+ def _create_screen_in_scroll_container(self, containerW, containerH, x, y, w, h):
+ return self._driver.execute_script(
+ f'''
+ return testSupport.initializeScreenInScrollContainer(
+ {containerW}, {containerH}, {x}, {y}, {w}, {h});
+ '''
+ )
+
+ def _create_window(self, x, y, w, h, screen, screen_id, title):
+ self._driver.execute_script(
+ f'''
+ instance.createWindow({x}, {y}, {w}, {h}, '{screen_id}', '{title}');
+ '''
+ )
+ window_id = self._window_information()[-1]["id"]
+ return screen.shadow_root.find_element(By.CSS_SELECTOR, f'#qt-window-{window_id}')
+
+unittest.main()
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp
new file mode 100644
index 00000000000..678d60b936a
--- /dev/null
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp
@@ -0,0 +1,151 @@
+// 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/qscreen.h>
+#include <QtGui/qwindow.h>
+#include <QtGui/qguiapplication.h>
+
+#include <emscripten.h>
+#include <emscripten/bind.h>
+#include <emscripten/val.h>
+
+#include <memory>
+#include <sstream>
+#include <vector>
+
+namespace qwasmwindow_harness {
+namespace {
+class Window : public QWindow
+{
+ Q_OBJECT
+public:
+ Window() = default;
+ ~Window() = default;
+
+private:
+ void closeEvent(QCloseEvent *ev) override
+ {
+ Q_UNUSED(ev);
+ delete this;
+ }
+};
+} // namespace
+} // namespace qwasmwindow_harness
+
+using namespace emscripten;
+
+std::string toJSArray(const std::vector<std::string> &elements)
+{
+ std::ostringstream out;
+ out << "[";
+ bool comma = false;
+ for (const auto &element : elements) {
+ out << (comma ? "," : "");
+ out << element;
+ comma = true;
+ }
+ out << "]";
+ return out.str();
+}
+
+std::string toJSString(const QString &qstring)
+{
+ return "'" + qstring.toStdString() + "'";
+}
+
+std::string rectToJSObject(const QRect &rect)
+{
+ std::ostringstream out;
+ out << "{"
+ << " x: " << std::to_string(rect.x()) << ","
+ << " y: " << std::to_string(rect.y()) << ","
+ << " width: " << std::to_string(rect.width()) << ","
+ << " height: " << std::to_string(rect.height()) << "}";
+ return out.str();
+}
+
+std::string screenToJSObject(const QScreen &screen)
+{
+ std::ostringstream out;
+ out << "{"
+ << " name: " << toJSString(screen.name()) << ","
+ << " geometry: " << rectToJSObject(screen.geometry()) << "}";
+ return out.str();
+}
+
+std::string windowToJSObject(const QWindow &window)
+{
+ std::ostringstream out;
+ out << "{"
+ << " id: " << std::to_string(window.winId()) << ","
+ << " geometry: " << rectToJSObject(window.geometry()) << ","
+ << " frameGeometry: " << rectToJSObject(window.frameGeometry()) << "}";
+ return out.str();
+}
+
+void windowInformation()
+{
+ auto windows = qGuiApp->allWindows();
+
+ std::vector<std::string> windowsAsJsObjects;
+ windowsAsJsObjects.reserve(windows.size());
+ std::transform(windows.begin(), windows.end(), std::back_inserter(windowsAsJsObjects),
+ [](const QWindow *window) { return windowToJSObject(*window); });
+
+ emscripten::val::global("window").call<void>("windowInformationCallback",
+ emscripten::val(toJSArray(windowsAsJsObjects)));
+}
+
+void screenInformation()
+{
+ auto screens = qGuiApp->screens();
+
+ std::vector<std::string> screensAsJsObjects;
+ screensAsJsObjects.reserve(screens.size());
+ std::transform(screens.begin(), screens.end(), std::back_inserter(screensAsJsObjects),
+ [](const QScreen *screen) { return screenToJSObject(*screen); });
+ emscripten::val::global("window").call<void>("screenInformationCallback",
+ emscripten::val(toJSArray(screensAsJsObjects)));
+}
+
+void createWindow(int x, int y, int w, int h, std::string screenId, std::string title)
+{
+ auto screens = qGuiApp->screens();
+ auto screen_it = std::find_if(screens.begin(), screens.end(), [&screenId](QScreen *screen) {
+ return screen->name() == QString::fromLatin1(screenId);
+ });
+ if (screen_it == screens.end()) {
+ qWarning() << "No such screen: " << screenId;
+ return;
+ }
+
+ auto *window = new qwasmwindow_harness::Window;
+
+ window->setFlag(Qt::WindowTitleHint);
+ window->setFlag(Qt::WindowMaximizeButtonHint);
+ window->setTitle(QString::fromLatin1(title));
+ window->setGeometry(x, y, w, h);
+ window->setScreen(*screen_it);
+ window->showNormal();
+}
+
+EMSCRIPTEN_BINDINGS(qwasmwindow)
+{
+ emscripten::function("screenInformation", &screenInformation);
+ emscripten::function("windowInformation", &windowInformation);
+ emscripten::function("createWindow", &createWindow);
+}
+
+int main(int argc, char **argv)
+{
+ QGuiApplication app(argc, argv);
+
+ qDebug() << "exec";
+ app.exec();
+ qDebug() << "returning";
+ return 0;
+}
+
+#include "qwasmwindow_harness.moc"
diff --git a/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html
new file mode 100644
index 00000000000..f8be66809c1
--- /dev/null
+++ b/tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html
@@ -0,0 +1,65 @@
+<!doctype html>
+
+<head>
+ <script type="text/javascript" src="qwasmwindow_harness.js"></script>
+ <script>
+ (async () => {
+ const instance = await createQtAppInstance({});
+ window.instance = instance;
+
+ const testSandbox = document.createElement('div');
+ testSandbox.id = 'test-sandbox';
+ document.body.appendChild(testSandbox);
+
+ const makeSizedDiv = (left, top, width, height) => {
+ const screenDiv = document.createElement('div');
+
+ screenDiv.style.left = `${left}px`;
+ screenDiv.style.top = `${top}px`;
+ screenDiv.style.width = `${width}px`;
+ screenDiv.style.height = `${height}px`;
+ screenDiv.style.backgroundColor = 'lightblue';
+
+ return screenDiv;
+ };
+
+ window.testSupport = {
+ initializeScreenWithFixedPosition: (left, top, width, height) => {
+ const screenDiv = makeSizedDiv(left, top, width, height);
+ testSandbox.appendChild(screenDiv);
+
+ screenDiv.style.position = 'fixed';
+ instance.qtAddContainerElement(screenDiv);
+
+ return screenDiv;
+ },
+ initializeScreenWithRelativePosition: (left, top, width, height) => {
+ const screenDiv = makeSizedDiv(left, top, width, height);
+ testSandbox.appendChild(screenDiv);
+
+ screenDiv.style.position = 'relative';
+ instance.qtAddContainerElement(screenDiv);
+
+ return screenDiv;
+ },
+ initializeScreenInScrollContainer: (scrollWidth, scrollHeight, left, top, width, height) => {
+ const scrollContainer = document.createElement('div');
+ scrollContainer.style.height = `${scrollHeight}px`;
+ scrollContainer.style.width = `${scrollWidth}px`;
+ testSandbox.appendChild(scrollContainer);
+
+ const screenDiv = makeSizedDiv(left, top, width, height);
+ scrollContainer.appendChild(screenDiv);
+ screenDiv.style.position = 'relative';
+
+ instance.qtAddContainerElement(screenDiv);
+
+ return [scrollContainer, screenDiv];
+ }
+ };
+ })();
+ </script>
+</head>
+
+<body>
+</body>
diff --git a/tests/manual/wasm/qwasmwindow/run.sh b/tests/manual/wasm/qwasmwindow/run.sh
new file mode 100755
index 00000000000..38b85e6c309
--- /dev/null
+++ b/tests/manual/wasm/qwasmwindow/run.sh
@@ -0,0 +1,26 @@
+#! /bin/bash
+
+# Copyright (C) 2022 The Qt Company Ltd.
+# SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only
+
+set -m
+
+function removeServer()
+{
+ [ -z "$cleanupPid" ] || kill $cleanupPid
+}
+
+trap removeServer EXIT
+
+script_dir=`dirname ${BASH_SOURCE[0]}`
+cd "$script_dir"
+python3 qtwasmserver.py -p 8001 > /dev/null 2>&1 &
+cleanupPid=$!
+
+python3 qwasmwindow.py $@
+
+echo 'Press any key to continue...' >&2
+read -n 1
+
+
+
diff --git a/tests/manual/wasm/shared/run.sh b/tests/manual/wasm/shared/run.sh
index d8ae18919c4..a8af94e9575 100755
--- a/tests/manual/wasm/shared/run.sh
+++ b/tests/manual/wasm/shared/run.sh
@@ -20,7 +20,7 @@ trap removeServer EXIT
script_dir=`dirname ${BASH_SOURCE[0]}`
cd "$script_dir/../../../../"
-python3 -m http.server 8001 &
+python3 util/wasm/qtwasmserver/qtwasmserver.py -p 8001 &
cleanupPid=$!
cd -