import { describe, it, expect } from 'vitest' import { readFileSync } from 'node:fs' import { fileURLToPath } from 'node:url' // The group vocabulary is split across four files and there is no runtime that // checks they agree. When they disagree the failure is SILENT: a panel in a // group nothing renders is fetched, held, and shown on no tab, with nothing // logged. That has eaten cards twice - tracked services moving out of // 'actions', and the 'metrics' to 'monitors' rename. // // So this reads the sources and asserts the vocabularies match. It is a source // check rather than a behaviour check because RENDERED_GROUPS is a module-local // constant in the .vue file with no export to assert against. function read(relative) { return readFileSync(fileURLToPath(new URL(relative, import.meta.url)), 'utf8') } function renderedGroups() { const source = read('./PluginAssetPanels.vue') const match = source.match(/const RENDERED_GROUPS = \[([^\]]*)\]/) if (!match) throw new Error('RENDERED_GROUPS not found in PluginAssetPanels.vue') return match[1].split(',') .map((entry) => entry.trim().replace(/^'|'$/g, '')) .filter(Boolean) } function pcDetailSource() { return read('../../../plugins/computers/frontend/views/PCDetail.vue') } describe('panel group vocabulary', () => { it('renders a group for every tab PCDetail offers', () => { const groups = renderedGroups() const source = pcDetailSource() const tabKeys = [...source.matchAll(/tabs\.push\(\{\s*key:\s*'([a-z]+)'/g)] .map((match) => match[1]) expect(tabKeys.length).toBeGreaterThan(0) for (const key of tabKeys) { expect(groups).toContain(key) } }) it('gives every rendered group somewhere to actually show', () => { // A group in RENDERED_GROUPS with no v-show block is the mirror failure: // the panel is kept rather than dropped, but still appears nowhere. const source = pcDetailSource() for (const group of renderedGroups()) { expect(source).toContain(`activegroup === '${group}'`) } }) it('keeps the service/process split wired end to end', () => { // Pins the specific split rather than only the general rule, so removing // the tab is a deliberate edit to this test and not a silent regression. const source = pcDetailSource() expect(renderedGroups()).toContain('serviceprocess') expect(source).toContain("key: 'serviceprocess'") expect(source).toContain("group=\"serviceprocess\"") }) it('still renders the system monitors group after the rename', () => { // The rename is label-only; the group key must not drift with it. expect(renderedGroups()).toContain('monitors') expect(pcDetailSource()).toContain("label: 'System Monitors'") }) })