This commit is contained in:
@@ -0,0 +1,230 @@
|
||||
import { cleanup, render, screen } from "@solidjs/testing-library";
|
||||
import { createSignal } from "solid-js";
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
||||
|
||||
const { mockAnimate } = vi.hoisted(() => ({
|
||||
mockAnimate: vi.fn().mockImplementation(() => ({
|
||||
pause: vi.fn(),
|
||||
then: vi.fn((cb: () => void) => {
|
||||
cb();
|
||||
return Promise.resolve();
|
||||
}),
|
||||
})),
|
||||
}));
|
||||
|
||||
vi.mock("animejs", () => ({
|
||||
animate: mockAnimate,
|
||||
}));
|
||||
|
||||
vi.mock("../../../../src/ts/utils/misc", () => ({
|
||||
applyReducedMotion: vi.fn((duration: number) => duration),
|
||||
}));
|
||||
|
||||
import { AnimeConditional } from "../../../../src/ts/components/common/anime/AnimeConditional";
|
||||
|
||||
describe("AnimeConditional", () => {
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks();
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
|
||||
it("renders `then` content when `if` is truthy", () => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={true}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
else={<div data-testid="else-content">else</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByTestId("then-content")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("else-content")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders `else` content when `if` is falsy", () => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={false}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
else={<div data-testid="else-content">else</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.queryByTestId("then-content")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("else-content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("renders `else` content when `if` is null", () => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={null}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
else={<div data-testid="else-content">else</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.queryByTestId("then-content")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("else-content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("switches reactively from `then` to `else`", () => {
|
||||
const [condition, setCondition] = createSignal(true);
|
||||
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={condition()}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
else={<div data-testid="else-content">else</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByTestId("then-content")).toBeInTheDocument();
|
||||
|
||||
setCondition(false);
|
||||
|
||||
expect(screen.queryByTestId("then-content")).not.toBeInTheDocument();
|
||||
expect(screen.getByTestId("else-content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("switches reactively from `else` to `then`", () => {
|
||||
const [condition, setCondition] = createSignal(false);
|
||||
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={condition()}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
else={<div data-testid="else-content">else</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByTestId("else-content")).toBeInTheDocument();
|
||||
|
||||
setCondition(true);
|
||||
|
||||
expect(screen.getByTestId("then-content")).toBeInTheDocument();
|
||||
expect(screen.queryByTestId("else-content")).not.toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("supports `then` as a function and passes the truthy value", () => {
|
||||
const obj = { label: "hello" };
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={obj}
|
||||
then={(value) => <div data-testid="fn-content">{value().label}</div>}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(screen.getByTestId("fn-content")).toHaveTextContent("hello");
|
||||
});
|
||||
|
||||
it("does not throw without `else` prop", () => {
|
||||
expect(() => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={true}
|
||||
then={<div data-testid="then-content">then</div>}
|
||||
/>
|
||||
));
|
||||
}).not.toThrow();
|
||||
|
||||
expect(screen.getByTestId("then-content")).toBeInTheDocument();
|
||||
});
|
||||
|
||||
it("does not throw on mount/unmount", () => {
|
||||
const [show, setShow] = createSignal(true);
|
||||
|
||||
expect(() => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={show()}
|
||||
then={<div>then</div>}
|
||||
else={<div>else</div>}
|
||||
/>
|
||||
));
|
||||
}).not.toThrow();
|
||||
|
||||
expect(() => setShow(false)).not.toThrow();
|
||||
expect(() => setShow(true)).not.toThrow();
|
||||
});
|
||||
|
||||
describe("default animations (opacity fade)", () => {
|
||||
it("applies default opacity animate on `then` branch", () => {
|
||||
render(() => <AnimeConditional if={true} then={<div>then</div>} />);
|
||||
|
||||
expect(mockAnimate).toHaveBeenCalledWith(
|
||||
expect.any(HTMLElement),
|
||||
expect.objectContaining({ opacity: 1, duration: 125 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("applies default opacity initial state on `then` branch", () => {
|
||||
render(() => <AnimeConditional if={true} then={<div>then</div>} />);
|
||||
|
||||
// Initial call: opacity:0 with duration:0
|
||||
expect(mockAnimate).toHaveBeenCalledWith(
|
||||
expect.any(HTMLElement),
|
||||
expect.objectContaining({ opacity: 0, duration: 0 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe("custom animeProps", () => {
|
||||
it("uses custom animate params when animeProps provided", () => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={true}
|
||||
then={<div>then</div>}
|
||||
animeProps={{
|
||||
initial: { opacity: 0, translateY: -10 },
|
||||
animate: { opacity: 1, translateY: 0, duration: 400 },
|
||||
exit: { opacity: 0, translateY: -10, duration: 200 },
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
expect(mockAnimate).toHaveBeenCalledWith(
|
||||
expect.any(HTMLElement),
|
||||
expect.objectContaining({ opacity: 1, translateY: 0, duration: 400 }),
|
||||
);
|
||||
});
|
||||
|
||||
it("uses custom initial state when animeProps provided", () => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
if={true}
|
||||
then={<div>then</div>}
|
||||
animeProps={{
|
||||
initial: { opacity: 0, translateY: -10 },
|
||||
animate: { opacity: 1, translateY: 0, duration: 400 },
|
||||
}}
|
||||
/>
|
||||
));
|
||||
|
||||
// Initial state applied with duration:0
|
||||
expect(mockAnimate).toHaveBeenCalledWith(
|
||||
expect.any(HTMLElement),
|
||||
expect.objectContaining({ opacity: 0, translateY: -10, duration: 0 }),
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
it("exitBeforeEnter prop does not throw on condition change", () => {
|
||||
const [cond, setCond] = createSignal(true);
|
||||
|
||||
expect(() => {
|
||||
render(() => (
|
||||
<AnimeConditional
|
||||
exitBeforeEnter
|
||||
if={cond()}
|
||||
then={<div>then</div>}
|
||||
else={<div>else</div>}
|
||||
/>
|
||||
));
|
||||
}).not.toThrow();
|
||||
|
||||
expect(() => setCond(false)).not.toThrow();
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user