优化后台界面与无操作退出体验

This commit is contained in:
Cheng Zhou
2026-06-10 15:20:06 +08:00
parent ea3f19e241
commit 84e55af8fe
39 changed files with 793 additions and 608 deletions
+26 -17
View File
@@ -9,10 +9,9 @@ vi.mock("../router", () => ({
vi.mock("../api/authClient", () => ({
extendToken: vi.fn(),
unlockSession: vi.fn(),
}));
describe("session manager idle recovery", () => {
describe("session manager idle logout", () => {
beforeEach(() => {
vi.resetModules();
vi.useFakeTimers();
@@ -50,37 +49,47 @@ describe("session manager idle recovery", () => {
});
});
it("locks instead of refreshing activity after long inactivity when user activity resumes", async () => {
it("shows a timeout warning one minute before automatic logout", async () => {
vi.setSystemTime(new Date("2026-03-10T01:00:00.000Z"));
const { useSessionStore } = await import("../store/session");
const { IDLE_TIMEOUT_MINUTES, AUTO_LOGOUT_TIMEOUT_HOURS, markUserActive } = await import("./sessionManager");
const { IDLE_TIMEOUT_MINUTES, TIMEOUT_WARNING_SECONDS, initSessionManager } = await import("./sessionManager");
const session = useSessionStore();
const oldTs = Date.now() - ((IDLE_TIMEOUT_MINUTES * 60 - TIMEOUT_WARNING_SECONDS) * 1000);
session.recordUserActivity(oldTs);
initSessionManager();
expect(session.timeoutWarningVisible).toBe(true);
expect(session.timeoutAt).toBe(oldTs + IDLE_TIMEOUT_MINUTES * 60 * 1000);
});
it("logs out after long inactivity when user activity resumes", async () => {
vi.setSystemTime(new Date("2026-03-10T01:00:00.000Z"));
const { useSessionStore } = await import("../store/session");
const router = (await import("../router")).default;
const { IDLE_TIMEOUT_MINUTES, LOGOUT_REASON_TIMEOUT, markUserActive } = await import("./sessionManager");
const session = useSessionStore();
const oldTs = Date.now() - (IDLE_TIMEOUT_MINUTES * 60 * 1000 + 1_000);
session.recordUserActivity(oldTs);
session.recordNetworkActivity(oldTs);
markUserActive(Date.now());
expect(session.locked).toBe(true);
expect(session.lockReason).toBe("idle");
expect(session.lockAutoLogoutDeadlineAt).toBe(oldTs + AUTO_LOGOUT_TIMEOUT_HOURS * 60 * 60 * 1000);
expect(window.sessionStorage.getItem("ctms_logout_reason")).toBe(LOGOUT_REASON_TIMEOUT);
expect(router.replace).toHaveBeenCalledWith("/login");
});
it("locks instead of refreshing activity after long inactivity when network activity resumes", async () => {
it("clears the timeout warning when user activity resumes", async () => {
vi.setSystemTime(new Date("2026-03-10T01:00:00.000Z"));
const { useSessionStore } = await import("../store/session");
const { IDLE_TIMEOUT_MINUTES, AUTO_LOGOUT_TIMEOUT_HOURS, markNetworkActive } = await import("./sessionManager");
const { markUserActive } = await import("./sessionManager");
const session = useSessionStore();
const oldTs = Date.now() - (IDLE_TIMEOUT_MINUTES * 60 * 1000 + 1_000);
session.recordUserActivity(oldTs);
session.recordNetworkActivity(oldTs);
session.showTimeoutWarning(Date.now() + 60_000);
markNetworkActive(Date.now());
markUserActive(Date.now());
expect(session.locked).toBe(true);
expect(session.lockReason).toBe("idle");
expect(session.lockAutoLogoutDeadlineAt).toBe(oldTs + AUTO_LOGOUT_TIMEOUT_HOURS * 60 * 60 * 1000);
expect(session.timeoutWarningVisible).toBe(false);
expect(session.timeoutAt).toBe(0);
});
});