From 0b213be52b8dd09d28663e97480d3408288922dd Mon Sep 17 00:00:00 2001 From: GHX5T-SOL <200635707+GHX5T-SOL@users.noreply.github.com> Date: Tue, 19 May 2026 23:10:41 +0200 Subject: [PATCH 1/2] fix(web): remove logout route --- docs/acceptance/auth.md | 2 +- .../src/auth/compass/hooks/useLogout.test.ts | 95 +++++++++++++++++++ .../web/src/auth/compass/hooks/useLogout.ts | 17 ++++ packages/web/src/common/constants/routes.ts | 1 - .../common/hooks/useLogoutCmdItems.test.ts | 56 +++++++++++ .../web/src/common/hooks/useLogoutCmdItems.ts | 21 ++++ packages/web/src/routers/index.tsx | 9 -- packages/web/src/routers/loaders.ts | 6 -- .../web/src/views/CmdPalette/CmdPalette.tsx | 14 +-- .../views/Day/components/DayCmdPalette.tsx | 13 +-- packages/web/src/views/Logout/Logout.test.tsx | 67 ------------- packages/web/src/views/Logout/Logout.tsx | 40 -------- packages/web/src/views/Logout/index.ts | 3 - packages/web/src/views/Logout/styled.ts | 26 ----- .../views/Now/components/NowCmdPalette.tsx | 13 +-- .../shortcuts/useGlobalShortcuts.test.tsx | 88 +++++++++++++++++ .../hooks/shortcuts/useGlobalShortcuts.ts | 5 +- 17 files changed, 290 insertions(+), 186 deletions(-) create mode 100644 packages/web/src/auth/compass/hooks/useLogout.test.ts create mode 100644 packages/web/src/auth/compass/hooks/useLogout.ts create mode 100644 packages/web/src/common/hooks/useLogoutCmdItems.test.ts create mode 100644 packages/web/src/common/hooks/useLogoutCmdItems.ts delete mode 100644 packages/web/src/views/Logout/Logout.test.tsx delete mode 100644 packages/web/src/views/Logout/Logout.tsx delete mode 100644 packages/web/src/views/Logout/index.ts delete mode 100644 packages/web/src/views/Logout/styled.ts create mode 100644 packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx diff --git a/docs/acceptance/auth.md b/docs/acceptance/auth.md index 7a8f9fead..c2defd986 100644 --- a/docs/acceptance/auth.md +++ b/docs/acceptance/auth.md @@ -274,7 +274,7 @@ Logout should end the server session, but the local rollout gate should still re ### Steps 1. Log in with email/password or Google. -2. Use the app logout path. +2. Use the `Z` shortcut or the command-palette logout action. 3. Confirm you are redirected back into the app. 4. Reload `/day`. 5. Open the command palette. diff --git a/packages/web/src/auth/compass/hooks/useLogout.test.ts b/packages/web/src/auth/compass/hooks/useLogout.test.ts new file mode 100644 index 000000000..30a06f480 --- /dev/null +++ b/packages/web/src/auth/compass/hooks/useLogout.test.ts @@ -0,0 +1,95 @@ +import { act, renderHook } from "@testing-library/react"; +import { + afterAll, + beforeEach, + describe, + expect, + it, + mock, + spyOn, +} from "bun:test"; + +const clearAuthenticationState = mock(); +const setAuthenticated = mock(); +const signOut = mock(); +const mockUseSession = mock(); + +mock.module("@web/auth/compass/session/useSession", () => ({ + useSession: mockUseSession, +})); + +mock.module("@web/auth/compass/state/auth.state.util", () => ({ + clearAuthenticationState, +})); + +mock.module("@web/common/classes/Session", () => ({ + session: { + signOut, + }, +})); + +const { useLogout } = await import("./useLogout"); + +describe("useLogout", () => { + beforeEach(() => { + clearAuthenticationState.mockClear(); + setAuthenticated.mockClear(); + signOut.mockReset(); + mockUseSession.mockReset(); + mockUseSession.mockReturnValue({ + authenticated: true, + setAuthenticated, + }); + signOut.mockResolvedValue(undefined); + }); + + it("signs out and clears authenticated state immediately", () => { + const { result } = renderHook(() => useLogout()); + + act(() => { + result.current(); + }); + + expect(signOut).toHaveBeenCalledTimes(1); + expect(clearAuthenticationState).toHaveBeenCalledTimes(1); + expect(setAuthenticated).toHaveBeenCalledWith(false); + }); + + it("does not wait for backend sign-out before clearing local state", () => { + signOut.mockReturnValue(new Promise(() => undefined)); + const { result } = renderHook(() => useLogout()); + + act(() => { + result.current(); + }); + + expect(clearAuthenticationState).toHaveBeenCalledTimes(1); + expect(setAuthenticated).toHaveBeenCalledWith(false); + }); + + it("logs backend sign-out failures after local logout completes", async () => { + const consoleWarn = spyOn(console, "warn").mockImplementation(() => {}); + const error = new Error("network"); + signOut.mockRejectedValue(error); + const { result } = renderHook(() => useLogout()); + + act(() => { + result.current(); + }); + + await Promise.resolve(); + + expect(clearAuthenticationState).toHaveBeenCalledTimes(1); + expect(setAuthenticated).toHaveBeenCalledWith(false); + expect(consoleWarn).toHaveBeenCalledWith( + "Failed to complete backend sign-out:", + error, + ); + + consoleWarn.mockRestore(); + }); +}); + +afterAll(() => { + mock.restore(); +}); diff --git a/packages/web/src/auth/compass/hooks/useLogout.ts b/packages/web/src/auth/compass/hooks/useLogout.ts new file mode 100644 index 000000000..26f411e8c --- /dev/null +++ b/packages/web/src/auth/compass/hooks/useLogout.ts @@ -0,0 +1,17 @@ +import { useCallback } from "react"; +import { useSession } from "@web/auth/compass/session/useSession"; +import { clearAuthenticationState } from "@web/auth/compass/state/auth.state.util"; +import { session } from "@web/common/classes/Session"; + +export function useLogout() { + const { setAuthenticated } = useSession(); + + return useCallback(() => { + void session.signOut().catch((error) => { + console.warn("Failed to complete backend sign-out:", error); + }); + + clearAuthenticationState(); + setAuthenticated(false); + }, [setAuthenticated]); +} diff --git a/packages/web/src/common/constants/routes.ts b/packages/web/src/common/constants/routes.ts index a48cef3b3..a592c1c3d 100644 --- a/packages/web/src/common/constants/routes.ts +++ b/packages/web/src/common/constants/routes.ts @@ -1,6 +1,5 @@ export const ROOT_ROUTES = { API: "/api", - LOGOUT: "/logout", CLEANUP: "/cleanup", GOOGLE_AUTH_CALLBACK: "/auth/google/callback", ROOT: "/", diff --git a/packages/web/src/common/hooks/useLogoutCmdItems.test.ts b/packages/web/src/common/hooks/useLogoutCmdItems.test.ts new file mode 100644 index 000000000..f9f23c4f7 --- /dev/null +++ b/packages/web/src/common/hooks/useLogoutCmdItems.test.ts @@ -0,0 +1,56 @@ +import { renderHook } from "@testing-library/react"; +import { act, type MouseEvent } from "react"; +import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test"; + +const logout = mock(); +const mockUseLogout = mock(); +const mockUseSession = mock(); + +mock.module("@web/auth/compass/hooks/useLogout", () => ({ + useLogout: mockUseLogout, +})); + +mock.module("@web/auth/compass/session/useSession", () => ({ + useSession: mockUseSession, +})); + +const { useLogoutCmdItems } = await import("./useLogoutCmdItems"); + +describe("useLogoutCmdItems", () => { + beforeEach(() => { + logout.mockClear(); + mockUseLogout.mockReset(); + mockUseSession.mockReset(); + mockUseLogout.mockReturnValue(logout); + mockUseSession.mockReturnValue({ + authenticated: true, + setAuthenticated: mock(), + }); + }); + + it("returns no items when logged out", () => { + mockUseSession.mockReturnValue({ + authenticated: false, + setAuthenticated: mock(), + }); + + const { result } = renderHook(() => useLogoutCmdItems()); + + expect(result.current).toEqual([]); + }); + + it("logs out from the command palette item", () => { + const { result } = renderHook(() => useLogoutCmdItems()); + const logoutItem = result.current[0]; + + act(() => { + logoutItem.onClick?.({} as MouseEvent); + }); + + expect(logout).toHaveBeenCalledTimes(1); + }); +}); + +afterAll(() => { + mock.restore(); +}); diff --git a/packages/web/src/common/hooks/useLogoutCmdItems.ts b/packages/web/src/common/hooks/useLogoutCmdItems.ts new file mode 100644 index 000000000..78a808fd2 --- /dev/null +++ b/packages/web/src/common/hooks/useLogoutCmdItems.ts @@ -0,0 +1,21 @@ +import { type JsonStructureItem } from "react-cmdk"; +import { useLogout } from "@web/auth/compass/hooks/useLogout"; +import { useSession } from "@web/auth/compass/session/useSession"; + +export const useLogoutCmdItems = (): JsonStructureItem[] => { + const { authenticated } = useSession(); + const logout = useLogout(); + + if (!authenticated) { + return []; + } + + return [ + { + id: "log-out", + children: "Log Out [z]", + icon: "ArrowRightOnRectangleIcon", + onClick: logout, + }, + ]; +}; diff --git a/packages/web/src/routers/index.tsx b/packages/web/src/routers/index.tsx index fe45d80d9..3cf5de797 100644 --- a/packages/web/src/routers/index.tsx +++ b/packages/web/src/routers/index.tsx @@ -70,15 +70,6 @@ export const router = createBrowserRouter( }, ], }, - { - path: ROOT_ROUTES.LOGOUT, - lazy: async () => - import(/* webpackChunkName: "logout" */ "@web/views/Logout").then( - (module) => ({ - Component: module.LogoutView, - }), - ), - }, { path: ROOT_ROUTES.WEEK, lazy: async () => diff --git a/packages/web/src/routers/loaders.ts b/packages/web/src/routers/loaders.ts index c4c561c22..40deee259 100644 --- a/packages/web/src/routers/loaders.ts +++ b/packages/web/src/routers/loaders.ts @@ -23,12 +23,6 @@ export async function loadAuthenticated() { return { authenticated }; } -export async function loadLogoutData() { - const { authenticated } = await loadAuthenticated(); - - return { authenticated }; -} - export function loadTodayData(): DayLoaderData { const dateInView = dayjs(); const dateFormat = dayjs.DateFormat.YEAR_MONTH_DAY_FORMAT; diff --git a/packages/web/src/views/CmdPalette/CmdPalette.tsx b/packages/web/src/views/CmdPalette/CmdPalette.tsx index 4ee47cdc2..f5c392ade 100644 --- a/packages/web/src/views/CmdPalette/CmdPalette.tsx +++ b/packages/web/src/views/CmdPalette/CmdPalette.tsx @@ -9,7 +9,7 @@ import { Categories_Event } from "@core/types/event.types"; import { moreCommandPaletteItems } from "@web/common/constants/more.cmd.constants"; import { useAuthCmdItems } from "@web/common/hooks/useAuthCmdItems"; import { useGoogleCmdItems } from "@web/common/hooks/useGoogleCmdItems"; -import { pressKey } from "@web/common/utils/dom/event-emitter.util"; +import { useLogoutCmdItems } from "@web/common/hooks/useLogoutCmdItems"; import { onEventTargetVisibility } from "@web/common/utils/dom/event-target-visibility.util"; import { createAlldayDraft, @@ -46,6 +46,7 @@ const CmdPalette = ({ const [search, setSearch] = useState(""); const authCmdItems = useAuthCmdItems(); const googleCmdItems = useGoogleCmdItems(); + const logoutCmdItems = useLogoutCmdItems(); const handleCreateSomedayDraft = async ( category: Categories_Event.SOMEDAY_WEEK | Categories_Event.SOMEDAY_MONTH, @@ -137,16 +138,7 @@ const CmdPalette = ({ { heading: "Settings", id: "settings", - items: [ - ...googleCmdItems, - ...authCmdItems, - { - id: "log-out", - children: "Log Out [z]", - icon: "ArrowRightOnRectangleIcon", - onClick: () => pressKey("z"), - }, - ], + items: [...googleCmdItems, ...authCmdItems, ...logoutCmdItems], }, ...moreCommandPaletteItems, ], diff --git a/packages/web/src/views/Day/components/DayCmdPalette.tsx b/packages/web/src/views/Day/components/DayCmdPalette.tsx index e3372685c..ae436c41f 100644 --- a/packages/web/src/views/Day/components/DayCmdPalette.tsx +++ b/packages/web/src/views/Day/components/DayCmdPalette.tsx @@ -6,6 +6,7 @@ import { moreCommandPaletteItems } from "@web/common/constants/more.cmd.constant import { VIEW_SHORTCUTS } from "@web/common/constants/shortcuts.constants"; import { useAuthCmdItems } from "@web/common/hooks/useAuthCmdItems"; import { useGoogleCmdItems } from "@web/common/hooks/useGoogleCmdItems"; +import { useLogoutCmdItems } from "@web/common/hooks/useLogoutCmdItems"; import { pressKey } from "@web/common/utils/dom/event-emitter.util"; import { openEventFormCreateEvent, @@ -30,6 +31,7 @@ export const DayCmdPalette = ({ onGoToToday }: DayCmdPaletteProps) => { const today = dayjs(); const authCmdItems = useAuthCmdItems(); const googleCmdItems = useGoogleCmdItems(); + const logoutCmdItems = useLogoutCmdItems(); const filteredItems = filterItems( [ @@ -74,16 +76,7 @@ export const DayCmdPalette = ({ onGoToToday }: DayCmdPaletteProps) => { { heading: "Settings", id: "settings", - items: [ - ...googleCmdItems, - ...authCmdItems, - { - id: "log-out", - children: "Log Out [z]", - icon: "ArrowRightOnRectangleIcon", - onClick: () => pressKey("z"), - }, - ], + items: [...googleCmdItems, ...authCmdItems, ...logoutCmdItems], }, ...moreCommandPaletteItems, ], diff --git a/packages/web/src/views/Logout/Logout.test.tsx b/packages/web/src/views/Logout/Logout.test.tsx deleted file mode 100644 index 29d2c392e..000000000 --- a/packages/web/src/views/Logout/Logout.test.tsx +++ /dev/null @@ -1,67 +0,0 @@ -import { render, screen, waitFor } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { MemoryRouter, Route, Routes } from "react-router-dom"; -import { ThemeProvider } from "styled-components"; -import { theme } from "@web/common/styles/theme"; -import { afterAll, describe, expect, it, mock } from "bun:test"; -import "@testing-library/jest-dom"; - -const signOut = mock(); -const clearAuthenticationState = mock(); -const getLastKnownEmail = mock(); -const markUserAsAuthenticated = mock(); - -mock.module("@web/common/classes/Session", () => ({ - session: { - signOut, - }, -})); - -mock.module("@web/auth/compass/state/auth.state.util", () => ({ - clearAnonymousCalendarChangeSignUpPrompt: mock(), - clearAuthenticationState, - getAuthState: mock(), - getLastKnownEmail, - hasUserEverAuthenticated: mock(), - markAnonymousCalendarChangeForSignUpPrompt: mock(), - markUserAsAuthenticated, - shouldShowAnonymousCalendarChangeSignUpPrompt: mock(), - subscribeToAuthState: mock(), - updateAuthState: mock(), -})); - -const { LogoutView } = await import("./Logout"); - -describe("LogoutView", () => { - it("navigates away even when session sign-out does not finish", async () => { - signOut.mockReturnValue(new Promise(() => undefined)); - - render( - - - - } /> - Day view} /> - - - , - ); - - await userEvent.click(screen.getByRole("button", { name: /signout/i })); - - await waitFor(() => { - expect(screen.getByText("Day view")).toBeInTheDocument(); - }); - expect(clearAuthenticationState).toHaveBeenCalledTimes(1); - }); -}); - -afterAll(() => { - mock.restore(); -}); diff --git a/packages/web/src/views/Logout/Logout.tsx b/packages/web/src/views/Logout/Logout.tsx deleted file mode 100644 index 544f90be2..000000000 --- a/packages/web/src/views/Logout/Logout.tsx +++ /dev/null @@ -1,40 +0,0 @@ -import { useState } from "react"; -import { useNavigate } from "react-router-dom"; -import { clearAuthenticationState } from "@web/auth/compass/state/auth.state.util"; -import { session } from "@web/common/classes/Session"; -import { ROOT_ROUTES } from "@web/common/constants/routes"; -import { AbsoluteOverflowLoader } from "@web/components/AbsoluteOverflowLoader"; -import { AlignItems, FlexDirections } from "@web/components/Flex/styled"; -import { StyledLogoutBtn, StyledLogoutContainer } from "./styled"; - -export const LogoutView = () => { - const navigate = useNavigate(); - - const [isLoggingOut, setIsLoggingOut] = useState(false); - - const logout = async () => { - setIsLoggingOut(true); - - void session.signOut().catch((error) => { - console.warn("Failed to complete backend sign-out:", error); - }); - - clearAuthenticationState(); - setIsLoggingOut(false); - - navigate(ROOT_ROUTES.DAY); - }; - - return ( - - {isLoggingOut && } - - void logout()}> - Signout - - - ); -}; diff --git a/packages/web/src/views/Logout/index.ts b/packages/web/src/views/Logout/index.ts deleted file mode 100644 index a3210b13f..000000000 --- a/packages/web/src/views/Logout/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import { LogoutView } from "./Logout"; - -export { LogoutView }; diff --git a/packages/web/src/views/Logout/styled.ts b/packages/web/src/views/Logout/styled.ts deleted file mode 100644 index b6afd0113..000000000 --- a/packages/web/src/views/Logout/styled.ts +++ /dev/null @@ -1,26 +0,0 @@ -import styled from "styled-components"; -import { Btn } from "@web/components/Button/styled"; -import { Flex } from "@web/components/Flex"; - -export const StyledLogoutBtn = styled(Btn)` - background: ${({ theme }) => theme.color.status.info}; - height: 35px; - min-width: 158px; - padding: 0 8px; - - &:hover { - filter: brightness(120%); - transition: brightness 0.5s; - } -`; - -export const StyledLogoutContainer = styled(Flex)` - bottom: 0; - justify-content: center; - left: 0; - min-height: 100vh; - position: fixed; - right: 0; - text-align: center; - top: 0; -`; diff --git a/packages/web/src/views/Now/components/NowCmdPalette.tsx b/packages/web/src/views/Now/components/NowCmdPalette.tsx index 2a8971c6a..4f5566c28 100644 --- a/packages/web/src/views/Now/components/NowCmdPalette.tsx +++ b/packages/web/src/views/Now/components/NowCmdPalette.tsx @@ -6,6 +6,7 @@ import { moreCommandPaletteItems } from "@web/common/constants/more.cmd.constant import { VIEW_SHORTCUTS } from "@web/common/constants/shortcuts.constants"; import { useAuthCmdItems } from "@web/common/hooks/useAuthCmdItems"; import { useGoogleCmdItems } from "@web/common/hooks/useGoogleCmdItems"; +import { useLogoutCmdItems } from "@web/common/hooks/useLogoutCmdItems"; import { pressKey } from "@web/common/utils/dom/event-emitter.util"; import { onEventTargetVisibility } from "@web/common/utils/dom/event-target-visibility.util"; import { resolveDefaultExport } from "@web/common/utils/resolve-default-export.util"; @@ -23,6 +24,7 @@ export const NowCmdPalette = () => { const [search, setSearch] = useState(""); const authCmdItems = useAuthCmdItems(); const googleCmdItems = useGoogleCmdItems(); + const logoutCmdItems = useLogoutCmdItems(); const filteredItems = filterItems( [ @@ -53,16 +55,7 @@ export const NowCmdPalette = () => { { heading: "Settings", id: "settings", - items: [ - ...googleCmdItems, - ...authCmdItems, - { - id: "log-out", - children: "Log Out [z]", - icon: "ArrowRightOnRectangleIcon", - onClick: () => pressKey("z"), - }, - ], + items: [...googleCmdItems, ...authCmdItems, ...logoutCmdItems], }, ...moreCommandPaletteItems, ], diff --git a/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx new file mode 100644 index 000000000..ffef108ca --- /dev/null +++ b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx @@ -0,0 +1,88 @@ +import { HotkeyManager, HotkeysProvider } from "@tanstack/react-hotkeys"; +import { renderHook, waitFor } from "@testing-library/react"; +import { type PropsWithChildren } from "react"; +import { MemoryRouter } from "react-router-dom"; +import { pressKey } from "@web/common/utils/dom/event-emitter.util"; +import { beforeEach, describe, expect, it, mock } from "bun:test"; + +const dispatch = mock(); +const logout = mock(); +const mockOpenModal = mock(); +const mockUseAuthModal = mock(); +const mockUseLogout = mock(); +const mockUseSession = mock(); + +mock.module("@web/auth/compass/hooks/useLogout", () => ({ + useLogout: mockUseLogout, +})); + +mock.module("@web/auth/compass/session/useSession", () => ({ + useSession: mockUseSession, +})); + +mock.module("@web/components/AuthModal/hooks/useAuthModal", () => ({ + useAuthModal: mockUseAuthModal, +})); + +mock.module("@web/store/store.hooks", () => ({ + useAppDispatch: () => dispatch, +})); + +const { useGlobalShortcuts } = await import("./useGlobalShortcuts"); + +function wrapper({ children }: PropsWithChildren) { + return ( + + + {children} + + + ); +} + +describe("useGlobalShortcuts", () => { + beforeEach(() => { + HotkeyManager.resetInstance(); + dispatch.mockClear(); + logout.mockClear(); + mockOpenModal.mockClear(); + mockUseAuthModal.mockReset(); + mockUseLogout.mockReset(); + mockUseSession.mockReset(); + mockUseAuthModal.mockReturnValue({ openModal: mockOpenModal }); + mockUseLogout.mockReturnValue(logout); + mockUseSession.mockReturnValue({ + authenticated: true, + setAuthenticated: mock(), + }); + }); + + it("logs out directly when authenticated users press Z", async () => { + renderHook(() => useGlobalShortcuts(), { wrapper }); + + pressKey("z"); + + await waitFor(() => { + expect(logout).toHaveBeenCalledTimes(1); + }); + expect(mockOpenModal).not.toHaveBeenCalled(); + }); + + it("opens login when logged-out users press Z", async () => { + mockUseSession.mockReturnValue({ + authenticated: false, + setAuthenticated: mock(), + }); + renderHook(() => useGlobalShortcuts(), { wrapper }); + + pressKey("z"); + + await waitFor(() => { + expect(mockOpenModal).toHaveBeenCalledWith("login"); + }); + expect(logout).not.toHaveBeenCalled(); + }); +}); diff --git a/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.ts b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.ts index 4fdb9de19..af065041e 100644 --- a/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.ts +++ b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.ts @@ -1,7 +1,7 @@ import { type RegisterableHotkey } from "@tanstack/react-hotkeys"; import { useLocation, useNavigate } from "react-router-dom"; +import { useLogout } from "@web/auth/compass/hooks/useLogout"; import { useSession } from "@web/auth/compass/session/useSession"; -import { ROOT_ROUTES } from "@web/common/constants/routes"; import { VIEW_SHORTCUTS } from "@web/common/constants/shortcuts.constants"; import { useAppHotkey, useAppHotkeyUp } from "@web/common/hooks/useAppHotkey"; import { useAuthModal } from "@web/components/AuthModal/hooks/useAuthModal"; @@ -16,6 +16,7 @@ import { useAppDispatch } from "@web/store/store.hooks"; export function useGlobalShortcuts() { const dispatch = useAppDispatch(); const { authenticated } = useSession(); + const logout = useLogout(); const { openModal } = useAuthModal(); const navigate = useNavigate(); const location = useLocation(); @@ -44,7 +45,7 @@ export function useGlobalShortcuts() { useAppHotkeyUp("Z", () => { if (authenticated) { - navigate(ROOT_ROUTES.LOGOUT); + logout(); return; } From 10c463d72b57cc66fa360f8164416e51cf45eca3 Mon Sep 17 00:00:00 2001 From: Tyler Dane Date: Sat, 23 May 2026 19:17:54 -0500 Subject: [PATCH 2/2] test(web): isolate logout hook tests --- .../common/hooks/useLogoutCmdItems.test.ts | 27 ++++++++++------- .../shortcuts/useGlobalShortcuts.test.tsx | 29 ++++++++++++------- 2 files changed, 35 insertions(+), 21 deletions(-) diff --git a/packages/web/src/common/hooks/useLogoutCmdItems.test.ts b/packages/web/src/common/hooks/useLogoutCmdItems.test.ts index f9f23c4f7..69f6ca38e 100644 --- a/packages/web/src/common/hooks/useLogoutCmdItems.test.ts +++ b/packages/web/src/common/hooks/useLogoutCmdItems.test.ts @@ -2,30 +2,36 @@ import { renderHook } from "@testing-library/react"; import { act, type MouseEvent } from "react"; import { afterAll, beforeEach, describe, expect, it, mock } from "bun:test"; -const logout = mock(); -const mockUseLogout = mock(); +const clearAuthenticationState = mock(); +const signOut = mock(); const mockUseSession = mock(); -mock.module("@web/auth/compass/hooks/useLogout", () => ({ - useLogout: mockUseLogout, -})); - mock.module("@web/auth/compass/session/useSession", () => ({ useSession: mockUseSession, })); +mock.module("@web/auth/compass/state/auth.state.util", () => ({ + clearAuthenticationState, +})); + +mock.module("@web/common/classes/Session", () => ({ + session: { + signOut, + }, +})); + const { useLogoutCmdItems } = await import("./useLogoutCmdItems"); describe("useLogoutCmdItems", () => { beforeEach(() => { - logout.mockClear(); - mockUseLogout.mockReset(); + clearAuthenticationState.mockClear(); + signOut.mockReset(); mockUseSession.mockReset(); - mockUseLogout.mockReturnValue(logout); mockUseSession.mockReturnValue({ authenticated: true, setAuthenticated: mock(), }); + signOut.mockResolvedValue(undefined); }); it("returns no items when logged out", () => { @@ -47,7 +53,8 @@ describe("useLogoutCmdItems", () => { logoutItem.onClick?.({} as MouseEvent); }); - expect(logout).toHaveBeenCalledTimes(1); + expect(signOut).toHaveBeenCalledTimes(1); + expect(clearAuthenticationState).toHaveBeenCalledTimes(1); }); }); diff --git a/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx index ffef108ca..6ad4e352e 100644 --- a/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx +++ b/packages/web/src/views/Week/hooks/shortcuts/useGlobalShortcuts.test.tsx @@ -6,20 +6,26 @@ import { pressKey } from "@web/common/utils/dom/event-emitter.util"; import { beforeEach, describe, expect, it, mock } from "bun:test"; const dispatch = mock(); -const logout = mock(); +const clearAuthenticationState = mock(); const mockOpenModal = mock(); const mockUseAuthModal = mock(); -const mockUseLogout = mock(); const mockUseSession = mock(); - -mock.module("@web/auth/compass/hooks/useLogout", () => ({ - useLogout: mockUseLogout, -})); +const signOut = mock(); mock.module("@web/auth/compass/session/useSession", () => ({ useSession: mockUseSession, })); +mock.module("@web/auth/compass/state/auth.state.util", () => ({ + clearAuthenticationState, +})); + +mock.module("@web/common/classes/Session", () => ({ + session: { + signOut, + }, +})); + mock.module("@web/components/AuthModal/hooks/useAuthModal", () => ({ useAuthModal: mockUseAuthModal, })); @@ -46,18 +52,18 @@ function wrapper({ children }: PropsWithChildren) { describe("useGlobalShortcuts", () => { beforeEach(() => { HotkeyManager.resetInstance(); + clearAuthenticationState.mockClear(); dispatch.mockClear(); - logout.mockClear(); mockOpenModal.mockClear(); mockUseAuthModal.mockReset(); - mockUseLogout.mockReset(); mockUseSession.mockReset(); + signOut.mockReset(); mockUseAuthModal.mockReturnValue({ openModal: mockOpenModal }); - mockUseLogout.mockReturnValue(logout); mockUseSession.mockReturnValue({ authenticated: true, setAuthenticated: mock(), }); + signOut.mockResolvedValue(undefined); }); it("logs out directly when authenticated users press Z", async () => { @@ -66,8 +72,9 @@ describe("useGlobalShortcuts", () => { pressKey("z"); await waitFor(() => { - expect(logout).toHaveBeenCalledTimes(1); + expect(signOut).toHaveBeenCalledTimes(1); }); + expect(clearAuthenticationState).toHaveBeenCalledTimes(1); expect(mockOpenModal).not.toHaveBeenCalled(); }); @@ -83,6 +90,6 @@ describe("useGlobalShortcuts", () => { await waitFor(() => { expect(mockOpenModal).toHaveBeenCalledWith("login"); }); - expect(logout).not.toHaveBeenCalled(); + expect(signOut).not.toHaveBeenCalled(); }); });