mirror of
https://github.com/oxynote/oxynote.git
synced 2026-10-17 02:49:18 +02:00
755 lines
19 KiB
TypeScript
755 lines
19 KiB
TypeScript
import { mockNuxtImport } from "@nuxt/test-utils/runtime"
|
|
import { flushPromises } from "@vue/test-utils"
|
|
import { afterEach, beforeEach, describe, it, vi } from "vitest"
|
|
import { toast } from "vue-sonner"
|
|
import {
|
|
clearQueryCache,
|
|
disposeMockEndpoints,
|
|
mockEndpoint,
|
|
seedQueryData,
|
|
} from "~/composables/api/test-helpers"
|
|
import NotificationBox from "./NotificationBox.vue"
|
|
import {
|
|
at,
|
|
findButtonByText,
|
|
mockAuthOrganization,
|
|
mountUnderTooltipProvider,
|
|
openTooltipText,
|
|
renderedIconNames,
|
|
seedAuthOrganization,
|
|
t,
|
|
} from "./test-helpers"
|
|
|
|
vi.mock("vue-sonner", () => ({
|
|
toast: { custom: vi.fn(), dismiss: vi.fn() },
|
|
}))
|
|
|
|
// opening a notification navigates; the destination it picks is the whole
|
|
// behaviour, and the test app has no page to land on
|
|
const navigateToMock = vi.hoisted(() => vi.fn())
|
|
mockNuxtImport("navigateTo", () => navigateToMock)
|
|
|
|
const TOOLTIP_DELAY_MS = 600
|
|
const DOC_ID = "doc1".padEnd(20, "0")
|
|
const USER_ID = "user".padEnd(20, "0")
|
|
|
|
function makeNotification(overrides: Record<string, unknown> = {}) {
|
|
return {
|
|
id: "notif-1",
|
|
userId: USER_ID,
|
|
organizationId: "org-1",
|
|
code: NotificationCode.DocumentReviewRequest,
|
|
metadata: { documentId: DOC_ID, branchId: "branch-1" },
|
|
read: false,
|
|
createdAt: "2026-03-14T11:00:00Z",
|
|
...overrides,
|
|
}
|
|
}
|
|
|
|
function seedNotifications(notifications: unknown[]) {
|
|
seedQueryData(["notifications", "list", 100, 1], {
|
|
notifications: notifications,
|
|
pageCount: 1,
|
|
})
|
|
seedQueryData(["notifications", "count", false], { count: 0 })
|
|
}
|
|
|
|
function seedTree() {
|
|
seedQueryData(
|
|
["documents", "tree"],
|
|
[
|
|
{
|
|
id: DOC_ID,
|
|
documentName: "Runbook",
|
|
icon: "lucide:file",
|
|
protected: false,
|
|
children: null,
|
|
},
|
|
],
|
|
)
|
|
}
|
|
|
|
function mountBox(props: Record<string, unknown> = {}) {
|
|
return mountUnderTooltipProvider(NotificationBox, { props: props })
|
|
}
|
|
|
|
function rows(wrapper: Awaited<ReturnType<typeof mountBox>>) {
|
|
return wrapper.findAll("div.cursor-pointer")
|
|
}
|
|
|
|
// the query cache, the pinia websocket store and the vue-sonner module
|
|
// mock are all app-wide singletons every mount in the file shares
|
|
describe("<NotificationBox>", { concurrent: false }, () => {
|
|
beforeEach(() => {
|
|
clearQueryCache()
|
|
vi.mocked(toast.custom).mockReset()
|
|
navigateToMock.mockReset()
|
|
vi.setSystemTime(new Date("2026-03-14T12:00:00Z"))
|
|
// a successful mark-read invalidates both notification queries; without
|
|
// these the refetch rejects and drags the mutation down with it, so the
|
|
// failure toast lands in whichever test is running by then
|
|
mockEndpoint("GET", "/api/notifications", () => ({
|
|
notifications: [],
|
|
pageCount: 1,
|
|
}))
|
|
mockEndpoint("GET", "/api/notifications/count", () => ({ count: 0 }))
|
|
})
|
|
|
|
afterEach(() => {
|
|
disposeMockEndpoints()
|
|
vi.useRealTimers()
|
|
})
|
|
|
|
it("says the inbox is empty when there are no notifications", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(t("notification.empty-title"))
|
|
})
|
|
|
|
it("lists one row per notification", async ({ expect }) => {
|
|
seedNotifications([makeNotification(), makeNotification({ id: "notif-2" })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(rows(wrapper)).toHaveLength(2)
|
|
})
|
|
|
|
it("names the document the notification is about", async ({ expect }) => {
|
|
seedTree()
|
|
seedNotifications([makeNotification()])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain("Runbook")
|
|
})
|
|
|
|
it("spells out the document name in a tooltip once hovered", async ({
|
|
expect,
|
|
}) => {
|
|
vi.useFakeTimers()
|
|
seedTree()
|
|
seedNotifications([makeNotification()])
|
|
|
|
const wrapper = await mountBox()
|
|
await at(wrapper.findAll("[data-slot='tooltip-trigger']"), 0).trigger(
|
|
"pointermove",
|
|
)
|
|
await vi.advanceTimersByTimeAsync(TOOLTIP_DELAY_MS)
|
|
|
|
expect(openTooltipText(wrapper)).toBe("Runbook")
|
|
})
|
|
|
|
it("falls back to a placeholder name for a deleted document", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([makeNotification()])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(t("notification.document-fallback"))
|
|
})
|
|
|
|
it("labels a notification from the last minute as new", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([makeNotification({ createdAt: "2026-03-14T11:59:30Z" })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(t("notification.now-time-label"))
|
|
})
|
|
|
|
it("shows how long ago an older notification arrived", async ({ expect }) => {
|
|
seedNotifications([makeNotification({ createdAt: "2026-03-14T11:00:00Z" })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain("1 hour ago")
|
|
})
|
|
|
|
it("dims a notification that has been read", async ({ expect }) => {
|
|
seedNotifications([makeNotification({ read: true })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(rows(wrapper)[0]?.classes()).toContain("opacity-60")
|
|
})
|
|
|
|
it("offers a mark-read button only on unread notifications", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([
|
|
makeNotification({ id: "notif-1", read: false }),
|
|
makeNotification({ id: "notif-2", read: true }),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text().match(/Mark as read/g)).toHaveLength(1)
|
|
})
|
|
|
|
describe("descriptions", { concurrent: false }, () => {
|
|
it("describes a review request", async ({ expect }) => {
|
|
seedNotifications([makeNotification()])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(
|
|
t("notification.messages.document-review-request-description"),
|
|
)
|
|
})
|
|
|
|
it.for([
|
|
{
|
|
name: "names the hook that fired",
|
|
input: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
type: DocumentHookType.URLWatcher,
|
|
},
|
|
expected: "notification.messages.document-hook-triggered-description",
|
|
},
|
|
{
|
|
name: "names the hook that needs attention",
|
|
input: {
|
|
code: NotificationCode.DocumentHookNeedsAttention,
|
|
type: DocumentHookType.GitHubTracking,
|
|
},
|
|
expected:
|
|
"notification.messages.document-hook-needs-attention-description",
|
|
},
|
|
])("$name", async ({ input, expected }, { expect }) => {
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: input.code,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: null,
|
|
type: input.type,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(
|
|
t(expected, { hook: t(`editor.hooks.${input.type}.title`) }),
|
|
)
|
|
})
|
|
|
|
it("names the commenter on a new comment", async ({ expect }) => {
|
|
seedAuthOrganization({
|
|
members: [{ userId: USER_ID, user: { name: "Ada" } }],
|
|
})
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: NotificationCode.DocumentNewComment,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
anchorBlockId: null,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain("Ada has posted a new comment")
|
|
})
|
|
|
|
it("names the commenter on a reply", async ({ expect }) => {
|
|
seedAuthOrganization({
|
|
members: [{ userId: USER_ID, user: { name: "Ada" } }],
|
|
})
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: NotificationCode.DocumentNewCommentReply,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
commentReplyId: "r",
|
|
anchorBlockId: null,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain("Ada has posted a reply to a comment")
|
|
})
|
|
|
|
it("calls an unknown commenter deleted", async ({ expect }) => {
|
|
seedAuthOrganization({ members: [] })
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: NotificationCode.DocumentNewComment,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
anchorBlockId: null,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain("deleted has posted a new comment")
|
|
})
|
|
|
|
it("falls back to a generic description for an unknown code", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([makeNotification({ code: "notification.unknown" })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).toContain(
|
|
t("notification.messages.default-description"),
|
|
)
|
|
})
|
|
})
|
|
|
|
describe("icons", { concurrent: false }, () => {
|
|
it("marks a review request with a reviewer icon", async ({ expect }) => {
|
|
seedNotifications([makeNotification()])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(renderedIconNames(wrapper)).toContain("lucide:file-user")
|
|
})
|
|
|
|
it.for([
|
|
{
|
|
name: "marks a url watcher trigger with a globe icon",
|
|
input: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
type: DocumentHookType.URLWatcher,
|
|
},
|
|
expected: "mingcute:earth-2-line",
|
|
},
|
|
{
|
|
name: "marks a github tracking trigger with a github icon",
|
|
input: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
type: DocumentHookType.GitHubTracking,
|
|
},
|
|
expected: "simple-icons:github",
|
|
},
|
|
{
|
|
name: "marks a scheduled reminder trigger with a timer icon",
|
|
input: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
type: DocumentHookType.ScheduledReminder,
|
|
},
|
|
expected: "lucide:timer",
|
|
},
|
|
{
|
|
name: "marks a container image watcher trigger with a container icon",
|
|
input: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
type: DocumentHookType.ContainerImageWatcher,
|
|
},
|
|
expected: "lucide:container",
|
|
},
|
|
{
|
|
name: "marks a hook that needs attention with its own icon",
|
|
input: {
|
|
code: NotificationCode.DocumentHookNeedsAttention,
|
|
type: DocumentHookType.ContainerImageWatcher,
|
|
},
|
|
expected: "lucide:container",
|
|
},
|
|
])("$name", async ({ input, expected }, { expect }) => {
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: input.code,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: null,
|
|
type: input.type,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(renderedIconNames(wrapper)).toContain(expected)
|
|
})
|
|
|
|
it("marks a hook type this build does not know with a warning icon", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: null,
|
|
type: "future-hook" as DocumentHookType,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(renderedIconNames(wrapper)).toContain(
|
|
"lucide:file-exclamation-point",
|
|
)
|
|
})
|
|
|
|
it("marks a comment notification with a message icon", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([
|
|
makeNotification({
|
|
code: NotificationCode.DocumentNewComment,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
anchorBlockId: null,
|
|
},
|
|
}),
|
|
])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(renderedIconNames(wrapper)).toContain("mingcute:message-4-fill")
|
|
})
|
|
})
|
|
|
|
describe("marking as read", { concurrent: false }, () => {
|
|
it("marks a single notification read", async ({ expect }) => {
|
|
const calls = mockEndpoint(
|
|
"PUT",
|
|
"/api/notifications/read-status",
|
|
() => ({}),
|
|
)
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await findButtonByText(
|
|
wrapper,
|
|
t("notification.actions.mark-read"),
|
|
).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(calls).toHaveLength(1)
|
|
expect(calls[0]?.body).toEqual({ ids: ["notif-1"] })
|
|
})
|
|
|
|
it("warns when marking a single notification read fails", async ({
|
|
expect,
|
|
}) => {
|
|
mockEndpoint("PUT", "/api/notifications/read-status", () => {
|
|
throw createError({ statusCode: 500 })
|
|
})
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await findButtonByText(
|
|
wrapper,
|
|
t("notification.actions.mark-read"),
|
|
).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(toast.custom).toHaveBeenCalledTimes(1)
|
|
})
|
|
|
|
it("marks every notification read with an empty id list", async ({
|
|
expect,
|
|
}) => {
|
|
const calls = mockEndpoint(
|
|
"PUT",
|
|
"/api/notifications/read-status",
|
|
() => ({}),
|
|
)
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await findButtonByText(
|
|
wrapper,
|
|
t("notification.read-all-button"),
|
|
).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(calls).toHaveLength(1)
|
|
expect(calls[0]?.body).toEqual({ ids: [] })
|
|
})
|
|
|
|
it("hides the button when every notification is already read", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([makeNotification({ read: true })])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(
|
|
wrapper
|
|
.findAll("button")
|
|
.some((b) => b.text().includes(t("notification.read-all-button"))),
|
|
).toBe(false)
|
|
})
|
|
|
|
it("warns when marking everything read fails", async ({ expect }) => {
|
|
mockEndpoint("PUT", "/api/notifications/read-status", () => {
|
|
throw createError({ statusCode: 500 })
|
|
})
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await findButtonByText(
|
|
wrapper,
|
|
t("notification.read-all-button"),
|
|
).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(toast.custom).toHaveBeenCalledTimes(1)
|
|
})
|
|
})
|
|
|
|
describe("navigation", { concurrent: false }, () => {
|
|
it("announces the navigation when a notification is opened", async ({
|
|
expect,
|
|
}) => {
|
|
mockEndpoint("PUT", "/api/notifications/read-status", () => ({}))
|
|
seedAuthOrganization({ slug: "acme" })
|
|
seedTree()
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await at(rows(wrapper), 0).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(
|
|
wrapper
|
|
.findComponent(NotificationBox)
|
|
.emitted("notification-navigation"),
|
|
).toHaveLength(1)
|
|
})
|
|
|
|
it.for([
|
|
{
|
|
name: "opens a review request at the document",
|
|
notification: {
|
|
code: NotificationCode.DocumentReviewRequest,
|
|
metadata: { documentId: DOC_ID, branchId: "b" },
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b`,
|
|
},
|
|
{
|
|
name: "opens a hook trigger at the block that fired",
|
|
notification: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: "block-7",
|
|
type: DocumentHookType.URLWatcher,
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b#block-7`,
|
|
},
|
|
{
|
|
name: "opens a document-wide hook trigger at the document",
|
|
notification: {
|
|
code: NotificationCode.DocumentHookTrigerred,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: null,
|
|
type: DocumentHookType.URLWatcher,
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b`,
|
|
},
|
|
{
|
|
name: "opens a hook that needs attention at the block it watches",
|
|
notification: {
|
|
code: NotificationCode.DocumentHookNeedsAttention,
|
|
metadata: {
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
blockId: "block-7",
|
|
type: DocumentHookType.URLWatcher,
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}#block-7`,
|
|
},
|
|
{
|
|
name: "opens a new comment at the block it is anchored to",
|
|
notification: {
|
|
code: NotificationCode.DocumentNewComment,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
anchorBlockId: "block-3",
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b#block-3`,
|
|
},
|
|
{
|
|
name: "opens an unanchored comment at the document",
|
|
notification: {
|
|
code: NotificationCode.DocumentNewComment,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
anchorBlockId: null,
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b`,
|
|
},
|
|
{
|
|
name: "opens a comment reply at the block it is anchored to",
|
|
notification: {
|
|
code: NotificationCode.DocumentNewCommentReply,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
commentReplyId: "r",
|
|
anchorBlockId: "block-4",
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b#block-4`,
|
|
},
|
|
{
|
|
name: "opens an unanchored comment reply at the document",
|
|
notification: {
|
|
code: NotificationCode.DocumentNewCommentReply,
|
|
metadata: {
|
|
userId: USER_ID,
|
|
documentId: DOC_ID,
|
|
branchId: "b",
|
|
commentId: "c",
|
|
commentReplyId: "r",
|
|
anchorBlockId: null,
|
|
},
|
|
},
|
|
expected: `/acme/Runbook-${DOC_ID}?branch=b`,
|
|
},
|
|
])("$name", async ({ notification, expected }, { expect }) => {
|
|
mockEndpoint("PUT", "/api/notifications/read-status", () => ({}))
|
|
mockAuthOrganization({ id: "org-1", slug: "acme", members: [] })
|
|
seedTree()
|
|
seedNotifications([makeNotification(notification)])
|
|
const wrapper = await mountBox()
|
|
|
|
await at(rows(wrapper), 0).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(navigateToMock).toHaveBeenCalledExactlyOnceWith(expected)
|
|
})
|
|
|
|
it("goes nowhere for a notification code it does not know", async ({
|
|
expect,
|
|
}) => {
|
|
mockAuthOrganization({ id: "org-1", slug: "acme", members: [] })
|
|
seedTree()
|
|
seedNotifications([makeNotification({ code: "notification.unknown" })])
|
|
const wrapper = await mountBox()
|
|
|
|
await at(rows(wrapper), 0).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(navigateToMock).toHaveBeenCalledTimes(0)
|
|
})
|
|
|
|
it("stays put when the notification's document no longer exists", async ({
|
|
expect,
|
|
}) => {
|
|
seedAuthOrganization({ slug: "acme" })
|
|
seedNotifications([makeNotification()])
|
|
const wrapper = await mountBox()
|
|
|
|
await at(rows(wrapper), 0).trigger("click")
|
|
await flushPromises()
|
|
|
|
expect(
|
|
wrapper
|
|
.findComponent(NotificationBox)
|
|
.emitted("notification-navigation"),
|
|
).toBeUndefined()
|
|
})
|
|
})
|
|
|
|
describe("mobile", { concurrent: false }, () => {
|
|
it("hides the close button on wide viewports", async ({ expect }) => {
|
|
seedNotifications([])
|
|
|
|
const wrapper = await mountBox()
|
|
|
|
expect(wrapper.text()).not.toContain(
|
|
t("notification.actions.close-notification-box"),
|
|
)
|
|
})
|
|
|
|
it("closes the box when its close button is pressed", async ({
|
|
expect,
|
|
}) => {
|
|
seedNotifications([])
|
|
const wrapper = await mountBox({ mobile: true })
|
|
|
|
await findButtonByText(
|
|
wrapper,
|
|
t("notification.actions.close-notification-box"),
|
|
).trigger("click")
|
|
|
|
expect(
|
|
wrapper
|
|
.findComponent(NotificationBox)
|
|
.emitted("close-notification-box"),
|
|
).toHaveLength(1)
|
|
})
|
|
})
|
|
|
|
describe("websocket updates", { concurrent: false }, () => {
|
|
it("subscribes to notification creations while mounted", async ({
|
|
expect,
|
|
}) => {
|
|
const subscribe = vi.fn().mockReturnValue(vi.fn())
|
|
useWebSocketStateStore().state = { subscribe } as never
|
|
seedNotifications([])
|
|
|
|
await mountBox()
|
|
|
|
expect(subscribe).toHaveBeenCalledTimes(1)
|
|
expect(subscribe.mock.calls[0]?.[0]).toBe(WS_NOTIFICATION_CREATION_TOPIC)
|
|
useWebSocketStateStore().state = null
|
|
})
|
|
|
|
it("unsubscribes when it goes away", async ({ expect }) => {
|
|
const unsubscribe = vi.fn()
|
|
useWebSocketStateStore().state = {
|
|
subscribe: vi.fn().mockReturnValue(unsubscribe),
|
|
} as never
|
|
seedNotifications([])
|
|
const wrapper = await mountBox()
|
|
|
|
wrapper.unmount()
|
|
|
|
expect(unsubscribe).toHaveBeenCalledTimes(1)
|
|
useWebSocketStateStore().state = null
|
|
})
|
|
})
|
|
})
|