Skip to content

Commit 14d929a

Browse files
authored
fix: clear select/load dialog layer ids when layers are removed (#1628)
* fix: clear select/load dialog layer ids when layers are removed Null the Select-by-Expression/Location and Load-Features preselected layer pointers on delete (and on new/load project) so dialogs cannot reopen against a gone layer. * fix: preserve dialog pointers when removeChildren is false Only clear selectByExpressionLayerId, selectByLocationLayerId, and loadEditorFeaturesLayerId when removeLayerGroup actually removes children. Adds regression test for the removeChildren=false path.
1 parent c7270cb commit 14d929a

2 files changed

Lines changed: 148 additions & 0 deletions

File tree

packages/core/src/store.ts

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1625,6 +1625,15 @@ export const useAppStore = create<AppState>()(
16251625
selectedFeatureId: s.selectedLayerId === id ? null : s.selectedFeatureId,
16261626
selectedFeatureIds: s.selectedLayerId === id ? [] : s.selectedFeatureIds,
16271627
identifyLayerId: s.identifyLayerId === id ? null : s.identifyLayerId,
1628+
ui: {
1629+
...s.ui,
1630+
selectByExpressionLayerId:
1631+
s.ui.selectByExpressionLayerId === id ? null : s.ui.selectByExpressionLayerId,
1632+
selectByLocationLayerId:
1633+
s.ui.selectByLocationLayerId === id ? null : s.ui.selectByLocationLayerId,
1634+
loadEditorFeaturesLayerId:
1635+
s.ui.loadEditorFeaturesLayerId === id ? null : s.ui.loadEditorFeaturesLayerId,
1636+
},
16281637
isDirty: true,
16291638
})),
16301639

@@ -1953,6 +1962,26 @@ export const useAppStore = create<AppState>()(
19531962
s.identifyLayerId !== null && removedIds.has(s.identifyLayerId)
19541963
? null
19551964
: s.identifyLayerId,
1965+
ui: removeChildren
1966+
? {
1967+
...s.ui,
1968+
selectByExpressionLayerId:
1969+
s.ui.selectByExpressionLayerId !== null &&
1970+
removedIds.has(s.ui.selectByExpressionLayerId)
1971+
? null
1972+
: s.ui.selectByExpressionLayerId,
1973+
selectByLocationLayerId:
1974+
s.ui.selectByLocationLayerId !== null &&
1975+
removedIds.has(s.ui.selectByLocationLayerId)
1976+
? null
1977+
: s.ui.selectByLocationLayerId,
1978+
loadEditorFeaturesLayerId:
1979+
s.ui.loadEditorFeaturesLayerId !== null &&
1980+
removedIds.has(s.ui.loadEditorFeaturesLayerId)
1981+
? null
1982+
: s.ui.loadEditorFeaturesLayerId,
1983+
}
1984+
: s.ui,
19561985
isDirty: true,
19571986
};
19581987
}),
@@ -2129,6 +2158,8 @@ export const useAppStore = create<AppState>()(
21292158
selectByExpressionLayerId: null,
21302159
selectByLocationOpen: false,
21312160
selectByLocationLayerId: null,
2161+
loadEditorFeaturesOpen: false,
2162+
loadEditorFeaturesLayerId: null,
21322163
},
21332164
}));
21342165
clearHistory();
@@ -2180,6 +2211,8 @@ export const useAppStore = create<AppState>()(
21802211
selectByExpressionLayerId: null,
21812212
selectByLocationOpen: false,
21822213
selectByLocationLayerId: null,
2214+
loadEditorFeaturesOpen: false,
2215+
loadEditorFeaturesLayerId: null,
21832216
},
21842217
}));
21852218
clearHistory();

tests/ui-layer-pointers.test.ts

Lines changed: 115 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,115 @@
1+
import assert from "node:assert/strict";
2+
import { beforeEach, describe, it } from "node:test";
3+
import { useAppStore } from "@geolibre/core";
4+
import { geojsonLayer } from "./helpers/layer-fixtures";
5+
6+
describe("UI layer pointer cleanup", () => {
7+
beforeEach(() => {
8+
useAppStore.getState().newProject({ name: "Pointer cleanup" });
9+
});
10+
11+
describe("removeLayer clears dialog layer ids", () => {
12+
it("clears selectByExpressionLayerId", () => {
13+
const store = useAppStore.getState();
14+
store.addLayer(geojsonLayer({ id: "target" }));
15+
store.setSelectByExpressionOpen(true, "target");
16+
assert.equal(useAppStore.getState().ui.selectByExpressionLayerId, "target");
17+
18+
useAppStore.getState().removeLayer("target");
19+
assert.equal(useAppStore.getState().ui.selectByExpressionLayerId, null);
20+
});
21+
22+
it("clears selectByLocationLayerId", () => {
23+
const store = useAppStore.getState();
24+
store.addLayer(geojsonLayer({ id: "target" }));
25+
store.setSelectByLocationOpen(true, "target");
26+
assert.equal(useAppStore.getState().ui.selectByLocationLayerId, "target");
27+
28+
useAppStore.getState().removeLayer("target");
29+
assert.equal(useAppStore.getState().ui.selectByLocationLayerId, null);
30+
});
31+
32+
it("clears loadEditorFeaturesLayerId", () => {
33+
const store = useAppStore.getState();
34+
store.addLayer(geojsonLayer({ id: "target" }));
35+
store.setLoadEditorFeaturesOpen(true, "target");
36+
assert.equal(useAppStore.getState().ui.loadEditorFeaturesLayerId, "target");
37+
38+
useAppStore.getState().removeLayer("target");
39+
assert.equal(useAppStore.getState().ui.loadEditorFeaturesLayerId, null);
40+
});
41+
42+
it("does not clear pointers for unrelated layers", () => {
43+
const store = useAppStore.getState();
44+
store.addLayer(geojsonLayer({ id: "keep", name: "Keep" }));
45+
store.addLayer(geojsonLayer({ id: "gone", name: "Gone" }));
46+
store.setSelectByExpressionOpen(true, "keep");
47+
store.setSelectByLocationOpen(true, "keep");
48+
store.setLoadEditorFeaturesOpen(true, "keep");
49+
50+
useAppStore.getState().removeLayer("gone");
51+
const ui = useAppStore.getState().ui;
52+
assert.equal(ui.selectByExpressionLayerId, "keep");
53+
assert.equal(ui.selectByLocationLayerId, "keep");
54+
assert.equal(ui.loadEditorFeaturesLayerId, "keep");
55+
});
56+
});
57+
58+
describe("removeLayerGroup clears dialog layer ids", () => {
59+
it("clears pointers when group children are removed", () => {
60+
const store = useAppStore.getState();
61+
const groupId = store.addLayerGroup("G");
62+
store.addLayer(geojsonLayer({ id: "child", groupId }));
63+
store.setSelectByExpressionOpen(true, "child");
64+
store.setSelectByLocationOpen(true, "child");
65+
store.setLoadEditorFeaturesOpen(true, "child");
66+
67+
useAppStore.getState().removeLayerGroup(groupId, { removeChildren: true });
68+
const ui = useAppStore.getState().ui;
69+
assert.equal(ui.selectByExpressionLayerId, null);
70+
assert.equal(ui.selectByLocationLayerId, null);
71+
assert.equal(ui.loadEditorFeaturesLayerId, null);
72+
});
73+
74+
it("preserves pointers when removeChildren is false", () => {
75+
const store = useAppStore.getState();
76+
const groupId = store.addLayerGroup("G");
77+
store.addLayer(geojsonLayer({ id: "child", groupId }));
78+
store.setSelectByExpressionOpen(true, "child");
79+
store.setSelectByLocationOpen(true, "child");
80+
store.setLoadEditorFeaturesOpen(true, "child");
81+
82+
useAppStore.getState().removeLayerGroup(groupId, { removeChildren: false });
83+
const ui = useAppStore.getState().ui;
84+
assert.equal(ui.selectByExpressionLayerId, "child");
85+
assert.equal(ui.selectByLocationLayerId, "child");
86+
assert.equal(ui.loadEditorFeaturesLayerId, "child");
87+
});
88+
});
89+
90+
describe("newProject/loadProject clear load-editor-features state", () => {
91+
it("newProject clears loadEditorFeaturesOpen and layerId", () => {
92+
const store = useAppStore.getState();
93+
store.addLayer(geojsonLayer({ id: "target" }));
94+
store.setLoadEditorFeaturesOpen(true, "target");
95+
assert.equal(useAppStore.getState().ui.loadEditorFeaturesOpen, true);
96+
97+
useAppStore.getState().newProject();
98+
const ui = useAppStore.getState().ui;
99+
assert.equal(ui.loadEditorFeaturesOpen, false);
100+
assert.equal(ui.loadEditorFeaturesLayerId, null);
101+
});
102+
103+
it("loadProject clears loadEditorFeaturesOpen and layerId", () => {
104+
const store = useAppStore.getState();
105+
store.addLayer(geojsonLayer({ id: "target" }));
106+
store.setLoadEditorFeaturesOpen(true, "target");
107+
assert.equal(useAppStore.getState().ui.loadEditorFeaturesOpen, true);
108+
109+
useAppStore.getState().loadProject({ name: "Loaded", layers: [], version: 1 });
110+
const ui = useAppStore.getState().ui;
111+
assert.equal(ui.loadEditorFeaturesOpen, false);
112+
assert.equal(ui.loadEditorFeaturesLayerId, null);
113+
});
114+
});
115+
});

0 commit comments

Comments
 (0)