import { fireEvent, render, screen } from "@testing-library/react"; import type { ArmaId } from "@/lib/jogo/tipos"; import type { Controle } from "./contrato"; import { Toque } from "./Toque"; function controleFalso() { return { ler: jest.fn(), definirMover: jest.fn(), somarOlhar: jest.fn(), definirAtirar: jest.fn(), definirArma: jest.fn(), soltar: jest.fn(), descartar: jest.fn(), } satisfies Controle; } function montar( c: Controle, extra: { arma?: ArmaId; municao?: number; mirando?: boolean } = {}, ) { const props = { arma: 0 as ArmaId, municao: 30, mirando: false, ...extra }; const vista = render(); return { ...vista, camada: vista.container.firstElementChild as HTMLElement, de: (novas: Partial) => vista.rerender(), }; } // a tela do jsdom tem 1024 px de largura: a metade esquerda vai até 511 const ESQUERDA = 100; const DIREITA = 800; describe("Toque: direcional", () => { it("aparece onde o dedo encosta, move e some ao soltar", () => { const c = controleFalso(); const { camada } = montar(c); expect(screen.queryByTestId("direcional")).toBeNull(); fireEvent.pointerDown(camada, { pointerId: 1, clientX: ESQUERDA, clientY: 300, }); expect(screen.getByTestId("direcional")).toHaveStyle({ left: "100px", top: "300px", }); fireEvent.pointerMove(camada, { pointerId: 1, clientX: ESQUERDA + 28, clientY: 300 - 28, }); const [direita, frente] = c.definirMover.mock.calls.at(-1) ?? []; expect(direita).toBeCloseTo(0.5); expect(frente).toBeCloseTo(0.5); fireEvent.pointerUp(camada, { pointerId: 1 }); expect(c.definirMover).toHaveBeenLastCalledWith(0, 0); expect(screen.queryByTestId("direcional")).toBeNull(); }); it("um segundo dedo na metade esquerda não rouba o direcional", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: 100, clientY: 300 }); fireEvent.pointerDown(camada, { pointerId: 2, clientX: 200, clientY: 200 }); fireEvent.pointerMove(camada, { pointerId: 2, clientX: 260, clientY: 200 }); expect(c.definirMover).not.toHaveBeenCalled(); }); it("cancelar o toque também solta o direcional", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: 100, clientY: 300 }); fireEvent.pointerCancel(camada, { pointerId: 1 }); expect(c.definirMover).toHaveBeenLastCalledWith(0, 0); }); }); describe("Toque: mira", () => { it("arrastar na metade direita soma o giro", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: DIREITA, clientY: 200, }); fireEvent.pointerMove(camada, { pointerId: 1, clientX: DIREITA + 40, clientY: 210, }); fireEvent.pointerMove(camada, { pointerId: 1, clientX: DIREITA + 60, clientY: 210, }); expect(c.somarOlhar).toHaveBeenCalledTimes(2); const [a, b] = c.somarOlhar.mock.calls; expect(a[0]).toBeCloseTo(-0.2); expect(a[1]).toBeCloseTo(-0.05); expect(b[0]).toBeCloseTo(-0.1); expect(b[1]).toBeCloseTo(0); expect(c.definirMover).not.toHaveBeenCalled(); }); it("depois de soltar, mover o ponteiro não gira mais", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: 800, clientY: 200 }); fireEvent.pointerUp(camada, { pointerId: 1 }); fireEvent.pointerMove(camada, { pointerId: 1, clientX: 900, clientY: 200 }); expect(c.somarOlhar).not.toHaveBeenCalled(); }); it("dois dedos ao mesmo tempo: um anda e o outro mira", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: 100, clientY: 300 }); fireEvent.pointerDown(camada, { pointerId: 2, clientX: 800, clientY: 200 }); fireEvent.pointerMove(camada, { pointerId: 2, clientX: 810, clientY: 200 }); fireEvent.pointerMove(camada, { pointerId: 1, clientX: 100, clientY: 250 }); expect(c.somarOlhar).toHaveBeenCalledTimes(1); expect(c.definirMover).toHaveBeenCalledTimes(1); expect(c.definirMover.mock.calls[0][1]).toBeCloseTo(0.89, 1); }); it("um segundo dedo na metade direita não vira outra mira", () => { const c = controleFalso(); const { camada } = montar(c); fireEvent.pointerDown(camada, { pointerId: 1, clientX: 800, clientY: 200 }); fireEvent.pointerDown(camada, { pointerId: 2, clientX: 900, clientY: 200 }); fireEvent.pointerMove(camada, { pointerId: 2, clientX: 950, clientY: 200 }); expect(c.somarOlhar).not.toHaveBeenCalled(); }); }); describe("Toque: botão Atirar", () => { it("segurar atira e soltar para", () => { const c = controleFalso(); montar(c); c.definirAtirar.mockClear(); const botao = screen.getByRole("button", { name: "Atirar" }); fireEvent.pointerDown(botao, { pointerId: 1, clientX: 900, clientY: 350 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(true); fireEvent.pointerUp(botao, { pointerId: 1 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(false); }); it("arrastar o dedo sobre o botão também mira", () => { const c = controleFalso(); montar(c); const botao = screen.getByRole("button", { name: "Atirar" }); fireEvent.pointerDown(botao, { pointerId: 1, clientX: 900, clientY: 350 }); fireEvent.pointerMove(botao, { pointerId: 1, clientX: 920, clientY: 330 }); const [rumo, inclinacao] = c.somarOlhar.mock.calls[0]; expect(rumo).toBeCloseTo(-0.1); expect(inclinacao).toBeCloseTo(0.1); }); it("tocar no botão não liga o direcional nem a mira da camada", () => { const c = controleFalso(); const { camada } = montar(c); const botao = screen.getByRole("button", { name: "Atirar" }); fireEvent.pointerDown(botao, { pointerId: 1, clientX: 100, clientY: 350 }); expect(screen.queryByTestId("direcional")).toBeNull(); fireEvent.pointerUp(camada, { pointerId: 1 }); expect(c.definirMover).not.toHaveBeenCalled(); }); }); describe("Toque: armas", () => { it("mostra as três armas, com as balas da automática e a escolhida marcada", () => { montar(controleFalso(), { arma: 1, municao: 42 }); expect(screen.getByRole("button", { name: "Pistola" })).toHaveAttribute( "aria-pressed", "false", ); expect( screen.getByRole("button", { name: "Automática 42" }), ).toHaveAttribute("aria-pressed", "true"); expect(screen.getByRole("button", { name: "Faca" })).toHaveAttribute( "aria-pressed", "false", ); }); it("tocar numa arma a escolhe, sem mirar nem mover", () => { const c = controleFalso(); montar(c); const faca = screen.getByRole("button", { name: "Faca" }); fireEvent.pointerDown(faca, { pointerId: 1, clientX: 700, clientY: 300 }); fireEvent.click(faca); fireEvent.pointerUp(faca, { pointerId: 1 }); expect(c.definirArma).toHaveBeenCalledWith(2); expect(c.somarOlhar).not.toHaveBeenCalled(); }); }); describe("Toque: tiro automático", () => { it("liga enquanto mira e desliga ao deixar de mirar", () => { const c = controleFalso(); const { de } = montar(c, { mirando: true }); expect(c.definirAtirar).toHaveBeenLastCalledWith(true); de({ mirando: false }); expect(c.definirAtirar).toHaveBeenLastCalledWith(false); }); it("não solta o tiro se o botão Atirar está apertado", () => { const c = controleFalso(); const { de } = montar(c, { mirando: true }); const botao = screen.getByRole("button", { name: "Atirar" }); fireEvent.pointerDown(botao, { pointerId: 1, clientX: 900, clientY: 350 }); de({ mirando: false }); expect(c.definirAtirar).toHaveBeenLastCalledWith(true); fireEvent.pointerUp(botao, { pointerId: 1 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(false); }); it("soltar o botão mantém o tiro se ainda está mirando", () => { const c = controleFalso(); montar(c, { mirando: true }); const botao = screen.getByRole("button", { name: "Atirar" }); fireEvent.pointerDown(botao, { pointerId: 1, clientX: 900, clientY: 350 }); fireEvent.pointerUp(botao, { pointerId: 1 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(true); }); it("não atira sozinho com a faca", () => { const c = controleFalso(); const { de } = montar(c, { arma: 2, mirando: true }); expect(c.definirAtirar).not.toHaveBeenCalledWith(true); de({ arma: 0 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(true); de({ arma: 2 }); expect(c.definirAtirar).toHaveBeenLastCalledWith(false); }); }); describe("Toque: camada", () => { it("cobre a tela sem rolagem por gesto e bloqueia o menu de contexto", () => { const { camada } = montar(controleFalso()); expect(camada.style.touchAction).toBe("none"); expect(fireEvent.contextMenu(camada)).toBe(false); }); });