114 lines
6.7 KiB
JavaScript
114 lines
6.7 KiB
JavaScript
// Browser check of the in-match 观战席 capsule (GitHub #120, PR #120 by @salt-fishes; opt-in, ~10 s) on the in-match mock
|
|
// harness (public/dev/game-mock.html):
|
|
// SP_E2E=1 CHROME_PATH=… node --test test/ui/spectator-roster.e2e.test.js
|
|
//
|
|
// The room screen lists the spectator seats and lets the host remove one, but the route after 开始 is the game screen: a
|
|
// running match showed nothing of them. Now the top bar carries a capsule beside the latency — an eye and how many are
|
|
// watching, nothing at all while the room has no spectator; a tap opens the roster (names, an offline mark, the host's ✕ on
|
|
// every row: room.removeSpectator). Asserted: no capsule without spectators (and none in the default mock), the count and
|
|
// the rows, the ✕ only for the host, the request the ✕ sends, the roster and the count following the room.state that
|
|
// comes back, the capsule gone with the last seat, and the roster closing with Esc / 关闭. No console error. The server side
|
|
// (the seat freed at any time, room.closed {kicked}, no frame after it) is test/lobby.test.js and test/match/lobby-integration.test.js.
|
|
|
|
import { test, describe, before, after } from 'node:test';
|
|
import assert from 'node:assert/strict';
|
|
import { existsSync } from 'node:fs';
|
|
|
|
const CHROME = process.env.CHROME_PATH || '/Applications/Google Chrome.app/Contents/MacOS/Google Chrome';
|
|
const ENABLED = process.env.SP_E2E === '1' && existsSync(CHROME);
|
|
const sleep = (ms) => new Promise((r) => setTimeout(r, ms));
|
|
|
|
describe('the 观战席 capsule of the match top bar', { skip: !ENABLED && 'set SP_E2E=1 (and have Chrome) to run' }, () => {
|
|
let srv;
|
|
let browser;
|
|
let base;
|
|
before(async () => {
|
|
const { startServer } = await import('../../server/index.js');
|
|
const puppeteer = (await import('puppeteer-core')).default;
|
|
srv = await startServer({ port: 0, host: '127.0.0.1', quiet: true });
|
|
base = `http://127.0.0.1:${srv.port}`;
|
|
browser = await puppeteer.launch({ executablePath: CHROME, headless: true, args: ['--no-sandbox', '--force-device-scale-factor=1'] });
|
|
});
|
|
after(async () => {
|
|
await browser?.close();
|
|
await srv?.close();
|
|
});
|
|
|
|
async function open(query) {
|
|
const page = await browser.newPage();
|
|
await page.setViewport({ width: 1600, height: 900 });
|
|
const problems = [];
|
|
page.on('console', (m) => { if (m.type() === 'error') problems.push(`console: ${m.text()}`); });
|
|
page.on('pageerror', (e) => problems.push(`pageerror: ${e.message}`));
|
|
await page.goto(`${base}/dev/game-mock.html?render=fallback&${query}`, { waitUntil: 'networkidle0', timeout: 60000 });
|
|
await page.waitForSelector('.gtop', { timeout: 15000 });
|
|
await sleep(400);
|
|
return { page, problems };
|
|
}
|
|
const pill = (page) => page.evaluate(() => {
|
|
const el = document.querySelector('[data-testid="spectators"]');
|
|
return el ? { text: el.textContent.trim(), me: el.classList.contains('is-me') } : null;
|
|
});
|
|
const roster = (page) => page.evaluate(() => {
|
|
const ul = document.querySelector('[data-testid="spectator-roster"]');
|
|
if (!ul) return null;
|
|
return [...ul.querySelectorAll('li')].map((li) => ({
|
|
name: li.querySelector('.spec__name')?.textContent.trim(), offline: li.classList.contains('is-offline'), remove: !!li.querySelector('[data-testid="spectator-remove"]'),
|
|
}));
|
|
});
|
|
const requests = (page) => page.evaluate(() => globalThis.__MOCK__.S().requests.filter((r) => r[0] === 'room.removeSpectator'));
|
|
|
|
test('no spectator, no capsule: the default match top bar is as it was', async () => {
|
|
const { page, problems } = await open('phase=COMBAT');
|
|
assert.equal(await pill(page), null);
|
|
assert.ok(await page.$('.gtop__net .ping'), 'the latency pill is still there');
|
|
assert.deepEqual(problems, []);
|
|
await page.close();
|
|
});
|
|
|
|
test('the host: the capsule counts the seats, the roster lists them with a ✕ each, removing one updates both, the last one removes the capsule', async () => {
|
|
const { page, problems } = await open('phase=COMBAT&variant=spectators');
|
|
assert.deepEqual(await pill(page), { text: '2', me: false }, 'an eye and the count');
|
|
assert.ok(await page.$('.gtop__net [data-testid="spectators"]'), 'beside the latency pill');
|
|
assert.equal(await roster(page), null, 'closed until asked');
|
|
await page.click('[data-testid="spectators"]');
|
|
await page.waitForSelector('[data-testid="spectator-roster"]', { timeout: 5000 });
|
|
assert.deepEqual(await roster(page), [
|
|
{ name: '观战者甲', offline: false, remove: true }, { name: '观战者乙', offline: true, remove: true },
|
|
]);
|
|
// ✕ on the first row: room.removeSpectator { playerId } goes out, the room.state that follows takes the row away
|
|
await page.click('[data-testid="spectator-roster"] li:first-child [data-testid="spectator-remove"]');
|
|
await page.waitForFunction(() => document.querySelectorAll('[data-testid="spectator-roster"] li').length === 1, { timeout: 5000 });
|
|
assert.deepEqual(await requests(page), [['room.removeSpectator', { playerId: 'sp_1' }]]);
|
|
assert.deepEqual((await roster(page)).map((r) => r.name), ['观战者乙']);
|
|
assert.equal((await pill(page)).text, '1');
|
|
// Esc closes the roster, the capsule stays
|
|
await page.keyboard.press('Escape');
|
|
await sleep(250);
|
|
assert.equal(await roster(page), null);
|
|
assert.equal((await pill(page)).text, '1');
|
|
// the last seat: the open roster and the capsule go with it
|
|
await page.click('[data-testid="spectators"]');
|
|
await page.waitForSelector('[data-testid="spectator-roster"]', { timeout: 5000 });
|
|
await page.click('[data-testid="spectator-remove"]');
|
|
await page.waitForFunction(() => !document.querySelector('[data-testid="spectators"]') && !document.querySelector('.modal'), { timeout: 5000 });
|
|
assert.deepEqual(problems, []);
|
|
await page.close();
|
|
});
|
|
|
|
test('a player who is not the host sees the roster but no ✕; 关闭 closes it', async () => {
|
|
const { page, problems } = await open('phase=COMBAT&variant=spectators,notHost');
|
|
assert.equal((await pill(page)).text, '2');
|
|
await page.click('[data-testid="spectators"]');
|
|
await page.waitForSelector('[data-testid="spectator-roster"]', { timeout: 5000 });
|
|
assert.deepEqual((await roster(page)).map((r) => r.remove), [false, false], 'only the host removes');
|
|
assert.match(await page.evaluate(() => document.querySelector('.modal')?.textContent ?? ''), /创建者可以把观战者移出/);
|
|
await page.evaluate(() => [...document.querySelectorAll('.modal button')].find((b) => /关闭/.test(b.textContent))?.click());
|
|
await sleep(250);
|
|
assert.equal(await roster(page), null);
|
|
assert.deepEqual(await requests(page), []);
|
|
assert.deepEqual(problems, []);
|
|
await page.close();
|
|
});
|
|
});
|