Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -12,12 +12,32 @@ vi.mock('$app/navigation', () => ({ goto: vi.fn() }));
|
||||
const now = get(uiNow);
|
||||
const iso = (offset = 0) => new Date(now + offset).toISOString();
|
||||
const server: FleetServer = {
|
||||
name: 't5_zombie_theater_1', game: 't5', map: 'zombie_theater', last_map: 'zombie_theater',
|
||||
last_gametype: 'zclassic', last_players: 4, last_seen_at: iso(-10_000), last_error: null,
|
||||
container_state: 'running', container_checked_at: iso(-5_000), last_checked_at: iso(-5_000),
|
||||
roster: [], health: 'online', held: [], active_since: iso(-120_000), inactive_since: null,
|
||||
game_started_at: iso(-120_000), game_ended_at: null, host_id: 'host-1',
|
||||
host: { id: 'host-1', name: 'Dallas 1', connected: true, connectionState: 'online', lastHeartbeat: iso(-1_000) }
|
||||
name: 't5_zombie_theater_1',
|
||||
game: 't5',
|
||||
map: 'zombie_theater',
|
||||
last_map: 'zombie_theater',
|
||||
last_gametype: 'zclassic',
|
||||
last_players: 4,
|
||||
last_seen_at: iso(-10_000),
|
||||
last_error: null,
|
||||
container_state: 'running',
|
||||
container_checked_at: iso(-5_000),
|
||||
last_checked_at: iso(-5_000),
|
||||
roster: [],
|
||||
health: 'online',
|
||||
held: [],
|
||||
active_since: iso(-120_000),
|
||||
inactive_since: null,
|
||||
game_started_at: iso(-120_000),
|
||||
game_ended_at: null,
|
||||
host_id: 'host-1',
|
||||
host: {
|
||||
id: 'host-1',
|
||||
name: 'Dallas 1',
|
||||
connected: true,
|
||||
connectionState: 'online',
|
||||
lastHeartbeat: iso(-1_000)
|
||||
}
|
||||
};
|
||||
|
||||
describe('lifecycle action applicability', () => {
|
||||
@@ -29,50 +49,83 @@ describe('lifecycle action applicability', () => {
|
||||
const stopped = { ...server, health: 'offline' as const, container_state: 'stopped' };
|
||||
expect(applicableRuntimeActions(stopped, now)).toEqual(['start', 'rebuild']);
|
||||
for (const change of [
|
||||
{ container_checked_at: iso(-91_000) }, { container_checked_at: null },
|
||||
{ telemetry_after: iso(-2_000) }, { last_seen_at: iso(-2_000) },
|
||||
{ host: null }, { host: { ...server.host!, connected: false } }
|
||||
]) expect(applicableRuntimeActions({ ...stopped, ...change }, now)).not.toContain('start');
|
||||
{ container_checked_at: iso(-91_000) },
|
||||
{ container_checked_at: null },
|
||||
{ telemetry_after: iso(-2_000) },
|
||||
{ last_seen_at: iso(-2_000) },
|
||||
{ host: null },
|
||||
{ host: { ...server.host!, connected: false } }
|
||||
])
|
||||
expect(applicableRuntimeActions({ ...stopped, ...change }, now)).not.toContain('start');
|
||||
});
|
||||
|
||||
it('permits recovery of an unresponsive game only with a current running-container observation', () => {
|
||||
const unresponsive = { ...server, health: 'stale' as const, last_seen_at: iso(-300_000), last_error: 'timeout:no reply' };
|
||||
const unresponsive = {
|
||||
...server,
|
||||
health: 'stale' as const,
|
||||
last_seen_at: iso(-300_000),
|
||||
last_error: 'timeout:no reply'
|
||||
};
|
||||
expect(applicableRuntimeActions(unresponsive, now)).toEqual(['restart', 'stop', 'rebuild']);
|
||||
expect(applicableRuntimeActions({ ...unresponsive, container_checked_at: iso(-91_000) }, now)).toEqual([]);
|
||||
expect(applicableRuntimeActions({ ...unresponsive, telemetry_after: iso(-2_000) }, now)).toEqual([]);
|
||||
expect(
|
||||
applicableRuntimeActions({ ...unresponsive, container_checked_at: iso(-91_000) }, now)
|
||||
).toEqual([]);
|
||||
expect(
|
||||
applicableRuntimeActions({ ...unresponsive, telemetry_after: iso(-2_000) }, now)
|
||||
).toEqual([]);
|
||||
});
|
||||
|
||||
it('does not offer runtime mutations during startup or without evidence', () => {
|
||||
for (const change of [
|
||||
{ health: 'starting' as const }, { health: 'provisioning' as const },
|
||||
{ health: 'starting' as const },
|
||||
{ health: 'provisioning' as const },
|
||||
{ host: { ...server.host!, connected: false } },
|
||||
{ health: 'offline' as const, container_state: 'unknown', last_seen_at: null }
|
||||
]) expect(applicableRuntimeActions({ ...server, ...change }, now)).toEqual([]);
|
||||
])
|
||||
expect(applicableRuntimeActions({ ...server, ...change }, now)).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('lifecycle impact copy', () => {
|
||||
it('names current players and round with correct singular grammar', () => {
|
||||
expect(lifecycleImpact('restart', 4, 30)).toBe('4 players are in round 30. Restart waits until the lobby is empty.');
|
||||
expect(lifecycleImpact('stop', 1, null)).toBe('1 player is currently online. Stop waits until the lobby is empty.');
|
||||
expect(lifecycleImpact('restart', 4, 30)).toBe(
|
||||
'4 players are in round 30. Restart waits until the lobby is empty.'
|
||||
);
|
||||
expect(lifecycleImpact('stop', 1, null)).toBe(
|
||||
'1 player is currently online. Stop waits until the lobby is empty.'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not confuse unknown or invalid player counts with an empty lobby', () => {
|
||||
for (const players of [null, -1, NaN, 1.5]) expect(lifecycleImpact('destroy', players, 30))
|
||||
.toBe('Player count is unknown. Destruction waits for a live check confirming the lobby is empty.');
|
||||
expect(lifecycleImpact('rebuild', 0, 30)).toBe('The lobby is currently empty. A live check will confirm this before rebuild begins.');
|
||||
for (const players of [null, -1, NaN, 1.5])
|
||||
expect(lifecycleImpact('destroy', players, 30)).toBe(
|
||||
'Player count is unknown. Destruction waits for a live check confirming the lobby is empty.'
|
||||
);
|
||||
expect(lifecycleImpact('rebuild', 0, 30)).toBe(
|
||||
'The lobby is currently empty. A live check will confirm this before rebuild begins.'
|
||||
);
|
||||
});
|
||||
|
||||
it('does not claim a round or occupancy wait when starting a stopped container', () => {
|
||||
expect(lifecycleImpact('start', null, null)).toBe('Start the existing container, then verify that the game is responding.');
|
||||
expect(lifecycleImpact('start', null, null)).toBe(
|
||||
'Start the existing container, then verify that the game is responding.'
|
||||
);
|
||||
expect(lifecycleImpact('restart', 4, NaN)).toContain('4 players are currently online.');
|
||||
});
|
||||
});
|
||||
|
||||
describe('contextual lifecycle confirmation', () => {
|
||||
it('names the server and impact without re-asking the instance or action', () => {
|
||||
const body = render(LifecycleDialog, { props: { open: true, initialServer: server.name,
|
||||
initialAction: 'restart', serverContext: server, players: 4, round: 30 } }).body;
|
||||
const body = render(LifecycleDialog, {
|
||||
props: {
|
||||
open: true,
|
||||
initialServer: server.name,
|
||||
initialAction: 'restart',
|
||||
serverContext: server,
|
||||
players: 4,
|
||||
round: 30
|
||||
}
|
||||
}).body;
|
||||
expect(body).toContain('Restart Kino Der Toten · Lobby 1?');
|
||||
expect(body).toContain('4 players are in round 30.');
|
||||
expect(body).toContain('Restart when empty');
|
||||
@@ -81,16 +134,26 @@ describe('contextual lifecycle confirmation', () => {
|
||||
});
|
||||
|
||||
it('explains an unavailable action when the evidence no longer supports it', () => {
|
||||
const body = render(LifecycleDialog, { props: { open: true, initialServer: server.name,
|
||||
initialAction: 'start', serverContext: server } }).body;
|
||||
const body = render(LifecycleDialog, {
|
||||
props: {
|
||||
open: true,
|
||||
initialServer: server.name,
|
||||
initialAction: 'start',
|
||||
serverContext: server
|
||||
}
|
||||
}).body;
|
||||
expect(body).toContain('Start is unavailable. The game is responding.');
|
||||
const startButton = [...body.matchAll(/<button\b[^>]*>[\s\S]*?<\/button>/g)].find(match => match[0].includes('Start server'))?.[0];
|
||||
const startButton = [...body.matchAll(/<button\b[^>]*>[\s\S]*?<\/button>/g)].find((match) =>
|
||||
match[0].includes('Start server')
|
||||
)?.[0];
|
||||
expect(startButton).toContain('disabled');
|
||||
expect(body).not.toContain('Start the existing container');
|
||||
});
|
||||
|
||||
it('uses Destroy server consistently and preserves recorded history in the explanation', () => {
|
||||
const body = render(DestroyServerModal, { props: { open: true, name: server.name, players: 4, round: 30 } }).body;
|
||||
const body = render(DestroyServerModal, {
|
||||
props: { open: true, name: server.name, players: 4, round: 30 }
|
||||
}).body;
|
||||
expect(body).toContain('Destroy Kino Der Toten · Lobby 1?');
|
||||
expect(body).toContain('4 players are in round 30.');
|
||||
expect(body).toContain('Destroy server when empty');
|
||||
|
||||
Reference in New Issue
Block a user