Skip to content

Commit e396ece

Browse files
committed
Merge PR #287: refactor: extract settings hub entry wrapper
2 parents 3a1c56e + bf626e6 commit e396ece

5 files changed

Lines changed: 286 additions & 2 deletions

File tree

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
import type { MenuItem, SelectOptions } from "../ui/select.js";
2+
import type { SettingsHubActionType } from "./unified-settings-controller.js";
3+
4+
export async function promptSettingsHubEntry<
5+
TAction extends { type: SettingsHubActionType },
6+
>(params: {
7+
initialFocus: TAction["type"];
8+
promptSettingsHubMenu: (
9+
initialFocus: TAction["type"],
10+
deps: {
11+
isInteractive: () => boolean;
12+
getUiRuntimeOptions: () => ReturnType<
13+
typeof import("../ui/runtime.js").getUiRuntimeOptions
14+
>;
15+
buildItems: () => MenuItem<TAction>[];
16+
findInitialCursor: (
17+
items: MenuItem<TAction>[],
18+
initialFocus: TAction["type"],
19+
) => number | undefined;
20+
select: <T>(
21+
items: MenuItem<T>[],
22+
options: SelectOptions<T>,
23+
) => Promise<T | null>;
24+
copy: {
25+
title: string;
26+
subtitle: string;
27+
help: string;
28+
};
29+
},
30+
) => Promise<TAction | null>;
31+
isInteractive: () => boolean;
32+
getUiRuntimeOptions: () => ReturnType<
33+
typeof import("../ui/runtime.js").getUiRuntimeOptions
34+
>;
35+
buildItems: () => MenuItem<TAction>[];
36+
findInitialCursor: (
37+
items: MenuItem<TAction>[],
38+
initialFocus: TAction["type"],
39+
) => number | undefined;
40+
select: <T>(
41+
items: MenuItem<T>[],
42+
options: SelectOptions<T>,
43+
) => Promise<T | null>;
44+
copy: {
45+
title: string;
46+
subtitle: string;
47+
help: string;
48+
};
49+
}): Promise<TAction | null> {
50+
return params.promptSettingsHubMenu(params.initialFocus, {
51+
isInteractive: params.isInteractive,
52+
getUiRuntimeOptions: params.getUiRuntimeOptions,
53+
buildItems: params.buildItems,
54+
findInitialCursor: params.findInitialCursor,
55+
select: params.select,
56+
copy: params.copy,
57+
});
58+
}

lib/codex-manager/settings-hub.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,6 +75,7 @@ import {
7575
} from "./experimental-settings-schema.js";
7676
import { loadExperimentalSyncTargetState } from "./experimental-sync-target.js";
7777
import { loadExperimentalSyncTargetEntry } from "./experimental-sync-target-entry.js";
78+
import { promptSettingsHubEntry } from "./settings-hub-entry.js";
7879
import {
7980
buildSettingsHubItems,
8081
findSettingsHubInitialCursor,
@@ -106,6 +107,7 @@ import {
106107
configureUnifiedSettingsController,
107108
type SettingsHubActionType,
108109
} from "./unified-settings-controller.js";
110+
import { configureUnifiedSettingsEntry } from "./unified-settings-entry.js";
109111

110112
const DASHBOARD_DISPLAY_OPTIONS: DashboardDisplaySettingOption[] = [
111113
{
@@ -740,7 +742,9 @@ async function configureBackendSettings(
740742
async function promptSettingsHub(
741743
initialFocus: SettingsHubAction["type"] = "account-list",
742744
): Promise<SettingsHubAction | null> {
743-
return promptSettingsHubMenu(initialFocus, {
745+
return promptSettingsHubEntry({
746+
initialFocus,
747+
promptSettingsHubMenu,
744748
isInteractive: () => input.isTTY && output.isTTY,
745749
getUiRuntimeOptions,
746750
buildItems: () => buildSettingsHubItems(UI_COPY.settings),
@@ -759,7 +763,8 @@ async function promptSettingsHub(
759763
async function configureUnifiedSettings(
760764
initialSettings?: DashboardDisplaySettings,
761765
): Promise<DashboardDisplaySettings> {
762-
return configureUnifiedSettingsController(initialSettings, {
766+
return configureUnifiedSettingsEntry(initialSettings, {
767+
configureUnifiedSettingsController,
763768
cloneDashboardSettings,
764769
cloneBackendPluginConfig,
765770
loadDashboardDisplaySettings,
Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,128 @@
1+
import type { DashboardDisplaySettings } from "../dashboard-settings.js";
2+
import type { PluginConfig } from "../types.js";
3+
import type { SettingsHubActionType } from "./unified-settings-controller.js";
4+
5+
export async function configureUnifiedSettingsEntry(
6+
initialSettings: DashboardDisplaySettings | undefined,
7+
deps: {
8+
configureUnifiedSettingsController: (
9+
initialSettings: DashboardDisplaySettings | undefined,
10+
deps: {
11+
cloneDashboardSettings: (
12+
settings: DashboardDisplaySettings,
13+
) => DashboardDisplaySettings;
14+
cloneBackendPluginConfig: (config: PluginConfig) => PluginConfig;
15+
loadDashboardDisplaySettings: () => Promise<DashboardDisplaySettings>;
16+
loadPluginConfig: () => PluginConfig;
17+
applyUiThemeFromDashboardSettings: (
18+
settings: DashboardDisplaySettings,
19+
) => void;
20+
promptSettingsHub: (
21+
focus: SettingsHubActionType,
22+
) => Promise<{ type: SettingsHubActionType } | null>;
23+
configureDashboardDisplaySettings: (
24+
current: DashboardDisplaySettings,
25+
) => Promise<DashboardDisplaySettings>;
26+
configureStatuslineSettings: (
27+
current: DashboardDisplaySettings,
28+
) => Promise<DashboardDisplaySettings>;
29+
promptBehaviorSettings: (
30+
current: DashboardDisplaySettings,
31+
) => Promise<DashboardDisplaySettings | null>;
32+
promptThemeSettings: (
33+
current: DashboardDisplaySettings,
34+
) => Promise<DashboardDisplaySettings | null>;
35+
dashboardSettingsEqual: (
36+
left: DashboardDisplaySettings,
37+
right: DashboardDisplaySettings,
38+
) => boolean;
39+
persistDashboardSettingsSelection: (
40+
selected: DashboardDisplaySettings,
41+
keys: readonly (keyof DashboardDisplaySettings)[],
42+
scope: string,
43+
) => Promise<DashboardDisplaySettings>;
44+
promptExperimentalSettings: (
45+
config: PluginConfig,
46+
) => Promise<PluginConfig | null>;
47+
backendSettingsEqual: (
48+
left: PluginConfig,
49+
right: PluginConfig,
50+
) => boolean;
51+
persistBackendConfigSelection: (
52+
config: PluginConfig,
53+
scope: string,
54+
) => Promise<PluginConfig>;
55+
configureBackendSettings: (
56+
config: PluginConfig,
57+
) => Promise<PluginConfig>;
58+
BEHAVIOR_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
59+
THEME_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
60+
},
61+
) => Promise<DashboardDisplaySettings>;
62+
cloneDashboardSettings: (
63+
settings: DashboardDisplaySettings,
64+
) => DashboardDisplaySettings;
65+
cloneBackendPluginConfig: (config: PluginConfig) => PluginConfig;
66+
loadDashboardDisplaySettings: () => Promise<DashboardDisplaySettings>;
67+
loadPluginConfig: () => PluginConfig;
68+
applyUiThemeFromDashboardSettings: (
69+
settings: DashboardDisplaySettings,
70+
) => void;
71+
promptSettingsHub: (
72+
focus: SettingsHubActionType,
73+
) => Promise<{ type: SettingsHubActionType } | null>;
74+
configureDashboardDisplaySettings: (
75+
current: DashboardDisplaySettings,
76+
) => Promise<DashboardDisplaySettings>;
77+
configureStatuslineSettings: (
78+
current: DashboardDisplaySettings,
79+
) => Promise<DashboardDisplaySettings>;
80+
promptBehaviorSettings: (
81+
current: DashboardDisplaySettings,
82+
) => Promise<DashboardDisplaySettings | null>;
83+
promptThemeSettings: (
84+
current: DashboardDisplaySettings,
85+
) => Promise<DashboardDisplaySettings | null>;
86+
dashboardSettingsEqual: (
87+
left: DashboardDisplaySettings,
88+
right: DashboardDisplaySettings,
89+
) => boolean;
90+
persistDashboardSettingsSelection: (
91+
selected: DashboardDisplaySettings,
92+
keys: readonly (keyof DashboardDisplaySettings)[],
93+
scope: string,
94+
) => Promise<DashboardDisplaySettings>;
95+
promptExperimentalSettings: (
96+
config: PluginConfig,
97+
) => Promise<PluginConfig | null>;
98+
backendSettingsEqual: (left: PluginConfig, right: PluginConfig) => boolean;
99+
persistBackendConfigSelection: (
100+
config: PluginConfig,
101+
scope: string,
102+
) => Promise<PluginConfig>;
103+
configureBackendSettings: (config: PluginConfig) => Promise<PluginConfig>;
104+
BEHAVIOR_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
105+
THEME_PANEL_KEYS: readonly (keyof DashboardDisplaySettings)[];
106+
},
107+
): Promise<DashboardDisplaySettings> {
108+
return deps.configureUnifiedSettingsController(initialSettings, {
109+
cloneDashboardSettings: deps.cloneDashboardSettings,
110+
cloneBackendPluginConfig: deps.cloneBackendPluginConfig,
111+
loadDashboardDisplaySettings: deps.loadDashboardDisplaySettings,
112+
loadPluginConfig: deps.loadPluginConfig,
113+
applyUiThemeFromDashboardSettings: deps.applyUiThemeFromDashboardSettings,
114+
promptSettingsHub: deps.promptSettingsHub,
115+
configureDashboardDisplaySettings: deps.configureDashboardDisplaySettings,
116+
configureStatuslineSettings: deps.configureStatuslineSettings,
117+
promptBehaviorSettings: deps.promptBehaviorSettings,
118+
promptThemeSettings: deps.promptThemeSettings,
119+
dashboardSettingsEqual: deps.dashboardSettingsEqual,
120+
persistDashboardSettingsSelection: deps.persistDashboardSettingsSelection,
121+
promptExperimentalSettings: deps.promptExperimentalSettings,
122+
backendSettingsEqual: deps.backendSettingsEqual,
123+
persistBackendConfigSelection: deps.persistBackendConfigSelection,
124+
configureBackendSettings: deps.configureBackendSettings,
125+
BEHAVIOR_PANEL_KEYS: deps.BEHAVIOR_PANEL_KEYS,
126+
THEME_PANEL_KEYS: deps.THEME_PANEL_KEYS,
127+
});
128+
}

test/settings-hub-entry.test.ts

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { promptSettingsHubEntry } from "../lib/codex-manager/settings-hub-entry.js";
3+
4+
describe("settings hub entry", () => {
5+
it("passes focus and dependencies through to the settings hub prompt helper", async () => {
6+
const promptSettingsHubMenu = vi.fn(async () => ({
7+
type: "back" as const,
8+
}));
9+
const buildItems = vi.fn(() => []);
10+
const findInitialCursor = vi.fn(() => 0);
11+
const select = vi.fn();
12+
13+
const result = await promptSettingsHubEntry({
14+
initialFocus: "account-list",
15+
promptSettingsHubMenu,
16+
isInteractive: () => true,
17+
getUiRuntimeOptions: vi.fn(() => ({ theme: {} }) as never),
18+
buildItems,
19+
findInitialCursor,
20+
select,
21+
copy: { title: "Settings", subtitle: "Subtitle", help: "Help" },
22+
});
23+
24+
expect(promptSettingsHubMenu).toHaveBeenCalledWith(
25+
"account-list",
26+
expect.objectContaining({
27+
isInteractive: expect.any(Function),
28+
getUiRuntimeOptions: expect.any(Function),
29+
buildItems,
30+
findInitialCursor,
31+
select,
32+
copy: { title: "Settings", subtitle: "Subtitle", help: "Help" },
33+
}),
34+
);
35+
expect(result).toEqual({ type: "back" });
36+
});
37+
38+
it("passes through null when the settings hub prompt is cancelled", async () => {
39+
const promptSettingsHubMenu = vi.fn(async () => null);
40+
const select = vi.fn();
41+
42+
const result = await promptSettingsHubEntry({
43+
initialFocus: "account-list",
44+
promptSettingsHubMenu,
45+
isInteractive: () => true,
46+
getUiRuntimeOptions: vi.fn(() => ({ theme: {} }) as never),
47+
buildItems: vi.fn(() => []),
48+
findInitialCursor: vi.fn(() => 0),
49+
select,
50+
copy: { title: "Settings", subtitle: "Subtitle", help: "Help" },
51+
});
52+
53+
expect(promptSettingsHubMenu).toHaveBeenCalledOnce();
54+
expect(result).toBeNull();
55+
});
56+
});
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
import { describe, expect, it, vi } from "vitest";
2+
import { configureUnifiedSettingsEntry } from "../lib/codex-manager/unified-settings-entry.js";
3+
4+
describe("unified settings entry", () => {
5+
it("delegates to the unified settings controller with provided deps", async () => {
6+
const configureUnifiedSettingsController = vi.fn(async () => ({
7+
menuShowStatusBadge: true,
8+
}));
9+
10+
const result = await configureUnifiedSettingsEntry(undefined, {
11+
configureUnifiedSettingsController,
12+
cloneDashboardSettings: vi.fn((settings) => settings),
13+
cloneBackendPluginConfig: vi.fn((config) => config),
14+
loadDashboardDisplaySettings: vi.fn(async () => ({
15+
menuShowStatusBadge: false,
16+
})),
17+
loadPluginConfig: vi.fn(() => ({ fetchTimeoutMs: 1000 })),
18+
applyUiThemeFromDashboardSettings: vi.fn(),
19+
promptSettingsHub: vi.fn(),
20+
configureDashboardDisplaySettings: vi.fn(),
21+
configureStatuslineSettings: vi.fn(),
22+
promptBehaviorSettings: vi.fn(),
23+
promptThemeSettings: vi.fn(),
24+
dashboardSettingsEqual: vi.fn(),
25+
persistDashboardSettingsSelection: vi.fn(),
26+
promptExperimentalSettings: vi.fn(),
27+
backendSettingsEqual: vi.fn(),
28+
persistBackendConfigSelection: vi.fn(),
29+
configureBackendSettings: vi.fn(),
30+
BEHAVIOR_PANEL_KEYS: [],
31+
THEME_PANEL_KEYS: [],
32+
});
33+
34+
expect(configureUnifiedSettingsController).toHaveBeenCalled();
35+
expect(result).toEqual({ menuShowStatusBadge: true });
36+
});
37+
});

0 commit comments

Comments
 (0)