Skip to content

Commit

Permalink
Move character tests
Browse files Browse the repository at this point in the history
  • Loading branch information
bduhbya committed Jun 17, 2024
1 parent 641d3a0 commit 2b08925
Show file tree
Hide file tree
Showing 8 changed files with 130 additions and 67 deletions.
80 changes: 48 additions & 32 deletions src/app/components/CombatTracker.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,8 +4,10 @@ import "@testing-library/jest-dom";
import React from "react";
import strings from "../../strings";
import {
mockBadCharacterFile,
mockCharacterFile,
mockBadmockSingleCharacterFile_warrior,
mockSingleCharacterMageFile,
mockSingleCharacterWarrior,
mockSingleCharacterWarriorFile,
} from "../lib/definitionMocks";
import { promptForFile } from "../lib/fileInput";
import { InitiativeInputDialogProps } from "./InitiativeInputDialog";
Expand All @@ -19,21 +21,18 @@ jest.mock("../lib/fileInput", () => ({
promptForFile: jest.fn(),
}));

jest.mock(
"./InitiativeInputDialog",
() => {
const MockInitiativeInputDialog = (props: InitiativeInputDialogProps) => {
handleCancelHook = props.onCancel;
handleConfirmHook = props.onConfirm;
addedCharacter = props.character;
return <div>Mock InitiativeInputDialog</div>;
};
jest.mock("./InitiativeInputDialog", () => {
const MockInitiativeInputDialog = (props: InitiativeInputDialogProps) => {
handleCancelHook = props.onCancel;
handleConfirmHook = props.onConfirm;
addedCharacter = props.character;
return <div>Mock InitiativeInputDialog</div>;
};

MockInitiativeInputDialog.displayName = 'MockInitiativeInputDialog';
MockInitiativeInputDialog.displayName = "MockInitiativeInputDialog";

return MockInitiativeInputDialog;
},
);
return MockInitiativeInputDialog;
});

describe("CombatTracker", () => {
it("renders component initial ui", () => {
Expand Down Expand Up @@ -61,6 +60,7 @@ describe("CombatTracker", () => {
expect(getByText(strings.descendingLabel)).toBeInTheDocument();
});

// This test is not working as expected
// it("handles null file input", async () => {
// const { getByText } = render(<CombatTracker />);
// (
Expand All @@ -85,7 +85,7 @@ describe("CombatTracker", () => {
const { getByText } = render(<CombatTracker />);
(
promptForFile as jest.MockedFunction<typeof promptForFile>
).mockResolvedValue(mockBadCharacterFile);
).mockResolvedValue(mockBadmockSingleCharacterFile_warrior);

fireEvent.click(getByText(strings.addToCombatButton));

Expand All @@ -101,7 +101,7 @@ describe("CombatTracker", () => {
const { getByText } = render(<CombatTracker />);
(
promptForFile as jest.MockedFunction<typeof promptForFile>
).mockResolvedValue(mockCharacterFile);
).mockResolvedValue(mockSingleCharacterWarriorFile);

fireEvent.click(getByText(strings.addToCombatButton));

Expand All @@ -118,21 +118,37 @@ describe("CombatTracker", () => {
expect(getByText(addedCharacter.initiative.toString())).toBeInTheDocument();
});

it("confirms adding a character correctly", () => {
const { getByText } = render(<CombatTracker />);

// TODO: Mock the file input and FileReader to test confirming a character
});

it("cancels adding a character correctly", () => {
it("moves current active character down correctly", async () => {
const { getByText } = render(<CombatTracker />);

// TODO: Mock the file input and FileReader to test canceling a character
});

it("moves a character up and down correctly", () => {
const { getByText } = render(<CombatTracker />);

// TODO: Mock the file input and FileReader to test moving a character
const moveDownButton = getByText(strings.moveDownLabel);
const mockCharacterFiles = [
mockSingleCharacterWarriorFile,
mockSingleCharacterWarriorFile,
];
const mockCharacters = [
mockSingleCharacterWarrior,
mockSingleCharacterWarrior,
];

for (let i = 0; i < mockCharacters.length; i++) {
(
promptForFile as jest.MockedFunction<typeof promptForFile>
).mockResolvedValue(mockCharacterFiles[i]);

fireEvent.click(getByText(strings.addToCombatButton));

// Wait for promises to resolve
new Promise((resolve) => setTimeout(resolve, 0));
await waitFor(() =>
expect(getByText("Mock InitiativeInputDialog")).toBeInTheDocument(),
);
handleConfirmHook(addedCharacter);
await waitFor(() =>
expect(getByText(addedCharacter.name)).toBeInTheDocument(),
);
expect(
getByText(addedCharacter.initiative.toString()),
).toBeInTheDocument();
}
});
});
37 changes: 19 additions & 18 deletions src/app/components/CombatTracker.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@ const CombatTracker: React.FC = () => {

const [combatCharacters, setCombatCharacters] = useState<Character[]>([]);
const [sortDescending, toggleSortDescending] = useState(true);
const [currentCharacterIndex, setCurrentCharacterIndex] = useState<number>(0);
const toggleButtonText = sortDescending
? strings.descendingLabel
: strings.ascendingLabel;
Expand All @@ -28,14 +27,6 @@ const CombatTracker: React.FC = () => {
const handleToggleSortDescending = () => {
// Toggle the sort order
toggleSortDescending(!sortDescending);

// Update the selected character index based on the new order
// When the ascending or descending order is toggled, the current
// index should now be the same distance from the top of the array
// as it was from the bottom of the array before toggling
const newIndex = combatCharacters.length - currentCharacterIndex - 1;

setCurrentCharacterIndex(newIndex);
};

const handleAddToCombat = async () => {
Expand All @@ -53,6 +44,7 @@ const CombatTracker: React.FC = () => {
fileReference: file,
dynamicData: jsonData,
initiative: 0,
active: false,
});
} catch (error) {
setCurrentDialogData(
Expand All @@ -72,6 +64,9 @@ const CombatTracker: React.FC = () => {
};

const handleConfirmAddCharacter = (newCharacter: Character) => {
if (combatCharacters.length === 0) {
newCharacter.active = true;
}
// Add the character to combatCharacters
setCombatCharacters([...combatCharacters, newCharacter]);

Expand Down Expand Up @@ -108,14 +103,21 @@ const CombatTracker: React.FC = () => {
window.alert(JSON.stringify(character.dynamicData, null, 2));
};

const handleMoveCharacter = (direction: Direction) => {
const handleMoveActiveCharacter = (direction: Direction) => {
var currentCharacterIndex = combatCharacters.findIndex(
(char) => char.active,
);
const newIndex =
direction === DIRECTION_UP
? (currentCharacterIndex - 1 + combatCharacters.length) %
combatCharacters.length
: (currentCharacterIndex + 1) % combatCharacters.length;

setCurrentCharacterIndex(newIndex);
// Update the active character
const newCombatCharacters = [...combatCharacters];
newCombatCharacters[currentCharacterIndex].active = false;
newCombatCharacters[newIndex].active = true;
setCombatCharacters(newCombatCharacters);
};

// Check for duplicate names
Expand Down Expand Up @@ -175,15 +177,14 @@ const CombatTracker: React.FC = () => {
</tr>
</thead>
<tbody>
{/* Render rows based on combatCharacters */}
{combatCharacters.map((character, index) => (
<tr
key={index}
onClick={() => handleCharacterClick(character)}
// className={index === currentCharacterIndex ? "bg-gray-200" : ""}
className={character.active ? "bg-gray-200" : ""}
>
<td className="border p-2">
{index === currentCharacterIndex && <CheckmarkIconPositive />}
{character.active && <CheckmarkIconPositive />}
</td>
<td className="border p-2">{character.name}</td>
<td className="border p-2">{character.initiative}</td>
Expand All @@ -195,15 +196,15 @@ const CombatTracker: React.FC = () => {
<div className="flex p-2">
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded-full mb-4 mr-4"
onClick={() => handleMoveCharacter(DIRECTION_UP)}
onClick={() => handleMoveActiveCharacter(DIRECTION_UP)}
>
Move Up
{strings.moveUpLabel}
</button>
<button
className="bg-blue-500 hover:bg-blue-700 text-white font-bold py-1 px-2 rounded-full mb-4"
onClick={() => handleMoveCharacter(DIRECTION_DOWN)}
onClick={() => handleMoveActiveCharacter(DIRECTION_DOWN)}
>
Move Down
{strings.moveDownLabel}
</button>
</div>
</div>
Expand Down
12 changes: 6 additions & 6 deletions src/app/components/InitiativeInputDialog.test.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { render, fireEvent } from "@testing-library/react";
import InitiativeInputDialog from "./InitiativeInputDialog";
import { mockCharacter } from "../lib/definitionMocks";
import { mockSingleCharacterWarrior } from "../lib/definitionMocks";
import { DEFAULT_INITIATIVE } from "./InitiativeInputDialog";
import "@testing-library/jest-dom";
import React from "react";
Expand All @@ -13,7 +13,7 @@ describe("InitiativeInputDialog", () => {
it("renders correctly", () => {
const { getByText, getByLabelText } = render(
<InitiativeInputDialog
character={mockCharacter}
character={mockSingleCharacterWarrior}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
duplicateEntryOrEmpty={false}
Expand All @@ -22,7 +22,7 @@ describe("InitiativeInputDialog", () => {

expect(getByText(strings.initiativePrompt)).toBeInTheDocument();
expect(getByLabelText(strings.characterLabel)).toHaveValue(
mockCharacter.name,
mockSingleCharacterWarrior.name,
);
expect(getByLabelText(strings.initiativeLabel)).toHaveValue(
DEFAULT_INITIATIVE,
Expand All @@ -32,7 +32,7 @@ describe("InitiativeInputDialog", () => {
it("changes input values correctly", () => {
const { getByLabelText } = render(
<InitiativeInputDialog
character={mockCharacter}
character={mockSingleCharacterWarrior}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
duplicateEntryOrEmpty={false}
Expand All @@ -53,7 +53,7 @@ describe("InitiativeInputDialog", () => {
it("prevents non-numeric initiative input", () => {
const { getByLabelText } = render(
<InitiativeInputDialog
character={mockCharacter}
character={mockSingleCharacterWarrior}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
duplicateEntryOrEmpty={false}
Expand All @@ -74,7 +74,7 @@ describe("InitiativeInputDialog", () => {
it("calls onConfirm and onCancel correctly", () => {
const { getByText } = render(
<InitiativeInputDialog
character={mockCharacter}
character={mockSingleCharacterWarrior}
onConfirm={mockOnConfirm}
onCancel={mockOnCancel}
duplicateEntryOrEmpty={false}
Expand Down
57 changes: 47 additions & 10 deletions src/app/lib/definitionMocks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,27 @@ import { Character } from "./definitions";
import path from "path";
import fs from "fs";

const mockCharacterDataPath = path.join(
const mockSingleCharacterWarriorDataPath = path.join(
__dirname,
"..",
"testData",
"character_data.json",
);
const mockCharacterData = fs.readFileSync(mockCharacterDataPath, "utf8");
const mockSingleCharacterWarriorData = fs.readFileSync(
mockSingleCharacterWarriorDataPath,
"utf8",
);

const mockSingleCharacterMageDataPath = path.join(
__dirname,
"..",
"testData",
"character_data_mage.json",
);
const mockSingleCharacterMageData = fs.readFileSync(
mockSingleCharacterMageDataPath,
"utf8",
);

const mockBadCharacterDataPath = path.join(
__dirname,
Expand All @@ -18,19 +32,42 @@ const mockBadCharacterDataPath = path.join(
);
const mockBadCharacterData = fs.readFileSync(mockBadCharacterDataPath, "utf8");

const mockCharacterDataParsed = JSON.parse(mockCharacterData);
const characterFile = new File([mockCharacterData], mockCharacterDataPath);
const mockSingleCharacterWarriorDataParsed = JSON.parse(
mockSingleCharacterWarriorData,
);
const mockSingleCharacterMageDataParsed = JSON.parse(
mockSingleCharacterMageData,
);

const mockSingleCharacterFile_warrior = new File(
[mockSingleCharacterWarriorData],
mockSingleCharacterWarriorDataPath,
);
const mockSingleCharacterFile_mage = new File(
[mockSingleCharacterMageData],
mockSingleCharacterMageDataPath,
);

export const mockBadCharacterFile = new File(
export const mockBadmockSingleCharacterFile_warrior = new File(
[mockBadCharacterData],
mockBadCharacterDataPath,
);

export const mockCharacter: Character = {
name: mockCharacterDataParsed.name,
export const mockSingleCharacterWarrior: Character = {
name: mockSingleCharacterWarriorDataParsed.name,
initiative: 0,
fileReference: mockSingleCharacterFile_warrior,
dynamicData: mockSingleCharacterWarriorDataParsed,
active: false,
};

export const mockSingleCharacterMage: Character = {
name: mockSingleCharacterMageDataParsed.name,
initiative: 0,
fileReference: characterFile,
dynamicData: mockCharacterDataParsed,
fileReference: mockSingleCharacterFile_mage,
dynamicData: mockSingleCharacterMageDataParsed,
active: false,
};

export const mockCharacterFile = characterFile;
export const mockSingleCharacterWarriorFile = mockSingleCharacterFile_warrior;
export const mockSingleCharacterMageFile = mockSingleCharacterFile_mage;
2 changes: 2 additions & 0 deletions src/app/lib/definitions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,7 @@ export class Character {
name!: string;
initiative!: number;
fileReference!: File;
active!: boolean;
// TODO: remove this field and read the dynamic data from the fileReference
dynamicData!: Record<string, string | number>; // Object to store dynamic data fields
}
1 change: 1 addition & 0 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import Image from "next/image";
import CombatTracker from "./components/CombatTracker";

export default function Home() {
// TODO: add round tracker and eslapsed real time
return (
<main className="flex min-h-screen flex-col items-left justify-between p-24">
{/* <div
Expand Down
4 changes: 4 additions & 0 deletions src/app/testData/character_data_mage.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"name": "Test Mage",
"health": 100
}
4 changes: 3 additions & 1 deletion src/strings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ const strings = {
descendingLabel: "Descending",
fileNotSelected: "No file selected",
fileParsingError: "Unable to parse JSON file",
moveUpLabel: "Move Up",
moveDownLabel: "Move Down",

// InitiativeInputDialog strings
initiativePrompt: "Enter Initiative",
Expand All @@ -32,4 +34,4 @@ const strings = {
infoIcon: "ℹ️",
};

export default strings;
export default strings;

0 comments on commit 2b08925

Please sign in to comment.