fix(web): clear stale access selection

This commit is contained in:
root
2026-07-13 12:31:49 +02:00
parent f0a364fe7f
commit e83d0b25be
4 changed files with 55 additions and 2 deletions
@@ -67,6 +67,12 @@ test("R6 console semantics keep real icons, native entity lists, and dynamic acc
const accessSource = read("src/components/access/AccessMatrixPanel.vue");
assert.match(accessSource, /--permission-tool-count/u);
assert.doesNotMatch(accessSource, /repeat\(6,/u);
assert.match(accessSource, /if \(!value\) access\.clearSelection\(\)/u);
assert.match(accessSource, /function closeInspector\(\): void \{ access\.clearSelection\(\);/u);
const accessStoreSource = read("src/stores/access.ts");
assert.match(accessStoreSource, /function clearSelection\(\): void/u);
assert.match(accessStoreSource, /selectedUserId\.value = null/u);
assert.match(accessStoreSource, /detail\.value = null/u);
});
test("R6 Sub2API source comparison is recorded in parent/child issue scope", () => {
@@ -48,13 +48,14 @@ onMounted(async () => {
watch(() => route.params.userId, (userId) => {
const value = typeof userId === "string" ? userId : "";
if (value && value !== access.selectedUserId) void access.selectUser(value);
if (!value) access.clearSelection();
});
function selectRow(row: AccessUserRow): void {
if (route.params.userId !== row.id) void router.push({ name: "AccessUserDetail", params: { userId: row.id }, query: route.query });
void access.selectUser(row.id);
}
function closeInspector(): void { void router.push({ name: "Access", query: route.query }); }
function closeInspector(): void { access.clearSelection(); void router.push({ name: "Access", query: route.query }); }
function onRoleChange(event: Event): void { const role = (event.target as HTMLSelectElement).value as AccessUserRole; void access.updateSelectedUser({ role }); }
function onStatusChange(event: Event): void { const status = (event.target as HTMLSelectElement).value as AccessUserStatus; void access.updateSelectedUser({ status }); }
function onToolChange(toolId: string, event: Event): void { void access.setTool(toolId, (event.target as HTMLInputElement).checked); }
@@ -0,0 +1,37 @@
import { createPinia, setActivePinia } from "pinia";
import { beforeEach, expect, test, vi } from "vitest";
const { userRequest } = vi.hoisted(() => ({ userRequest: vi.fn() }));
vi.mock("@/api", () => ({
accessAPI: {
summary: vi.fn(),
users: vi.fn(),
user: userRequest,
updateUser: vi.fn(),
setTool: vi.fn()
}
}));
import { useAccessStore } from "./access";
beforeEach(() => {
setActivePinia(createPinia());
userRequest.mockReset();
});
test("clearSelection ignores a stale detail response", async () => {
let resolveRequest: (value: { ok: true; status: number; data: { user?: { id: string } } }) => void = () => undefined;
userRequest.mockReturnValue(new Promise((resolve) => { resolveRequest = resolve; }));
const store = useAccessStore();
const pending = store.selectUser("user-a");
store.clearSelection();
resolveRequest({ ok: true, status: 200, data: { user: { id: "user-a" } } });
await pending;
expect(store.selectedUserId).toBeNull();
expect(store.detail).toBeNull();
expect(store.selectedUser).toBeNull();
expect(store.detailLoading).toBe(false);
});
+10 -1
View File
@@ -37,6 +37,7 @@ export const useAccessStore = defineStore("access", () => {
detailLoading.value = true;
mutationError.value = null;
const response = await accessAPI.user(userId);
if (selectedUserId.value !== userId) return;
if (response.ok) {
detail.value = response.data;
} else {
@@ -45,6 +46,14 @@ export const useAccessStore = defineStore("access", () => {
detailLoading.value = false;
}
function clearSelection(): void {
selectedUserId.value = null;
detail.value = null;
detailLoading.value = false;
savingKey.value = null;
mutationError.value = null;
}
async function updateSelectedUser(payload: { role?: AccessUserRole; status?: AccessUserStatus }): Promise<void> {
if (!selectedUserId.value) return;
savingKey.value = `user:${selectedUserId.value}`;
@@ -75,5 +84,5 @@ export const useAccessStore = defineStore("access", () => {
savingKey.value = null;
}
return { summary, users, detail, selectedUserId, summaryView, userRows, detailView, toolRows, selectedUser, loading, detailLoading, savingKey, error, mutationError, refresh, selectUser, updateSelectedUser, setTool };
return { summary, users, detail, selectedUserId, summaryView, userRows, detailView, toolRows, selectedUser, loading, detailLoading, savingKey, error, mutationError, refresh, selectUser, clearSelection, updateSelectedUser, setTool };
});