// @vitest-environment jsdom
import React from "react"
import { render, screen, fireEvent, cleanup } from "@testing-library/react"
import { describe, expect, it, vi, afterEach } from "vitest"
import { z } from "zod"
import { TieredPriceForm } from "./tiered-price-form"
import { CurrencyInfo } from "../../../lib/data/currencies"
afterEach(() => {
cleanup()
})
vi.mock("react-i18next", () => ({
useTranslation: () => ({
t: (key: string) => key,
}),
}))
vi.mock("@medusajs/icons", () => ({
TriangleDownMini: () => ,
XMarkMini: () => ,
}))
let closeSpy: any
vi.mock("../../../components/modals", () => {
const MockComponent = ({ children }: { children: React.ReactNode }) => (
{children}
)
return {
StackedFocusModal: {
Content: MockComponent,
Header: MockComponent,
Body: MockComponent,
Footer: MockComponent,
Title: MockComponent,
Description: MockComponent,
Close: ({ children }: { children: React.ReactNode }) => (
closeSpy?.()}>{children}
),
},
}
})
describe("TieredPriceForm", () => {
const mockCurrency: CurrencyInfo = {
code: "usd",
name: "US Dollar",
symbol_native: "$",
decimal_digits: 2,
}
const schema = z.object({
prices: z.array(
z.object({
amount: z.string().min(1, "Amount is required"),
min: z.string().min(1, "Min is required"),
max: z.string().optional(),
})
),
})
const defaultProps = {
schema,
initialValues: [{ amount: "10", min: "1", max: "10" }],
onSubmit: vi.fn(),
onClose: vi.fn(),
currency: mockCurrency,
header: "Tiered Price Header",
description: "Tiered Price Description",
addPriceLabel: "Add Price",
fieldConfig: {
min: "min",
max: "max",
minLabel: "Min",
maxLabel: "Max",
},
renderConditionItem: vi.fn(({ index }) => (
Condition Item {index}
)),
renderConditionTrigger: vi.fn(({ index }) => (
Condition Trigger {index}
)),
}
it("should render with initial values", () => {
render()
expect(screen.getByText("Tiered Price Header")).toBeTruthy()
expect(screen.getByText("Tiered Price Description")).toBeTruthy()
expect(screen.getByText("Add Price")).toBeTruthy()
expect(screen.getByTestId("condition-trigger-0")).toBeTruthy()
expect(screen.getByTestId("condition-item-0")).toBeTruthy()
})
it("should add a new price tier when add button is clicked", () => {
render()
const addButton = screen.getByText("Add Price")
fireEvent.click(addButton)
expect(screen.getByTestId("condition-trigger-1")).toBeTruthy()
expect(screen.getByTestId("condition-item-1")).toBeTruthy()
})
it("should remove a price tier when remove button is clicked", () => {
render()
const removeButton = screen.getByTestId("x-mark-mini").closest("button")
if (!removeButton) {
throw new Error("Remove button not found")
}
fireEvent.click(removeButton)
expect(screen.queryByTestId("condition-trigger-0")).toBeNull()
})
it("should call onSubmit with valid data", async () => {
const onSubmit = vi.fn()
render()
const saveButton = screen.getByText("actions.save")
fireEvent.click(saveButton)
// Since handleSubmit is async and uses react-hook-form, we might need to wait
// But in this case, initialValues are already valid according to the schema
await vi.waitFor(() => {
expect(onSubmit).toHaveBeenCalledWith({
prices: [{ amount: "10", min: "1", max: "10" }],
})
})
expect(defaultProps.onClose).toHaveBeenCalled()
})
it("should handle validation errors on submit", async () => {
const onSubmit = vi.fn()
// Initial values that are invalid (amount is empty)
const initialValues = [{ amount: "", min: "1", max: "10" }]
render(
)
const saveButton = screen.getByText("actions.save")
fireEvent.click(saveButton)
await vi.waitFor(() => {
expect(onSubmit).not.toHaveBeenCalled()
})
})
it("should trigger submit on Ctrl+Enter / Cmd+Enter", async () => {
const onSubmit = vi.fn()
render()
const form = document.querySelector("form")
if (!form) {
throw new Error("Form not found")
}
fireEvent.keyDown(form, {
key: "Enter",
ctrlKey: true,
})
await vi.waitFor(() => {
expect(onSubmit).toHaveBeenCalled()
})
})
it("should call onClose when cancel is clicked", () => {
closeSpy = vi.fn()
render()
const cancelButton = screen.getByText("actions.cancel")
fireEvent.click(cancelButton)
expect(closeSpy).toHaveBeenCalled()
})
})