tests/monitor-platform-toggle.test.cjs
Rama main · réplica de lectura del repositorio original.
const fs = require('fs');
const path = require('path');
const read = (...p) => fs.readFileSync(path.join(__dirname, '..', ...p), 'utf8');
const assert = (cond, msg) => { if (!cond) throw new Error(msg); };
// ---- Frontend API contract (client.ts) ----
const client = read('frontend', 'src', 'api', 'client.ts');
assert(
client.includes('`/platforms/${platformId}/monitored`'),
'client.ts setPlatformMonitored must PUT /platforms/{id}/monitored'
);
assert(
client.includes("apiClient.get<Record<string, PlatformMonitorCount>>('/platforms/monitored-counts')"),
'client.ts getPlatformMonitoredCounts must GET /platforms/monitored-counts'
);
assert(
/export interface PlatformMonitorCount\s*\{[\s\S]*monitorDefault: boolean \| null;/.test(client),
'client.ts must export PlatformMonitorCount with monitorDefault'
);
assert(
/export interface GameListDto\s*\{[\s\S]*monitored: boolean;[\s\S]*\}/.test(client),
'GameListDto type must carry monitored'
);
// ---- Per-game marker on the card (GameCard.tsx) ----
const card = read('frontend', 'src', 'components', 'GameCard.tsx');
assert(card.includes('faBookmark'), 'GameCard must import a bookmark icon');
assert(card.includes('game-card-monitor-btn'), 'GameCard must render the monitor marker button');
assert(
card.includes('await monitorApi.setMonitored(game.id, next)'),
'GameCard marker must toggle via monitorApi.setMonitored'
);
assert(
card.includes('e.stopPropagation()'),
'GameCard marker click must not bubble to card navigation'
);
assert(card.includes('onMonitoredChange'), 'GameCard must expose onMonitoredChange');
// ---- Platform toggle: Library filter bar ----
const library = read('frontend', 'src', 'pages', 'Library.tsx');
assert(
library.includes('handlePlatformMonitorToggle(selectedPlatformData.id)'),
'Library platform action bar must call handlePlatformMonitorToggle'
);
assert(
library.includes('monitorApi.setPlatformMonitored('),
'Library must invoke setPlatformMonitored'
);
assert(
library.includes('onMonitoredChange={handleCardMonitoredChange}'),
'Library grid card must wire onMonitoredChange'
);
// ---- Platform toggle: Platforms shelf header ----
const platforms = read('frontend', 'src', 'pages', 'Platforms.tsx');
assert(
platforms.includes('shelf__monitor'),
'Platforms shelf header must render the platform monitor toggle'
);
assert(
platforms.includes('handlePlatformMonitorToggle(shelf.platform.id)'),
'Platforms shelf toggle must call handlePlatformMonitorToggle'
);
assert(
platforms.includes('monitored: dto.monitored'),
'Platforms dtoToGame must pass monitored through so the card marker shows state'
);
// ---- Per-game monitor button on the game detail page ----
const details = read('frontend', 'src', 'pages', 'GameDetails.tsx');
assert(
details.includes('monitor-toggle-btn'),
'GameDetails must render a prominent monitor toggle button'
);
assert(
details.includes('await monitorApi.setMonitored(game.id, next)'),
'GameDetails monitor button must toggle via monitorApi.setMonitored'
);
assert(
details.includes('const toggleGameMonitored'),
'GameDetails must define the toggleGameMonitored handler'
);
// The MonitorPanel checkbox must follow the prop so the header button and the
// panel stay consistent.
const panel = read('frontend', 'src', 'components', 'MonitorPanel.tsx');
assert(
/useEffect\(\(\) => \{ setMonitored\(initialMonitored\); \}, \[initialMonitored\]\);/.test(panel),
'MonitorPanel must sync internal monitored state from initialMonitored'
);
// ---- Card marker sits top-left (per user preference) ----
const cardCss = read('frontend', 'src', 'components', 'GameCard.css');
assert(
/\.game-card-monitor-btn\s*\{[^}]*top:\s*8px;[^}]*left:\s*8px;/.test(cardCss),
'Card monitor marker must be anchored top-left'
);
// ---- Backend route + field contract (static string pins; runs without dotnet) ----
const controller = read('src', 'RetroArr.Api.V3', 'Monitor', 'MonitorController.cs');
assert(
controller.includes('[HttpPut("platforms/{platformId:int}/monitored")]'),
'MonitorController must expose PUT platforms/{platformId}/monitored'
);
assert(
controller.includes('[HttpGet("platforms/monitored-counts")]'),
'MonitorController must expose GET platforms/monitored-counts'
);
assert(
controller.includes('PlatformService.SetMonitorNewItemsDefault(platformId, request.Monitored)'),
'Platform toggle must persist the per-platform default'
);
const platformService = read('src', 'RetroArr.Core', 'Games', 'PlatformService.cs');
assert(
platformService.includes('GetMonitorNewItemsDefault(int platformId, bool fallback)'),
'PlatformService must provide GetMonitorNewItemsDefault with a fallback'
);
assert(
platformService.includes('SetMonitorNewItemsDefault(int platformId, bool monitored)'),
'PlatformService must provide SetMonitorNewItemsDefault'
);
assert(
platformService.includes('platform_monitoring.json'),
'PlatformService must persist the monitoring default to platform_monitoring.json'
);
const scanner = read('src', 'RetroArr.Core', 'Games', 'MediaScannerService.cs');
assert(
scanner.includes('finalGame.Monitored = PlatformService.GetMonitorNewItemsDefault(finalGame.PlatformId, false)'),
'New scanned games must inherit the platform monitoring default'
);
const dto = read('src', 'RetroArr.Core', 'Games', 'GameListDto.cs');
assert(dto.includes('public bool Monitored { get; set; }'), 'GameListDto must carry Monitored');
const repo = read('src', 'RetroArr.Core', 'Games', 'SqliteGameRepository.cs');
assert(repo.includes('Monitored = g.Monitored'), 'Paged projection must include Monitored');
console.log('monitor-platform-toggle: all contract checks passed');