113 lines
8.0 KiB
JavaScript
113 lines
8.0 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync } from "node:fs";
|
|
import { createServer } from "node:http";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import { test } from "node:test";
|
|
import { build } from "esbuild";
|
|
|
|
test("floating uploads collapse, keep live progress across navigation and show processing and failure", {
|
|
skip: !process.env.PGAMES_PLAYWRIGHT_PATH,
|
|
}, async () => {
|
|
const { chromium } = await import(pathToFileURL(process.env.PGAMES_PLAYWRIGHT_PATH + "/index.mjs").href);
|
|
const compiled = await build({stdin: {contents: `
|
|
import {createElement as h} from 'react';
|
|
import {createRoot} from 'react-dom/client';
|
|
import {MemoryRouter, Link, Routes, Route} from 'react-router-dom';
|
|
import {AuthProvider} from './auth'; import {UiProvider} from './ui/Ui';
|
|
import {TranslationRunProvider} from './translator/runner';
|
|
import {UploadProvider, useUploads} from './uploads'; import {GamePage} from './pages/GamePage';
|
|
function Harness() { const uploads = useUploads(); return h('div', null,
|
|
h('button', {onClick: () => uploads.start({gameId: 1, file: new File(['archive'], 'MIST.zip')})}, 'Начать'),
|
|
h(Link, {to: '/games/1'}, 'Первая игра'), h(Link, {to: '/games/2'}, 'Вторая игра'),
|
|
h(Routes, null, h(Route, {path: '/games/:gameId', element: h(GamePage)}))); }
|
|
createRoot(document.querySelector('main')).render(h(MemoryRouter, {initialEntries: ['/games/1']},
|
|
h(UiProvider, null, h(AuthProvider, null, h(TranslationRunProvider, null, h(UploadProvider, null, h(Harness)))))));`,
|
|
resolveDir: fileURLToPath(new URL("../src/", import.meta.url)), loader: "tsx"},
|
|
bundle: true, write: false, format: "iife", platform: "browser", logLevel: "silent"});
|
|
const css = readFileSync(new URL("../src/styles.css", import.meta.url), "utf8");
|
|
let ready = false;
|
|
const server = createServer((req, res) => {
|
|
if (req.url === "/bundle.js") { res.setHeader("Content-Type", "application/javascript"); res.end(compiled.outputFiles[0].text); return; }
|
|
if (req.url === "/styles.css") { res.setHeader("Content-Type", "text/css"); res.end(css); return; }
|
|
if (req.url === "/blobs/banner.webp") {
|
|
res.setHeader("Content-Type", "image/svg+xml");
|
|
res.end('<svg xmlns="http://www.w3.org/2000/svg" width="1200" height="500"><rect width="1200" height="500" fill="#273d5c"/><rect width="80" height="500" fill="#f90"/><rect x="1120" width="80" height="500" fill="#0b9"/><text x="600" y="290" text-anchor="middle" fill="white" font-size="100">FULL BANNER</text></svg>'); return;
|
|
}
|
|
if (req.url === "/ready") { ready = true; res.end("ok"); return; }
|
|
if (req.url.startsWith("/api/")) {
|
|
res.setHeader("Content-Type", "application/json");
|
|
if (req.url === "/api/auth/me") res.end(JSON.stringify({id: 1, permissions: ["games.play"]}));
|
|
else if (req.url.startsWith("/api/versions/")) res.end(JSON.stringify({id: 1, status: ready ? "ready" : "converting", progress: "Обработка"}));
|
|
else res.end(JSON.stringify({id: Number(req.url.split("/").at(-1)), title: req.url.endsWith("/2") ? "No cover" : "Mist",
|
|
tags: Array.from({length: 30}, (_, i) => `tag-${i}`), description: Array(8).fill("Long description of the game and its story.").join("\n\n"),
|
|
cover_hash: req.url.endsWith("/2") ? null : "banner.webp", gallery_hashes: [], versions: [], main_version_id: null}));
|
|
return;
|
|
}
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.end('<!doctype html><meta name="viewport" content="width=device-width, initial-scale=1"><link rel="stylesheet" href="/styles.css"><main></main><script src="/bundle.js"></script>');
|
|
});
|
|
await new Promise(resolve => server.listen(0, "127.0.0.1", resolve));
|
|
let browser;
|
|
try {
|
|
browser = await chromium.launch({headless: true, executablePath: process.env.PGAMES_CHROMIUM_EXECUTABLE});
|
|
const page = await browser.newPage({colorScheme: "dark", viewport: {width: 1906, height: 910}});
|
|
page.setDefaultTimeout(10000);
|
|
await page.addInitScript(() => {
|
|
window.XMLHttpRequest = class {
|
|
upload = {};
|
|
open() {}
|
|
setRequestHeader() {}
|
|
send() { window.uploadRequest = this; }
|
|
};
|
|
window.uploadProgress = value => window.uploadRequest.upload.onprogress({lengthComputable: true, loaded: value, total: 100});
|
|
window.uploadComplete = failed => {
|
|
const xhr = window.uploadRequest;
|
|
xhr.status = failed ? 500 : 201;
|
|
xhr.responseText = JSON.stringify(failed ? {detail: "Test failure"} : {id: 1, status: "queued"});
|
|
xhr.onload();
|
|
};
|
|
});
|
|
await page.goto(`http://127.0.0.1:${server.address().port}`);
|
|
await page.locator(".hero .cover img").evaluate(image => image.decode());
|
|
const layout = await page.locator(".hero-inner").evaluate(element => {
|
|
const image = element.querySelector(".cover img"), cover = element.querySelector(".cover"), info = element.querySelector(".hero-info");
|
|
return {ratio: image.getBoundingClientRect().width / image.getBoundingClientRect().height,
|
|
naturalRatio: image.naturalWidth / image.naturalHeight, topOffset: cover.getBoundingClientRect().top - info.getBoundingClientRect().top};
|
|
});
|
|
assert.ok(Math.abs(layout.ratio - layout.naturalRatio) < 0.01);
|
|
assert.ok(Math.abs(layout.topOffset) < 1);
|
|
await page.getByRole("button", {name: "Начать", exact: true}).click();
|
|
await page.waitForFunction(() => !!window.uploadRequest);
|
|
await page.evaluate(() => window.uploadProgress(38));
|
|
await page.getByText("Загрузка архива… 38%", {exact: true}).waitFor();
|
|
await page.getByRole("button", {name: "Свернуть фоновые задачи", exact: true}).click();
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Прогресс: 38%", exact: true}).waitFor();
|
|
await page.evaluate(() => window.uploadProgress(65));
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Прогресс: 65%", exact: true}).waitFor();
|
|
await page.getByRole("link", {name: "Вторая игра", exact: true}).click();
|
|
await page.getByRole("heading", {name: "No cover", exact: true}).waitFor();
|
|
assert.equal(await page.locator(".hero .cover").evaluate(element => element.getBoundingClientRect().height > 0), true);
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Прогресс: 65%", exact: true}).press("Enter");
|
|
await page.getByText("Загрузка архива… 65%", {exact: true}).waitFor();
|
|
await page.getByRole("button", {name: "Свернуть фоновые задачи", exact: true}).click();
|
|
await page.evaluate(() => window.uploadComplete(false));
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Обработка на сервере", exact: true}).waitFor();
|
|
await page.request.get(`http://127.0.0.1:${server.address().port}/ready`);
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Фоновые задачи завершены", exact: true}).waitFor();
|
|
await page.getByRole("link", {name: "Первая игра", exact: true}).click();
|
|
await page.getByRole("heading", {name: "Mist", exact: true}).waitFor();
|
|
for (const width of [1906, 375]) {
|
|
await page.setViewportSize({width, height: 910});
|
|
assert.equal(await page.evaluate(() => document.documentElement.scrollWidth <= innerWidth), true);
|
|
if (process.env.PGAMES_F95_SCREENSHOT_DIR) await page.screenshot({path: `${process.env.PGAMES_F95_SCREENSHOT_DIR}/hero-progress-${width}.png`, fullPage: true});
|
|
}
|
|
await page.getByRole("button", {name: "Начать", exact: true}).click();
|
|
await page.evaluate(() => window.uploadComplete(true));
|
|
await page.getByRole("button", {name: "Развернуть фоновые задачи. Ошибка фоновой задачи", exact: true}).click();
|
|
await page.getByText("Test failure", {exact: true}).waitFor();
|
|
} finally {
|
|
await browser?.close();
|
|
await new Promise(resolve => server.close(resolve));
|
|
}
|
|
});
|