Cover infrastructure operations, thresholds and chart API branch arms

Four more frontend modules with a measured residue their existing specs never
reached, including seven functions no test called at all. Deltas are uncovered
counts, module scoped, existing specs versus existing plus new.

- infrastructureOperationsModel: branches 38 -> 1 uncovered and
  createSurfaceScopedRow, previously never called, is now covered. This was
  the largest proportional branch gap left in the frontend.
- summaryTableFocus: branches 26 -> 11 uncovered and scheduleViewportRefresh,
  previously never called, is now covered.
- thresholdsResourceModel: hasThresholdDiff, dockerContainerOverrideIdCandidates,
  findOverrideByCandidates and buildAgentHeaderMeta were all uncalled; covered
  branches move 114 -> 130.
- api/charts: getStorageSummaryCharts and timeRangeToMinutes were uncalled;
  covered branches move 41 -> 55. The fetch mocking follows the existing specs
  and asserts parsed results, not that the mock was called.

No source file is modified. Adversarial review found no rejects; one subtest
duplicating an arm a sibling already covered was removed before committing and
the coverage was re-measured unchanged.

A fifth candidate, resourceBadgePresentation, was written and discarded rather
than landed: 263 tests moved the module by a single branch, because its
residual arms are unreachable defensive fallbacks.

PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no source or contract change

Contract-Neutral: test-only branch coverage, no source or contract change
This commit is contained in:
rcourtman
2026-07-23 18:24:21 +01:00
parent e8740159cc
commit 1d96b918c2
4 changed files with 1587 additions and 0 deletions
@@ -0,0 +1,241 @@
/**
* Branch-coverage tests for the two currently-uncalled ChartsAPI surface points:
* - ChartsAPI.getStorageSummaryCharts (function never invoked by existing specs)
* - timeRangeToMinutes (module-private helper, never invoked;
* not exported, so exercised solely
* through getStorageSummaryCharts by
* asserting the resulting ?range=<minutes>
* query value for every switch arm)
*
* Mock harness matches charts.branchcov0718.test.ts / chartsApi.test.ts:
* vi.mock('@/utils/apiClient', () => ({ apiFetchJSON: vi.fn() }))
* No real network call is made. Nothing already covered by charts.test.ts,
* chartsApi.test.ts or charts.branchcov0718.test.ts is re-asserted here.
*
* Branches exercised:
* getStorageSummaryCharts:
* - range_ default ('1h') when called with no args
* - signal present vs undefined (always wrapped in { signal } either way)
* - options undefined entirely -> `node=` omitted
* - options.nodeId undefined -> `node=` omitted
* - options.nodeId '' -> `node=` omitted (falsy branch)
* - options.nodeId truthy string -> `node=<id>` appended
* - special chars in nodeId URL-encoded by URLSearchParams.toString
* - combined range + node + signal in a single request
* - response with EMPTY pools/disks returned verbatim (empty arm)
* - fully-populated response returned verbatim
* - rejection from apiFetchJSON propagates verbatim (error arm)
* timeRangeToMinutes (observed via the ?range=<minutes> value):
* - every recognised token: 5m/15m/30m/1h/4h/12h/24h/7d/30d
* - default fallback for an UNRECOGNISED token -> 60
*/
import { beforeEach, describe, expect, it, vi } from 'vitest';
vi.mock('@/utils/apiClient', () => ({
apiFetchJSON: vi.fn(),
}));
import { ChartsAPI, type StorageSummaryChartsResponse, type TimeRange } from '@/api/charts';
import { apiFetchJSON } from '@/utils/apiClient';
describe('ChartsAPI.getStorageSummaryCharts — branch coverage', () => {
const apiFetchJSONMock = vi.mocked(apiFetchJSON);
beforeEach(() => {
apiFetchJSONMock.mockReset();
});
it('routes to /api/storage-charts with range=60 (default 1h) and signal undefined when called with no args', async () => {
// Exercises BOTH the default `range_ = '1h'` parameter AND the default
// `signal = undefined` / `options = undefined` branches together.
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts();
expect(apiFetchJSONMock).toHaveBeenCalledTimes(1);
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
it('forwards an AbortSignal through to apiFetchJSON wrapped in { signal }', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
const controller = new AbortController();
await ChartsAPI.getStorageSummaryCharts('1h', controller.signal);
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: controller.signal,
});
});
it('omits the node query param when options is undefined entirely', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts('1h', undefined, undefined);
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
it('omits the node query param when options.nodeId is undefined', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts('1h', undefined, { nodeId: undefined });
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
it('omits the node query param when options.nodeId is an empty string (falsy branch)', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts('1h', undefined, { nodeId: '' });
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
it('appends node=<id> when options.nodeId is a non-empty string', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts('1h', undefined, { nodeId: 'pve1' });
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60&node=pve1', {
signal: undefined,
});
});
it('URL-encodes special characters in the node id (URLSearchParams.toString)', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts('1h', undefined, { nodeId: 'node a/b' });
// space -> '+', '/' -> '%2F'
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60&node=node+a%2Fb', {
signal: undefined,
});
});
it('combines range + node + signal in a single request with range-first/node-second ordering', async () => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
const controller = new AbortController();
await ChartsAPI.getStorageSummaryCharts('4h', controller.signal, { nodeId: 'pve1' });
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=240&node=pve1', {
signal: controller.signal,
});
});
it('returns a payload with EMPTY pools/disks verbatim from apiFetchJSON (empty arm)', async () => {
const emptyPayload: StorageSummaryChartsResponse = {
pools: {},
disks: {},
stats: { oldestDataTimestamp: 1733700000000 },
};
apiFetchJSONMock.mockResolvedValueOnce(emptyPayload as never);
const result = await ChartsAPI.getStorageSummaryCharts('1h');
expect(result).toBe(emptyPayload);
expect(result.pools).toEqual({});
expect(result.disks).toEqual({});
});
it('returns a fully-populated StorageSummaryChartsResponse payload verbatim', async () => {
const payload: StorageSummaryChartsResponse = {
pools: {
'local-zfs': {
name: 'local-zfs',
usage: [{ timestamp: 1700000000000, value: 42 }],
used: [{ timestamp: 1700000000000, value: 2100 }],
avail: [{ timestamp: 1700000000000, value: 2900 }],
},
},
disks: {
'serial-1': {
name: 'sda',
node: 'pve1',
temperature: [{ timestamp: 1700000000000, value: 38 }],
},
},
stats: {
oldestDataTimestamp: 1699900000000,
range: '1h',
rangeSeconds: 3600,
metricsStoreEnabled: true,
},
};
apiFetchJSONMock.mockResolvedValueOnce(payload as never);
const result = await ChartsAPI.getStorageSummaryCharts('1h');
expect(result).toBe(payload);
expect(result.pools['local-zfs'].used![0].value).toBe(2100);
expect(result.disks['serial-1'].temperature[0].value).toBe(38);
});
it('propagates a rejection from apiFetchJSON verbatim (error arm)', async () => {
const error = new Error('storage-charts 503');
apiFetchJSONMock.mockRejectedValueOnce(error);
await expect(ChartsAPI.getStorageSummaryCharts('1h')).rejects.toBe(error);
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
});
describe('timeRangeToMinutes — every switch arm (observed via getStorageSummaryCharts)', () => {
const apiFetchJSONMock = vi.mocked(apiFetchJSON);
beforeEach(() => {
apiFetchJSONMock.mockReset();
});
// Each entry asserts the specific minute value the helper computes for a
// given range token, observed as the `?range=<minutes>` query value. This
// is real observable behaviour, not a tautology: the source maps tokens to
// distinct magic numbers and we pin each one down.
const cases: Array<{ token: TimeRange; minutes: number }> = [
{ token: '5m', minutes: 5 },
{ token: '15m', minutes: 15 },
{ token: '30m', minutes: 30 },
{ token: '1h', minutes: 60 },
{ token: '4h', minutes: 240 },
{ token: '12h', minutes: 720 },
{ token: '24h', minutes: 1440 },
{ token: '7d', minutes: 10080 },
{ token: '30d', minutes: 43200 },
];
it.each(cases)(
'maps TimeRange="$token" to range=$minutes minutes in the storage-charts URL',
async ({ token, minutes }) => {
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts(token);
expect(apiFetchJSONMock).toHaveBeenCalledWith(`/api/storage-charts?range=${minutes}`, {
signal: undefined,
});
},
);
it('falls back to range=60 for an UNRECOGNISED range token (default switch arm)', async () => {
// '2h' is not in the TimeRange union and not handled by any case in
// timeRangeToMinutes -> hits the `default: return 60` branch.
const unknownToken = '2h' as unknown as TimeRange;
apiFetchJSONMock.mockResolvedValueOnce({} as never);
await ChartsAPI.getStorageSummaryCharts(unknownToken);
expect(apiFetchJSONMock).toHaveBeenCalledWith('/api/storage-charts?range=60', {
signal: undefined,
});
});
});
@@ -0,0 +1,613 @@
import { describe, expect, it } from 'vitest';
import type { AgentCapability } from '@/utils/agentCapabilityPresentation';
import type { ConnectedInfrastructureItem, ConnectedInfrastructureSurface } from '@/types/api';
import type { UnifiedAgentRow, UnifiedAgentSurface } from '../infrastructureOperationsModel';
import {
createSurfaceScopedRow,
getCapabilityManagementPath,
getCapabilitySurfaceLabel,
getPlatformConnectionsViewForCapability,
getStopMonitoringScopeLabel,
getStopMonitoringSurfaces,
hasMachineInstallActions,
isPlatformConnectionsCapability,
rowFromConnectedInfrastructureItem,
} from '../infrastructureOperationsModel';
type SurfaceKey = Parameters<typeof createSurfaceScopedRow>[1];
const makeRow = (overrides: Partial<UnifiedAgentRow>): UnifiedAgentRow => ({
rowKey: 'row-1',
id: 'agent-1',
name: 'node-a',
capabilities: [],
status: 'active',
upgradePlatform: 'linux',
scope: { label: 'Default', category: 'default' },
installFlags: [],
searchText: '',
surfaces: [],
...overrides,
});
const surface = (
label: string,
kind: AgentCapability,
overrides: Partial<UnifiedAgentSurface> = {},
): UnifiedAgentSurface => ({
key: kind,
kind,
label,
detail: '',
...overrides,
});
const connectedSurface = (
kind: ConnectedInfrastructureSurface['kind'],
overrides: Partial<ConnectedInfrastructureSurface> = {},
): ConnectedInfrastructureSurface => ({
id: `surface-${kind}`,
kind,
label: `${kind} label`,
...overrides,
});
const makeItem = (
overrides: Partial<ConnectedInfrastructureItem>,
): ConnectedInfrastructureItem => ({
id: 'agent-1',
name: 'node-a',
status: 'active',
surfaces: [],
...overrides,
});
describe('createSurfaceScopedRow', () => {
// A row that carries every surface kind plus every action id / linked node
// so each scope block can be distinguished by what it clears vs preserves.
const fullRow = makeRow({
rowKey: 'host-1',
capabilities: ['agent', 'docker', 'kubernetes', 'proxmox', 'pbs', 'pmg', 'truenas'],
agentActionId: 'agent-act',
dockerActionId: 'docker-act',
kubernetesActionId: 'k8s-act',
linkedNodeId: 'node-99',
surfaces: [
surface('Host telemetry', 'agent'),
surface('Docker runtime data', 'docker'),
surface('Kubernetes cluster data', 'kubernetes'),
surface('Proxmox data', 'proxmox'),
surface('PBS data', 'pbs'),
surface('PMG data', 'pmg'),
surface('TrueNAS data', 'truenas'),
],
});
it('scopes to docker: clears agent + k8s action ids and the linked node, keeps docker', () => {
const scoped = createSurfaceScopedRow(fullRow, 'docker');
expect(scoped.rowKey).toBe('host-1-docker-surface');
expect(scoped.capabilities).toEqual(['docker']);
expect(scoped.agentActionId).toBeUndefined();
expect(scoped.kubernetesActionId).toBeUndefined();
expect(scoped.linkedNodeId).toBeUndefined();
expect(scoped.dockerActionId).toBe('docker-act');
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['docker']);
});
it('scopes to kubernetes: clears agent + docker action ids and the linked node, keeps k8s', () => {
const scoped = createSurfaceScopedRow(fullRow, 'kubernetes');
expect(scoped.rowKey).toBe('host-1-kubernetes-surface');
expect(scoped.capabilities).toEqual(['kubernetes']);
expect(scoped.agentActionId).toBeUndefined();
expect(scoped.dockerActionId).toBeUndefined();
expect(scoped.linkedNodeId).toBeUndefined();
expect(scoped.kubernetesActionId).toBe('k8s-act');
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['kubernetes']);
});
it('scopes to pbs: clears docker + k8s action ids but keeps the agent action id and node', () => {
const scoped = createSurfaceScopedRow(fullRow, 'pbs');
expect(scoped.rowKey).toBe('host-1-pbs-surface');
expect(scoped.capabilities).toEqual(['pbs']);
expect(scoped.dockerActionId).toBeUndefined();
expect(scoped.kubernetesActionId).toBeUndefined();
expect(scoped.agentActionId).toBe('agent-act');
expect(scoped.linkedNodeId).toBe('node-99');
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['pbs']);
});
it('scopes to pmg: clears docker + k8s action ids but keeps the agent action id and node', () => {
const scoped = createSurfaceScopedRow(fullRow, 'pmg');
expect(scoped.rowKey).toBe('host-1-pmg-surface');
expect(scoped.capabilities).toEqual(['pmg']);
expect(scoped.dockerActionId).toBeUndefined();
expect(scoped.kubernetesActionId).toBeUndefined();
expect(scoped.agentActionId).toBe('agent-act');
expect(scoped.linkedNodeId).toBe('node-99');
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['pmg']);
});
it('scopes to proxmox via the explicit surfaceKey', () => {
const scoped = createSurfaceScopedRow(fullRow, 'proxmox');
expect(scoped.rowKey).toBe('host-1-proxmox-surface');
expect(scoped.capabilities).toEqual(['proxmox']);
expect(scoped.dockerActionId).toBeUndefined();
expect(scoped.kubernetesActionId).toBeUndefined();
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['proxmox']);
});
it('falls back to the proxmox block for an unrecognised surfaceKey', () => {
// 'availability' is not a member of the surfaceKey union, so it misses
// every `if` and lands in the default (proxmox) return.
const scoped = createSurfaceScopedRow(fullRow, 'availability' as unknown as SurfaceKey);
expect(scoped.rowKey).toBe('host-1-proxmox-surface');
expect(scoped.capabilities).toEqual(['proxmox']);
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['proxmox']);
});
it('scopes to agent with a host-only row: capabilities collapse to just ["agent"]', () => {
const scoped = createSurfaceScopedRow(
makeRow({
capabilities: ['agent'],
surfaces: [surface('Host telemetry', 'agent')],
}),
'agent',
);
expect(scoped.rowKey).toBe('row-1-agent-surface');
expect(scoped.capabilities).toEqual(['agent']);
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['agent']);
});
it('scopes to agent with docker present: appends docker to the host-managed set', () => {
const scoped = createSurfaceScopedRow(
makeRow({
capabilities: ['agent', 'docker'],
surfaces: [
surface('Host telemetry', 'agent'),
surface('Docker runtime data', 'docker'),
surface('PBS data', 'pbs'),
],
}),
'agent',
);
expect(scoped.capabilities).toEqual(['agent', 'docker']);
// The agent block surfaces filter keeps agent/docker/kubernetes only, so
// the pbs surface is dropped even though it exists on the source row.
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['agent', 'docker']);
});
it('scopes to agent with kubernetes present (no docker): appends kubernetes only', () => {
const scoped = createSurfaceScopedRow(
makeRow({
capabilities: ['agent', 'kubernetes'],
surfaces: [
surface('Host telemetry', 'agent'),
surface('Kubernetes cluster data', 'kubernetes'),
],
}),
'agent',
);
expect(scoped.capabilities).toEqual(['agent', 'kubernetes']);
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['agent', 'kubernetes']);
});
it('scopes to agent with docker + kubernetes: keeps all three host-managed kinds', () => {
const scoped = createSurfaceScopedRow(fullRow, 'agent');
expect(scoped.capabilities).toEqual(['agent', 'docker', 'kubernetes']);
expect(scoped.dockerActionId).toBeUndefined();
expect(scoped.kubernetesActionId).toBeUndefined();
// Unlike the docker/kubernetes blocks, the agent block does NOT clear the
// host action id or the linked node.
expect(scoped.agentActionId).toBe('agent-act');
expect(scoped.linkedNodeId).toBe('node-99');
expect(scoped.surfaces.map((s) => s.kind)).toEqual(['agent', 'docker', 'kubernetes']);
});
});
describe('getStopMonitoringSurfaces', () => {
it('returns only the stop-monitoring surfaces when no agent surface is host-managed (false arm)', () => {
// The docker surface has a stop-monitoring action but there is no agent
// surface at all, so hostManagedStopApplies is false and the function
// returns just the filtered stop-monitoring list instead of expanding.
const row = makeRow({
surfaces: [
surface('Docker runtime data', 'docker', { action: 'stop-monitoring' }),
surface('PBS data', 'pbs'),
],
});
expect(getStopMonitoringSurfaces(row).map((s) => s.kind)).toEqual(['docker']);
});
it('returns an empty list when no surface carries a stop-monitoring action', () => {
const row = makeRow({
surfaces: [
surface('Host telemetry', 'agent', { action: 'allow-reconnect' }),
surface('Docker runtime data', 'docker', { action: 'allow-reconnect' }),
],
});
expect(getStopMonitoringSurfaces(row)).toEqual([]);
});
});
describe('getStopMonitoringScopeLabel', () => {
it('falls back to the static copy when there are no stop-monitoring surfaces', () => {
const row = makeRow({
surfaces: [surface('Host telemetry', 'agent', { action: 'allow-reconnect' })],
});
expect(getStopMonitoringScopeLabel(row)).toBe('Reporting for this item');
});
});
describe('hasMachineInstallActions', () => {
it('returns false when both ids are undefined', () => {
expect(hasMachineInstallActions(makeRow({}))).toBe(false);
});
it('returns false when both ids are whitespace-only (trim() zeroes them out)', () => {
expect(hasMachineInstallActions(makeRow({ agentActionId: ' ', agentId: '\t' }))).toBe(false);
});
it('returns true when agentActionId is whitespace but agentId is present', () => {
expect(hasMachineInstallActions(makeRow({ agentActionId: ' ', agentId: 'agent-1' }))).toBe(
true,
);
});
it('returns true when only agentActionId is present', () => {
expect(hasMachineInstallActions(makeRow({ agentActionId: 'uninstall-1' }))).toBe(true);
});
});
describe('getCapabilitySurfaceLabel', () => {
it.each<[AgentCapability, string]>([
['agent', 'Host telemetry'],
['docker', 'Docker runtime data'],
['kubernetes', 'Kubernetes cluster data'],
['proxmox', 'Proxmox data'],
['pbs', 'PBS data'],
['pmg', 'PMG data'],
['truenas', 'TrueNAS data'],
])('returns the dedicated surface label for %s', (capability, label) => {
expect(getCapabilitySurfaceLabel(capability)).toBe(label);
});
it('falls through to getAgentCapabilityLabel for an unrecognised capability, yielding undefined', () => {
// The default arm delegates to getAgentCapabilityLabel, which itself has no
// default case and returns undefined for a value outside its switch.
expect(getCapabilitySurfaceLabel('foobar' as unknown as AgentCapability)).toBeUndefined();
});
});
describe('getPlatformConnectionsViewForCapability', () => {
it.each<[AgentCapability, 'proxmox' | 'truenas']>([
['proxmox', 'proxmox'],
['pbs', 'proxmox'],
['pmg', 'proxmox'],
['truenas', 'truenas'],
])('routes %s to the %s connections view', (capability, view) => {
expect(getPlatformConnectionsViewForCapability(capability)).toBe(view);
});
it.each<AgentCapability>(['agent', 'docker', 'kubernetes'])(
'returns null for the host-managed capability %s (default arm)',
(capability) => {
expect(getPlatformConnectionsViewForCapability(capability)).toBeNull();
},
);
});
describe('getCapabilityManagementPath', () => {
it.each<AgentCapability>(['agent', 'docker', 'kubernetes'])(
'returns null for non-platform host capabilities (%s)',
(capability) => {
expect(getCapabilityManagementPath(capability)).toBeNull();
},
);
});
describe('isPlatformConnectionsCapability', () => {
it.each<AgentCapability>(['docker', 'kubernetes', 'agent'])(
'returns false for the host capability %s',
(capability) => {
expect(isPlatformConnectionsCapability(capability)).toBe(false);
},
);
it.each<AgentCapability>(['proxmox', 'pbs', 'pmg', 'truenas'])(
'returns true for the platform capability %s',
(capability) => {
expect(isPlatformConnectionsCapability(capability)).toBe(true);
},
);
});
describe('installFlagsForCapabilities (via rowFromConnectedInfrastructureItem)', () => {
const scope = { label: 'Default', category: 'default' as const };
it('emits the docker host-disable flag pair for a docker-only item', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('docker')] }),
scope,
);
expect(row.installFlags).toEqual(['--enable-docker', '--disable-host']);
});
it('emits only --enable-kubernetes for a kubernetes-only item', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('kubernetes')] }),
scope,
);
expect(row.installFlags).toEqual(['--enable-kubernetes']);
});
it('emits the pve proxmox type for a proxmox-only item', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('proxmox')] }),
scope,
);
expect(row.installFlags).toEqual(['--enable-proxmox', '--proxmox-type pve']);
});
it('prefers proxmox-pve over pbs when both capabilities are present (else-if precedence)', () => {
// installFlagsForCapabilities uses `else if (capabilities.includes('pbs'))`,
// so a proxmox+pbs item pins to pve and never emits the pbs type.
const row = rowFromConnectedInfrastructureItem(
makeItem({
surfaces: [connectedSurface('proxmox'), connectedSurface('pbs')],
}),
scope,
);
expect(row.installFlags).toEqual(['--enable-proxmox', '--proxmox-type pve']);
});
it('emits no flags for an agent-only item', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.installFlags).toEqual([]);
});
});
describe('rowFromConnectedInfrastructureItem', () => {
const scope = { label: 'Default', category: 'default' as const };
describe('name fallback chain (name || displayName || hostname || id)', () => {
it('falls back to displayName when name is empty', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ name: '', displayName: 'Display Name' }),
scope,
);
expect(row.name).toBe('Display Name');
});
it('falls back to hostname when name and displayName are empty', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ name: '', displayName: '', hostname: 'host.internal' }),
scope,
);
expect(row.name).toBe('host.internal');
});
it('falls back to the id when name, displayName and hostname are all empty', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ id: 'fallback-id', name: '', displayName: '', hostname: '' }),
scope,
);
expect(row.name).toBe('fallback-id');
});
});
describe('rowKey derivation', () => {
it('prefixes removed-docker for an ignored item whose first surface is docker', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
id: 'd-1',
status: 'ignored',
surfaces: [connectedSurface('docker')],
}),
scope,
);
expect(row.rowKey).toBe('removed-docker-d-1');
});
it('prefixes removed-k8s for an ignored item whose first surface is kubernetes', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
id: 'k-1',
status: 'ignored',
surfaces: [connectedSurface('kubernetes')],
}),
scope,
);
expect(row.rowKey).toBe('removed-k8s-k-1');
});
it('prefixes removed-host for an ignored item whose first surface is agent', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
id: 'h-1',
status: 'ignored',
surfaces: [connectedSurface('agent')],
}),
scope,
);
expect(row.rowKey).toBe('removed-host-h-1');
});
it('prefixes removed-host for an ignored item with no surfaces', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ id: 'h-2', status: 'ignored', surfaces: [] }),
scope,
);
expect(row.rowKey).toBe('removed-host-h-2');
});
it('uses the kubernetes controlId for a kubernetes-only active item', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
id: 'k-2',
surfaces: [connectedSurface('kubernetes', { controlId: 'k8s-ctrl' })],
}),
scope,
);
expect(row.rowKey).toBe('k8s-k8s-ctrl');
});
it('falls back to the item id when a kubernetes-only active item has no controlId', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
id: 'k-3',
surfaces: [connectedSurface('kubernetes', { controlId: undefined })],
}),
scope,
);
expect(row.rowKey).toBe('k8s-k-3');
});
});
describe('status mapping', () => {
it('maps an ignored item status to the removed unified status', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ status: 'ignored', surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.status).toBe('removed');
});
it('keeps an active item status as active', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ status: 'active', surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.status).toBe('active');
});
});
describe('upgradePlatform fallback', () => {
it('defaults to linux when upgradePlatform is absent', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.upgradePlatform).toBe('linux');
});
it('preserves an explicit freebsd upgrade platform', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ upgradePlatform: 'freebsd', surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.upgradePlatform).toBe('freebsd');
});
});
describe('agentActionId / agentId fallbacks', () => {
it('prefers uninstallAgentId over the agent surface controlId for agentActionId', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
uninstallAgentId: 'uninstall-1',
surfaces: [connectedSurface('agent', { controlId: 'agent-ctrl' })],
}),
scope,
);
expect(row.agentActionId).toBe('uninstall-1');
});
it('uses the agent surface controlId when uninstallAgentId is absent', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
surfaces: [connectedSurface('agent', { controlId: 'agent-ctrl' })],
}),
scope,
);
expect(row.agentActionId).toBe('agent-ctrl');
});
it('prefers scopeAgentId over uninstallAgentId for agentId', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
scopeAgentId: 'scope-1',
uninstallAgentId: 'uninstall-1',
surfaces: [connectedSurface('agent')],
}),
scope,
);
expect(row.agentId).toBe('scope-1');
});
it('falls back to uninstallAgentId for agentId when scopeAgentId is absent', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
uninstallAgentId: 'uninstall-1',
surfaces: [connectedSurface('agent')],
}),
scope,
);
expect(row.agentId).toBe('uninstall-1');
});
});
describe('kubernetesInfo', () => {
it('builds a kubernetesInfo object when a kubernetes surface is present', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('kubernetes')] }),
scope,
);
expect(row.kubernetesInfo).toEqual({
server: undefined,
context: undefined,
tokenName: undefined,
});
});
it('leaves kubernetesInfo undefined for a host-only item with no kubernetes', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('agent')] }),
scope,
);
expect(row.kubernetesInfo).toBeUndefined();
});
});
describe('surfaceBreakdown label/detail fallbacks', () => {
it('substitutes the capability surface label when the surface label is empty', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({ surfaces: [connectedSurface('docker', { label: '' })] }),
scope,
);
expect(row.surfaces[0].label).toBe('Docker runtime data');
});
it('defaults an absent detail to an empty string', () => {
const row = rowFromConnectedInfrastructureItem(
makeItem({
surfaces: [connectedSurface('agent', { label: 'Host', detail: undefined })],
}),
scope,
);
expect(row.surfaces[0].detail).toBe('');
});
it('routes an unrecognised surface kind through the agent default of agentCapabilityFromSurfaceKind', () => {
// agentCapabilityFromSurfaceKind has a default -> 'agent' arm that is
// only reachable when surface.kind is not one of the known union
// members. Cast a bogus kind to exercise it; the capability collapses to
// 'agent'.
const row = rowFromConnectedInfrastructureItem(
makeItem({
surfaces: [
connectedSurface('agent', {
id: 'bogus',
kind: 'not-a-real-kind' as unknown as ConnectedInfrastructureSurface['kind'],
label: 'Bogus',
}),
],
}),
scope,
);
expect(row.capabilities).toEqual(['agent']);
expect(row.surfaces[0].kind).toBe('agent');
});
});
});
@@ -0,0 +1,514 @@
import { renderHook } from '@solidjs/testing-library';
import { createSignal } from 'solid-js';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import {
useSummaryPageInteractionState,
useSummaryTableFocusBridge,
} from '@/components/shared/summaryTableFocus';
// Branch-coverage tests for the UNCOVERED guard arms of summaryTableFocus.ts.
// The existing spec (summaryTableFocus.test.tsx) exercises the happy paths;
// this file targets the null/empty/early-return arms that never fire there:
// - scheduleViewportRefresh (scroll/resize -> viewportVersion tick) — the
// primary measured gap; never invoked by the existing suite.
// - activeRow / focusedGroupRow guards: missing container ref, empty id,
// querySelector miss, attribute-selector escaping.
// - jumpToActiveRow: no-active-id no-op + frame-exhaustion arm.
// - shouldShowJumpToActiveRow: empty summary collections + already-visible.
// - Escape handler: defaultPrevented, every modifier arm, dialog target.
// Every asserted value below is hand-computed against the source in
// src/components/shared/summaryTableFocus.ts — no snapshots, no constant-
// equals-itself tautologies.
const buildRect = (top: number, height = 32): DOMRect =>
({
x: 0,
y: top,
width: 240,
height,
top,
bottom: top + height,
left: 0,
right: 240,
toJSON: () => ({}),
}) as DOMRect;
describe('summaryTableFocus.branchcov0723pm', () => {
beforeEach(() => {
vi.stubGlobal('innerHeight', 800);
// Default synchronous rAF (matches the existing spec convention).
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
cb(0);
return 1;
});
vi.stubGlobal('cancelAnimationFrame', () => {});
});
afterEach(() => {
document.body.innerHTML = '';
vi.unstubAllGlobals();
});
// -------------------------------------------------------------------------
// PRIMARY TARGET: scheduleViewportRefresh
// (createEffect at summaryTableFocus.ts:226). The existing spec never
// dispatches scroll/resize after mounting a table root, so the entire
// effect body — listener registration, the rAF callback that ticks
// viewportVersion, the dedup guard, and the cancel-on-cleanup arm — is
// uncovered.
// -------------------------------------------------------------------------
describe('scheduleViewportRefresh', () => {
it('ticks viewportVersion via the rAF callback after a scroll event, recomputing isActiveRowVisible', () => {
// Deferred rAF: capture the callback so we control when viewportVersion
// increments (the synchronous default stub would tick instantly and hide
// the dedup arm tested below).
const scheduled: FrameRequestCallback[] = [];
let nextId = 7;
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
scheduled.push(cb);
return nextId++;
});
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-series-id', 'workload-a');
let rowTop = 1200; // below innerHeight(800) -> not visible
Object.defineProperty(row, 'getBoundingClientRect', {
configurable: true,
value: () => buildRect(rowTop),
});
root.appendChild(row);
document.body.appendChild(root);
const [hoveredSeriesId] = createSignal<string | null>('workload-a');
const { result } = renderHook(() => useSummaryPageInteractionState({ hoveredSeriesId }));
result.setTableRootRef(root);
// Row below the viewport -> jump affordance shown.
expect(result.shouldShowJumpToActiveRow()).toBe(true);
// Slide the row into the viewport. The memo must NOT pick this up yet
// because viewportVersion has not ticked (no rAF has fired).
rowTop = 64;
expect(result.shouldShowJumpToActiveRow()).toBe(true);
// scroll -> scheduleViewportRefresh schedules exactly one rAF.
window.dispatchEvent(new Event('scroll'));
expect(scheduled).toHaveLength(1);
// Fire the rAF -> viewportVersion++ -> isActiveRowVisible recomputes ->
// row is now in-viewport -> affordance hidden.
scheduled[0](0);
expect(result.shouldShowJumpToActiveRow()).toBe(false);
});
it('coalesces back-to-back scroll events into a single rAF (rafId !== undefined dedup arm)', () => {
const scheduled: FrameRequestCallback[] = [];
let nextId = 3;
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
scheduled.push(cb);
return nextId++;
});
const root = document.createElement('div');
document.body.appendChild(root);
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => null }),
);
result.setTableRootRef(root);
window.dispatchEvent(new Event('scroll'));
window.dispatchEvent(new Event('scroll'));
window.dispatchEvent(new Event('scroll'));
// Only the first scroll schedules an rAF; subsequent ones hit
// `if (rafId !== undefined) return;` while the callback is pending.
expect(scheduled).toHaveLength(1);
});
it('refreshes viewportVersion on window resize as well as scroll (resize listener arm)', () => {
const scheduled: FrameRequestCallback[] = [];
let nextId = 11;
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
scheduled.push(cb);
return nextId++;
});
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-series-id', 'workload-a');
let rowTop = 900;
Object.defineProperty(row, 'getBoundingClientRect', {
configurable: true,
value: () => buildRect(rowTop),
});
root.appendChild(row);
document.body.appendChild(root);
const [hoveredSeriesId] = createSignal<string | null>('workload-a');
const { result } = renderHook(() => useSummaryPageInteractionState({ hoveredSeriesId }));
result.setTableRootRef(root);
expect(result.shouldShowJumpToActiveRow()).toBe(true);
rowTop = 50;
window.dispatchEvent(new Event('resize'));
expect(scheduled).toHaveLength(1);
scheduled[0](0);
expect(result.shouldShowJumpToActiveRow()).toBe(false);
});
it('cancels the pending rAF id when the owner is disposed (cancelAnimationFrame cleanup arm)', () => {
const scheduled: FrameRequestCallback[] = [];
const cancelSpy = vi.fn();
const pendingId = 42;
vi.stubGlobal('requestAnimationFrame', (cb: FrameRequestCallback) => {
scheduled.push(cb);
return pendingId;
});
vi.stubGlobal('cancelAnimationFrame', cancelSpy);
const root = document.createElement('div');
document.body.appendChild(root);
const { result, cleanup } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => null }),
);
result.setTableRootRef(root);
window.dispatchEvent(new Event('scroll'));
expect(cancelSpy).not.toHaveBeenCalled();
cleanup();
// The rAF id returned by the deferred stub (42) is cancelled on dispose.
expect(cancelSpy).toHaveBeenCalledWith(pendingId);
});
it('does not attach scroll/resize listeners when tableRoot ref is missing (missing container ref arm)', () => {
const rafSpy = vi.fn((cb: FrameRequestCallback) => {
cb(0);
return 1;
});
vi.stubGlobal('requestAnimationFrame', rafSpy);
renderHook(() => useSummaryTableFocusBridge({ activeSeriesId: () => null }));
// No setTableRootRef -> effect hits `if (!root ...) return;` -> no
// listeners -> scroll/resize schedule no rAF.
window.dispatchEvent(new Event('scroll'));
window.dispatchEvent(new Event('resize'));
expect(rafSpy).not.toHaveBeenCalled();
});
});
// -------------------------------------------------------------------------
// activeRow guard arms: missing container ref, empty id, querySelector miss,
// attribute-selector escaping.
// -------------------------------------------------------------------------
describe('activeRow guard arms', () => {
it('returns null when the tableRoot ref has never been set (missing container ref)', () => {
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => 'workload-a' }),
);
expect(result.activeRow()).toBeNull();
});
it('returns null when setTableRootRef receives undefined (setter `element ?? null` right arm)', () => {
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => 'workload-a' }),
);
result.setTableRootRef(undefined);
expect(result.activeRow()).toBeNull();
});
it('returns null for null / whitespace-only active ids and the row for a real id (normalizedActiveSeriesId guard)', () => {
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-series-id', 'workload-a');
root.appendChild(row);
document.body.appendChild(root);
const [activeSeriesId, setActiveSeriesId] = createSignal<string | null>(null);
const { result } = renderHook(() => useSummaryTableFocusBridge({ activeSeriesId }));
result.setTableRootRef(root);
expect(result.activeRow()).toBeNull();
setActiveSeriesId(' ');
expect(result.activeRow()).toBeNull();
setActiveSeriesId('workload-a');
expect(result.activeRow()).toBe(row);
});
it('returns null when no descendant carries the matching data attribute (querySelector miss)', () => {
const root = document.createElement('div');
const decoy = document.createElement('div');
decoy.setAttribute('data-summary-series-id', 'other');
root.appendChild(decoy);
document.body.appendChild(root);
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => 'workload-a' }),
);
result.setTableRootRef(root);
expect(result.activeRow()).toBeNull();
});
it('escapes backslashes and double quotes in the series id before injecting it into the attribute selector', () => {
const root = document.createElement('div');
const row = document.createElement('div');
// Literal id containing both a double-quote and a backslash — the exact
// characters escapeAttributeSelectorValue rewrites.
row.setAttribute('data-summary-series-id', 'id"with\\special');
root.appendChild(row);
document.body.appendChild(root);
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => 'id"with\\special' }),
);
result.setTableRootRef(root);
// If escaping were broken the selector would not match the literal id.
expect(result.activeRow()).toBe(row);
});
});
// -------------------------------------------------------------------------
// jumpToActiveRow guard arms
// -------------------------------------------------------------------------
describe('jumpToActiveRow guard arms', () => {
it('is a no-op when there is no active series id (normalizedActiveSeriesId falsy -> early return)', () => {
const revealActiveSeries = vi.fn();
const rafSpy = vi.fn((cb: FrameRequestCallback) => {
cb(0);
return 1;
});
vi.stubGlobal('requestAnimationFrame', rafSpy);
const root = document.createElement('div');
document.body.appendChild(root);
const { result } = renderHook(() =>
useSummaryTableFocusBridge({
activeSeriesId: () => null,
revealActiveSeries,
}),
);
result.setTableRootRef(root);
result.jumpToActiveRow();
expect(revealActiveSeries).not.toHaveBeenCalled();
expect(rafSpy).not.toHaveBeenCalled();
});
it('exhausts the 6-frame retry budget without scrolling when the active row never mounts (no matching row)', () => {
const revealActiveSeries = vi.fn();
const rafSpy = vi.fn((cb: FrameRequestCallback) => {
cb(0);
return 1;
});
vi.stubGlobal('requestAnimationFrame', rafSpy);
const root = document.createElement('div');
document.body.appendChild(root);
const { result } = renderHook(() =>
useSummaryTableFocusBridge({
activeSeriesId: () => 'workload-a',
revealActiveSeries,
}),
);
result.setTableRootRef(root);
result.jumpToActiveRow();
// revealActiveSeries fires once before the scroll retry loop.
expect(revealActiveSeries).toHaveBeenCalledWith('workload-a');
expect(revealActiveSeries).toHaveBeenCalledTimes(1);
// attemptScroll(6) recurses once per synchronous rAF until remainingHits
// 0 -> 6 rAF calls total, then the `remainingFrames <= 0` arm returns.
expect(rafSpy).toHaveBeenCalledTimes(6);
});
});
// -------------------------------------------------------------------------
// shouldShowJumpToActiveRow — empty summary collections + already-visible row
// -------------------------------------------------------------------------
describe('shouldShowJumpToActiveRow arms', () => {
it('is false when no hover/focus/group signal resolves an active series (empty summary collections / page-default state)', () => {
const root = document.createElement('div');
document.body.appendChild(root);
const { result } = renderHook(() => useSummaryPageInteractionState({}));
result.setTableRootRef(root);
// resolveSummaryScopeState returns { kind: 'page', seriesId: null }.
expect(result.activeSeriesId()).toBeNull();
expect(result.activeGroupScope()).toBeNull();
// Boolean(null) && !isActiveRowVisible() -> false && ... -> false.
expect(result.shouldShowJumpToActiveRow()).toBe(false);
});
it('is false when the active row is already inside the viewport (already-focused / visible arm)', () => {
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-series-id', 'workload-a');
// top=200, height=40, innerHeight=800 -> 0 < 200 and 240 < 800 -> visible.
Object.defineProperty(row, 'getBoundingClientRect', {
configurable: true,
value: () => buildRect(200, 40),
});
root.appendChild(row);
document.body.appendChild(root);
const [hoveredSeriesId] = createSignal<string | null>('workload-a');
const { result } = renderHook(() => useSummaryPageInteractionState({ hoveredSeriesId }));
result.setTableRootRef(root);
expect(result.activeSeriesId()).toBe('workload-a');
// Boolean('workload-a') && !true -> false.
expect(result.shouldShowJumpToActiveRow()).toBe(false);
});
});
// -------------------------------------------------------------------------
// Focused-group reveal effect (summaryTableFocus.ts:350) — the
// already-visible arm skips scrollIntoView; the out-of-viewport arm scrolls.
// -------------------------------------------------------------------------
describe('focused-group reveal arms', () => {
it('does not call scrollIntoView when the focused group row is already visible (already-focused arm)', () => {
const scrollIntoView = vi.fn();
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-group-id', 'group-a');
Object.defineProperty(row, 'getBoundingClientRect', {
configurable: true,
value: () => buildRect(120, 40), // inside viewport
});
Object.defineProperty(row, 'scrollIntoView', {
configurable: true,
value: scrollIntoView,
});
root.appendChild(row);
document.body.appendChild(root);
const [focusedGroupId] = createSignal<string | null>('group-a');
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => null, focusedGroupId }),
);
result.setTableRootRef(root);
// `!isElementVisibleWithinViewport(row) && ...` short-circuits on the
// left operand when the row is already on-screen.
expect(scrollIntoView).not.toHaveBeenCalled();
});
it('calls scrollIntoView with nearest-block when the focused group row sits below the viewport', () => {
const scrollIntoView = vi.fn();
const root = document.createElement('div');
const row = document.createElement('div');
row.setAttribute('data-summary-group-id', 'group-a');
Object.defineProperty(row, 'getBoundingClientRect', {
configurable: true,
value: () => buildRect(1200, 40), // below innerHeight(800)
});
Object.defineProperty(row, 'scrollIntoView', {
configurable: true,
value: scrollIntoView,
});
root.appendChild(row);
document.body.appendChild(root);
const [focusedGroupId] = createSignal<string | null>('group-a');
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => null, focusedGroupId }),
);
result.setTableRootRef(root);
expect(scrollIntoView).toHaveBeenCalledWith({
behavior: 'smooth',
block: 'nearest',
});
});
it('retries via rAF then stops when the focused group row never mounts (no matching group row -> remainingFrames exhausted)', () => {
const rafSpy = vi.fn((cb: FrameRequestCallback) => {
cb(0);
return 1;
});
vi.stubGlobal('requestAnimationFrame', rafSpy);
const root = document.createElement('div');
document.body.appendChild(root);
const [focusedGroupId] = createSignal<string | null>('group-a');
const { result } = renderHook(() =>
useSummaryTableFocusBridge({ activeSeriesId: () => null, focusedGroupId }),
);
result.setTableRootRef(root);
// Initial rAF + one per decrement: remainingFrames 12->0 = 13 calls.
expect(rafSpy).toHaveBeenCalledTimes(13);
});
});
// -------------------------------------------------------------------------
// Escape handler guard arms (summaryTableFocus.ts:174). The existing spec
// only covers the happy path; every short-circuit in the `||` chain and the
// dialog-target guard are uncovered.
// -------------------------------------------------------------------------
describe('escape handler guard arms', () => {
it('does not invoke onEscapeClear when the event default is already prevented', () => {
const onEscapeClear = vi.fn();
renderHook(() => useSummaryPageInteractionState({ onEscapeClear }));
const event = new KeyboardEvent('keydown', {
key: 'Escape',
bubbles: true,
cancelable: true,
});
event.preventDefault();
document.dispatchEvent(event);
expect(onEscapeClear).not.toHaveBeenCalled();
});
it.each([
['altKey', { altKey: true }],
['ctrlKey', { ctrlKey: true }],
['metaKey', { metaKey: true }],
['shiftKey', { shiftKey: true }],
] as const)(
'ignores Escape pressed while %s is held (modifier short-circuit arm)',
(_label, modifiers) => {
const onEscapeClear = vi.fn();
renderHook(() => useSummaryPageInteractionState({ onEscapeClear }));
document.dispatchEvent(
new KeyboardEvent('keydown', { key: 'Escape', bubbles: true, ...modifiers }),
);
expect(onEscapeClear).not.toHaveBeenCalled();
},
);
it('ignores Escape dispatched from inside an open dialog (dialog-target guard)', () => {
const onEscapeClear = vi.fn();
renderHook(() => useSummaryPageInteractionState({ onEscapeClear }));
const dialog = document.createElement('div');
dialog.setAttribute('role', 'dialog');
document.body.appendChild(dialog);
dialog.dispatchEvent(new KeyboardEvent('keydown', { key: 'Escape', bubbles: true }));
expect(onEscapeClear).not.toHaveBeenCalled();
});
it('ignores non-Escape keys (event.key !== "Escape" arm)', () => {
const onEscapeClear = vi.fn();
renderHook(() => useSummaryPageInteractionState({ onEscapeClear }));
document.dispatchEvent(new KeyboardEvent('keydown', { key: 'Enter', bubbles: true }));
expect(onEscapeClear).not.toHaveBeenCalled();
});
});
});
@@ -0,0 +1,219 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import type { Override } from '../types';
import {
buildAgentHeaderMeta,
dockerContainerOverrideIdCandidates,
findOverrideByCandidates,
hasThresholdDiff,
} from '../thresholdsResourceModel';
/**
* Build a minimal Resource fixture. `as unknown as Resource` keeps strict
* TypeScript clean while letting us exercise the runtime guards with
* deliberately partial / malformed payloads.
*/
const makeAgent = (overrides: Partial<Resource> = {}): Resource =>
({
id: 'agent-id',
type: 'agent',
name: 'agent-name',
displayName: 'agent-display',
platformId: 'platform-id',
platformType: 'generic',
sourceType: 'agent',
status: 'online',
lastSeen: 0,
...overrides,
}) as unknown as Resource;
const makeOverride = (id: string, thresholds: Override['thresholds'] = {}): Override =>
({
id,
name: id,
type: 'guest',
thresholds,
}) as Override;
describe('thresholdsResourceModel branch coverage 0723pm', () => {
describe('hasThresholdDiff', () => {
it('returns false when the override is undefined (?.thresholds short-circuit)', () => {
expect(hasThresholdDiff(undefined, { cpu: 80 })).toBe(false);
});
it('returns false when every threshold equals its default', () => {
const override = makeOverride('a', { cpu: 80, memory: 70 });
expect(hasThresholdDiff(override, { cpu: 80, memory: 70 })).toBe(false);
});
it('returns true when one threshold diverges while a sibling matches', () => {
const override = makeOverride('a', { cpu: 80, memory: 90 });
expect(hasThresholdDiff(override, { cpu: 80, memory: 70 })).toBe(true);
});
it('returns true when the first keyed threshold already diverges (some early-exit)', () => {
const override = makeOverride('a', { cpu: 99, memory: 99 });
expect(hasThresholdDiff(override, { cpu: 80, memory: 70 })).toBe(true);
});
it('returns false for an empty thresholds object (some() over an empty key list)', () => {
const override = makeOverride('a', {});
expect(hasThresholdDiff(override, { cpu: 80 })).toBe(false);
});
it('ignores keys whose override value is undefined even when a default exists', () => {
// The predicate's `!== undefined` guard must filter out absent values.
const override = makeOverride('a', { cpu: undefined });
expect(hasThresholdDiff(override, { cpu: 80 })).toBe(false);
});
});
describe('dockerContainerOverrideIdCandidates', () => {
it('prefixes every host candidate as docker:<hostId>/<shortId>, preserving order', () => {
const host = makeAgent({
id: 'runtime-1',
discoveryTarget: {
resourceType: 'app-container',
resourceId: 'container-123',
agentId: 'agent-7',
},
platformData: {
docker: { hostSourceId: 'docker-platform' },
hostSourceId: 'host-platform',
},
});
expect(dockerContainerOverrideIdCandidates(host, 'a1b2c3d4e5f6')).toEqual([
'docker:container-123/a1b2c3d4e5f6',
'docker:docker-platform/a1b2c3d4e5f6',
'docker:host-platform/a1b2c3d4e5f6',
'docker:agent-7/a1b2c3d4e5f6',
'docker:runtime-1/a1b2c3d4e5f6',
]);
});
it('produces a single prefixed entry when the host exposes only its resource id', () => {
const host = makeAgent({ id: 'lone-host' });
expect(dockerContainerOverrideIdCandidates(host, 'deadbeef')).toEqual([
'docker:lone-host/deadbeef',
]);
});
it('falls back to platformData.docker.hostSourceId when discoveryTarget is absent', () => {
const host = makeAgent({
id: 'host-1',
platformData: { docker: { hostSourceId: 'docker-src' } },
});
expect(dockerContainerOverrideIdCandidates(host, 'cid')).toEqual([
'docker:docker-src/cid',
'docker:host-1/cid',
]);
});
it('returns an empty array when the host has no usable id signal at all', () => {
// resource.id is whitespace -> readString drops it -> uniqueIds yields [].
const host = makeAgent({ id: ' ' });
expect(dockerContainerOverrideIdCandidates(host, 'cid')).toEqual([]);
});
});
describe('findOverrideByCandidates', () => {
it('returns the override held under the first candidate (early return)', () => {
const first = makeOverride('first', { cpu: 1 });
const map = new Map<string, Override>([
['c1', first],
['c2', makeOverride('second', { cpu: 2 })],
]);
expect(findOverrideByCandidates(map, ['c1', 'c2'])).toBe(first);
});
it('skips missing candidates and returns the first hit further down the list', () => {
const later = makeOverride('later', { cpu: 5 });
const map = new Map<string, Override>([['c3', later]]);
expect(findOverrideByCandidates(map, ['c1', 'c2', 'c3'])).toBe(later);
});
it('returns undefined when no candidate is present in the map', () => {
const map = new Map<string, Override>([['unrelated', makeOverride('x')]]);
expect(findOverrideByCandidates(map, ['c1', 'c2'])).toBeUndefined();
});
it('returns undefined for an empty candidate list (loop body never executes)', () => {
const map = new Map<string, Override>([['c1', makeOverride('x')]]);
expect(findOverrideByCandidates(map, [])).toBeUndefined();
});
it('skips a candidate whose map value is explicitly undefined (truthy guard)', () => {
// Map.get yields undefined for both missing keys and keys set to undefined;
// the `if (override)` guard must treat them identically and keep scanning.
const real = makeOverride('real', { cpu: 9 });
const map = new Map<string, Override | undefined>([
['hole', undefined],
['real', real],
]) as unknown as Map<string, Override>;
expect(findOverrideByCandidates(map, ['hole', 'real'])).toBe(real);
});
});
describe('buildAgentHeaderMeta', () => {
it('uses identity.hostname for rawName and collects distinct display/hostname/id keys', () => {
const agent = makeAgent({
id: 'agent-7',
name: 'titan-host',
displayName: 'Titan Server',
status: 'warning',
identity: { hostname: 'titan.example.com' },
});
const { headerMeta, keys } = buildAgentHeaderMeta(agent);
expect(headerMeta.type).toBe('agent');
expect(headerMeta.status).toBe('warning');
expect(headerMeta.rawName).toBe('titan.example.com');
expect(headerMeta.displayName).toBe('Titan Server');
expect([...keys]).toEqual(['Titan Server', 'titan.example.com', 'agent-7']);
});
it('falls back to the trimmed agent.name for rawName when no hostname signal is present', () => {
const agent = makeAgent({
id: 'agent-2',
name: 'box-2',
displayName: undefined,
});
const { headerMeta, keys } = buildAgentHeaderMeta(agent);
// getPreferredResourceHostname bottoms out at asTrimmedString(resource.name).
expect(headerMeta.rawName).toBe('box-2');
// displayName collapses onto the same value, so keys dedupe to two entries.
expect(headerMeta.displayName).toBe('box-2');
expect([...keys]).toEqual(['box-2', 'agent-2']);
});
it('falls back to agent.id for rawName when both hostname and name are absent', () => {
const agent = makeAgent({
id: 'agent-3',
name: '',
displayName: undefined,
platformId: undefined,
});
const { headerMeta, keys } = buildAgentHeaderMeta(agent);
expect(headerMeta.rawName).toBe('agent-3');
expect(headerMeta.displayName).toBe('agent-3');
expect([...keys]).toEqual(['agent-3']);
});
it('uses a whitespace-only name for rawName but keeps it out of the keys set', () => {
// asTrimmedString drops the whitespace name (and platformId is cleared),
// so getPreferredResourceHostname is undefined and the `|| agent.name` arm
// fires, making rawName the raw " " string. The keys loop then skips it
// via its `value.trim()` guard.
const agent = makeAgent({
id: 'real-id',
name: ' ',
displayName: undefined,
platformId: undefined,
});
const { headerMeta, keys } = buildAgentHeaderMeta(agent);
expect(headerMeta.rawName).toBe(' ');
expect([...keys]).toEqual(['real-id']);
});
});
});