Cover eight frontend modules no prior coverage pass had reached

These are pure modules that already had a spec but had never been through a
branch-coverage pass, so their cold arms had never been measured. Counts are
uncovered branches, module scoped, existing spec versus existing plus new.

- resourceTypeCompat 12 -> 0, the legacy type-alias paths: unrecognised type
  strings, casing variants and the fallback identity mapping.
- summaryCardInteraction 8 -> 0 and diskDetailPresentation 6 -> 0.
- commercialBillingModel: asUnlimitedLimit, buildHostedCommercialPlanModel and
  buildHostedCommercialUsageModel had never been called by any test. All three
  are now covered across unlimited and numeric limits and usage at, below and
  above the limit.
- alertDetailPresentation 4 -> 0, resourceTypePresentation 2 -> 0,
  unifiedResourceTableStateModel 4 -> 0, assistantSlashCommands 4 -> 1.

No source file is modified. Adversarial review returned no rejects but found
the heaviest padding of this run, around twenty-five near-duplicate cases plus
one assertion that mirrored the source's own date formatting. Every one was
removed or replaced with a concrete literal, and each module's coverage was
re-measured as identical afterwards, proving the removed cases carried nothing.

PULSE_ALLOW_CONTRACT_NEUTRAL_COMMIT=test-only branch coverage, no source or contract change
This commit is contained in:
rcourtman 2026-07-25 01:35:31 +01:00
parent c035c5b514
commit ae1f9c2ad3
8 changed files with 1248 additions and 0 deletions

View file

@ -0,0 +1,96 @@
import { afterEach, describe, expect, it, vi } from 'vitest';
import { filterAssistantSlashCommands } from '../assistantSlashCommands';
// Branch-coverage companion to assistantSlashCommands.test.ts. Each test below
// drives a specific cold arm uncovered by the happy-path spec and asserts
// against concrete outputs rather than truthiness.
//
// Cold arms identified by scoped v8 coverage (coverage-final.json):
// L66 - `import.meta.env.DEV || import.meta.env.MODE === 'test'`: the
// right operand (`MODE === 'test'`) is never evaluated because DEV is
// truthy in the test environment. Forcing DEV falsy exercises it.
// L275 - commandMatchScore `return 2`: command name *contains* the query but
// does not *start* with it (and no alias starts with it).
// L276 - commandMatchScore `return 3`: an alias *contains* the query but no
// alias/name *starts* with it and the name does not contain it.
//
// L214 (`if (!token) return null;`) is intentionally NOT exercised here: the
// regex `^(\S+)...` guarantees `match[1]` is a non-empty string, so `!token`
// is always false and the `return null` arm is provably unreachable through
// the public API. See GLM_REPORT_fe3-slashcmd.md for details.
const PROD_COMMAND_NAMES_WITHOUT_DEV = [
'new',
'sessions',
'queue',
'compact',
'fork',
'undo',
'redo',
'models',
'providers',
'status',
'copy',
'export',
'help',
];
describe('assistantSlashCommands branch coverage', () => {
describe('DEV-gated dev-command surface (L66 logical-OR right operand)', () => {
const originalDev = import.meta.env.DEV;
const originalMode = import.meta.env.MODE;
afterEach(() => {
vi.stubEnv('DEV', originalDev);
vi.stubEnv('MODE', originalMode);
});
it('admits dev fixture commands when DEV is off but MODE is test (right operand true)', () => {
vi.stubEnv('DEV', false);
vi.stubEnv('MODE', 'test');
const names = filterAssistantSlashCommands('', undefined, {}).map((command) => command.name);
expect(names).toContain('fixture');
// Sanity: the rest of the production surface is still present and ordered.
expect(names).toEqual([...PROD_COMMAND_NAMES_WITHOUT_DEV, 'fixture']);
});
it('hides dev fixture commands when both DEV and MODE deny the dev surface (right operand false)', () => {
vi.stubEnv('DEV', false);
vi.stubEnv('MODE', 'production');
const names = filterAssistantSlashCommands('', undefined, {}).map((command) => command.name);
expect(names).not.toContain('fixture');
expect(names).toEqual(PROD_COMMAND_NAMES_WITHOUT_DEV);
});
});
describe('commandMatchScore name-substring ranking (L275 `return 2`)', () => {
it('ranks a name-substring match ahead of a description-substring match', () => {
// Two commands match "ew", so the sort comparator actually invokes
// commandMatchScore: "new" contains "ew" in its name (name-substring,
// score 2) while "fork" only matches via its description ("...into a new
// copy", score 4). New must therefore sort first, driving the `return 2`
// arm that a single-match query never reaches (the comparator is not
// called on a one-element array).
expect(filterAssistantSlashCommands('ew').map((command) => command.name)).toEqual([
'new',
'fork',
]);
});
});
describe('commandMatchScore alias-substring ranking (L276 `return 3`)', () => {
it('ranks an alias-substring match ahead of description-substring matches', () => {
// Three commands match "ear", so the comparator scores each. "new" has
// alias "clear" which contains "ear" but neither the name nor any alias
// starts with "ear" and the name does not contain it -> alias-substring
// arm (score 3). "sessions" and "models" match only via their
// descriptions ("Search..."), scoring 4. New sorts first, exercising the
// `return 3` arm.
expect(filterAssistantSlashCommands('ear').map((command) => command.name)).toEqual([
'new',
'sessions',
'models',
]);
});
});
});

View file

@ -0,0 +1,173 @@
import { describe, expect, it } from 'vitest';
import type { Resource } from '@/types/resource';
import {
UNIFIED_RESOURCE_TABLE_DEFAULT_LAYOUT_WIDTH,
buildHostRowIndexById,
buildHostTableItems,
getHostRevealTargetIndex,
getHostSpacerHeights,
getNextUnifiedResourceTableSortState,
normalizeUnifiedResourceTableLayoutWidth,
shouldShowClusterGroupTypeLabel,
sortServiceResources,
} from '@/components/Infrastructure/unifiedResourceTableStateModel';
/**
* Branch-coverage additions for unifiedResourceTableStateModel.ts targeting the
* arms left cold by the existing happy-path spec
* (unifiedResourceTableStateModel.test.ts).
*
* Each test drives a specific measured-cold branch (default fallback, map miss,
* nullish coalescing RHS, non-windowed early return, ternary consequent) and
* asserts on the concrete returned value — never a tautology.
*
* The makeResource fixture mirrors the existing spec's builder so import style
* and field shape stay consistent.
*/
const makeResource = (id: string, overrides: Partial<Resource> = {}): Resource =>
({
id,
type: 'agent',
name: `name-${id}`,
displayName: `Display ${id}`,
platformId: `platform-${id}`,
platformType: 'proxmox-pve',
sourceType: 'api',
status: 'online',
lastSeen: Date.now(),
platformData: { sources: ['proxmox'] },
...overrides,
}) as Resource;
describe('normalizeUnifiedResourceTableLayoutWidth — default-fallback arm', () => {
// Drives branch 6 arm 0 (lines 69-71): the trailing
// `return UNIFIED_RESOURCE_TABLE_DEFAULT_LAYOUT_WIDTH;` reached only when the
// width argument is invalid AND the fallback is also invalid. The existing
// spec only exercises a valid width or an invalid width with a valid
// fallback, so the final default return is never reached.
it('falls back to the default constant when the width is absent and the fallback is NaN', () => {
expect(normalizeUnifiedResourceTableLayoutWidth(null, Number.NaN)).toBe(
UNIFIED_RESOURCE_TABLE_DEFAULT_LAYOUT_WIDTH,
);
});
it('falls back to the default constant when the width is absent and the fallback is 0', () => {
expect(normalizeUnifiedResourceTableLayoutWidth(null, 0)).toBe(
UNIFIED_RESOURCE_TABLE_DEFAULT_LAYOUT_WIDTH,
);
});
});
describe('getHostRevealTargetIndex — map-miss returns null', () => {
// Drives branch 31 arm 0 (line 150: `rowIndexById.get(targetId) ?? null`):
// the LHS of `??` is undefined because targetId is absent from the map. The
// existing spec always resolves a present id, so the nullish-coalescing RHS
// is never taken.
const items = buildHostTableItems(
[{ cluster: 'cluster-a', resources: [makeResource('a'), makeResource('b')] }],
'grouped',
);
const rowIndexById = buildHostRowIndexById(items);
it('returns null when expandedResourceId is not present in the row index', () => {
// rowIndexById only holds 'a' (1) and 'b' (2); 'missing' is absent.
expect(getHostRevealTargetIndex(rowIndexById, 'missing', null)).toBeNull();
});
it('returns null when revealedResourceId is the chosen target but absent from the row index', () => {
// expandedResourceId is null so revealedResourceId 'ghost' wins the chain,
// then misses the map lookup.
expect(getHostRevealTargetIndex(rowIndexById, null, null, 'ghost')).toBeNull();
});
});
describe('shouldShowClusterGroupTypeLabel — nullish clusterLabel arm', () => {
// Drives branch 35 arm 0 (line 166: `(clusterLabel ?? '')`): the RHS of `??`
// is only evaluated when clusterLabel is null/undefined. The existing spec
// passes only concrete strings, so the coalescing RHS stays cold.
it('returns false for a null clusterLabel without throwing', () => {
expect(shouldShowClusterGroupTypeLabel(null)).toBe(false);
});
it('returns false for an undefined clusterLabel without throwing', () => {
expect(shouldShowClusterGroupTypeLabel(undefined)).toBe(false);
});
});
describe('getHostSpacerHeights — non-windowed early-return arm', () => {
// Drives branch 38 arm 0 (lines 177-179): the `if (!isWindowed)` consequent
// that returns `{ top: 0, bottom: 0 }`. The existing spec always calls with
// isWindowed = true, so the zero-spacer early return is never produced.
it('returns zero spacers regardless of window bounds when windowing is disabled', () => {
const result = getHostSpacerHeights(100, 5, 20, false);
expect(result).toEqual({ top: 0, bottom: 0 });
});
it('returns zero spacers even when a custom row height is supplied', () => {
// Discriminator: a custom estimatedRowHeight (40) would otherwise scale the
// top spacer to 5 * 40 = 200; the early return suppresses it entirely.
const result = getHostSpacerHeights(100, 5, 20, false, 40);
expect(result).toEqual({ top: 0, bottom: 0 });
expect(result.top).not.toBe(200);
});
});
describe('getNextUnifiedResourceTableSortState — asc default for name/source', () => {
// Drives branch 43 arm 0 (line 204: the `'asc'` consequent of
// `nextKey === 'name' || nextKey === 'source' ? 'asc' : 'desc'`). The existing
// spec only enters the function with currentKey === nextKey (so it never
// reaches line 204) or with nextKey 'cpu' (which takes the 'desc' alternate).
it('initializes a name sort to ascending when switching from a different key', () => {
expect(getNextUnifiedResourceTableSortState('default', 'asc', 'name')).toEqual({
key: 'name',
direction: 'asc',
});
});
it('initializes a source sort to ascending when switching from a different key', () => {
// Also drives the second operand of the `||` (`nextKey === 'source'`) true.
expect(getNextUnifiedResourceTableSortState('cpu', 'desc', 'source')).toEqual({
key: 'source',
direction: 'asc',
});
});
});
describe('sortServiceResources — uncovered exported function', () => {
// Drives fn 15 (sortServiceResources, line 217): the only exported function
// the existing spec never invokes. It must (a) filter to the requested
// service type and (b) delegate to sortResources with 'default'/'asc', which
// ranks online resources first then by display name. Assertions are made on
// the concrete id sequence so they cannot pass against an unfiltered or
// unsorted list.
const services: Resource[] = [
makeResource('zebra', { type: 'pbs', displayName: 'Zeta', status: 'offline' }),
makeResource('alpha', { type: 'pbs', displayName: 'Alpha', status: 'online' }),
makeResource('mango', { type: 'pbs', displayName: 'Mango', status: 'online' }),
makeResource('pmg-only', { type: 'pmg', displayName: 'Should Be Filtered', status: 'online' }),
makeResource('agent-only', { type: 'agent', displayName: 'Also Filtered', status: 'online' }),
];
it('returns only pbs resources, online-first then by display name', () => {
const result = sortServiceResources(services, 'pbs');
expect(result.map((r) => r.id)).toEqual(['alpha', 'mango', 'zebra']);
expect(result.every((r) => r.type === 'pbs')).toBe(true);
});
it('returns only pmg resources when type is pmg', () => {
const result = sortServiceResources(services, 'pmg');
expect(result.map((r) => r.id)).toEqual(['pmg-only']);
expect(result.every((r) => r.type === 'pmg')).toBe(true);
});
it('returns an empty array when the input list is empty', () => {
expect(sortServiceResources([], 'pbs')).toEqual([]);
});
it('does not mutate the input array', () => {
const snapshot = services.map((r) => r.id);
sortServiceResources(services, 'pbs');
expect(services.map((r) => r.id)).toEqual(snapshot);
});
});

View file

@ -0,0 +1,254 @@
import { describe, expect, it } from 'vitest';
import {
filterSummarySeriesByGroupScope,
isSummarySeriesInGroupScope,
normalizeSummarySeriesGroupScope,
resolveSummaryGroupMemberInteractionState,
resolveSummaryScopeState,
type SummarySeriesGroupScope,
} from '@/components/shared/summaryCardInteraction';
// Branch-coverage tests for the UNCOVERED guard arms of
// summaryCardInteraction.ts. The existing spec (summaryCardInteraction.test.ts)
// exercises the precedence/happy paths; this file targets the null/empty/
// malformed-input arms that never fire there. A scoped v8 coverage run against
// the existing spec alone leaves exactly 8 of 56 branch arms cold (0 functions):
// b6 normalizeSummarySeriesGroupScope: `scope.seriesIds ?? []` nullish-fallback
// b8 normalizeSummarySeriesGroupScope: valid id but `seriesIds.length === 0` -> null
// b10 normalizeSummarySeriesGroupScope: `typeof scope.label !== 'string'` else arm
// b11 normalizeSummarySeriesGroupScope: `label || null` right operand (falsy label)
// b14 isSummarySeriesInGroupScope: valid scope but empty seriesId -> false
// b19 resolveSummaryGroupMemberInteractionState: empty seriesId -> 'default'
// b37 resolveSummaryScopeState: hoveredGroupScope truthy -> group/preview return
// b43 filterSummarySeriesByGroupScope: null/invalid scope -> `[...series]`
// Every asserted value below is hand-computed against the source in
// src/components/shared/summaryCardInteraction.ts — no snapshots, no
// constant-equals-itself tautologies. Deliberately-malformed inputs (missing
// required `seriesIds`, non-string `label`) are cast via `as unknown as`.
const validScope: SummarySeriesGroupScope = {
id: 'cluster-a',
label: 'Cluster A',
seriesIds: ['alpha', 'beta'],
};
describe('summaryCardInteraction.branchcov0724pm', () => {
// -------------------------------------------------------------------------
// normalizeSummarySeriesGroupScope — the 4 normalization cold arms live here.
// The existing spec always passes a fully-populated scope with at least one
// valid series id and a string label, so every defensive branch is cold.
// -------------------------------------------------------------------------
describe('normalizeSummarySeriesGroupScope', () => {
it('returns null when seriesIds is nullish, exercising the `?? []` fallback (b6) and the empty -> null path (b8)', () => {
// `scope.seriesIds` is undefined -> the `?? []` right operand is taken
// (b6). The resulting list is empty, so `seriesIds.length === 0` is true
// (b8) and the function returns null. seriesIds is a required field on the
// type, so the malformed object is cast.
const malformed = { id: 'cluster-a' } as unknown as SummarySeriesGroupScope;
expect(normalizeSummarySeriesGroupScope(malformed)).toBeNull();
});
it('returns null when seriesIds is present but empty, proving b8 fires independently of the `?? []` fallback', () => {
// seriesIds is a real (empty) array -> `?? []` NOT taken (b6 stays cold
// here), but `seriesIds.length === 0` is still true -> b8 -> null.
expect(normalizeSummarySeriesGroupScope({ id: 'cluster-a', seriesIds: [] })).toBeNull();
});
it('coerces a non-string label to "" then null, covering the ternary else arm (b10) and the `|| null` arm (b11)', () => {
// scope.label omitted -> `typeof undefined === 'string'` is false (b10
// else arm) -> label = '' -> `'' || null` takes the right operand (b11)
// -> returned label is null. Asserts the full observable shape, not just
// the label slot.
expect(normalizeSummarySeriesGroupScope({ id: 'cluster-a', seriesIds: ['alpha'] })).toEqual({
id: 'cluster-a',
label: null,
seriesIds: ['alpha'],
});
});
it('yields label: null for a whitespace-only string label that trims to empty', () => {
// label IS a string (ternary then-arm, already covered) but trims to ''
// -> `'' || null` right operand (b11) -> label: null.
expect(
normalizeSummarySeriesGroupScope({
id: 'cluster-a',
label: ' ',
seriesIds: ['alpha'],
}),
).toEqual({
id: 'cluster-a',
label: null,
seriesIds: ['alpha'],
});
});
it('trims and preserves a real label string (then + left-operand path, regression guard for the b10/b11 neighbours)', () => {
// label is a non-empty string -> then-arm trims it -> `label || null`
// left operand -> returned verbatim. Locks the non-cold neighbour so the
// b10/b11 coverage cannot be silently regarded by a refactor. Asserting
// the full shape avoids a null-deref (the helper returns ... | null).
expect(
normalizeSummarySeriesGroupScope({
id: 'cluster-a',
label: ' Cluster A ',
seriesIds: ['alpha'],
}),
).toEqual({
id: 'cluster-a',
label: 'Cluster A',
seriesIds: ['alpha'],
});
});
});
// -------------------------------------------------------------------------
// isSummarySeriesInGroupScope — the `!normalizedSeriesId` short-circuit.
// The existing spec only ever calls this with a non-empty seriesId.
// -------------------------------------------------------------------------
describe('isSummarySeriesInGroupScope', () => {
it('returns false when the scope is valid but the seriesId is empty/blank (b14)', () => {
// normalizedScope is valid (truthy) so `!normalizedScope` is false; the
// `||` evaluates `!normalizedSeriesId` which is true for '' / ' ' /
// null -> returns false without reaching .includes.
expect(isSummarySeriesInGroupScope(validScope, '')).toBe(false);
expect(isSummarySeriesInGroupScope(validScope, ' ')).toBe(false);
expect(isSummarySeriesInGroupScope(validScope, null)).toBe(false);
expect(isSummarySeriesInGroupScope(validScope, undefined)).toBe(false);
});
it('still returns true for a real match so the b14 guard is proven not to over-reject', () => {
expect(isSummarySeriesInGroupScope(validScope, 'alpha')).toBe(true);
// 'alpha' with surrounding whitespace still matches after trim.
expect(isSummarySeriesInGroupScope(validScope, ' alpha ')).toBe(true);
});
});
// -------------------------------------------------------------------------
// resolveSummaryGroupMemberInteractionState — the empty-seriesId early
// return. The existing spec always passes a concrete seriesId.
// -------------------------------------------------------------------------
describe('resolveSummaryGroupMemberInteractionState', () => {
it("returns 'default' when seriesId is missing/blank, before consulting any group scope (b19)", () => {
// The very first guard `if (!normalizedSeriesId) return 'default';` fires
// for '' / ' ' / undefined / null.
expect(resolveSummaryGroupMemberInteractionState({})).toBe('default');
expect(resolveSummaryGroupMemberInteractionState({ seriesId: '' })).toBe('default');
expect(resolveSummaryGroupMemberInteractionState({ seriesId: ' ' })).toBe('default');
expect(resolveSummaryGroupMemberInteractionState({ seriesId: null })).toBe('default');
});
it("returns 'default' for an empty seriesId even when a hoveredGroupScope is supplied, proving the guard precedes the scope checks", () => {
// A valid hoveredGroupScope would otherwise yield 'preview' for an in-scope
// id; the empty-seriesId guard must short-circuit first.
expect(
resolveSummaryGroupMemberInteractionState({
seriesId: '',
hoveredGroupScope: validScope,
focusedGroupScope: validScope,
}),
).toBe('default');
});
});
// -------------------------------------------------------------------------
// resolveSummaryScopeState — the hoveredGroupScope truthy arm. Every existing
// call either resolves an active entity series first, or reaches this point
// only with hoveredGroupScope === null/undefined (focused path).
// -------------------------------------------------------------------------
describe('resolveSummaryScopeState', () => {
it('returns a group/preview scope state when only hoveredGroupScope is supplied (b37)', () => {
// No series ids and no explicit groupScope option -> groupScope resolves
// to hoveredGroupScope via resolveSummaryGroupScope; all three series
// guards are skipped (empty ids) -> hoveredGroupScope is truthy at
// line 150 -> kind 'group', source 'preview'.
expect(resolveSummaryScopeState({ hoveredGroupScope: validScope })).toEqual({
groupScope: validScope,
kind: 'group',
seriesId: null,
source: 'preview',
});
});
it('falls through to the hoveredGroupScope group return even when a hovered series id is filtered out by that same scope (b37 via the scope-mismatch skip)', () => {
// groupScope resolves to hoveredGroupScope (alpha,beta). hoveredSeriesId
// 'gamma' is truthy but NOT in the resolved scope -> the entity condition
// is false -> skipped -> reaches the hoveredGroupScope check -> b37.
expect(
resolveSummaryScopeState({
hoveredSeriesId: 'gamma',
hoveredGroupScope: validScope,
}),
).toEqual({
groupScope: validScope,
kind: 'group',
seriesId: null,
source: 'preview',
});
});
it('prefers a valid chartHoveredSeriesId over the hoveredGroupScope group return, confirming b37 is precedence-gated (regression guard)', () => {
// chartHoveredSeriesId 'alpha' is in the resolved scope -> entity/preview
// return wins; the hoveredGroupScope group arm (b37) is NOT taken here.
expect(
resolveSummaryScopeState({
chartHoveredSeriesId: 'alpha',
hoveredGroupScope: validScope,
}),
).toEqual({
groupScope: validScope,
kind: 'entity',
seriesId: 'alpha',
source: 'preview',
});
});
});
// -------------------------------------------------------------------------
// filterSummarySeriesByGroupScope — the no-scope -> shallow-copy arm. The
// existing spec only calls this with a valid scope (the .filter path).
// -------------------------------------------------------------------------
describe('filterSummarySeriesByGroupScope', () => {
it('returns a copy of every series when the scope is null/undefined (b43)', () => {
const series = [{ id: 'alpha' }, { id: 'beta' }, { id: 'gamma' }];
// null scope -> normalizedScope null -> `return [...series]` (b43).
const fromNull = filterSummarySeriesByGroupScope(series, null);
expect(fromNull.map((s) => s.id)).toEqual(['alpha', 'beta', 'gamma']);
// Must be a NEW array reference (shallow copy), not the input identity.
expect(fromNull).not.toBe(series);
expect(fromNull).toEqual(series);
const fromUndefined = filterSummarySeriesByGroupScope(series, undefined);
expect(fromUndefined.map((s) => s.id)).toEqual(['alpha', 'beta', 'gamma']);
expect(fromUndefined).not.toBe(series);
});
it('returns a copy of every series when the scope normalizes to null because its id is blank (b43 via invalid id)', () => {
const series = [{ id: 'alpha' }];
// id '' -> normalizeSummarySeriesGroupScope returns null -> b43.
const result = filterSummarySeriesByGroupScope(series, {
id: '',
seriesIds: ['alpha'],
});
expect(result.map((s) => s.id)).toEqual(['alpha']);
expect(result).not.toBe(series);
});
it('returns a copy of every series when the scope normalizes to null because its seriesIds are empty (b43 via empty list)', () => {
const series = [{ id: 'alpha' }, { id: 'beta' }];
// valid id but empty seriesIds -> normalize returns null -> b43.
const result = filterSummarySeriesByGroupScope(series, {
id: 'cluster-a',
seriesIds: [],
});
expect(result.map((s) => s.id)).toEqual(['alpha', 'beta']);
expect(result).not.toBe(series);
});
it('still filters down to in-scope series for a valid scope so the b43 copy path is proven not to over-include', () => {
const series = [{ id: 'alpha' }, { id: 'beta' }, { id: 'gamma' }];
const result = filterSummarySeriesByGroupScope(series, validScope);
expect(result.map((s) => s.id)).toEqual(['alpha', 'beta']);
});
});
});

View file

@ -0,0 +1,132 @@
import { describe, expect, it } from 'vitest';
import type { PhysicalDiskPresentationData } from '@/features/storageBackups/diskPresentation';
import {
getDiskDetailAttributeCards,
getDiskDetailHistoryCharts,
getLinkedDiskTemperatureTextClass,
} from '@/features/storageBackups/diskDetailPresentation';
function makeDiskData(
overrides: Partial<PhysicalDiskPresentationData> = {},
): PhysicalDiskPresentationData {
return {
node: '',
instance: '',
devPath: '',
model: '',
serial: '',
wwn: '',
size: 0,
health: 'UNKNOWN',
riskReasons: [],
wearout: -1,
type: '',
temperature: 0,
rpm: 0,
used: '',
...overrides,
};
}
describe('diskDetailPresentation.branchcov0724pm', () => {
describe('getLinkedDiskTemperatureTextClass non-positive / non-finite guard (L20)', () => {
it('returns text-muted for NaN temperature', () => {
expect(getLinkedDiskTemperatureTextClass(NaN)).toBe('text-muted');
});
it('returns text-muted for zero temperature', () => {
expect(getLinkedDiskTemperatureTextClass(0)).toBe('text-muted');
});
it('short-circuits before consulting thresholds when the guard trips', () => {
// 0 would be flagged critical by these inverted thresholds, but the
// guard returns text-muted without ever calling getMetricSeverity.
expect(getLinkedDiskTemperatureTextClass(0, { warning: -10, critical: -5 })).toBe(
'text-muted',
);
});
});
describe('getDiskDetailAttributeCards empty-collection guard (L86)', () => {
it('returns an empty array when smartAttributes is absent', () => {
expect(getDiskDetailAttributeCards(makeDiskData({ type: 'hdd' }))).toEqual([]);
});
});
describe('getDiskDetailAttributeCards SATA offlineUncorrectable arm (L129)', () => {
it('emits an Offline Uncorrectable card marked ok when the count is zero', () => {
const cards = getDiskDetailAttributeCards(
makeDiskData({ type: 'hdd', smartAttributes: { offlineUncorrectable: 0 } }),
);
expect(cards).toContainEqual({
label: 'Offline Uncorrectable',
value: '0',
ok: true,
});
});
it('emits an Offline Uncorrectable card marked not-ok when the count is non-zero', () => {
const cards = getDiskDetailAttributeCards(
makeDiskData({ type: 'hdd', smartAttributes: { offlineUncorrectable: 7 } }),
);
expect(cards).toContainEqual({
label: 'Offline Uncorrectable',
value: '7',
ok: false,
});
});
it('omits the Offline Uncorrectable card for an NVMe disk even when the field is set', () => {
const cards = getDiskDetailAttributeCards(
makeDiskData({ type: 'nvme', smartAttributes: { offlineUncorrectable: 7 } }),
);
expect(cards.find((c) => c.label === 'Offline Uncorrectable')).toBeUndefined();
});
});
describe('getDiskDetailAttributeCards SATA udmaCrcErrors arm (L136)', () => {
it('emits a CRC Errors card marked ok when the count is zero', () => {
const cards = getDiskDetailAttributeCards(
makeDiskData({ type: 'hdd', smartAttributes: { udmaCrcErrors: 0 } }),
);
expect(cards).toContainEqual({ label: 'CRC Errors', value: '0', ok: true });
});
it('emits a CRC Errors card marked not-ok when the count is non-zero', () => {
const cards = getDiskDetailAttributeCards(
makeDiskData({ type: 'hdd', smartAttributes: { udmaCrcErrors: 42 } }),
);
expect(cards).toContainEqual({ label: 'CRC Errors', value: '42', ok: false });
});
});
describe('getDiskDetailHistoryCharts SATA reallocated-sectors arm (L192)', () => {
it('emits only the smart_reallocated_sectors chart for a SATA disk with the field set', () => {
const charts = getDiskDetailHistoryCharts(
makeDiskData({
type: 'hdd',
temperature: 0,
smartAttributes: { reallocatedSectors: 3 },
}),
);
expect(charts.map((c) => c.metric)).toEqual(['smart_reallocated_sectors']);
expect(charts[0]).toMatchObject({
metric: 'smart_reallocated_sectors',
label: 'Reallocated Sectors',
unit: 'sectors',
color: '#f59e0b',
});
});
it('omits the reallocated-sectors chart for an NVMe disk even when the field is set', () => {
const charts = getDiskDetailHistoryCharts(
makeDiskData({ type: 'nvme', smartAttributes: { reallocatedSectors: 3 } }),
);
expect(charts.find((c) => c.metric === 'smart_reallocated_sectors')).toBeUndefined();
});
it('returns an empty chart list when smartAttributes is absent', () => {
expect(getDiskDetailHistoryCharts(makeDiskData({ type: 'hdd' }))).toEqual([]);
});
});
});

View file

@ -0,0 +1,140 @@
/**
* Branch-coverage tests for alertDetailPresentation.ts (0724pm batch).
*
* Measured gap against the module's own existing spec
* (alertDetailPresentation.test.ts): 4 of 28 branches uncovered,
* 0 uncovered functions. This file targets exactly those 4 cold arms:
*
* - formatPlatformAlertResourceType: `?? type` label fallback (line 87)
* reached when a provider's label table has no entry for the given
* ResourceType (a realistic cold arm — the backend emitting a type
* the provider's presentation table does not cover).
* - formatPlatformAlertEntityType: the `normalized === 'host'` arm (line 92)
* existing tests cover 'vm' / 'datastore' but never 'host'.
* - formatPlatformAlertStartedAt: the valid-date fall-through to toLocaleString
* (line 102 -> 110); existing tests only feed absent / pre-2000 values.
* - formatPlatformAlertDetailDateTime: the valid-date fall-through to
* toLocaleString (line 117 -> 126); existing tests only feed absent /
* NaN / pre-2000 values.
*
* Does NOT modify the source module or any existing test file.
*/
import { beforeAll, afterAll, describe, expect, it } from 'vitest';
import {
formatPlatformAlertDetailDateTime,
formatPlatformAlertEntityType,
formatPlatformAlertResourceType,
formatPlatformAlertStartedAt,
} from '@/utils/alertDetailPresentation';
/* ------------------------------------------------------------------ *
* formatPlatformAlertResourceType — `?? type` fallback (line 87)
*
* RESOURCE_TYPE_LABELS[provider] is a Partial<Record<ResourceType,string>>.
* A provider alert whose resource type the table does not list falls
* through to the raw type string. This arm was never exercised by the
* existing suite, which only passes in-table (provider, type) pairs.
* ------------------------------------------------------------------ */
describe('formatPlatformAlertResourceType - unmapped resource-type label fallback', () => {
it('returns the raw type when the docker label table has no entry for it', () => {
// 'pbs' is a valid ResourceType but is absent from the docker label
// table (docker only labels containers/services/images/etc.).
expect(formatPlatformAlertResourceType('pbs', 'docker')).toBe('pbs');
});
});
/* ------------------------------------------------------------------ *
* formatPlatformAlertEntityType — `normalized === 'host'` arm (line 92)
*
* Existing tests cover 'vm', 'datastore', the generic title-case arm, and
* the empty-string placeholder, but never the dedicated 'host' canonical
* spelling.
* ------------------------------------------------------------------ */
describe('formatPlatformAlertEntityType - host canonicalization', () => {
it('canonicalizes a lower-case host token to the Host label', () => {
expect(formatPlatformAlertEntityType('host')).toBe('Host');
});
it('canonicalizes host regardless of surrounding whitespace or casing', () => {
// value.trim().toLowerCase() normalizes before the comparison, so a
// whitespace-padded / mixed-case host still hits the canonical arm
// rather than the generic title-case arm.
expect(formatPlatformAlertEntityType(' HoSt ')).toBe('Host');
});
});
/* ------------------------------------------------------------------ *
* Date formatters — valid-timestamp happy paths
*
* Both functions hardcode their Intl options and accept no timeZone /
* locale overrides, so output is locale- and timezone-dependent. To stay
* deterministic AND non-tautological we:
* 1. pin process.env.TZ = 'UTC' so the rendered day is fixed, then
* restore it afterwards;
* 2. assert the exact locale-rendered string by mirroring the same
* option set the function hardcodes (matches the runtime default
* locale the function itself uses);
* 3. assert a structural, locale-independent fact that distinguishes the
* two formats — startedAt OMITS the year, detailDateTime INCLUDES it.
* A swapped-options regression would be caught here.
*
* The existing suite only covers the placeholder / fallback arms of these
* functions (absent value, NaN date, pre-2000 date); the post-2000 valid
* timestamp fall-through was never reached.
* ------------------------------------------------------------------ */
describe('platform alert date formatting - present, post-2000 timestamps', () => {
const previousTz = process.env.TZ;
beforeAll(() => {
process.env.TZ = 'UTC';
});
afterAll(() => {
if (previousTz === undefined) delete process.env.TZ;
else process.env.TZ = previousTz;
});
it('formatPlatformAlertStartedAt renders a valid timestamp, omitting the year (table format)', () => {
const value = '2024-03-15T14:30:00Z';
const result = formatPlatformAlertStartedAt(value);
// Happy path, not the placeholder and not a raw passthrough.
expect(result).not.toBe('-');
expect(result).not.toBe(value);
// The compact table format has no year — the structural difference
// from the detail format and from the raw ISO string.
expect(result).not.toMatch(/2024/);
// Under TZ=UTC the day-of-month renders as 15 in every locale.
expect(result).toMatch(/15/);
// Mirrors the exact option set the function hardcodes; pinned to the
// concrete en-US rendering under TZ=UTC (the structural regex checks
// above carry the locale-independent verification).
expect(result).toBe('Mar 15, 02:30 PM');
});
it('formatPlatformAlertDetailDateTime renders a valid timestamp, including the year (detail format)', () => {
const value = '2024-03-15T14:30:00Z';
const result = formatPlatformAlertDetailDateTime(value);
expect(result).not.toBe('-');
expect(result).not.toBe(value);
// The detail format includes the year — distinguishing it from the
// table format and proving the two option sets were not swapped.
expect(result).toMatch(/2024/);
// Under TZ=UTC the day-of-month renders as 15 in every locale.
expect(result).toMatch(/15/);
// Concrete en-US rendering under TZ=UTC; structural checks above carry the
// locale-independent verification (presence of the year + day 15).
expect(result).toBe('Mar 15, 2024, 02:30 PM');
});
});

View file

@ -0,0 +1,290 @@
/**
* Branch-coverage tests for commercialBillingModel.ts (0724pm batch).
*
* Measured baseline (existing commercialBillingModel.test.ts only):
* functions: 2/5 covered
* - asUnlimitedLimit (line 50) NEVER called
* - buildHostedCommercialPlanModel (line 120) NEVER called
* - buildHostedCommercialUsageModel (line 153) NEVER called
* branches : 3/5 arms covered
* - cold: `input.licensedEmail || 'Not available'` (line 62) false arm
* - cold: `input.planTerms ? [...] : []` (line 70) false arm
*
* This file exercises:
* - asUnlimitedLimit (private) via buildHostedCommercialUsageModel:
* positive limit, zero, negative, absent (status null), and a
* deliberately-malformed non-number value.
* - buildHostedCommercialPlanModel: status present (email shown) and
* status null ('Not configured' fallback), plus full summary shape.
* - buildHostedCommercialUsageModel: guests limit shown vs absent;
* guest usage below / at / above the limit; zero usage.
* - buildSelfHostedBaseDetails cold arms reached via
* buildSelfHostedCommercialPlanModel: empty licensedEmail and
* absent planTerms.
*
* Does NOT modify the source module or any existing test file.
*/
import { describe, expect, it } from 'vitest';
import {
buildHostedCommercialPlanModel,
buildHostedCommercialUsageModel,
buildSelfHostedCommercialPlanModel,
type HostedCommercialModelInput,
} from '../commercialBillingModel';
const createHostedBaseInput = (): HostedCommercialModelInput => ({
status: {
email: 'owner@example.com',
is_lifetime: false,
expires_at: '2026-12-31',
max_guests: 10,
},
tierLabel: 'Business',
licenseStatusLabel: 'Active',
organizationCount: 3,
memberCount: 24,
nodeUsage: 7,
guestUsage: 5,
renewsOrExpires: 'Renews 2026-12-31',
});
/* ------------------------------------------------------------------ *
* asUnlimitedLimit — exercised solely through buildHostedCommercialUsageModel
* typeof value === 'number' && value > 0 ? value : undefined
*
* Arms:
* (A) number && > 0 → returns the value (limit shown)
* (B) else → undefined (covers 0, negative, absent, non-number)
* ------------------------------------------------------------------ */
describe('asUnlimitedLimit via buildHostedCommercialUsageModel - positive limit shown', () => {
it('surfaces max_guests as the guests meter limit when it is a positive number', () => {
const model = buildHostedCommercialUsageModel(createHostedBaseInput());
const guests = model.meters[1];
expect(guests.limit).toBe(10);
expect(guests.current).toBe(5);
expect(guests.label).toBe('Guests');
expect(guests.accentClass).toBe('bg-emerald-600 dark:bg-emerald-500');
});
});
describe('asUnlimitedLimit via buildHostedCommercialUsageModel - zero limit hidden', () => {
it('drops the guests limit when max_guests is 0 (returns undefined, no limit shown)', () => {
// NOTE: src/api/license.ts:19 documents "0 means unlimited" for limit
// fields, but asUnlimitedLimit treats 0 as "no limit to display"
// (value > 0 is false). See GLM_REPORT for the suspected source mismatch.
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
status: {
email: 'owner@example.com',
is_lifetime: false,
expires_at: '2026-12-31',
max_guests: 0,
},
});
expect(model.meters[1].limit).toBeUndefined();
});
});
describe('asUnlimitedLimit via buildHostedCommercialUsageModel - absent status', () => {
it('drops the guests limit and still reports node/guest usage when status is null', () => {
// Exercises both the `status?.` optional-chaining short-circuit and the
// asUnlimitedLimit(undefined) arm.
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
status: null,
});
expect(model.meters[1].limit).toBeUndefined();
// Monitored Systems meter is unaffected by status.
expect(model.meters[0]).toEqual({
label: 'Monitored Systems',
current: 7,
accentClass: 'bg-blue-600 dark:bg-blue-500',
});
expect(model.meters[1].current).toBe(5);
});
});
describe('asUnlimitedLimit via buildHostedCommercialUsageModel - malformed non-number', () => {
it('drops the guests limit when max_guests is a non-number string (typeof guard false arm)', () => {
// Deliberately-malformed input: the declared type is number, but a string
// still reaches the guard at runtime; `typeof value === 'number'` is false.
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
status: {
email: 'owner@example.com',
is_lifetime: false,
expires_at: '2026-12-31',
max_guests: '10' as unknown as number,
},
});
expect(model.meters[1].limit).toBeUndefined();
});
});
/* ------------------------------------------------------------------ *
* buildHostedCommercialPlanModel — optional `status` field
*
* Arms in `input.status?.email || 'Not configured'` (line 144):
* (A) status present + email present → email
* (B) status null / email absent → 'Not configured'
* ------------------------------------------------------------------ */
describe('buildHostedCommercialPlanModel - status present surfaces licensed email', () => {
it('builds the full summary and shows the licensed email in details', () => {
const model = buildHostedCommercialPlanModel(createHostedBaseInput());
expect(model.summary).toEqual([
{ label: 'Plan Tier', value: 'Business' },
{ label: 'License Status', value: 'Active' },
{ label: 'Organizations', value: 3 },
{ label: 'Members (Current Org)', value: 24 },
]);
expect(model.details).toEqual([
{ label: 'Licensed Email', value: 'owner@example.com' },
{ label: 'Renews / Expires', value: 'Renews 2026-12-31' },
]);
});
});
describe('buildHostedCommercialPlanModel - status null falls back to Not configured', () => {
it('shows "Not configured" for the licensed email when status is absent', () => {
const model = buildHostedCommercialPlanModel({
...createHostedBaseInput(),
status: null,
});
expect(model.details[0]).toEqual({
label: 'Licensed Email',
value: 'Not configured',
});
// Summary still reflects the non-optional scalar inputs.
expect(model.summary[2]).toEqual({ label: 'Organizations', value: 3 });
});
});
describe('buildHostedCommercialPlanModel - status present but email empty', () => {
it('falls back to "Not configured" when status exists but email is empty string', () => {
const model = buildHostedCommercialPlanModel({
...createHostedBaseInput(),
status: {
email: '',
is_lifetime: true,
expires_at: null,
max_guests: undefined,
},
});
expect(model.details[0]).toEqual({
label: 'Licensed Email',
value: 'Not configured',
});
});
});
/* ------------------------------------------------------------------ *
* buildHostedCommercialUsageModel — guest usage relative to the limit
*
* The model is a pure passthrough for `current`; it does not branch on
* whether usage is at / below / above the limit. These cases assert the
* observable current+limit pair is faithfully recorded in each regime.
* ------------------------------------------------------------------ */
describe('buildHostedCommercialUsageModel - guest usage below the limit', () => {
it('records current 5 against limit 10', () => {
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
guestUsage: 5,
});
expect(model.meters[1]).toMatchObject({ current: 5, limit: 10 });
});
});
describe('buildHostedCommercialUsageModel - guest usage at the limit', () => {
it('records current 10 against limit 10', () => {
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
guestUsage: 10,
});
expect(model.meters[1]).toMatchObject({ current: 10, limit: 10 });
});
});
describe('buildHostedCommercialUsageModel - guest usage above the limit', () => {
it('records current 15 against limit 10 (model does not clamp)', () => {
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
guestUsage: 15,
});
expect(model.meters[1]).toMatchObject({ current: 15, limit: 10 });
});
});
describe('buildHostedCommercialUsageModel - zero guest usage', () => {
it('records current 0 and still surfaces the configured limit', () => {
const model = buildHostedCommercialUsageModel({
...createHostedBaseInput(),
guestUsage: 0,
});
expect(model.meters[1]).toMatchObject({ current: 0, limit: 10 });
});
});
/* ------------------------------------------------------------------ *
* buildSelfHostedBaseDetails — cold arms reached via
* buildSelfHostedCommercialPlanModel (no retailPlanDefinition so the
* base-details path is exercised).
*
* Cold arm 1: `input.licensedEmail || 'Not available'` (line 62)
* Cold arm 2: `input.planTerms ? [...] : []` (line 70)
* ------------------------------------------------------------------ */
describe('buildSelfHostedBaseDetails - empty licensed email falls back to Not available', () => {
it('shows "Not available" when licensedEmail is undefined', () => {
const model = buildSelfHostedCommercialPlanModel({
statusLabel: 'Active',
tierLabel: 'Pro',
expires: '12/31/2026',
daysRemaining: 30,
retailPlanDefinition: null,
});
const licensedEmailRow = model.details.find((d) => d.label === 'Licensed Email');
expect(licensedEmailRow).toEqual({ label: 'Licensed Email', value: 'Not available' });
});
});
describe('buildSelfHostedBaseDetails - absent plan terms omits the Plan Terms row', () => {
it('produces only 4 detail rows (no Plan Terms) when planTerms is undefined', () => {
const model = buildSelfHostedCommercialPlanModel({
licensedEmail: 'owner@example.com',
statusLabel: 'Active',
tierLabel: 'Pro',
expires: '12/31/2026',
daysRemaining: 30,
retailPlanDefinition: null,
});
expect(model.details.map((d) => d.label)).toEqual([
'Tier',
'Licensed Email',
'Expires',
'Days Remaining',
]);
expect(model.details.some((d) => d.label === 'Plan Terms')).toBe(false);
});
});
describe('buildSelfHostedBaseDetails - both cold arms together', () => {
it('shows "Not available" and omits Plan Terms when both are absent', () => {
const model = buildSelfHostedCommercialPlanModel({
statusLabel: 'Active',
tierLabel: 'Pro',
expires: '12/31/2026',
daysRemaining: 0,
retailPlanDefinition: null,
});
expect(model.details).toEqual([
{ label: 'Tier', value: 'Pro' },
{ label: 'Licensed Email', value: 'Not available' },
{ label: 'Expires', value: '12/31/2026' },
{ label: 'Days Remaining', value: 0 },
]);
});
});

View file

@ -0,0 +1,38 @@
import { describe, expect, it } from 'vitest';
import { canonicalizeFrontendResourceType } from '@/utils/resourceTypeCompat';
describe('resourceTypeCompat.branchcov0724pm', () => {
it('rejects non-string input by returning undefined', () => {
expect(canonicalizeFrontendResourceType(123)).toBeUndefined();
expect(canonicalizeFrontendResourceType(0)).toBeUndefined();
expect(canonicalizeFrontendResourceType(true)).toBeUndefined();
expect(canonicalizeFrontendResourceType(null)).toBeUndefined();
expect(canonicalizeFrontendResourceType(undefined)).toBeUndefined();
expect(canonicalizeFrontendResourceType({ type: 'agent' })).toBeUndefined();
expect(canonicalizeFrontendResourceType(['agent'])).toBeUndefined();
});
it('rejects empty and whitespace-only strings by returning undefined', () => {
expect(canonicalizeFrontendResourceType('')).toBeUndefined();
expect(canonicalizeFrontendResourceType(' ')).toBeUndefined();
expect(canonicalizeFrontendResourceType('\t\n')).toBeUndefined();
});
it('canonicalizes the remaining hyphenated / unseparated legacy aliases', () => {
expect(canonicalizeFrontendResourceType('dockerhost')).toBe('docker-host');
expect(canonicalizeFrontendResourceType('swarm-secret')).toBe('docker-secret');
expect(canonicalizeFrontendResourceType('swarm-config')).toBe('docker-config');
expect(canonicalizeFrontendResourceType('kubernetes-cluster')).toBe('k8s-cluster');
});
it('identity-maps canonical type strings that only appear in the second switch arm', () => {
expect(canonicalizeFrontendResourceType('agent')).toBe('agent');
expect(canonicalizeFrontendResourceType('storage')).toBe('storage');
expect(canonicalizeFrontendResourceType('disk')).toBe('disk');
expect(canonicalizeFrontendResourceType('docker-host')).toBe('docker-host');
});
it('maps the bare "node" legacy alias to agent', () => {
expect(canonicalizeFrontendResourceType('node')).toBe('agent');
});
});

View file

@ -0,0 +1,125 @@
/**
* Branch-coverage tests for resourceTypePresentation.ts (0724pm batch).
*
* Measured baseline (existing resourceTypePresentation.test.ts only):
* functions: 4/4 covered
* branches : 6/10 arms covered -> 4 cold
*
* Cold arms found by scoped v8 coverage (coverage-final.json):
* branch#2 L240:40-72 `canonicalResourceTypeForDisplay`
* `canonicalizeFrontendResourceType(value) || value.trim().toLowerCase()`
* -> the `value.trim().toLowerCase()` fallback, taken when the
* input is a non-empty string that is NOT a known canonical alias.
* branch#4 L245:21-33 `getResourceTypePresentation`
* `if (!resourceType) return null;`
* -> the `return null` arm (falsy / empty / undefined resourceType).
* branch#7 L251:27-256 `getResourceTypePresentation`
* `if (presentation) return presentation;` fall-through to the
* default-badge object returned for an unknown canonical type.
* branch#9 L259:45-59 `getResourceTypeLabel`
* `getResourceTypePresentation(resourceType)?.label || null`
* -> the `|| null` arm, taken when no presentation resolves.
*
* This file exercises each cold arm with real observable values. It does NOT
* modify the source module or any existing test file.
*
* Suspected SOURCE bug (reported, NOT fixed): canonicalResourceTypeForDisplay
* (line 239) is typed `(value: string)` and falls back to `value.trim().toLowerCase()`.
* Its callee canonicalizeFrontendResourceType accepts `unknown` and gracefully
* returns undefined for non-strings, but this wrapper calls `.trim()` on the
* raw value and throws TypeError if a non-string reaches it at runtime
* (see the "malformed non-string input" test below).
*/
import { describe, expect, it } from 'vitest';
import {
canonicalResourceTypeForDisplay,
getResourceTypeLabel,
getResourceTypePresentation,
} from '@/utils/resourceTypePresentation';
const DEFAULT_BADGE_CLASSES = 'bg-surface-alt text-base-content';
/* ------------------------------------------------------------------ *
* branch#2 - canonicalResourceTypeForDisplay fallback arm
* canonicalizeFrontendResourceType(value) || value.trim().toLowerCase()
*
* The fallback fires for any non-empty string that is NOT one of the
* canonical aliases defined in resourceTypeCompat.ts.
* ------------------------------------------------------------------ */
describe('canonicalResourceTypeForDisplay - unknown string falls back to trimmed lowercase form', () => {
it('returns the trimmed, lowercased value when canonicalization has no mapping', () => {
expect(canonicalResourceTypeForDisplay('totally-unknown-xyz')).toBe('totally-unknown-xyz');
});
});
/* ------------------------------------------------------------------ *
* branch#2 + branch#7 - getResourceTypePresentation unknown-type default
*
* externalPresentation undefined (line 247-248, already hot)
* -> canonicalType from canonicalResourceTypeForDisplay fallback (branch#2)
* -> presentation undefined (line 250-251)
* -> fall through to the default badge object (branch#7)
* ------------------------------------------------------------------ */
describe('getResourceTypePresentation - unknown type resolves to a default badge', () => {
it('uses the canonicalized type as the label with the default badge classes', () => {
const presentation = getResourceTypePresentation('totally-unknown-xyz');
expect(presentation).toEqual({
label: 'totally-unknown-xyz',
badgeClasses: DEFAULT_BADGE_CLASSES,
});
});
it('returns a distinct object per call (default is built inline, not cached)', () => {
const a = getResourceTypePresentation('unknown-a');
const b = getResourceTypePresentation('unknown-b');
expect(a).not.toBe(b);
expect(a?.label).toBe('unknown-a');
expect(b?.label).toBe('unknown-b');
});
});
/* ------------------------------------------------------------------ *
* branch#4 - getResourceTypePresentation falsy resourceType arm
* if (!resourceType) return null;
* ------------------------------------------------------------------ */
describe('getResourceTypePresentation - falsy resourceType returns null', () => {
it('returns null for an empty string', () => {
expect(getResourceTypePresentation('')).toBeNull();
});
});
/* ------------------------------------------------------------------ *
* branch#9 - getResourceTypeLabel null-when-unresolved arm
* getResourceTypePresentation(resourceType)?.label || null
*
* Fires when getResourceTypePresentation itself returns null (falsy input),
* leaving `?.label` undefined and the `|| null` arm to provide the value.
* ------------------------------------------------------------------ */
describe('getResourceTypeLabel - returns null when no presentation resolves', () => {
it('returns null for an empty string', () => {
expect(getResourceTypeLabel('')).toBeNull();
});
});
/* ------------------------------------------------------------------ *
* Malformed input (deliberately mistyped) - documents suspected source bug.
*
* canonicalResourceTypeForDisplay is typed (value: string) but its callee
* accepts unknown. A non-string passed at runtime reaches the fallback
* `value.trim()` and throws TypeError instead of degrading gracefully.
* Reported in GLM_REPORT; NOT fixed here.
* ------------------------------------------------------------------ */
describe('canonicalResourceTypeForDisplay - malformed non-string input', () => {
it('throws TypeError because the fallback calls .trim() on the raw value', () => {
expect(() => canonicalResourceTypeForDisplay(123 as unknown as string)).toThrow(TypeError);
expect(() => canonicalResourceTypeForDisplay(123 as unknown as string)).toThrow(
'value.trim is not a function',
);
});
});