mirror of
https://github.com/koala73/worldmonitor.git
synced 2026-04-25 17:14:57 +02:00
fix(19): address koala P2 review — sync enforcement test, fail on unassigned panels
P2.3: Add tests/chunk-assignment.test.mjs — cross-checks *_PANEL_FILES
sets against src/components/*.ts at test time. Catches drift, duplicates,
ghost entries, and verifies the silent catch-all is gone.
P2.4: Replace `endsWith('Panel')` silent catch-all with a build error.
New panels must be explicitly assigned to a chunk group or the build fails.
Also: assign 11 previously-unassigned panels to correct chunk sets,
remove 2 ghost entries (CommoditiesPanel, HeatmapPanel are sub-exports
of MarketPanel.ts, not standalone files).
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
77
tests/chunk-assignment.test.mjs
Normal file
77
tests/chunk-assignment.test.mjs
Normal file
@@ -0,0 +1,77 @@
|
||||
import { describe, it } from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync, readdirSync } from 'node:fs';
|
||||
import { dirname, resolve } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url));
|
||||
const viteConfigSource = readFileSync(resolve(__dirname, '../vite.config.ts'), 'utf-8');
|
||||
|
||||
/**
|
||||
* Extract all quoted string entries from a `new Set([...])` block.
|
||||
*/
|
||||
function extractSetEntries(varName) {
|
||||
const re = new RegExp(`const ${varName}\\s*=\\s*new Set\\(\\[([\\s\\S]*?)\\]\\)`, 'm');
|
||||
const match = viteConfigSource.match(re);
|
||||
if (!match) return new Set();
|
||||
// Match only quoted strings — ignores comments and whitespace
|
||||
const entries = match[1].match(/'([^']+)'/g) || [];
|
||||
return new Set(entries.map(s => s.replace(/^'|'$/g, '')));
|
||||
}
|
||||
|
||||
const CORE = extractSetEntries('CORE_PANEL_FILES');
|
||||
const HAPPY = extractSetEntries('HAPPY_PANEL_FILES');
|
||||
const FINANCE = extractSetEntries('FINANCE_PANEL_FILES');
|
||||
const FULL = extractSetEntries('FULL_PANEL_FILES');
|
||||
const TECH = extractSetEntries('TECH_PANEL_FILES');
|
||||
const ALL_ASSIGNED = new Set([...CORE, ...HAPPY, ...FINANCE, ...FULL, ...TECH]);
|
||||
|
||||
const componentFiles = readdirSync(resolve(__dirname, '../src/components'))
|
||||
.filter(f => f.endsWith('.ts') && !f.includes('.test.'))
|
||||
.map(f => f.replace(/\.ts$/, ''));
|
||||
|
||||
const panelFiles = componentFiles.filter(f => f.endsWith('Panel') || f === 'Panel');
|
||||
|
||||
describe('chunk assignment sync enforcement', () => {
|
||||
it('every *Panel.ts component is assigned to exactly one chunk set', () => {
|
||||
const unassigned = panelFiles.filter(f => !ALL_ASSIGNED.has(f));
|
||||
assert.deepEqual(
|
||||
unassigned,
|
||||
[],
|
||||
`Unassigned panel files (add to a *_PANEL_FILES set in vite.config.ts): ${unassigned.join(', ')}`
|
||||
);
|
||||
});
|
||||
|
||||
it('no panel appears in multiple chunk sets', () => {
|
||||
const sets = { CORE, HAPPY, FINANCE, FULL, TECH };
|
||||
const dupes = [];
|
||||
for (const file of ALL_ASSIGNED) {
|
||||
const inSets = Object.entries(sets)
|
||||
.filter(([, s]) => s.has(file))
|
||||
.map(([name]) => name);
|
||||
if (inSets.length > 1) {
|
||||
dupes.push(`${file} → ${inSets.join(', ')}`);
|
||||
}
|
||||
}
|
||||
assert.deepEqual(dupes, [], `Panels in multiple chunk sets:\n${dupes.join('\n')}`);
|
||||
});
|
||||
|
||||
it('chunk sets do not reference non-existent component files', () => {
|
||||
const fileSet = new Set(componentFiles);
|
||||
const ghosts = [...ALL_ASSIGNED].filter(f => !fileSet.has(f));
|
||||
assert.deepEqual(
|
||||
ghosts,
|
||||
[],
|
||||
`Chunk sets reference files that don't exist in src/components/: ${ghosts.join(', ')}`
|
||||
);
|
||||
});
|
||||
|
||||
it('vite.config.ts has no silent Panel catch-all fallback', () => {
|
||||
assert.doesNotMatch(
|
||||
viteConfigSource,
|
||||
/if\s*\(fileName\.endsWith\('Panel'\).*\)\s*return\s*'core-panels'/,
|
||||
'The silent catch-all that routes unassigned panels to core-panels must not exist. ' +
|
||||
'Use the throw-on-unassigned pattern instead.'
|
||||
);
|
||||
});
|
||||
});
|
||||
@@ -27,10 +27,14 @@ const CORE_PANEL_FILES = new Set([
|
||||
'EconomicPanel', 'MacroSignalsPanel', 'ETFFlowsPanel', 'StablecoinPanel',
|
||||
'WorldClockPanel', 'AirlineIntelPanel',
|
||||
// Shared across full + finance + commodity (3 variants)
|
||||
'EnergyComplexPanel', 'OilInventoriesPanel', 'CommoditiesPanel',
|
||||
'HeatmapPanel', 'TradePolicyPanel', 'SupplyChainPanel',
|
||||
// Note: CommoditiesPanel and HeatmapPanel are sub-exports of MarketPanel.ts, not standalone files
|
||||
'EnergyComplexPanel', 'OilInventoriesPanel',
|
||||
'TradePolicyPanel', 'SupplyChainPanel',
|
||||
'SanctionsPressurePanel', 'GulfEconomiesPanel', 'ConsumerPricesPanel',
|
||||
'LiquidityShiftsPanel', 'GoldIntelligencePanel',
|
||||
// Programmatic / multi-variant panels (used dynamically or across 2+ variants)
|
||||
'CustomWidgetPanel', 'McpDataPanel', 'CountryBriefPanel',
|
||||
'CountryDeepDivePanel', 'TechHubsPanel', 'RegulationPanel',
|
||||
]);
|
||||
|
||||
const HAPPY_PANEL_FILES = new Set([
|
||||
@@ -60,6 +64,9 @@ const FULL_PANEL_FILES = new Set([
|
||||
'MarketImplicationsPanel', 'SatelliteFiresPanel', 'HormuzPanel',
|
||||
'UcdpEventsPanel', 'ClimateNewsPanel', 'DiseaseOutbreaksPanel',
|
||||
'SocialVelocityPanel', 'EnergyCrisisPanel',
|
||||
// Full-only panels
|
||||
'ForecastPanel', 'ChatAnalystPanel', 'CrossSourceSignalsPanel',
|
||||
'DeductionPanel', 'GeoHubsPanel',
|
||||
]);
|
||||
|
||||
const TECH_PANEL_FILES = new Set([
|
||||
@@ -907,8 +914,14 @@ export default defineConfig(({ mode }) => {
|
||||
if (FINANCE_PANEL_FILES.has(fileName)) return 'finance-panels';
|
||||
if (FULL_PANEL_FILES.has(fileName)) return 'full-panels';
|
||||
if (TECH_PANEL_FILES.has(fileName)) return 'tech-panels';
|
||||
// Unassigned Panel files default to core-panels to avoid orphan chunks
|
||||
if (fileName.endsWith('Panel') || fileName === 'Panel') return 'core-panels';
|
||||
// Fail on unassigned panel files — add new panels to the correct set above
|
||||
if (fileName.endsWith('Panel') || fileName === 'Panel') {
|
||||
throw new Error(
|
||||
`[manualChunks] Unassigned panel component: ${fileName}. ` +
|
||||
`Add it to CORE_PANEL_FILES, FULL_PANEL_FILES, HAPPY_PANEL_FILES, ` +
|
||||
`FINANCE_PANEL_FILES, or TECH_PANEL_FILES in vite.config.ts.`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
// Give lazy-loaded locale chunks a recognizable prefix so the
|
||||
|
||||
Reference in New Issue
Block a user