Files
adopt-a-street/frontend/src/__tests__/context/NotificationProvider.test.js
T
William Valentin bb9c8ec1c3 feat: Migrate from Socket.IO to Server-Sent Events (SSE)
- Replace Socket.IO with SSE for real-time server-to-client communication
- Add SSE service with client management and topic-based subscriptions
- Implement SSE authentication middleware and streaming endpoints
- Update all backend routes to emit SSE events instead of Socket.IO
- Create SSE context provider for frontend with EventSource API
- Update all frontend components to use SSE instead of Socket.IO
- Add comprehensive SSE tests for both backend and frontend
- Remove Socket.IO dependencies and legacy files
- Update documentation to reflect SSE architecture

Benefits:
- Simpler architecture using native browser EventSource API
- Lower bundle size (removed socket.io-client dependency)
- Better compatibility with reverse proxies and load balancers
- Reduced resource usage for Raspberry Pi deployment
- Standard HTTP-based real-time communication

🤖 Generated with [AI Assistant]

Co-Authored-By: AI Assistant <noreply@ai-assistant.com>
2025-12-05 22:49:22 -08:00

142 lines
4.8 KiB
JavaScript

import React from "react";
import { render, screen, waitFor } from "@testing-library/react";
import { toast } from "react-toastify";
import NotificationProvider, { notify } from "../../context/NotificationProvider";
import { SSEContext } from "../../context/SSEContext";
import { AuthContext } from "../../context/AuthContext";
// Mock axios to prevent import errors
jest.mock("axios");
// Mock react-toastify
jest.mock("react-toastify", () => ({
toast: {
success: jest.fn(),
error: jest.fn(),
info: jest.fn(),
warning: jest.fn(),
dismiss: jest.fn(),
},
}));
describe("NotificationProvider", () => {
let mockSSEContext;
let mockAuthContext;
beforeEach(() => {
jest.clearAllMocks();
mockSSEContext = {
connected: true,
notifications: [],
on: jest.fn(),
off: jest.fn(),
subscribe: jest.fn().mockResolvedValue({ subscribed: [] }),
unsubscribe: jest.fn().mockResolvedValue({ unsubscribed: [] }),
clearNotification: jest.fn(),
clearAllNotifications: jest.fn(),
};
mockAuthContext = {
auth: {
isAuthenticated: true,
user: { id: "user123", name: "Test User" },
},
};
});
const renderWithProviders = (children) => {
return render(
<AuthContext.Provider value={mockAuthContext}>
<SSEContext.Provider value={mockSSEContext}>
<NotificationProvider>{children}</NotificationProvider>
</SSEContext.Provider>
</AuthContext.Provider>
);
};
test("renders children correctly", () => {
renderWithProviders(<div>Test Content</div>);
expect(screen.getByText("Test Content")).toBeInTheDocument();
});
test("subscribes to custom events via context", () => {
renderWithProviders(<div>Test</div>);
// Verify custom event listeners were registered via context
expect(mockSSEContext.on).toHaveBeenCalledWith("eventUpdate", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("taskUpdate", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("streetUpdate", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("achievementUnlocked", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("newPost", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("newComment", expect.any(Function));
expect(mockSSEContext.on).toHaveBeenCalledWith("notification", expect.any(Function));
});
test("cleans up event listeners on unmount", () => {
const { unmount } = renderWithProviders(<div>Test</div>);
unmount();
// Verify custom event listeners were removed via context
expect(mockSSEContext.off).toHaveBeenCalledWith("eventUpdate", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("taskUpdate", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("streetUpdate", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("achievementUnlocked", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("newPost", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("newComment", expect.any(Function));
expect(mockSSEContext.off).toHaveBeenCalledWith("notification", expect.any(Function));
});
test("does not subscribe when not connected", () => {
mockSSEContext.connected = false;
renderWithProviders(<div>Test</div>);
// Event listeners should not be registered when not connected
expect(mockSSEContext.on).not.toHaveBeenCalled();
});
});
describe("notify utility", () => {
beforeEach(() => {
jest.clearAllMocks();
});
test("notify.success calls toast.success", () => {
notify.success("Test message");
expect(toast.success).toHaveBeenCalledWith("Test message", {});
});
test("notify.error calls toast.error", () => {
notify.error("Error message");
expect(toast.error).toHaveBeenCalledWith("Error message", {});
});
test("notify.info calls toast.info", () => {
notify.info("Info message");
expect(toast.info).toHaveBeenCalledWith("Info message", {});
});
test("notify.warning calls toast.warning", () => {
notify.warning("Warning message");
expect(toast.warning).toHaveBeenCalledWith("Warning message", {});
});
test("notify.success accepts custom options", () => {
const options = { autoClose: 3000, position: "bottom-right" };
notify.success("Test", options);
expect(toast.success).toHaveBeenCalledWith("Test", options);
});
test("notify.dismiss calls toast.dismiss with toastId", () => {
notify.dismiss("test-toast-id");
expect(toast.dismiss).toHaveBeenCalledWith("test-toast-id");
});
test("notify.dismissAll calls toast.dismiss without arguments", () => {
notify.dismissAll();
expect(toast.dismiss).toHaveBeenCalledWith();
});
});