132 lines
3.8 KiB
TypeScript
132 lines
3.8 KiB
TypeScript
/* @vitest-environment jsdom */
|
|
|
|
import { act } from "react";
|
|
import { createRoot, type Root } from "react-dom/client";
|
|
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
|
|
import { PrivateAlbumSchema } from "@/data/schemas/private-zoom";
|
|
|
|
import { PrivateAlbumCard } from "../private-album-card";
|
|
|
|
describe("PrivateAlbumCard interactions", () => {
|
|
let container: HTMLDivElement;
|
|
let root: Root;
|
|
|
|
beforeEach(() => {
|
|
(globalThis as { IS_REACT_ACT_ENVIRONMENT?: boolean })
|
|
.IS_REACT_ACT_ENVIRONMENT = true;
|
|
container = document.createElement("div");
|
|
document.body.append(container);
|
|
root = createRoot(container);
|
|
});
|
|
|
|
afterEach(() => {
|
|
act(() => root.unmount());
|
|
container.remove();
|
|
});
|
|
|
|
it("opens the selected thumbnail with its original source index", () => {
|
|
const onOpenGallery = vi.fn();
|
|
const album = PrivateAlbumSchema.parse({
|
|
albumId: "album-1",
|
|
title: "Private afternoon",
|
|
imageCount: 4,
|
|
images: [
|
|
{ url: "", locked: false, index: 0 },
|
|
{ url: "/images/private-zoom/locked.png", locked: true, index: 1 },
|
|
{ url: "/images/private-zoom/photo-2.png", locked: false, index: 2 },
|
|
{ url: "/images/private-zoom/photo-3.png", locked: false, index: 3 },
|
|
],
|
|
locked: false,
|
|
unlocked: true,
|
|
lockDetail: { locked: false },
|
|
});
|
|
|
|
act(() => {
|
|
root.render(
|
|
<PrivateAlbumCard
|
|
album={album}
|
|
displayName="Elio Silvestri"
|
|
avatarUrl="/images/avatar/elio.png"
|
|
index={0}
|
|
isUnlocking={false}
|
|
onOpenGallery={onOpenGallery}
|
|
onUnlock={() => undefined}
|
|
/>,
|
|
);
|
|
});
|
|
|
|
const thumbnail = container.querySelector<HTMLButtonElement>(
|
|
'button[data-image-index="3"]',
|
|
);
|
|
act(() => thumbnail?.click());
|
|
|
|
expect(onOpenGallery).toHaveBeenCalledOnce();
|
|
expect(onOpenGallery).toHaveBeenCalledWith(3);
|
|
});
|
|
|
|
it("unlocks only from the collection CTA and disables repeat actions", () => {
|
|
const onUnlock = vi.fn();
|
|
const album = PrivateAlbumSchema.parse({
|
|
albumId: "album-locked",
|
|
title: "Locked afternoon",
|
|
imageCount: 3,
|
|
images: [
|
|
{ url: "/images/private-zoom/locked.png", locked: true, index: 0 },
|
|
],
|
|
locked: true,
|
|
unlocked: false,
|
|
unlockCost: 40,
|
|
lockDetail: { locked: true },
|
|
});
|
|
|
|
renderLockedCard(album, false, onUnlock);
|
|
const avatar = container.querySelector<HTMLElement>(
|
|
'[aria-label="Elio Silvestri locked collection"]',
|
|
);
|
|
act(() => avatar?.parentElement?.click());
|
|
expect(onUnlock).not.toHaveBeenCalled();
|
|
|
|
const button = findCollectionButton();
|
|
act(() => button.click());
|
|
expect(onUnlock).toHaveBeenCalledOnce();
|
|
|
|
renderLockedCard(album, true, onUnlock);
|
|
const disabledButton = findCollectionButton();
|
|
expect(disabledButton.disabled).toBe(true);
|
|
expect(disabledButton.textContent).toBe("Opening...");
|
|
act(() => disabledButton.click());
|
|
expect(onUnlock).toHaveBeenCalledOnce();
|
|
});
|
|
|
|
function renderLockedCard(
|
|
album: ReturnType<typeof PrivateAlbumSchema.parse>,
|
|
isUnlocking: boolean,
|
|
onUnlock: () => void,
|
|
): void {
|
|
act(() => {
|
|
root.render(
|
|
<PrivateAlbumCard
|
|
album={album}
|
|
displayName="Elio Silvestri"
|
|
avatarUrl="/images/avatar/elio.png"
|
|
index={0}
|
|
isUnlocking={isUnlocking}
|
|
onOpenGallery={() => undefined}
|
|
onUnlock={onUnlock}
|
|
/>,
|
|
);
|
|
});
|
|
}
|
|
|
|
function findCollectionButton(): HTMLButtonElement {
|
|
const button = Array.from(container.querySelectorAll("button")).find(
|
|
(item) =>
|
|
item.textContent === "View collection" ||
|
|
item.textContent === "Opening...",
|
|
);
|
|
if (!button) throw new Error("Missing View collection button");
|
|
return button;
|
|
}
|
|
});
|