diff --git a/apps/web/e2e/tests/chat/message-favorite.spec.ts b/apps/web/e2e/tests/chat/message-favorite.spec.ts index fba302b4cf..9662a9e1d7 100644 --- a/apps/web/e2e/tests/chat/message-favorite.spec.ts +++ b/apps/web/e2e/tests/chat/message-favorite.spec.ts @@ -8,7 +8,7 @@ import { SessionPage } from "../../pages/session-page"; const SEEDED_MESSAGE = "Favorite toggle regression fixture message"; test.describe("Chat message favorite toggle", () => { - test("stars a message, tints its background yellow, and un-stars it back", async ({ + test("keeps a starred message highlighted after reload and un-stars it", async ({ testPage, apiClient, seedData, @@ -48,6 +48,13 @@ test.describe("Chat message favorite toggle", () => { await expect(unstar).toBeVisible(); await expect.poll(async () => highlight.getAttribute("class")).toMatch(/yellow/); + await testPage.reload(); + await session.waitForLoad(); + + await expect(chat.getByText(SEEDED_MESSAGE)).toBeVisible(); + await expect(unstar).toBeVisible(); + await expect.poll(async () => highlight.getAttribute("class")).toMatch(/yellow/); + await unstar.click(); await expect(star).toBeVisible(); diff --git a/apps/web/lib/state/slices/message-favorites/favorites-store.test.ts b/apps/web/lib/state/slices/message-favorites/favorites-store.test.ts index 9bf6ac8360..4f2fcbe2a0 100644 --- a/apps/web/lib/state/slices/message-favorites/favorites-store.test.ts +++ b/apps/web/lib/state/slices/message-favorites/favorites-store.test.ts @@ -1,4 +1,4 @@ -import { beforeEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { useMessageFavoritesStore } from "./favorites-store"; import { MESSAGE_FAVORITES_STORAGE_PREFIX, loadSessionFavorites } from "./persistence"; @@ -11,6 +11,10 @@ describe("message favorites store", () => { useMessageFavoritesStore.setState({ bySession: {} }); }); + afterEach(() => { + vi.restoreAllMocks(); + }); + it("starts with no favorites for an unhydrated session", () => { const state = useMessageFavoritesStore.getState(); expect(state.bySession[SESSION_ID]).toBeUndefined(); @@ -60,6 +64,15 @@ describe("message favorites store", () => { expect(state.bySession[SESSION_ID]).toEqual({ "msg-1": true, "msg-2": true }); }); + it("hydrates an empty session from storage only once", () => { + const getItem = vi.spyOn(window.sessionStorage, "getItem"); + + useMessageFavoritesStore.getState().hydrateSession(SESSION_ID); + useMessageFavoritesStore.getState().hydrateSession(SESSION_ID); + + expect(getItem).toHaveBeenCalledTimes(1); + }); + it("ignores malformed persisted favorites", () => { window.sessionStorage.setItem( `${MESSAGE_FAVORITES_STORAGE_PREFIX}${SESSION_ID}`, @@ -88,7 +101,7 @@ describe("message favorites store", () => { ); expect(() => useMessageFavoritesStore.getState().hydrateSession(SESSION_ID)).not.toThrow(); - expect(useMessageFavoritesStore.getState().bySession[SESSION_ID]).toBeUndefined(); + expect(useMessageFavoritesStore.getState().bySession[SESSION_ID]).toEqual({}); }); it("ignores a persisted array containing non-string entries", () => { @@ -98,6 +111,6 @@ describe("message favorites store", () => { ); expect(() => useMessageFavoritesStore.getState().hydrateSession(SESSION_ID)).not.toThrow(); - expect(useMessageFavoritesStore.getState().bySession[SESSION_ID]).toBeUndefined(); + expect(useMessageFavoritesStore.getState().bySession[SESSION_ID]).toEqual({}); }); }); diff --git a/apps/web/lib/state/slices/message-favorites/favorites-store.ts b/apps/web/lib/state/slices/message-favorites/favorites-store.ts index cc6a8146fa..3e7012683b 100644 --- a/apps/web/lib/state/slices/message-favorites/favorites-store.ts +++ b/apps/web/lib/state/slices/message-favorites/favorites-store.ts @@ -21,9 +21,8 @@ export const useMessageFavoritesStore = create()( hydrateSession: (sessionId: string) => set((state) => { const existing = state.bySession[sessionId]; - if (existing && Object.keys(existing).length > 0) return; + if (existing) return; const ids = loadSessionFavorites(sessionId); - if (ids.length === 0) return; state.bySession[sessionId] = Object.fromEntries(ids.map((id) => [id, true as const])); }),