diff options
author | Mikolaj Boc <[email protected]> | 2022-12-06 13:17:30 +0100 |
---|---|---|
committer | Mikolaj Boc <[email protected]> | 2023-02-14 17:48:09 +0100 |
commit | d846ea423d93c4575c3973f98b536ad7dec273af (patch) | |
tree | 42ae8b44f4d553c7f60cbbac4e270d9ee8f21843 | |
parent | 21453410713650a97a074a8f3d78afbb4c70e6b3 (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.txt | 36 | ||||
-rw-r--r-- | tests/manual/wasm/qwasmwindow/qwasmwindow.py | 256 | ||||
-rw-r--r-- | tests/manual/wasm/qwasmwindow/qwasmwindow_harness.cpp | 151 | ||||
-rw-r--r-- | tests/manual/wasm/qwasmwindow/qwasmwindow_harness.html | 65 | ||||
-rwxr-xr-x | tests/manual/wasm/qwasmwindow/run.sh | 26 | ||||
-rwxr-xr-x | tests/manual/wasm/shared/run.sh | 2 |
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 - |