refactor: centralize newsletter dialogs and optimize newsletter card

This commit is contained in:
Leon
2025-07-17 19:47:58 +02:00
parent fe55a49f8d
commit 95170e7201
7 changed files with 245 additions and 448 deletions

View File

@@ -1,77 +0,0 @@
import React from "react"
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import "@testing-library/jest-dom"
import { AddNewsletterDialog } from "../AddNewsletterDialog"
import * as api from "@/lib/api"
// Mock the API module
jest.mock("@/lib/api", () => ({
...jest.requireActual("@/lib/api"),
createNewsletter: jest.fn(),
}))
const mockedApi = api as jest.Mocked<typeof api>
describe("AddNewsletterDialog", () => {
const handleOpenChange = jest.fn()
const handleSuccess = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
})
it("allows user to fill out the form and submit", async () => {
mockedApi.createNewsletter.mockResolvedValueOnce({
id: 1,
name: "My New Newsletter",
is_active: true,
extract_content: false,
senders: [{ id: 1, email: "test@example.com", newsletter_id: 1 }],
entries_count: 0,
})
render(<AddNewsletterDialog isOpen={true} folderOptions={["INBOX", "Archive"]} onOpenChange={handleOpenChange} onSuccess={handleSuccess} />)
// Fill out the form
fireEvent.change(screen.getByLabelText(/Newsletter Name/i), { target: { value: "My New Newsletter" } })
fireEvent.change(screen.getByPlaceholderText(/Enter email address/i), { target: { value: "test@example.com" } })
// Submit the form
fireEvent.click(screen.getByRole("button", { name: /Register Newsletter/i }))
// Wait for the async operation to complete
await waitFor(() => {
expect(mockedApi.createNewsletter).toHaveBeenCalledWith({
name: "My New Newsletter",
sender_emails: ["test@example.com"],
move_to_folder: "",
extract_content: false,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
it("allows adding and removing email fields", () => {
render(<AddNewsletterDialog isOpen={true} folderOptions={[]} onOpenChange={() => {}} onSuccess={() => {}} />)
// Initial state
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(1)
// Add another email
fireEvent.click(screen.getByRole("button", { name: /Add Another Email/i }))
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(2)
// Remove the first email
fireEvent.click(screen.getAllByRole("button", { name: /Remove/i })[0])
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(1)
})
it("closes the dialog when cancel is clicked", () => {
render(<AddNewsletterDialog isOpen={true} folderOptions={["INBOX", "Archive"]} onOpenChange={handleOpenChange} onSuccess={handleSuccess} />)
fireEvent.click(screen.getByRole("button", { name: /Cancel/i }))
expect(handleOpenChange).toHaveBeenCalledWith(false)
expect(handleSuccess).not.toHaveBeenCalled()
})
})

View File

@@ -1,114 +0,0 @@
import React from "react"
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import "@testing-library/jest-dom"
import { EditNewsletterDialog } from "../EditNewsletterDialog"
import { Newsletter } from "@/lib/api"
import * as api from "@/lib/api"
// Mock the API module
jest.mock("@/lib/api", () => ({
...jest.requireActual("@/lib/api"),
updateNewsletter: jest.fn(),
deleteNewsletter: jest.fn(),
}))
const mockedApi = api as jest.Mocked<typeof api>
const mockNewsletter: Newsletter = {
id: 1,
name: "Existing Newsletter",
is_active: true,
extract_content: false,
senders: [{ id: 1, email: "current@example.com", newsletter_id: 1 }],
entries_count: 5,
}
describe("EditNewsletterDialog", () => {
const handleOpenChange = jest.fn()
const handleSuccess = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
// Mock window.confirm for the delete action
window.confirm = jest.fn(() => true)
})
it("renders with initial newsletter data and allows updates", async () => {
mockedApi.updateNewsletter.mockResolvedValueOnce({ ...mockNewsletter, name: "Updated Name" })
render(
<EditNewsletterDialog
newsletter={mockNewsletter}
isOpen={true}
folderOptions={["INBOX", "Archive"]}
onOpenChange={handleOpenChange}
onSuccess={handleSuccess}
/>
)
// Check that initial data is present
const nameInput = screen.getByLabelText(/Newsletter Name/i)
expect(nameInput).toHaveValue("Existing Newsletter")
expect(screen.getByDisplayValue("current@example.com")).toBeInTheDocument()
// Update the name
fireEvent.change(nameInput, { target: { value: "Updated Name" } })
// Submit
fireEvent.click(screen.getByRole("button", { name: /Save Changes/i }))
await waitFor(() => {
expect(mockedApi.updateNewsletter).toHaveBeenCalledWith(1, {
name: "Updated Name",
sender_emails: ["current@example.com"],
move_to_folder: "",
extract_content: false,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
it("calls deleteNewsletter when delete button is clicked and confirmed", async () => {
mockedApi.deleteNewsletter.mockResolvedValueOnce()
render(
<EditNewsletterDialog
newsletter={mockNewsletter}
isOpen={true}
folderOptions={["INBOX", "Archive"]}
onOpenChange={handleOpenChange}
onSuccess={handleSuccess}
/>
)
fireEvent.click(screen.getByRole("button", { name: /Delete Newsletter/i }))
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete the "Existing Newsletter" newsletter?')
await waitFor(() => {
expect(mockedApi.deleteNewsletter).toHaveBeenCalledWith(1)
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
it("does not call deleteNewsletter when delete is not confirmed", () => {
window.confirm = jest.fn(() => false) // User clicks "Cancel"
render(
<EditNewsletterDialog
newsletter={mockNewsletter}
isOpen={true}
folderOptions={["INBOX", "Archive"]}
onOpenChange={handleOpenChange}
onSuccess={handleSuccess}
/>
)
fireEvent.click(screen.getByRole("button", { name: /Delete Newsletter/i }))
expect(mockedApi.deleteNewsletter).not.toHaveBeenCalled()
expect(handleSuccess).not.toHaveBeenCalled()
})
})

View File

@@ -0,0 +1,136 @@
import React from "react"
import { render, screen, fireEvent, waitFor } from "@testing-library/react"
import "@testing-library/jest-dom"
import { NewsletterDialog } from "../NewsletterDialog"
import { Newsletter } from "@/lib/api"
import * as api from "@/lib/api"
// Mock the API module
jest.mock("@/lib/api", () => ({
...jest.requireActual("@/lib/api"),
createNewsletter: jest.fn(),
updateNewsletter: jest.fn(),
deleteNewsletter: jest.fn(),
}))
const mockedApi = api as jest.Mocked<typeof api>
const mockNewsletter: Newsletter = {
id: "1",
name: "Existing Newsletter",
is_active: true,
extract_content: false,
senders: [{ id: "1", email: "current@example.com" }],
entries_count: 5,
move_to_folder: "",
}
describe("NewsletterDialog", () => {
const handleOpenChange = jest.fn()
const handleSuccess = jest.fn()
beforeEach(() => {
jest.clearAllMocks()
window.confirm = jest.fn(() => true)
})
describe("Add Mode", () => {
it("allows user to fill out the form and submit", async () => {
mockedApi.createNewsletter.mockResolvedValueOnce({
id: "2",
name: "My New Newsletter",
is_active: true,
extract_content: false,
senders: [{ id: "2", email: "test@example.com" }],
entries_count: 0,
})
render(<NewsletterDialog isOpen={true} folderOptions={["INBOX", "Archive"]} onOpenChange={handleOpenChange} onSuccess={handleSuccess} />)
expect(screen.getByText("Register New Newsletter")).toBeInTheDocument()
fireEvent.change(screen.getByLabelText(/Newsletter Name/i), { target: { value: "My New Newsletter" } })
fireEvent.change(screen.getByPlaceholderText(/Enter email address/i), { target: { value: "test@example.com" } })
fireEvent.click(screen.getByRole("button", { name: /Register Newsletter/i }))
await waitFor(() => {
expect(mockedApi.createNewsletter).toHaveBeenCalledWith({
name: "My New Newsletter",
sender_emails: ["test@example.com"],
move_to_folder: "",
extract_content: false,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
it("allows adding and removing email fields", () => {
render(<NewsletterDialog isOpen={true} folderOptions={[]} onOpenChange={() => {}} onSuccess={() => {}} />)
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(1)
fireEvent.click(screen.getByRole("button", { name: /Add Another Email/i }))
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(2)
fireEvent.click(screen.getAllByRole("button", { name: /Remove/i })[0])
expect(screen.getAllByPlaceholderText(/Enter email address/i)).toHaveLength(1)
})
})
describe("Edit Mode", () => {
it("renders with initial newsletter data and allows updates", async () => {
mockedApi.updateNewsletter.mockResolvedValueOnce({ ...mockNewsletter, name: "Updated Name" })
render(
<NewsletterDialog
newsletter={mockNewsletter}
isOpen={true}
folderOptions={["INBOX", "Archive"]}
onOpenChange={handleOpenChange}
onSuccess={handleSuccess}
/>
)
expect(screen.getByText("Edit Newsletter")).toBeInTheDocument()
const nameInput = screen.getByLabelText(/Newsletter Name/i)
expect(nameInput).toHaveValue("Existing Newsletter")
expect(screen.getByDisplayValue("current@example.com")).toBeInTheDocument()
fireEvent.change(nameInput, { target: { value: "Updated Name" } })
fireEvent.click(screen.getByRole("button", { name: /Save Changes/i }))
await waitFor(() => {
expect(mockedApi.updateNewsletter).toHaveBeenCalledWith("1", {
name: "Updated Name",
sender_emails: ["current@example.com"],
move_to_folder: "",
extract_content: false,
})
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
it("calls deleteNewsletter when delete button is clicked and confirmed", async () => {
mockedApi.deleteNewsletter.mockResolvedValueOnce(undefined)
render(
<NewsletterDialog
newsletter={mockNewsletter}
isOpen={true}
folderOptions={["INBOX", "Archive"]}
onOpenChange={handleOpenChange}
onSuccess={handleSuccess}
/>
)
fireEvent.click(screen.getByRole("button", { name: /Delete Newsletter/i }))
expect(window.confirm).toHaveBeenCalledWith('Are you sure you want to delete the "Existing Newsletter" newsletter?')
await waitFor(() => {
expect(mockedApi.deleteNewsletter).toHaveBeenCalledWith("1")
expect(handleSuccess).toHaveBeenCalledTimes(1)
expect(handleOpenChange).toHaveBeenCalledWith(false)
})
})
})
})