diff --git a/frontend/javascript/index.js b/frontend/javascript/index.js index fcb7647..d804013 100644 --- a/frontend/javascript/index.js +++ b/frontend/javascript/index.js @@ -82,6 +82,7 @@ function startButtonClickHandler() { case FINISHED: testState.speedtest.start(); testState.state = RUNNING; + requestAnimationFrame(scrollActiveGaugeIntoView); return; case RUNNING: testState.speedtest.abort(); @@ -92,6 +93,30 @@ function startButtonClickHandler() { } } +/** + * Scroll the active gauge into view on narrow viewports when starting a test + */ +function scrollActiveGaugeIntoView() { + if (!window.matchMedia("(max-width: 800px)").matches) { + return; + } + + const activeGauge = document.querySelector("#download-gauge"); + if (!activeGauge) { + return; + } + + const { top, bottom } = activeGauge.getBoundingClientRect(); + if (top >= 0 && bottom <= window.innerHeight) { + return; + } + + activeGauge.scrollIntoView({ + block: "center", + inline: "nearest", + }); +} + /** * Event listener for clicks on the "Copy link" button in the modal */ diff --git a/tests/e2e/mobile-gauge-visibility.spec.js b/tests/e2e/mobile-gauge-visibility.spec.js new file mode 100644 index 0000000..817c182 --- /dev/null +++ b/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); + }); +});