瀏覽代碼

Keep the modern mobile gauge row in view when a test starts (#854)

* Initial plan

* fix: keep mobile gauge visible on start

Co-authored-by: sstidl <12804296+sstidl@users.noreply.github.com>

* refactor: clarify mobile gauge scroll helper

Co-authored-by: sstidl <12804296+sstidl@users.noreply.github.com>

* fix: defer mobile gauge scroll until running render

Co-authored-by: sstidl <12804296+sstidl@users.noreply.github.com>

---------

Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: sstidl <12804296+sstidl@users.noreply.github.com>
Copilot 1 周之前
父節點
當前提交
449e9962ed
共有 2 個文件被更改,包括 141 次插入0 次删除
  1. 42 0
      frontend/javascript/index.js
  2. 99 0
      tests/e2e/mobile-gauge-visibility.spec.js

+ 42 - 0
frontend/javascript/index.js

@@ -16,6 +16,8 @@ const testState = {
   state: INITIALIZING,
   speedtest: null,
   servers: [],
+  initialGaugeScrollPending: false,
+  initialGaugeScrollScheduled: false,
   selectedServerDirty: false,
   testData: null,
   testDataDirty: false,
@@ -81,6 +83,7 @@ function startButtonClickHandler() {
     case READY:
     case FINISHED:
       testState.speedtest.start();
+      testState.initialGaugeScrollPending = true;
       testState.state = RUNNING;
       return;
     case RUNNING:
@@ -92,6 +95,30 @@ function startButtonClickHandler() {
   }
 }
 
+/**
+ * Scroll the initial download gauge into view on narrow viewports when starting a test
+ */
+function scrollInitialDownloadGaugeIntoView() {
+  if (!window.matchMedia("(max-width: 800px)").matches) {
+    return;
+  }
+
+  const downloadGauge = document.querySelector("#download-gauge");
+  if (!downloadGauge) {
+    return;
+  }
+
+  const { top, bottom } = downloadGauge.getBoundingClientRect();
+  if (top >= 0 && bottom <= window.innerHeight) {
+    return;
+  }
+
+  downloadGauge.scrollIntoView({
+    block: "center",
+    inline: "nearest",
+  });
+}
+
 /**
  * Event listener for clicks on the "Copy link" button in the modal
  */
@@ -349,6 +376,21 @@ function startRenderingLoop() {
       )
     );
 
+    if (
+      testState.state === RUNNING &&
+      testState.initialGaugeScrollPending &&
+      !testState.initialGaugeScrollScheduled
+    ) {
+      testState.initialGaugeScrollScheduled = true;
+      requestAnimationFrame(() => {
+        if (testState.state === RUNNING) {
+          scrollInitialDownloadGaugeIntoView();
+        }
+        testState.initialGaugeScrollPending = false;
+        testState.initialGaugeScrollScheduled = false;
+      });
+    }
+
     // Show ping and jitter if data is available
     pingAndJitter.forEach((e) =>
       e.classList.toggle(

+ 99 - 0
tests/e2e/mobile-gauge-visibility.spec.js

@@ -0,0 +1,99 @@
+const { test, expect } = require("@playwright/test");
+const { modernStartButton } = require("./helpers/ui");
+
+test.use({ viewport: { width: 390, height: 560 } });
+
+test.describe("Mobile gauge visibility", () => {
+  test("keeps the active gauge visible after pressing Start", async ({ page }) => {
+    await page.route("**/speedtest.js", async (route) => {
+      await route.fulfill({
+        contentType: "text/javascript; charset=utf-8",
+        body: `
+          class Speedtest {
+            constructor() {
+              this.selectedServer = null;
+              this.onupdate = null;
+              this.onend = null;
+            }
+
+            start() {
+              this.onupdate?.({
+                dlProgress: 0.1,
+                ulProgress: 0,
+                dlStatus: "123.45",
+                ulStatus: "0",
+                pingStatus: "10.5",
+                jitterStatus: "0.9",
+                testState: 1
+              });
+            }
+
+            abort() {
+              this.onend?.(true);
+            }
+
+            setParameter() {}
+            addTestPoints() {}
+            selectServer(callback) {
+              callback(this.selectedServer);
+            }
+            setSelectedServer(server) {
+              this.selectedServer = server;
+            }
+            getSelectedServer() {
+              return this.selectedServer;
+            }
+          }
+
+          window.Speedtest = Speedtest;
+        `,
+      });
+    });
+
+    await page.route("**/settings.json", async (route) => {
+      await route.fulfill({
+        contentType: "application/json; charset=utf-8",
+        body: JSON.stringify({ telemetry_level: "off" }),
+      });
+    });
+
+    await page.route("**/server-list.json", async (route) => {
+      await route.fulfill({
+        contentType: "application/json; charset=utf-8",
+        body: JSON.stringify([
+          {
+            name: "Test Server",
+            server: "http://127.0.0.1:18184/backend/",
+          },
+        ]),
+      });
+    });
+
+    await page.goto("http://127.0.0.1:18184/index-modern.html");
+
+    const startButton = modernStartButton(page);
+    const downloadGauge = page.locator("#download-gauge");
+
+    await expect(startButton).toHaveText("Let's start", { timeout: 10_000 });
+
+    await page.evaluate(() => window.scrollTo(0, 0));
+
+    const gaugeStartsBelowFold = await downloadGauge.evaluate((element) => {
+      const { top, bottom } = element.getBoundingClientRect();
+      return bottom > window.innerHeight || top < 0;
+    });
+    expect(gaugeStartsBelowFold).toBe(true);
+
+    await startButton.click();
+    await expect(startButton).toHaveText("Abort");
+
+    await expect
+      .poll(() =>
+        downloadGauge.evaluate((element) => {
+          const { top, bottom } = element.getBoundingClientRect();
+          return top >= 0 && bottom <= window.innerHeight;
+        })
+      )
+      .toBe(true);
+  });
+});