#!/bin/bash # How large is a lesson in Wonder Ink, and do the keys change it? # # Runs the INSTALLED classroom-wonderink under a real QtWebEngine # (offscreen), builds its window the way main() does, and asks the page # itself what it is rendering at. Keys are delivered through Qt's own key # path to the focused page — the same shortcut map a keyboard reaches — # because the lab's injected keyboard does not reach the nested session. # A physical Ctrl+Plus is a hardware check. # # tools/lab/wonderink-zoom-check set -uo pipefail NAME="${LAB_NAME:-classroom-v2-lab}" COMMAND="${WONDERINK:-/usr/local/bin/classroom-wonderink}" docker exec -i \ -e QT_QPA_PLATFORM=offscreen \ -e QTWEBENGINE_CHROMIUM_FLAGS="--no-sandbox --disable-gpu" \ -e COMMAND="$COMMAND" \ "$NAME" bash -c 'export XDG_RUNTIME_DIR=$(mktemp -d) HOME=$(mktemp -d); python3 -' <<'PY' import importlib.util, os, sys from importlib.machinery import SourceFileLoader loader = SourceFileLoader("wonderink", os.environ["COMMAND"]) spec = importlib.util.spec_from_loader("wonderink", loader) ink = importlib.util.module_from_spec(spec) loader.exec_module(ink) from PyQt6.QtCore import QEventLoop, Qt, QTimer from PyQt6.QtTest import QTest from PyQt6.QtWebEngineCore import QWebEngineProfile from PyQt6.QtWidgets import QApplication app = QApplication(sys.argv[:1]) passed = failed = 0 def check(label, condition, detail=""): global passed, failed if condition: print(f" \033[32mPASS\033[0m {label}") passed += 1 else: print(f" \033[31mFAIL\033[0m {label}" + (f" [{detail}]" if detail else "")) failed += 1 def wait(ms): loop = QEventLoop() QTimer.singleShot(ms, loop.quit) loop.exec() def page_dpr(view): answer = [] view.page().runJavaScript("window.devicePixelRatio", answer.append) for _ in range(50): if answer: return answer[0] wait(100) return None PAGE = "data:text/html,
Noah built an ark.
" KEY = { "Ctrl+=": (Qt.Key.Key_Equal, Qt.KeyboardModifier.ControlModifier), "Ctrl+Plus": (Qt.Key.Key_Plus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier), "keypad Ctrl+Plus": (Qt.Key.Key_Plus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.KeypadModifier), "Ctrl+Minus": (Qt.Key.Key_Minus, Qt.KeyboardModifier.ControlModifier), "keypad Ctrl+Minus": (Qt.Key.Key_Minus, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.KeypadModifier), "Ctrl+0": (Qt.Key.Key_0, Qt.KeyboardModifier.ControlModifier), "Ctrl+Shift+Tab": (Qt.Key.Key_Backtab, Qt.KeyboardModifier.ControlModifier | Qt.KeyboardModifier.ShiftModifier), } def press(window, name): target = QApplication.focusWidget() or window.current() QTest.keyClick(target, *KEY[name]) wait(400) def open_window(room_zoom): profile = QWebEngineProfile(f"zoomcheck{room_zoom}", app) window = ink.WonderInk(profile, room_zoom=room_zoom) window.resize(1280, 800) window.show() for _ in range(3): window.open_tab(PAGE) wait(2500) window.current().setFocus() wait(300) return window def factors(window): return [round(window._tabs.widget(i).zoomFactor(), 3) for i in range(window._tabs.count())] print("1. a room's normal size is the browser's") for room in (1.0, 1.5, 2.0): ink.SETTINGS.unlink(missing_ok=True) window = open_window(room) check(f"UI_SCALE {room}: every tab starts at {room}", factors(window) == [room] * 3, factors(window)) window.close() wait(300) print() print("2. the keys a teacher knows, at UI_SCALE 1.5") ink.SETTINGS.unlink(missing_ok=True) window = open_window(1.5) for name, level in (("Ctrl+=", 1.1), ("Ctrl+Plus", 1.25), ("keypad Ctrl+Plus", 1.5), ("Ctrl+Minus", 1.25), ("keypad Ctrl+Minus", 1.1), ("Ctrl+0", 1.0)): press(window, name) check(f"{name} makes it {round(level * 100)}% in every tab", window.level() == level and factors(window) == [round(1.5 * level, 3)] * 3, f"level={window.level()} factors={factors(window)}") press(window, "Ctrl+Plus") dpr = page_dpr(window.current()) check("and the page itself renders at it", dpr is not None and abs(dpr - 1.65) < 0.01, dpr) print() print("3. new tabs, and the next time Wonder Ink opens") view = window.open_tab(PAGE) wait(1500) check("a new tab starts at the teacher's size", round(view.zoomFactor(), 3) == 1.65, view.zoomFactor()) check("the level is saved on its own", ink.SETTINGS.read_text().strip() == "LEVEL=1.1", ink.SETTINGS.read_text()) check("in its own file, not the web profile", "wonderink-shell" not in str(ink.SETTINGS) and ink.SETTINGS.name == "wonderink.conf") index = window._tabs.currentIndex() press(window, "Ctrl+Shift+Tab") check("Ctrl+Shift+Tab still goes back a tab", window._tabs.currentIndex() == index - 1, f"{index} -> {window._tabs.currentIndex()}") window.close() wait(300) window = open_window(2.0) check("reopened in a room at 2.0, the teacher's 110% is kept", window.level() == 1.1 and factors(window) == [2.2] * 3, factors(window)) window.close() ink.SETTINGS.write_text("LEVEL=banana\n") check("a damaged setting is the normal size", ink.load_level() == 1.0) ink.SETTINGS.write_text("LEVEL=40\n") check("an absurd one too", ink.load_level() == 1.0) window = open_window(3.0) press(window, "keypad Ctrl+Plus"); press(window, "keypad Ctrl+Plus"); press(window, "keypad Ctrl+Plus") check("a large room zoomed further never passes the engine's limit", max(factors(window)) <= 5.0, factors(window)) window.close() print() print(f"{passed} passed, {failed} failed") sys.exit(1 if failed else 0) PY