Skip to content

Commit 74d521b

Browse files
committed
Merge pull request #569 from ndycode/claude/audit-50-dashboard-settings-data-tests
test: cover dashboard settings clone defaults and equality semantics
2 parents 764a38e + 730d6aa commit 74d521b

1 file changed

Lines changed: 197 additions & 0 deletions

File tree

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
import { describe, expect, it } from "vitest";
2+
import {
3+
cloneDashboardSettingsData,
4+
dashboardSettingsDataEqual,
5+
} from "../lib/codex-manager/dashboard-settings-data.js";
6+
import {
7+
DEFAULT_DASHBOARD_DISPLAY_SETTINGS,
8+
type DashboardDisplaySettings,
9+
type DashboardStatuslineField,
10+
} from "../lib/dashboard-settings.js";
11+
import { resolveMenuLayoutMode } from "../lib/codex-manager/settings-hub.js";
12+
13+
const DEFAULT_FIELDS: DashboardStatuslineField[] = [
14+
"last-used",
15+
"limits",
16+
"status",
17+
];
18+
19+
// The real production deps: the actual layout-mode resolver, and a
20+
// normalizer matching dashboard-settings semantics (default on absence).
21+
const deps = {
22+
resolveMenuLayoutMode,
23+
normalizeStatuslineFields: (
24+
fields: DashboardDisplaySettings["menuStatuslineFields"],
25+
) => fields ?? DEFAULT_FIELDS,
26+
};
27+
28+
function settings(
29+
overrides: Partial<DashboardDisplaySettings> = {},
30+
): DashboardDisplaySettings {
31+
return { ...DEFAULT_DASHBOARD_DISPLAY_SETTINGS, ...overrides };
32+
}
33+
34+
// A sparse settings object as it comes from an older settings.json that
35+
// predates most optional fields.
36+
const SPARSE: DashboardDisplaySettings = {
37+
showPerAccountRows: true,
38+
showQuotaDetails: false,
39+
showForecastReasons: true,
40+
showRecommendations: false,
41+
showLiveProbeNotes: true,
42+
};
43+
44+
describe("cloneDashboardSettingsData", () => {
45+
it("fills every optional field with its documented default", () => {
46+
const clone = cloneDashboardSettingsData(SPARSE, deps);
47+
48+
expect(clone).toMatchObject({
49+
actionAutoReturnMs: 2_000,
50+
actionPauseOnKey: true,
51+
menuAutoFetchLimits: true,
52+
menuSortEnabled: true,
53+
menuSortMode: "ready-first",
54+
menuSortPinCurrent: false,
55+
menuSortQuickSwitchVisibleRow: true,
56+
uiThemePreset: "green",
57+
uiAccentColor: "green",
58+
menuShowStatusBadge: true,
59+
menuShowCurrentBadge: true,
60+
menuShowLastUsed: true,
61+
menuShowQuotaSummary: true,
62+
menuShowQuotaCooldown: true,
63+
menuShowFetchStatus: true,
64+
menuQuotaTtlMs: 5 * 60_000,
65+
menuFocusStyle: "row-invert",
66+
menuHighlightCurrentRow: true,
67+
menuLayoutMode: "compact-details",
68+
menuShowDetailsForUnselectedRows: false,
69+
menuStatuslineFields: DEFAULT_FIELDS,
70+
});
71+
// The required flags pass through unchanged.
72+
expect(clone.showQuotaDetails).toBe(false);
73+
expect(clone.showRecommendations).toBe(false);
74+
});
75+
76+
it("derives layout mode and the unselected-rows flag together", () => {
77+
const expanded = cloneDashboardSettingsData(
78+
{ ...SPARSE, menuShowDetailsForUnselectedRows: true },
79+
deps,
80+
);
81+
expect(expanded.menuLayoutMode).toBe("expanded-rows");
82+
expect(expanded.menuShowDetailsForUnselectedRows).toBe(true);
83+
84+
// An explicit layout mode wins over the legacy boolean.
85+
const compact = cloneDashboardSettingsData(
86+
{
87+
...SPARSE,
88+
menuLayoutMode: "compact-details",
89+
menuShowDetailsForUnselectedRows: true,
90+
},
91+
deps,
92+
);
93+
expect(compact.menuLayoutMode).toBe("compact-details");
94+
expect(compact.menuShowDetailsForUnselectedRows).toBe(false);
95+
});
96+
97+
it("copies the statusline fields instead of aliasing the input array", () => {
98+
const fields: DashboardStatuslineField[] = ["limits"];
99+
const clone = cloneDashboardSettingsData(
100+
{ ...SPARSE, menuStatuslineFields: fields },
101+
deps,
102+
);
103+
104+
expect(clone.menuStatuslineFields).toEqual(["limits"]);
105+
expect(clone.menuStatuslineFields).not.toBe(fields);
106+
});
107+
});
108+
109+
describe("dashboardSettingsDataEqual", () => {
110+
it("treats absent optional fields as equal to their defaults", () => {
111+
expect(
112+
dashboardSettingsDataEqual(
113+
SPARSE,
114+
cloneDashboardSettingsData(SPARSE, deps),
115+
deps,
116+
),
117+
).toBe(true);
118+
// A fully-defaulted object equals the sparse one when the required
119+
// flags agree.
120+
expect(
121+
dashboardSettingsDataEqual(
122+
settings({ showQuotaDetails: false, showRecommendations: false }),
123+
SPARSE,
124+
deps,
125+
),
126+
).toBe(true);
127+
});
128+
129+
it.each([
130+
["menuQuotaTtlMs", { menuQuotaTtlMs: 60_000 }],
131+
["uiThemePreset", { uiThemePreset: "mono" }],
132+
["uiAccentColor", { uiAccentColor: "cyan" }],
133+
["showQuotaDetails", { showQuotaDetails: false }],
134+
["showPerAccountRows", { showPerAccountRows: false }],
135+
["actionAutoReturnMs", { actionAutoReturnMs: 500 }],
136+
["actionPauseOnKey", { actionPauseOnKey: false }],
137+
["menuAutoFetchLimits", { menuAutoFetchLimits: false }],
138+
["menuSortEnabled", { menuSortEnabled: false }],
139+
["menuSortPinCurrent", { menuSortPinCurrent: true }],
140+
[
141+
"menuSortQuickSwitchVisibleRow",
142+
{ menuSortQuickSwitchVisibleRow: false },
143+
],
144+
["menuShowStatusBadge", { menuShowStatusBadge: false }],
145+
["menuShowCurrentBadge", { menuShowCurrentBadge: false }],
146+
["menuShowLastUsed", { menuShowLastUsed: false }],
147+
["menuShowQuotaSummary", { menuShowQuotaSummary: false }],
148+
["menuShowQuotaCooldown", { menuShowQuotaCooldown: false }],
149+
["menuShowFetchStatus", { menuShowFetchStatus: false }],
150+
["menuHighlightCurrentRow", { menuHighlightCurrentRow: false }],
151+
] as const satisfies ReadonlyArray<
152+
readonly [string, Partial<DashboardDisplaySettings>]
153+
>)("detects a difference in %s alone", (_field, override) => {
154+
expect(
155+
dashboardSettingsDataEqual(settings(), settings(override), deps),
156+
).toBe(false);
157+
});
158+
159+
it("compares layout through the resolver, not the raw fields", () => {
160+
// expanded-rows expressed via the legacy boolean equals the explicit
161+
// layout-mode spelling (no explicit menuLayoutMode on the left, since
162+
// an explicit mode would win over the boolean).
163+
expect(
164+
dashboardSettingsDataEqual(
165+
{ ...SPARSE, menuShowDetailsForUnselectedRows: true },
166+
{ ...SPARSE, menuLayoutMode: "expanded-rows" },
167+
deps,
168+
),
169+
).toBe(true);
170+
expect(
171+
dashboardSettingsDataEqual(
172+
settings({ menuLayoutMode: "expanded-rows" }),
173+
settings({ menuLayoutMode: "compact-details" }),
174+
deps,
175+
),
176+
).toBe(false);
177+
});
178+
179+
it("compares statusline fields through the normalizer", () => {
180+
// Absent fields equal the normalized default list...
181+
expect(
182+
dashboardSettingsDataEqual(
183+
settings({ menuStatuslineFields: undefined }),
184+
settings({ menuStatuslineFields: DEFAULT_FIELDS }),
185+
deps,
186+
),
187+
).toBe(true);
188+
// ...but a different order is a real difference.
189+
expect(
190+
dashboardSettingsDataEqual(
191+
settings({ menuStatuslineFields: ["limits", "status", "last-used"] }),
192+
settings({ menuStatuslineFields: DEFAULT_FIELDS }),
193+
deps,
194+
),
195+
).toBe(false);
196+
});
197+
});

0 commit comments

Comments
 (0)