Skip to content

Commit 405d24f

Browse files
committed
Fix global worktrees root resync after dirty edits
1 parent 50676b5 commit 405d24f

2 files changed

Lines changed: 84 additions & 34 deletions

File tree

src/features/settings/components/SettingsView.test.tsx

Lines changed: 66 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -440,32 +440,38 @@ const renderEnvironmentsSection = (
440440
options.onUpdateAppSettings ?? vi.fn().mockResolvedValue(undefined);
441441
const onUpdateWorkspaceSettings =
442442
options.onUpdateWorkspaceSettings ?? vi.fn().mockResolvedValue(undefined);
443-
444-
const props: ComponentProps<typeof SettingsView> = {
443+
const defaultGroupedWorkspaces =
444+
options.groupedWorkspaces ??
445+
[
446+
{
447+
id: null,
448+
name: "Ungrouped",
449+
workspaces: [
450+
workspace({
451+
id: "w1",
452+
name: "Project One",
453+
settings: {
454+
sidebarCollapsed: false,
455+
worktreeSetupScript: "echo one",
456+
},
457+
}),
458+
],
459+
},
460+
];
461+
462+
const buildProps = (
463+
nextOptions: {
464+
appSettings?: Partial<AppSettings>;
465+
groupedWorkspaces?: ComponentProps<typeof SettingsView>["groupedWorkspaces"];
466+
} = {},
467+
): ComponentProps<typeof SettingsView> => ({
445468
reduceTransparency: false,
446469
onToggleTransparency: vi.fn(),
447-
appSettings: { ...baseSettings, ...options.appSettings },
470+
appSettings: { ...baseSettings, ...options.appSettings, ...nextOptions.appSettings },
448471
openAppIconById: {},
449472
onUpdateAppSettings,
450473
workspaceGroups: [],
451-
groupedWorkspaces:
452-
options.groupedWorkspaces ??
453-
[
454-
{
455-
id: null,
456-
name: "Ungrouped",
457-
workspaces: [
458-
workspace({
459-
id: "w1",
460-
name: "Project One",
461-
settings: {
462-
sidebarCollapsed: false,
463-
worktreeSetupScript: "echo one",
464-
},
465-
}),
466-
],
467-
},
468-
],
474+
groupedWorkspaces: nextOptions.groupedWorkspaces ?? defaultGroupedWorkspaces,
469475
ungroupedLabel: "Ungrouped",
470476
onClose: vi.fn(),
471477
onMoveWorkspace: vi.fn(),
@@ -486,10 +492,19 @@ const renderEnvironmentsSection = (
486492
onCancelDictationDownload: vi.fn(),
487493
onRemoveDictationModel: vi.fn(),
488494
initialSection: "environments",
489-
};
495+
});
490496

491-
render(<SettingsView {...props} />);
492-
return { onUpdateAppSettings, onUpdateWorkspaceSettings };
497+
const renderResult = render(<SettingsView {...buildProps()} />);
498+
return {
499+
onUpdateAppSettings,
500+
onUpdateWorkspaceSettings,
501+
rerender: (
502+
nextOptions: {
503+
appSettings?: Partial<AppSettings>;
504+
groupedWorkspaces?: ComponentProps<typeof SettingsView>["groupedWorkspaces"];
505+
} = {},
506+
) => renderResult.rerender(<SettingsView {...buildProps(nextOptions)} />),
507+
};
493508
};
494509

495510
describe("SettingsView Display", () => {
@@ -869,6 +884,33 @@ describe("SettingsView Environments", () => {
869884
});
870885
});
871886

887+
it("resyncs the global worktrees root baseline after dirty state clears", async () => {
888+
const { rerender } = renderEnvironmentsSection({
889+
groupedWorkspaces: [],
890+
appSettings: { globalWorktreesFolder: null },
891+
});
892+
893+
const input = screen.getByLabelText("Global worktrees root");
894+
fireEvent.change(input, { target: { value: "I:/typing" } });
895+
896+
rerender({
897+
groupedWorkspaces: [],
898+
appSettings: { globalWorktreesFolder: "I:/loaded-from-settings" },
899+
});
900+
901+
expect((screen.getByLabelText("Global worktrees root") as HTMLInputElement).value).toBe(
902+
"I:/typing",
903+
);
904+
905+
fireEvent.click(screen.getByRole("button", { name: "Reset" }));
906+
907+
await waitFor(() => {
908+
expect((screen.getByLabelText("Global worktrees root") as HTMLInputElement).value).toBe(
909+
"I:/loaded-from-settings",
910+
);
911+
});
912+
});
913+
872914
it("shows save errors for the global worktrees root when there are no projects", async () => {
873915
const onUpdateAppSettings = vi
874916
.fn()

src/features/settings/hooks/useSettingsEnvironmentsSection.ts

Lines changed: 18 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ export const useSettingsEnvironmentsSection = ({
5454
);
5555
const [worktreesFolderDraft, setWorktreesFolderDraft] = useState("");
5656
const [worktreesFolderSaved, setWorktreesFolderSaved] = useState<string | null>(null);
57-
const lastGlobalWorktreesFolderProp = useRef(appSettings.globalWorktreesFolder);
57+
const lastSyncedGlobalWorktreesFolderProp = useRef(appSettings.globalWorktreesFolder);
5858

5959
const environmentWorkspace = useMemo(() => {
6060
if (mainWorkspaces.length === 0) return null;
@@ -83,15 +83,15 @@ export const useSettingsEnvironmentsSection = ({
8383
const worktreesFolderDirty = (worktreesFolderDraft.trim() || null) !== worktreesFolderSaved;
8484

8585
useEffect(() => {
86-
const previousGlobalWorktreesFolder = lastGlobalWorktreesFolderProp.current;
87-
lastGlobalWorktreesFolderProp.current = appSettings.globalWorktreesFolder;
88-
if (previousGlobalWorktreesFolder === appSettings.globalWorktreesFolder) {
86+
if (lastSyncedGlobalWorktreesFolderProp.current === appSettings.globalWorktreesFolder) {
8987
return;
9088
}
91-
if (!globalWorktreesFolderDirty) {
92-
setGlobalWorktreesFolderSaved(appSettings.globalWorktreesFolder);
93-
setGlobalWorktreesFolderDraft(appSettings.globalWorktreesFolder ?? "");
89+
if (globalWorktreesFolderDirty) {
90+
return;
9491
}
92+
lastSyncedGlobalWorktreesFolderProp.current = appSettings.globalWorktreesFolder;
93+
setGlobalWorktreesFolderSaved(appSettings.globalWorktreesFolder);
94+
setGlobalWorktreesFolderDraft(appSettings.globalWorktreesFolder ?? "");
9595
}, [appSettings.globalWorktreesFolder, globalWorktreesFolderDirty]);
9696

9797
useEffect(() => {
@@ -102,16 +102,24 @@ export const useSettingsEnvironmentsSection = ({
102102
setEnvironmentDraftScript("");
103103
setEnvironmentError(null);
104104
setEnvironmentSaving(false);
105-
setGlobalWorktreesFolderDraft(appSettings.globalWorktreesFolder ?? "");
106-
setGlobalWorktreesFolderSaved(appSettings.globalWorktreesFolder);
105+
if (!globalWorktreesFolderDirty) {
106+
lastSyncedGlobalWorktreesFolderProp.current = appSettings.globalWorktreesFolder;
107+
setGlobalWorktreesFolderDraft(appSettings.globalWorktreesFolder ?? "");
108+
setGlobalWorktreesFolderSaved(appSettings.globalWorktreesFolder);
109+
}
107110
setWorktreesFolderDraft("");
108111
setWorktreesFolderSaved(null);
109112
return;
110113
}
111114
if (environmentWorkspaceId !== environmentWorkspace.id) {
112115
setEnvironmentWorkspaceId(environmentWorkspace.id);
113116
}
114-
}, [appSettings.globalWorktreesFolder, environmentWorkspace, environmentWorkspaceId]);
117+
}, [
118+
appSettings.globalWorktreesFolder,
119+
environmentWorkspace,
120+
environmentWorkspaceId,
121+
globalWorktreesFolderDirty,
122+
]);
115123

116124
useEffect(() => {
117125
if (!environmentWorkspace) return;

0 commit comments

Comments
 (0)