asset_panels() rewrites any group missing from ASSET_PANEL_GROUPS to 'details' before the client sees it, and Phase 3 never added 'serviceprocess' there. Both panels were relabelled on the wire, so the tab could not appear no matter how consistent the frontend was. That is why the tab was still missing after the schema fix - a second, unrelated cause wearing the same symptom. Operator Reports moves out of System Monitors into a tab of its own: that tab is machine-collected health, this is a person reporting something, and the thumbnail grid dwarfed the fact cards it sat between. Full width, since it is alone in the tab. panelGroups.spec.js claimed to cover four files and checked two; it now reads the server tuple as well. The plugin test's hand-copied set was a fifth copy and now derives from the server tuple instead.
96 lines
3.8 KiB
JavaScript
96 lines
3.8 KiB
JavaScript
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'")
|
|
})
|
|
|
|
it('agrees with the server vocabulary that resolves groups first', () => {
|
|
// The fourth file this comment block claims to cover, and the one that was
|
|
// NOT actually being checked. asset_panels() rewrites any group missing
|
|
// from ASSET_PANEL_GROUPS to 'details' before the client sees it, so the
|
|
// frontend can be perfectly consistent with itself and still show nothing:
|
|
// that is exactly how the Service/Process tab went missing after Phase 3.
|
|
const source = read('../../../shopdb/core/api/pluginui.py')
|
|
const match = source.match(/ASSET_PANEL_GROUPS = \(([\s\S]*?)\)/)
|
|
if (!match) throw new Error('ASSET_PANEL_GROUPS not found in pluginui.py')
|
|
const serverGroups = match[1].split(',')
|
|
.map((entry) => entry.trim().replace(/^'|'$/g, ''))
|
|
.filter(Boolean)
|
|
|
|
for (const group of renderedGroups()) {
|
|
expect(serverGroups).toContain(group)
|
|
}
|
|
})
|
|
|
|
it('keeps operator reports in a tab of its own', () => {
|
|
const source = pcDetailSource()
|
|
|
|
expect(renderedGroups()).toContain('reports')
|
|
expect(source).toContain("key: 'reports'")
|
|
expect(source).toContain('group="reports"')
|
|
})
|
|
})
|