Files
jobtrackingapp/job-tracker-ui/src/notifications-popover.test.tsx
T
cesnimda 998ee07a9a
CI and Deploy / test (pull_request) Failing after 2m55s
CI and Deploy / deploy (pull_request) Has been skipped
feat(notifications): open bell popover
2026-08-15 13:37:29 +02:00

85 lines
3.1 KiB
TypeScript

import React from "react";
import "@testing-library/jest-dom";
import { fireEvent, render, screen, waitFor } from "@testing-library/react";
import { api } from "./api";
import NotificationsPopover from "./components/NotificationsPopover";
import { I18nProvider } from "./i18n/I18nProvider";
jest.mock("./api", () => ({
api: {
get: jest.fn(),
post: jest.fn(),
delete: jest.fn(),
interceptors: { request: { use: jest.fn() }, response: { use: jest.fn() } },
},
getApiErrorMessage: (_error: unknown, fallback?: string) => fallback || "Request failed.",
}));
const mockedApi = api as jest.Mocked<typeof api>;
const notification = {
id: "22222222-2222-2222-2222-222222222222",
operationId: null,
kind: "operation_failed",
title: "CV export failed",
message: "Review the operation and try again.",
linkPath: "/operations",
createdAtUtc: "2026-08-15T12:00:00Z",
readAtUtc: null,
};
function renderPopover(overrides: Partial<React.ComponentProps<typeof NotificationsPopover>> = {}) {
const anchor = document.createElement("button");
document.body.appendChild(anchor);
const props = {
anchorEl: anchor,
onClose: jest.fn(),
onNavigate: jest.fn(),
onChanged: jest.fn(),
...overrides,
};
const result = render(<I18nProvider><NotificationsPopover {...props} /></I18nProvider>);
return { ...result, props, anchor };
}
beforeEach(() => {
jest.clearAllMocks();
mockedApi.get.mockResolvedValue({ data: [notification] } as any);
mockedApi.post.mockResolvedValue({ data: {} } as any);
mockedApi.delete.mockResolvedValue({ data: {} } as any);
});
test("shows notifications and updates read and dismissed state in place", async () => {
const { props } = renderPopover();
expect(await screen.findByText("CV export failed")).toBeInTheDocument();
expect(mockedApi.get).toHaveBeenCalledWith("/notifications?limit=10");
fireEvent.click(screen.getByRole("button", { name: "Mark read" }));
await waitFor(() => expect(mockedApi.post).toHaveBeenCalledWith(`/notifications/${notification.id}/read`));
expect(props.onChanged).toHaveBeenCalledTimes(1);
await waitFor(() => expect(screen.queryByRole("button", { name: "Mark read" })).not.toBeInTheDocument());
fireEvent.click(screen.getByRole("button", { name: "Dismiss" }));
await waitFor(() => expect(mockedApi.delete).toHaveBeenCalledWith(`/notifications/${notification.id}`));
await waitFor(() => expect(screen.queryByText("CV export failed")).not.toBeInTheDocument());
expect(props.onChanged).toHaveBeenCalledTimes(2);
});
test("opens a notification destination instead of routing the bell itself", async () => {
const { props } = renderPopover();
fireEvent.click(await screen.findByRole("button", { name: "Open" }));
await waitFor(() => expect(props.onNavigate).toHaveBeenCalledWith("/operations"));
expect(mockedApi.post).toHaveBeenCalledWith(`/notifications/${notification.id}/read`);
expect(props.onClose).toHaveBeenCalledTimes(1);
});
test("shows an honest empty state", async () => {
mockedApi.get.mockResolvedValue({ data: [] } as any);
renderPopover();
expect(await screen.findByText("You're all caught up")).toBeInTheDocument();
});