Files
shopdb-flask/frontend/src/components/tabSectionsTable.spec.js
benjamin.baker e00bb44d6b merge storage and disk health into one card with two sections
Volume capacity and physical drive health were two adjacent cards, and the operator reading a full disk is usually the operator reading a dying disk in the same minute. They are now one Storage card with two sections. Visual grouping only - the two tables share no key, storage being per volume and health per physical device, and one drive can back several volumes or none the operator sees. Nothing is reconciled across the sections; this is not the mapped-drive merge where two sources described the same drive and the worse status won. The tabs render type was the only sections-in-one-card mechanism and emitted label/value fields with no table path, so it now takes table sections too. That is the renderer every plugin uses, so both behaviour changes in it are mutation-tested. Two things only surfaced on contact: the empty-section filter had to grow a table branch, because a table section declares columns up front and dropping it when it has no rows takes its tab with it and reads as no disks rather than nothing reported; and full-width span was keyed off the declared render type, but a tabs panel holding a six-column table needs the same span and that is only knowable from fetched data. The diskhealth route stays. It no longer backs a panel, but it is a stable read for callers outside the renderer and removing it for a UI-only change would break them.
2026-09-05 07:05:30 -04:00

100 lines
3.7 KiB
JavaScript

import { describe, it, expect } from 'vitest'
import { tabSections, panelVisible, spansFullWidth } from './pluginAssetPanels'
// The tabs render type carried label/value sections only. It now also carries
// table sections, so that two tables which belong together for the reader but
// share no key can sit in one card. This is the shared renderer every plugin
// uses, so the label/value path is pinned here alongside the new one.
function tabsPanel(sections) {
return { id: 'p', render: 'tabs', _data: { sections } }
}
const FIELD_SECTION = { label: 'Fields', fields: [{ label: 'A', value: '1' }] }
const TABLE_SECTION = {
label: 'Volumes',
columns: [{ key: 'drive', label: 'Drive' }],
rows: [{ drive: 'C:' }],
empty: 'No disks reported.',
}
describe('tabSections with table sections', () => {
it('keeps rendering label/value sections as before', () => {
const sections = tabSections(tabsPanel([FIELD_SECTION]))
expect(sections).toHaveLength(1)
expect(sections[0].fields).toEqual([{ label: 'A', value: '1', mono: false }])
expect(sections[0].columns).toBeUndefined()
})
it('carries columns and rows through for a table section', () => {
const sections = tabSections(tabsPanel([TABLE_SECTION]))
expect(sections[0].columns).toEqual([{ key: 'drive', label: 'Drive' }])
expect(sections[0].rows).toEqual([{ drive: 'C:' }])
})
it('lets one panel mix a table section and a field section', () => {
const sections = tabSections(tabsPanel([TABLE_SECTION, FIELD_SECTION]))
expect(sections.map((s) => s.label)).toEqual(['Volumes', 'Fields'])
})
// The reason the empty-section filter needed a table branch at all. A card
// whose second section is dropped loses that tab, and "no disks" is a very
// different claim from "the collector reported nothing".
it('keeps an empty table section so its tab and message survive', () => {
const sections = tabSections(tabsPanel([{ ...TABLE_SECTION, rows: [] }]))
expect(sections).toHaveLength(1)
expect(sections[0].empty).toBe('No disks reported.')
})
it('still drops a label/value section with no fields', () => {
const sections = tabSections(tabsPanel([{ label: 'Empty', fields: [] }]))
expect(sections).toHaveLength(0)
})
it('derives a column label from the key when none is given', () => {
const sections = tabSections(tabsPanel([
{ label: 'V', columns: [{ key: 'drive' }], rows: [] },
]))
expect(sections[0].columns[0].label).toBe('Drive')
})
it('treats a missing rows array as no rows rather than throwing', () => {
const sections = tabSections(tabsPanel([{ label: 'V', columns: [{ key: 'drive' }] }]))
expect(sections[0].rows).toEqual([])
})
})
describe('panelVisible for a tabs panel of tables', () => {
it('shows a panel whose only section is an empty table', () => {
expect(panelVisible(tabsPanel([{ ...TABLE_SECTION, rows: [] }]))).toBe(true)
})
it('still hides a tabs panel with no sections at all', () => {
expect(panelVisible(tabsPanel([]))).toBe(false)
})
})
describe('spansFullWidth', () => {
// A six-column table squeezed into one half of a two-column grid is the
// failure this prevents.
it('spans a tabs panel that contains a table section', () => {
expect(spansFullWidth(tabsPanel([TABLE_SECTION]))).toBe(true)
})
it('does not span a tabs panel of label/value sections', () => {
expect(spansFullWidth(tabsPanel([FIELD_SECTION]))).toBe(false)
})
it('still spans a plain table panel', () => {
expect(spansFullWidth({ render: 'table' })).toBe(true)
})
it('does not span a keyvalue panel', () => {
expect(spansFullWidth({ render: 'keyvalue' })).toBe(false)
})
it('tolerates a null panel', () => {
expect(spansFullWidth(null)).toBe(false)
})
})