feat: secure and document book reviews system
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 10m3s
Some checks failed
Dev Deployment (Zero Downtime) / deploy-dev (push) Failing after 10m3s
Added rate limiting to APIs, cleaned up docs, implemented fallback logic for reviews without text, and added comprehensive n8n guide.
This commit is contained in:
102
app/__tests__/components/CurrentlyReading.test.tsx
Normal file
102
app/__tests__/components/CurrentlyReading.test.tsx
Normal file
@@ -0,0 +1,102 @@
|
||||
import { render, screen, waitFor } from "@testing-library/react";
|
||||
import CurrentlyReading from "@/app/components/CurrentlyReading";
|
||||
|
||||
// Mock next-intl
|
||||
jest.mock("next-intl", () => ({
|
||||
useTranslations: () => (key: string) => {
|
||||
const translations: Record<string, string> = {
|
||||
title: "Reading",
|
||||
progress: "Progress",
|
||||
};
|
||||
return translations[key] || key;
|
||||
},
|
||||
}));
|
||||
|
||||
// Mock next/image
|
||||
jest.mock("next/image", () => ({
|
||||
__esModule: true,
|
||||
default: (props: any) => <img {...props} />,
|
||||
}));
|
||||
|
||||
// Mock fetch
|
||||
global.fetch = jest.fn();
|
||||
|
||||
describe("CurrentlyReading Component", () => {
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
});
|
||||
|
||||
it("renders nothing when loading", () => {
|
||||
// Return a never-resolving promise to simulate loading state
|
||||
(global.fetch as jest.Mock).mockReturnValue(new Promise(() => {}));
|
||||
const { container } = render(<CurrentlyReading />);
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("renders nothing when no books are returned", async () => {
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ currentlyReading: null }),
|
||||
});
|
||||
|
||||
const { container } = render(<CurrentlyReading />);
|
||||
await waitFor(() => expect(global.fetch).toHaveBeenCalled());
|
||||
expect(container).toBeEmptyDOMElement();
|
||||
});
|
||||
|
||||
it("renders a book when data is fetched", async () => {
|
||||
const mockBook = {
|
||||
title: "Test Book",
|
||||
authors: ["Test Author"],
|
||||
image: "/test-image.jpg",
|
||||
progress: 50,
|
||||
startedAt: "2023-01-01",
|
||||
};
|
||||
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ currentlyReading: mockBook }),
|
||||
});
|
||||
|
||||
render(<CurrentlyReading />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Reading (1)")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Book")).toBeInTheDocument();
|
||||
expect(screen.getByText("Test Author")).toBeInTheDocument();
|
||||
expect(screen.getByText("50%")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
|
||||
it("renders multiple books correctly", async () => {
|
||||
const mockBooks = [
|
||||
{
|
||||
title: "Book 1",
|
||||
authors: ["Author 1"],
|
||||
image: "/img1.jpg",
|
||||
progress: 10,
|
||||
startedAt: "2023-01-01",
|
||||
},
|
||||
{
|
||||
title: "Book 2",
|
||||
authors: ["Author 2"],
|
||||
image: "/img2.jpg",
|
||||
progress: 90,
|
||||
startedAt: "2023-02-01",
|
||||
},
|
||||
];
|
||||
|
||||
(global.fetch as jest.Mock).mockResolvedValue({
|
||||
ok: true,
|
||||
json: async () => ({ currentlyReading: mockBooks }),
|
||||
});
|
||||
|
||||
render(<CurrentlyReading />);
|
||||
|
||||
await waitFor(() => {
|
||||
expect(screen.getByText("Reading (2)")).toBeInTheDocument();
|
||||
expect(screen.getByText("Book 1")).toBeInTheDocument();
|
||||
expect(screen.getByText("Book 2")).toBeInTheDocument();
|
||||
});
|
||||
});
|
||||
});
|
||||
53
app/__tests__/components/ThemeToggle.test.tsx
Normal file
53
app/__tests__/components/ThemeToggle.test.tsx
Normal file
@@ -0,0 +1,53 @@
|
||||
import React from "react";
|
||||
import { render, screen, fireEvent, waitFor } from "@testing-library/react";
|
||||
import { ThemeToggle } from "@/app/components/ThemeToggle";
|
||||
import { useTheme } from "next-themes";
|
||||
|
||||
// Mock next-themes
|
||||
jest.mock("next-themes", () => ({
|
||||
useTheme: jest.fn(),
|
||||
}));
|
||||
|
||||
describe("ThemeToggle Component", () => {
|
||||
const setThemeMock = jest.fn();
|
||||
|
||||
beforeEach(() => {
|
||||
jest.clearAllMocks();
|
||||
(useTheme as jest.Mock).mockReturnValue({
|
||||
theme: "light",
|
||||
setTheme: setThemeMock,
|
||||
});
|
||||
});
|
||||
|
||||
it("renders a placeholder initially (to avoid hydration mismatch)", () => {
|
||||
const { container } = render(<ThemeToggle />);
|
||||
// Initial render should be the loading div
|
||||
expect(container.firstChild).toHaveClass("w-9 h-9");
|
||||
});
|
||||
|
||||
it("toggles to dark mode when clicked", async () => {
|
||||
render(<ThemeToggle />);
|
||||
|
||||
// Wait for effect to set mounted=true
|
||||
const button = await screen.findByRole("button", { name: /toggle theme/i });
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(setThemeMock).toHaveBeenCalledWith("dark");
|
||||
});
|
||||
|
||||
it("toggles to light mode when clicked if currently dark", async () => {
|
||||
(useTheme as jest.Mock).mockReturnValue({
|
||||
theme: "dark",
|
||||
setTheme: setThemeMock,
|
||||
});
|
||||
|
||||
render(<ThemeToggle />);
|
||||
|
||||
const button = await screen.findByRole("button", { name: /toggle theme/i });
|
||||
|
||||
fireEvent.click(button);
|
||||
|
||||
expect(setThemeMock).toHaveBeenCalledWith("light");
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user