bb9c8ec1c3
- 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>
142 lines
4.8 KiB
JavaScript
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();
|
|
});
|
|
});
|