98 lines
6.9 KiB
JavaScript
98 lines
6.9 KiB
JavaScript
import assert from "node:assert/strict";
|
|
import { readFileSync, createReadStream, statSync } from "node:fs";
|
|
import { createServer } from "node:http";
|
|
import { resolve } from "node:path";
|
|
import { fileURLToPath, pathToFileURL } from "node:url";
|
|
import { test } from "node:test";
|
|
import { build } from "esbuild";
|
|
|
|
test("MIST gallery scene accepts Skip and menu clicks, keeps video centered and bounds overlapping music", {
|
|
skip: !process.env.PGAMES_PLAYWRIGHT_PATH || !process.env.PGAMES_MIST_RUNTIME || !process.env.PGAMES_MIST_MANIFEST || !process.env.PGAMES_MIST_BLOBS,
|
|
}, async () => {
|
|
const { chromium } = await import(pathToFileURL(process.env.PGAMES_PLAYWRIGHT_PATH + "/index.mjs").href);
|
|
const runtime = readFileSync(process.env.PGAMES_MIST_RUNTIME);
|
|
const manifest = { ...JSON.parse(readFileSync(process.env.PGAMES_MIST_MANIFEST)).manifest, galleryUnlocked: true, versionId: 1 };
|
|
const compiled = await build({ stdin: { contents: `
|
|
import {Engine} from './player/engine';
|
|
const r = await (await fetch('/runtime.json')).json();
|
|
const manifest = await (await fetch('/manifest.json')).json();
|
|
const tr = {lang: 'en', renpyName: null, str: s => s, line: (_, s) => s};
|
|
window.audioCalls = [];
|
|
const e = window.engine = new Engine(document.querySelector('main'), r.game, manifest, r.modules,
|
|
{list: async () => [], get: async () => null, put: async () => {}, remove: async () => {}},
|
|
'mist-playback-browser', () => {}, tr, async () => tr);
|
|
e.settings.cps = 0;
|
|
e.renderer.transitionsOff = true;
|
|
e.audio.setVolumes({music: 0, sound: 0, voice: 0});
|
|
e.audio.setMixer('ambiant', 0);
|
|
const play = e.audio.play.bind(e.audio);
|
|
e.audio.play = (...args) => { window.audioCalls.push(args); return play(...args); };
|
|
e.openUnlockedGallery();`, resolveDir: fileURLToPath(new URL("../src/", import.meta.url)), loader: "tsx" },
|
|
bundle: true, write: false, format: "esm", platform: "browser", logLevel: "silent" });
|
|
const css = readFileSync(new URL("../src/player/player.css", import.meta.url));
|
|
const server = createServer((req, res) => {
|
|
if (req.url === "/bundle.js") { res.setHeader("Content-Type", "text/javascript"); res.end(compiled.outputFiles[0].text); return; }
|
|
if (req.url === "/style.css") { res.setHeader("Content-Type", "text/css"); res.end(css); return; }
|
|
if (req.url === "/runtime.json") { res.setHeader("Content-Type", "application/json"); res.end(runtime); return; }
|
|
if (req.url === "/manifest.json") { res.setHeader("Content-Type", "application/json"); res.end(JSON.stringify(manifest)); return; }
|
|
const hash = req.url?.match(/^\/api\/versions\/1\/files\/([a-f0-9]{64}\.[a-z0-9]+)$/)?.[1];
|
|
if (hash) {
|
|
const path = resolve(process.env.PGAMES_MIST_BLOBS, hash.slice(0, 2), hash.slice(2, 4), hash);
|
|
try {
|
|
const size = statSync(path).size;
|
|
const range = req.headers.range?.match(/^bytes=(\d+)-(\d*)$/);
|
|
const start = range ? Number(range[1]) : 0, end = range?.[2] ? Number(range[2]) : size - 1;
|
|
res.setHeader("Accept-Ranges", "bytes");
|
|
res.setHeader("Content-Type", hash.endsWith(".webm") ? "video/webm" : hash.endsWith(".opus") ? "audio/ogg" : hash.endsWith(".webp") ? "image/webp" : "application/octet-stream");
|
|
res.setHeader("Content-Length", end - start + 1);
|
|
if (range) { res.statusCode = 206; res.setHeader("Content-Range", `bytes ${start}-${end}/${size}`); }
|
|
createReadStream(path, { start, end }).pipe(res);
|
|
} catch { res.statusCode = 404; res.end(); }
|
|
return;
|
|
}
|
|
res.setHeader("Content-Type", "text/html");
|
|
res.end('<!doctype html><meta charset="utf-8"><link rel="stylesheet" href="/style.css"><style>body{margin:0}main{width:960px;height:540px}</style><main></main><script type="module" 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, channel: process.env.PGAMES_BROWSER_CHANNEL });
|
|
const page = await browser.newPage({ viewport: { width: 1000, height: 700 } });
|
|
page.setDefaultTimeout(7000);
|
|
await page.goto(`http://127.0.0.1:${server.address().port}`);
|
|
await page.waitForFunction(() => window.engine?.screens.shown.has('rewatchgirlselectscreen'));
|
|
await page.waitForFunction(() => [...document.querySelectorAll('.pg-simgbtn img')].every(el => el.complete && el.naturalWidth > 0));
|
|
await page.locator(".pg-simgbtn").nth(1).click();
|
|
await page.waitForFunction(() => window.engine.screens.shown.has('robgallery'));
|
|
await page.evaluate(() => window.engine.screens.shown.get('robgallery').el.dataset.test = 'robgallery');
|
|
await page.waitForFunction(() => [...document.querySelectorAll('.pg-simgbtn img')].every(el => el.complete && el.naturalWidth > 0));
|
|
await page.locator('[data-test="robgallery"] .pg-simgbtn').nth(1).click();
|
|
await page.waitForFunction(() => !!window.engine.onAdvance && !window.engine.screens.shown.size);
|
|
await page.locator('[data-act="skip"]').click();
|
|
await page.getByRole('button', { name: 'End Scene.', exact: true }).waitFor();
|
|
await page.getByRole('button', { name: 'Keep Looking.', exact: true }).click();
|
|
await page.waitForFunction(() => document.querySelector('.pg-image video, video.pg-image')?.videoWidth > 0);
|
|
const state = await page.evaluate(() => {
|
|
const stage = document.querySelector('.pg-stage').getBoundingClientRect();
|
|
const video = document.querySelector('.pg-image video, video.pg-image').getBoundingClientRect();
|
|
return { dx: video.x - stage.x, dy: video.y - stage.y, width: video.width, height: video.height,
|
|
stageWidth: stage.width, stageHeight: stage.height,
|
|
music: [...window.engine.audio.playing.values()].filter(p => p.channel === 'music').length,
|
|
calls: window.audioCalls.filter(call => call[0] === 'sound').map(call => call[1][0]) };
|
|
});
|
|
assert.ok(Math.abs(state.dx) < 1 && Math.abs(state.dy) < 1, `video drift: ${JSON.stringify(state)}`);
|
|
assert.ok(Math.abs(state.width - state.stageWidth) < 1 && Math.abs(state.height - state.stageHeight) < 1);
|
|
assert.ok(state.music <= 2, `overlapping music tracks: ${state.music}`);
|
|
assert.equal(new Set(state.calls).size, state.calls.length, "the scene's sounds are not replayed by duplicate interpreter loops");
|
|
await page.locator('[data-act="skip"]').click();
|
|
await page.getByRole('button', { name: 'End Scene.', exact: true }).click();
|
|
await page.waitForFunction(() => window.engine.screens.shown.has('rewatchgirlselectscreen'));
|
|
const returned = await page.evaluate(() => ({ screens: [...window.engine.screens.shown.keys()],
|
|
audio: window.engine.audio.playing.size, images: window.engine.renderer.snapshot().length }));
|
|
assert.deepEqual(returned, { screens: ['rewatchgirlselectscreen'], audio: 0, images: 0 });
|
|
} finally {
|
|
await browser?.close();
|
|
await new Promise((resolve) => server.close(resolve));
|
|
}
|
|
});
|