112 lines
4.7 KiB
TypeScript
112 lines
4.7 KiB
TypeScript
import type { PlayerState } from "@deadhead/schema";
|
||
import { cleanup, screen, waitFor, within } from "@testing-library/react";
|
||
import userEvent from "@testing-library/user-event";
|
||
import { describe, expect, it } from "vitest";
|
||
import { usePreferences } from "../src/state/preferences";
|
||
import { fakeApi, renderApp, samplePlayerState, sampleTutorialBoard, unauthorized } from "./render";
|
||
|
||
/** A fresh guest: "Start your company", then the new company with its welcome unseen. */
|
||
function newCompany() {
|
||
let started = false;
|
||
let state: PlayerState = samplePlayerState({ welcomeSeen: false });
|
||
const api = fakeApi({
|
||
getPlayerState: () => (started ? state : unauthorized()),
|
||
createGuest: () => {
|
||
started = true;
|
||
return { playerId: "p1", state };
|
||
},
|
||
getTutorialBoard: () => sampleTutorialBoard(),
|
||
markWelcomeSeen: () => {
|
||
state = { ...state, welcomeSeen: true };
|
||
return state;
|
||
},
|
||
});
|
||
return { api, state: () => state };
|
||
}
|
||
|
||
async function start(api: ReturnType<typeof newCompany>["api"]) {
|
||
renderApp(api, "/");
|
||
await userEvent.click(await screen.findByRole("button", { name: "Start your company" }));
|
||
return screen.findByRole("dialog", { name: "Welcome to Deadhead" });
|
||
}
|
||
|
||
describe("welcome cards (Step 22.2, decision 0.5.131)", () => {
|
||
it("walks through three cards and lands on Contracts with the intro card", async () => {
|
||
const { api, state } = newCompany();
|
||
const dialog = await start(api);
|
||
expect(dialog).toHaveTextContent("1 of 3");
|
||
expect(dialog).toHaveTextContent("never fly home empty");
|
||
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Next" }));
|
||
expect(dialog).toHaveTextContent("2 of 3");
|
||
expect(within(dialog).getByRole("heading")).toHaveTextContent("Your ships keep flying");
|
||
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Next" }));
|
||
expect(dialog).toHaveTextContent("3 of 3");
|
||
expect(dialog).toHaveTextContent(
|
||
"You're flying for Halvorsen Lunar Freight on the Earth–Moon run.",
|
||
);
|
||
expect(within(dialog).queryByRole("button", { name: "Next" })).not.toBeInTheDocument();
|
||
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Let's fly" }));
|
||
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
||
expect(api.call).toHaveBeenCalledWith("markWelcomeSeen", {});
|
||
expect(state().welcomeSeen).toBe(true);
|
||
expect(await screen.findByRole("heading", { level: 1, name: "Jobs" })).toBeInTheDocument();
|
||
expect(await screen.findByLabelText("Intro")).toHaveTextContent("needs a pilot");
|
||
});
|
||
|
||
it("closes from Skip on any card, marking it seen and landing on Contracts", async () => {
|
||
for (const nexts of [0, 1, 2]) {
|
||
const { api, state } = newCompany();
|
||
const dialog = await start(api);
|
||
for (let i = 0; i < nexts; i++) {
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Next" }));
|
||
}
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Skip" }));
|
||
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
||
expect(state().welcomeSeen).toBe(true);
|
||
expect(await screen.findByLabelText("Intro")).toBeInTheDocument();
|
||
cleanup();
|
||
}
|
||
});
|
||
|
||
it("doesn't come back after a reload once seen", async () => {
|
||
const { api } = newCompany();
|
||
const dialog = await start(api);
|
||
await userEvent.click(within(dialog).getByRole("button", { name: "Skip" }));
|
||
await waitFor(() => expect(screen.queryByRole("dialog")).not.toBeInTheDocument());
|
||
cleanup();
|
||
|
||
renderApp(api, "/contracts");
|
||
expect(await screen.findByLabelText("Intro")).toBeInTheDocument();
|
||
expect(screen.queryByRole("dialog", { name: "Welcome to Deadhead" })).not.toBeInTheDocument();
|
||
});
|
||
|
||
it("slides cards in, but not with reduced motion on", async () => {
|
||
const { api } = newCompany();
|
||
await start(api);
|
||
expect(screen.getByTestId("welcome-card")).toHaveClass("animate-slide");
|
||
cleanup();
|
||
|
||
usePreferences.getState().set({ reducedMotion: true });
|
||
const again = newCompany();
|
||
await start(again.api);
|
||
expect(screen.getByTestId("welcome-card")).not.toHaveClass("animate-slide");
|
||
usePreferences.getState().set({ reducedMotion: null });
|
||
});
|
||
|
||
it("keeps focus inside the pop-up until it closes", async () => {
|
||
const { api } = newCompany();
|
||
const dialog = await start(api);
|
||
// Focus starts on the main button.
|
||
expect(within(dialog).getByRole("button", { name: "Next" })).toHaveFocus();
|
||
for (let i = 0; i < 5; i++) {
|
||
await userEvent.tab();
|
||
expect(dialog).toContainElement(document.activeElement as HTMLElement);
|
||
}
|
||
await userEvent.tab({ shift: true });
|
||
expect(dialog).toContainElement(document.activeElement as HTMLElement);
|
||
});
|
||
});
|