fix(web): clear stale access selection
This commit is contained in:
@@ -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);
|
||||
});
|
||||
@@ -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 };
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user