A panel with many fields was unusable. The detail page is a two-column
multicol (.content-column is display:contents, so the left/right wrappers are
dissolved and cards flow into one balanced flow), and a card cannot be split
across a column break. One 30-row panel therefore became a single unbreakable
block that dragged its column far past the other.
The tabs mode renders sections one at a time, so such a panel stays the height
of its largest section. Payload is {sections: [{label, fields}]}; sections that
end up with no fields are dropped rather than left as empty tabs. The active
tab is marked by an underline AND weight, not colour alone.
keyvalue rows now sit in two aligned columns instead of space-between: a ragged
right edge is hard to scan, and one long value (a UNC path) otherwise pushed
its label away from every other row.
Four specs cover the new mode, including that it drives panel visibility.
133 lines
4.8 KiB
JavaScript
133 lines
4.8 KiB
JavaScript
import { describe, it, expect } from 'vitest'
|
|
import {
|
|
toApiPath, subst, rows, mapTitle, mapBadge, mapMeta, manageLink,
|
|
keyvalueFields, tableColumns, badges, panelVisible, tabSections,
|
|
} from './pluginAssetPanels'
|
|
|
|
const warrantyPanel = {
|
|
id: 'warranty', title: 'Warranty', render: 'list',
|
|
map: {
|
|
title: 'vendor',
|
|
badge: {
|
|
label: 'status', color: 'statuscolor',
|
|
labelmap: { active: 'Active', expired: 'Expired' },
|
|
},
|
|
meta: [
|
|
{ key: 'servicelevel' },
|
|
{ key: 'enddate', label: 'Ends', format: 'date' },
|
|
{ key: 'servicetag', label: 'Tag', mono: true },
|
|
],
|
|
},
|
|
empty: 'No warranty on record.',
|
|
manage: { to: '/warranties?addfor={assetid}', label: 'Add / manage' },
|
|
_data: [
|
|
{
|
|
vendor: 'Dell', status: 'active', statuscolor: '#4CAF50',
|
|
servicelevel: 'ProSupport', enddate: '2027-01-01', servicetag: 'ABC123',
|
|
},
|
|
],
|
|
}
|
|
|
|
describe('endpoint + subst', () => {
|
|
it('substitutes {assetid} and strips the leading /api', () => {
|
|
expect(toApiPath('/api/warranty/asset/{assetid}', 5)).toBe('/warranty/asset/5')
|
|
expect(subst('/warranties?addfor={assetid}', 9)).toBe('/warranties?addfor=9')
|
|
})
|
|
it('only strips a leading /api segment, not /apixyz', () => {
|
|
expect(toApiPath('/apixyz/thing', 1)).toBe('/apixyz/thing')
|
|
})
|
|
})
|
|
|
|
describe('list render mapping (warranty)', () => {
|
|
it('maps title, badge (labelmap + color), and formatted meta', () => {
|
|
const item = rows(warrantyPanel)[0]
|
|
expect(mapTitle(warrantyPanel, item)).toBe('Dell')
|
|
|
|
const badge = mapBadge(warrantyPanel, item)
|
|
expect(badge.label).toBe('Active')
|
|
expect(badge.color).toBe('#4CAF50')
|
|
|
|
const meta = mapMeta(warrantyPanel, item)
|
|
expect(meta[0].text).toBe('ProSupport')
|
|
expect(meta[1].text).toMatch(/^Ends /) // date formatted + labelled
|
|
expect(meta[2]).toEqual({ text: 'Tag ABC123', mono: true })
|
|
})
|
|
|
|
it('drops empty meta values', () => {
|
|
const item = { vendor: 'HP', status: 'expired', statuscolor: '#F44336' }
|
|
const meta = mapMeta(warrantyPanel, { ...item })
|
|
expect(meta).toEqual([]) // no servicelevel/enddate/servicetag
|
|
expect(mapBadge(warrantyPanel, item).label).toBe('Expired')
|
|
})
|
|
|
|
it('builds the manage link with the assetid', () => {
|
|
expect(manageLink(warrantyPanel, 7)).toBe('/warranties?addfor=7')
|
|
})
|
|
|
|
it('is visible when it has rows or an empty message', () => {
|
|
expect(panelVisible(warrantyPanel)).toBe(true)
|
|
expect(panelVisible({ ...warrantyPanel, _data: [] })).toBe(true) // has empty
|
|
expect(panelVisible({ render: 'list', _data: [] })).toBe(false)
|
|
})
|
|
})
|
|
|
|
describe('keyvalue + table + badge', () => {
|
|
it('keyvalue reads a fields array or a plain object', () => {
|
|
const fromFields = keyvalueFields({
|
|
render: 'keyvalue', _data: { fields: [{ label: 'Cal due', value: '2026-01-01', format: 'date' }] },
|
|
})
|
|
expect(fromFields[0].label).toBe('Cal due')
|
|
|
|
const fromObject = keyvalueFields({ render: 'keyvalue', _data: { hostname: 'PC1', online: true } })
|
|
expect(fromObject).toEqual([
|
|
{ label: 'Hostname', value: 'PC1' },
|
|
{ label: 'Online', value: 'Yes' },
|
|
])
|
|
})
|
|
|
|
it('table uses declared columns, else infers from row keys', () => {
|
|
const declared = tableColumns({ columns: [{ key: 'a', label: 'Alpha' }], _data: [{ a: 1 }] })
|
|
expect(declared).toEqual([{ key: 'a', label: 'Alpha' }])
|
|
|
|
const inferred = tableColumns({ _data: [{ vendor: 'x', model: 'y' }] })
|
|
expect(inferred.map((c) => c.label)).toEqual(['Vendor', 'Model'])
|
|
})
|
|
|
|
it('badge reads an array or a badges field', () => {
|
|
expect(badges({ render: 'badge', _data: [{ label: 'A', color: '#111' }] })).toEqual([{ label: 'A', color: '#111' }])
|
|
expect(badges({ render: 'badge', _data: { badges: [{ label: 'B', color: '#222' }] } })).toEqual([{ label: 'B', color: '#222' }])
|
|
})
|
|
})
|
|
|
|
describe('tabSections', () => {
|
|
const panel = {
|
|
render: 'tabs',
|
|
_data: {
|
|
sections: [
|
|
{ label: 'General', fields: [{ label: 'Cnc', value: 'OKUMA' }] },
|
|
{ label: 'eFocas', fields: [{ label: 'IpAddr', value: '192.168.1.1', mono: true }] },
|
|
// A section whose fields all dropped out must not leave an empty tab.
|
|
{ label: 'NTSHR', fields: [] },
|
|
],
|
|
},
|
|
}
|
|
|
|
it('returns one entry per non-empty section', () => {
|
|
expect(tabSections(panel).map((s) => s.label)).toEqual(['General', 'eFocas'])
|
|
})
|
|
|
|
it('formats field values and carries mono through', () => {
|
|
const efocas = tabSections(panel)[1]
|
|
expect(efocas.fields[0]).toEqual({ label: 'IpAddr', value: '192.168.1.1', mono: true })
|
|
})
|
|
|
|
it('is empty for a payload with no sections', () => {
|
|
expect(tabSections({ render: 'tabs', _data: null })).toEqual([])
|
|
})
|
|
|
|
it('drives panel visibility', () => {
|
|
expect(panelVisible(panel)).toBe(true)
|
|
expect(panelVisible({ render: 'tabs', _data: { sections: [] } })).toBe(false)
|
|
})
|
|
})
|