feat(ui): restore Logo countdown and gated skip
This commit is contained in:
@@ -3,7 +3,7 @@ import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { test } from 'node:test';
|
||||
import type { PageModel, OverlayModel, PageId } from '../../YouleNexus/assets/framework/presentation/ui-contracts.ts';
|
||||
import type { ResolveRuntimeConfigOptions } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
|
||||
import type { ResolveRuntimeConfigOptions, RuntimeConfig } from '../../YouleNexus/assets/framework/config/runtime-config.ts';
|
||||
|
||||
function deferred() {
|
||||
let resolve!: () => void;
|
||||
@@ -31,7 +31,9 @@ class Socket {
|
||||
}
|
||||
async function setup(overrides: { config?: Partial<ResolveRuntimeConfigOptions>; render?: (model: PageModel) => void;
|
||||
cancelFrame?: () => void; showOverlay?: (model: OverlayModel) => void;
|
||||
record?: (event: { kind: string; data: unknown }) => void; onBusy?: (busy: boolean) => void } = {}) {
|
||||
record?: (event: { kind: string; data: unknown }) => void; onBusy?: (busy: boolean) => void;
|
||||
onConfigResolved?: (config: RuntimeConfig) => void; onResourcesReady?: () => void;
|
||||
onSocketReady?: (ready: boolean) => void; onStopped?: () => void } = {}) {
|
||||
const api = await import('../../YouleNexus/assets/scripts/local-platform-login/local-login-host.ts')
|
||||
.catch(error => assert.fail(`Local login host implementation must exist: ${String(error)}`));
|
||||
const resources = deferred(), minimum = deferred();
|
||||
@@ -40,6 +42,8 @@ async function setup(overrides: { config?: Partial<ResolveRuntimeConfigOptions>;
|
||||
const values = new Map<string, string>(), frames = new Set<() => void>();
|
||||
let fetches = 0;
|
||||
const host = api.createLocalLoginHost({
|
||||
onConfigResolved: overrides.onConfigResolved, onResourcesReady: overrides.onResourcesReady,
|
||||
onSocketReady: overrides.onSocketReady, onStopped: overrides.onStopped,
|
||||
configOptions: { hostKind: 'h5', win: {}, search: '?profile=local&version=731',
|
||||
fetcher: { async fetch() { fetches++; return { player_server_tcp: 'remote.example:3088' }; } }, ...overrides.config },
|
||||
storage: { getItem: key => values.get(key) ?? null, setItem: (key, value) => { values.set(key, value); } },
|
||||
@@ -57,6 +61,57 @@ async function setup(overrides: { config?: Partial<ResolveRuntimeConfigOptions>;
|
||||
return { host, resources, minimum, sockets, pages, overlays, activations, failures, busy, evidence, values, frames, draw, ready, fetches: () => fetches };
|
||||
}
|
||||
|
||||
test('host exposes resolved config and independent socket/resources readiness before display, and stops late events', async () => {
|
||||
const configs: RuntimeConfig[] = [], socket: boolean[] = []; let resources = 0, stopped = 0;
|
||||
const h = await setup({ onConfigResolved: config => configs.push(config), onResourcesReady: () => resources++,
|
||||
onSocketReady: ready => socket.push(ready), onStopped: () => stopped++ });
|
||||
const starting = h.host.start(); await flush();
|
||||
assert.equal(configs.length, 1); assert.equal(configs[0].identity.version, 731);
|
||||
assert.equal(configs[0].remoteConfig.getValue('player_server_tcp'), 'remote.example:3088');
|
||||
h.sockets[0].open(); await flush(); assert.equal(socket[socket.length - 1], true);
|
||||
assert.equal(h.host.getSnapshot().ready, false);
|
||||
h.resources.resolve(); await flush(); assert.equal(resources, 1);
|
||||
assert.equal(h.host.getSnapshot().ready, false);
|
||||
h.minimum.resolve(); await starting; h.host.stop(); assert.equal(stopped, 1);
|
||||
const events = socket.length; h.sockets[0].open(); await flush(); assert.equal(socket.length, events);
|
||||
});
|
||||
|
||||
test('host stop cancels presentation and ignores a resource completion still pending', async () => {
|
||||
let resources = 0, stopped = 0;
|
||||
const h = await setup({ onResourcesReady: () => resources++, onStopped: () => stopped++ });
|
||||
const starting = h.host.start(); const rejection = assert.rejects(starting, /stop|terminal/);
|
||||
await flush(); h.host.stop(); h.resources.resolve(); h.minimum.resolve();
|
||||
await rejection; assert.equal(resources, 0); assert.equal(stopped, 1);
|
||||
});
|
||||
|
||||
for (const order of [
|
||||
['resources', 'socket', 'minimum'], ['resources', 'minimum', 'socket'],
|
||||
['socket', 'resources', 'minimum'], ['socket', 'minimum', 'resources'],
|
||||
['minimum', 'resources', 'socket'], ['minimum', 'socket', 'resources'],
|
||||
] as const) test(`all startup gates remain independent in ${order.join(' -> ')} order`, async t => {
|
||||
const h = await setup(); t.after(() => h.host.stop());
|
||||
const starting = h.host.start(); await flush();
|
||||
for (let index = 0; index < order.length; index++) {
|
||||
const gate = order[index];
|
||||
if (gate === 'socket') h.sockets[0].open(); else h[gate].resolve();
|
||||
await flush();
|
||||
assert.equal(h.host.getSnapshot().ready, index === order.length - 1);
|
||||
assert.equal(h.activations.includes('login'), index === order.length - 1);
|
||||
}
|
||||
await starting; h.draw(); assert.equal(h.activations.includes('login'), true);
|
||||
});
|
||||
|
||||
test('pending config cannot be bypassed by previously completed resource/display tasks', async t => {
|
||||
const config = deferred(); let resolved = 0;
|
||||
const h = await setup({ config: { fetcher: { async fetch() { await config.promise; return {}; } } },
|
||||
onConfigResolved: () => resolved++ });
|
||||
t.after(() => h.host.stop()); const starting = h.host.start();
|
||||
h.resources.resolve(); h.minimum.resolve(); await flush();
|
||||
assert.equal(resolved, 0); assert.equal(h.sockets.length, 0); assert.equal(h.host.getSnapshot().ready, false);
|
||||
config.resolve(); await flush(); assert.equal(resolved, 1); assert.equal(h.host.getSnapshot().ready, false);
|
||||
h.sockets[0].open(); await starting; h.draw(); assert.equal(h.host.getSnapshot().ready, true);
|
||||
});
|
||||
|
||||
for (const missing of ['resources', 'minimum', 'socket'] as const) test(`login waits for ${missing}, and start composes only once`, async t => {
|
||||
const h = await setup(); t.after(() => h.host.stop());
|
||||
const starting = h.host.start(); const repeated = h.host.start();
|
||||
|
||||
Reference in New Issue
Block a user