Add Prettier and ESLint tooling (Refs #77)
This commit is contained in:
@@ -5,10 +5,24 @@ import { gameFileTransferProgress } from '../../src/lib/ui/game-file-transfer.js
|
||||
const now = Date.parse('2026-10-04T16:00:00Z');
|
||||
function cache(changes: Partial<HostLibraryCache> = {}): HostLibraryCache {
|
||||
return {
|
||||
hostId: 'host-1', libraryVersionId: 't5-upload', status: 'downloading', bytesDownloaded: 200, bytesVerified: 0, bytesTotal: 1000,
|
||||
manifestSha256: null, operationId: 'operation-1', error: null, updatedAt: new Date(now).toISOString(),
|
||||
transfer: { phase: 'downloading', startedAt: new Date(now - 10_000).toISOString(), elapsedSeconds: 10,
|
||||
phaseElapsedSeconds: 10, bytesPerSecond: 20, etaSeconds: 40 },
|
||||
hostId: 'host-1',
|
||||
libraryVersionId: 't5-upload',
|
||||
status: 'downloading',
|
||||
bytesDownloaded: 200,
|
||||
bytesVerified: 0,
|
||||
bytesTotal: 1000,
|
||||
manifestSha256: null,
|
||||
operationId: 'operation-1',
|
||||
error: null,
|
||||
updatedAt: new Date(now).toISOString(),
|
||||
transfer: {
|
||||
phase: 'downloading',
|
||||
startedAt: new Date(now - 10_000).toISOString(),
|
||||
elapsedSeconds: 10,
|
||||
phaseElapsedSeconds: 10,
|
||||
bytesPerSecond: 20,
|
||||
etaSeconds: 40
|
||||
},
|
||||
...changes
|
||||
};
|
||||
}
|
||||
@@ -16,59 +30,137 @@ function cache(changes: Partial<HostLibraryCache> = {}): HostLibraryCache {
|
||||
describe('game file transfer presentation', () => {
|
||||
it('ticks elapsed time without inventing downloaded bytes or counting down an old estimate', () => {
|
||||
expect(gameFileTransferProgress(cache(), true, now + 5000)).toMatchObject({
|
||||
phase: 'downloading', phaseBytes: 200, downloaded: 200, total: 1000,
|
||||
elapsedSeconds: 15, bytesPerSecond: 20, etaSeconds: 40, live: true
|
||||
phase: 'downloading',
|
||||
phaseBytes: 200,
|
||||
downloaded: 200,
|
||||
total: 1000,
|
||||
elapsedSeconds: 15,
|
||||
bytesPerSecond: 20,
|
||||
etaSeconds: 40,
|
||||
live: true
|
||||
});
|
||||
});
|
||||
|
||||
it('switches progress and the estimate to verification without resetting total elapsed', () => {
|
||||
const verifying = cache({ status: 'verifying', bytesDownloaded: 1000, bytesVerified: 100,
|
||||
transfer: { phase: 'verifying', startedAt: new Date(now - 60_000).toISOString(), elapsedSeconds: 60,
|
||||
phaseElapsedSeconds: 2, bytesPerSecond: 50, etaSeconds: 18 } });
|
||||
const verifying = cache({
|
||||
status: 'verifying',
|
||||
bytesDownloaded: 1000,
|
||||
bytesVerified: 100,
|
||||
transfer: {
|
||||
phase: 'verifying',
|
||||
startedAt: new Date(now - 60_000).toISOString(),
|
||||
elapsedSeconds: 60,
|
||||
phaseElapsedSeconds: 2,
|
||||
bytesPerSecond: 50,
|
||||
etaSeconds: 18
|
||||
}
|
||||
});
|
||||
expect(gameFileTransferProgress(verifying, true, now + 1000)).toMatchObject({
|
||||
phase: 'verifying', phaseBytes: 100, downloaded: 1000, elapsedSeconds: 61, bytesPerSecond: 50, etaSeconds: 18
|
||||
phase: 'verifying',
|
||||
phaseBytes: 100,
|
||||
downloaded: 1000,
|
||||
elapsedSeconds: 61,
|
||||
bytesPerSecond: 50,
|
||||
etaSeconds: 18
|
||||
});
|
||||
});
|
||||
|
||||
it('freezes at the reported duration and hides estimates while offline', () => {
|
||||
expect(gameFileTransferProgress(cache(), false, now + 12_000)).toMatchObject({ elapsedSeconds: 10, etaSeconds: null, bytesPerSecond: null, live: false });
|
||||
});
|
||||
|
||||
it('stops the clock and removes the estimate when reports become stale', () => {
|
||||
expect(gameFileTransferProgress(cache(), true, now + 30_001)).toMatchObject({ elapsedSeconds: 40, etaSeconds: null, bytesPerSecond: null, stale: true });
|
||||
expect(gameFileTransferProgress(cache(), true, now + 600_000).elapsedSeconds).toBe(40);
|
||||
});
|
||||
|
||||
it.each(['ready', 'outdated', 'failed'] as const)('keeps a final duration for %s copies', status => {
|
||||
expect(gameFileTransferProgress(cache({ status }), true, now + 600_000)).toMatchObject({
|
||||
elapsedSeconds: 10, bytesPerSecond: null, etaSeconds: null, active: false, stale: false
|
||||
expect(gameFileTransferProgress(cache(), false, now + 12_000)).toMatchObject({
|
||||
elapsedSeconds: 10,
|
||||
etaSeconds: null,
|
||||
bytesPerSecond: null,
|
||||
live: false
|
||||
});
|
||||
});
|
||||
|
||||
it('does not invent a timer for queued downloads or old agents', () => {
|
||||
expect(gameFileTransferProgress(cache({ transfer: undefined }), true, now + 60_000)).toMatchObject({ elapsedSeconds: null, etaSeconds: null, phaseBytes: 200 });
|
||||
expect(gameFileTransferProgress(cache({ status: 'verifying', transfer: undefined, bytesVerified: undefined, bytesDownloaded: 1000 }), true, now))
|
||||
.toMatchObject({ elapsedSeconds: null, etaSeconds: null, phase: 'verifying', phaseBytes: null });
|
||||
it('stops the clock and removes the estimate when reports become stale', () => {
|
||||
expect(gameFileTransferProgress(cache(), true, now + 30_001)).toMatchObject({
|
||||
elapsedSeconds: 40,
|
||||
etaSeconds: null,
|
||||
bytesPerSecond: null,
|
||||
stale: true
|
||||
});
|
||||
expect(gameFileTransferProgress(cache(), true, now + 600_000).elapsedSeconds).toBe(40);
|
||||
});
|
||||
|
||||
it.each([0, null, Number.NaN, Number.POSITIVE_INFINITY, -1])('hides estimates for an unavailable transfer rate (%s)', bytesPerSecond => {
|
||||
const current = cache();
|
||||
current.transfer!.bytesPerSecond = bytesPerSecond;
|
||||
expect(gameFileTransferProgress(current, true, now)).toMatchObject({ etaSeconds: null, bytesPerSecond: null });
|
||||
it.each(['ready', 'outdated', 'failed'] as const)(
|
||||
'keeps a final duration for %s copies',
|
||||
(status) => {
|
||||
expect(gameFileTransferProgress(cache({ status }), true, now + 600_000)).toMatchObject({
|
||||
elapsedSeconds: 10,
|
||||
bytesPerSecond: null,
|
||||
etaSeconds: null,
|
||||
active: false,
|
||||
stale: false
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('does not invent a timer for queued downloads or old agents', () => {
|
||||
expect(
|
||||
gameFileTransferProgress(cache({ transfer: undefined }), true, now + 60_000)
|
||||
).toMatchObject({ elapsedSeconds: null, etaSeconds: null, phaseBytes: 200 });
|
||||
expect(
|
||||
gameFileTransferProgress(
|
||||
cache({
|
||||
status: 'verifying',
|
||||
transfer: undefined,
|
||||
bytesVerified: undefined,
|
||||
bytesDownloaded: 1000
|
||||
}),
|
||||
true,
|
||||
now
|
||||
)
|
||||
).toMatchObject({
|
||||
elapsedSeconds: null,
|
||||
etaSeconds: null,
|
||||
phase: 'verifying',
|
||||
phaseBytes: null
|
||||
});
|
||||
});
|
||||
|
||||
it.each([0, null, Number.NaN, Number.POSITIVE_INFINITY, -1])(
|
||||
'hides estimates for an unavailable transfer rate (%s)',
|
||||
(bytesPerSecond) => {
|
||||
const current = cache();
|
||||
current.transfer!.bytesPerSecond = bytesPerSecond;
|
||||
expect(gameFileTransferProgress(current, true, now)).toMatchObject({
|
||||
etaSeconds: null,
|
||||
bytesPerSecond: null
|
||||
});
|
||||
}
|
||||
);
|
||||
|
||||
it('does not show a misleading estimate before any bytes arrive or after all bytes arrive', () => {
|
||||
for (const bytesDownloaded of [0, 1000]) {
|
||||
expect(gameFileTransferProgress(cache({ bytesDownloaded }), true, now)).toMatchObject({ etaSeconds: null, bytesPerSecond: null });
|
||||
expect(gameFileTransferProgress(cache({ bytesDownloaded }), true, now)).toMatchObject({
|
||||
etaSeconds: null,
|
||||
bytesPerSecond: null
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
it('does not reuse a download estimate after verification starts', () => {
|
||||
expect(gameFileTransferProgress(cache({ status: 'verifying', bytesDownloaded: 1000, bytesVerified: 50 }), true, now + 5000))
|
||||
.toMatchObject({ phase: 'verifying', phaseBytes: 50, etaSeconds: null, elapsedSeconds: 10 });
|
||||
expect(
|
||||
gameFileTransferProgress(
|
||||
cache({ status: 'verifying', bytesDownloaded: 1000, bytesVerified: 50 }),
|
||||
true,
|
||||
now + 5000
|
||||
)
|
||||
).toMatchObject({
|
||||
phase: 'verifying',
|
||||
phaseBytes: 50,
|
||||
etaSeconds: null,
|
||||
elapsedSeconds: 10
|
||||
});
|
||||
});
|
||||
|
||||
it('handles an invalid report timestamp without advancing the timer or displaying estimates', () => {
|
||||
expect(gameFileTransferProgress(cache({ updatedAt: 'invalid' }), true, now)).toMatchObject({ elapsedSeconds: 10, etaSeconds: null, stale: true });
|
||||
expect(gameFileTransferProgress(cache({ updatedAt: 'invalid' }), true, now)).toMatchObject({
|
||||
elapsedSeconds: 10,
|
||||
etaSeconds: null,
|
||||
stale: true
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user