diff --git a/package.json b/package.json
index 25ae5f3..3210321 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "modaq",
- "version": "1.17.0",
+ "version": "1.18.0",
"description": "Quiz Bowl Reader using TypeScript, React, and MobX",
"repository": {
"type": "git",
diff --git a/src/components/EventViewer.tsx b/src/components/EventViewer.tsx
index f6059ca..7cdff2a 100644
--- a/src/components/EventViewer.tsx
+++ b/src/components/EventViewer.tsx
@@ -1,17 +1,20 @@
import * as React from "react";
import { observer } from "mobx-react-lite";
-import { DetailsList, CheckboxVisibility, SelectionMode, IColumn, Label, Text } from "@fluentui/react";
+import { DetailsList, CheckboxVisibility, SelectionMode, IColumn, Label, Text, ISelection } from "@fluentui/react";
import { mergeStyleSets } from "@fluentui/react";
import { CycleItemList } from "./cycleItems/CycleItemList";
import { Cycle } from "../state/Cycle";
import { AppState } from "../state/AppState";
-import { GameState } from "../state/GameState";
import { StateContext } from "../contexts/StateContext";
const numberKey = "number";
const cycleKey = "cycle";
+// Needed for filling in dummy values into an interface
+// eslint-disable-next-line @typescript-eslint/no-empty-function
+function dummyFunction() {}
+
export const EventViewer = observer(function EventViewer(): JSX.Element | null {
const appState: AppState = React.useContext(StateContext);
const classes: IEventViewerClassNames = getClassNames(appState.uiState.isEventLogHidden);
@@ -31,7 +34,7 @@ export const EventViewer = observer(function EventViewer(): JSX.Element | null {
return <>>;
}
- return onRenderItemColumn(item, appState.game, index, column);
+ return onRenderItemColumn(item, appState, index, column);
},
[appState]
);
@@ -46,6 +49,7 @@ export const EventViewer = observer(function EventViewer(): JSX.Element | null {
ariaLabel: "Question number",
isResizable: true,
isRowHeader: true,
+ data: appState.uiState.cycleIndex,
},
{
key: cycleKey,
@@ -63,6 +67,35 @@ export const EventViewer = observer(function EventViewer(): JSX.Element | null {
},
];
+ // DetailsList doesn't know how to change its selection when the cycle index changes unless we tell it how to select it
+ const selection: ISelection = {
+ count: 1,
+ mode: SelectionMode.single,
+ canSelectItem: () => true,
+ setChangeEvents: dummyFunction,
+ setItems: dummyFunction,
+ getItems: () => [],
+ getSelection: () => [{}],
+ getSelectedIndices: () => [appState.uiState.cycleIndex],
+ getSelectedCount: () => 1,
+ isRangeSelected: (): boolean => false,
+ isAllSelected: () => appState.uiState.cycleIndex === appState.game.playableCycles.length,
+ isIndexSelected: (index) => index === appState.uiState.cycleIndex,
+ isKeySelected: () => false,
+ setAllSelected: dummyFunction,
+ setKeySelected: dummyFunction,
+ setIndexSelected: (index: number): void => appState.uiState.setCycleIndex(index),
+ selectToKey: dummyFunction,
+ selectToIndex: (index: number): void => appState.uiState.setCycleIndex(index),
+ toggleAllSelected: dummyFunction,
+ toggleKeySelected: dummyFunction,
+ toggleIndexSelected: (index: number): void => {
+ appState.uiState.setCycleIndex(index);
+ },
+ toggleRangeSelected: dummyFunction,
+ };
+
+ // This needs to re-render based on cycleIndex so it can select the current one
return (
);
});
-function onRenderItemColumn(item: Cycle, game: GameState, index: number, column: IColumn): JSX.Element {
+function onRenderItemColumn(item: Cycle, appState: AppState, index: number, column: IColumn): JSX.Element {
switch (column?.key) {
case numberKey:
if (index == undefined) {
return <>>;
}
+ if (column?.data === index) {
+ return (
+
+
+
+ );
+ }
+
return ;
case cycleKey:
const scores: [number, number][] = column.data;
@@ -91,7 +133,7 @@ function onRenderItemColumn(item: Cycle, game: GameState, index: number, column:
return (
<>
-
+
{`(${scoreInCurrentCycle[0]} - ${scoreInCurrentCycle[1]})`}
>
);
diff --git a/src/components/GameBar.tsx b/src/components/GameBar.tsx
index 332b164..331067d 100644
--- a/src/components/GameBar.tsx
+++ b/src/components/GameBar.tsx
@@ -91,6 +91,10 @@ export const GameBar = observer(function GameBar(): JSX.Element {
const openHelpHandler = React.useCallback(() => appState.uiState.dialogState.showHelpDialog(), [appState]);
+ const reorderPlayersHandler = React.useCallback(() => {
+ uiState.dialogState.showReorderPlayersDialog(game.players);
+ }, [uiState, game]);
+
const items: ICommandBarItemProps[] = appState.uiState.hideNewGame
? []
: [
@@ -142,6 +146,7 @@ export const GameBar = observer(function GameBar(): JSX.Element {
appState,
addPlayerHandler,
protestBonusHandler,
+ reorderPlayersHandler,
addQuestionsHandler
);
items.push({
@@ -213,6 +218,7 @@ function getActionSubMenuItems(
appState: AppState,
addPlayerHandler: () => void,
protestBonusHandler: () => void,
+ reorderPlayersHandler: () => void,
addQuestionsHandler: () => void
): ICommandBarItemProps[] {
const items: ICommandBarItemProps[] = [];
@@ -223,7 +229,8 @@ function getActionSubMenuItems(
appState,
game,
uiState,
- addPlayerHandler
+ addPlayerHandler,
+ reorderPlayersHandler
);
items.push(playerManagementSection);
@@ -382,7 +389,8 @@ function getPlayerManagementSubMenuItems(
appState: AppState,
game: GameState,
uiState: UIState,
- addPlayerHandler: () => void
+ addPlayerHandler: () => void,
+ reorderPlayersHandler: () => void
): ICommandBarItemProps {
const teamNames: string[] = game.teamNames;
const swapActivePlayerMenus: ICommandBarItemProps[] = [];
@@ -477,13 +485,20 @@ function getPlayerManagementSubMenuItems(
disabled: appState.game.cycles.length === 0,
};
+ const reorderPlayersItem: ICommandBarItemProps = {
+ key: "reorderPlayers",
+ text: "Reorder players...",
+ onClick: reorderPlayersHandler,
+ disabled: appState.game.cycles.length === 0,
+ };
+
return {
key: "playerManagement",
itemType: ContextualMenuItemType.Section,
sectionProps: {
bottomDivider: true,
title: "Player Management",
- items: [swapPlayerItem, addPlayerItem],
+ items: [swapPlayerItem, addPlayerItem, reorderPlayersItem],
},
};
}
diff --git a/src/components/ModalDialogContainer.tsx b/src/components/ModalDialogContainer.tsx
index b46225d..b70da79 100644
--- a/src/components/ModalDialogContainer.tsx
+++ b/src/components/ModalDialogContainer.tsx
@@ -12,6 +12,7 @@ import { CustomizeGameFormatDialog } from "./dialogs/CustomizeGameFormatDialog";
import { AddQuestionsDialog } from "./dialogs/AddQuestionsDialog";
import { MessageDialog } from "./dialogs/MessageDialog";
import { RenamePlayerDialog } from "./dialogs/RenamePlayerDialog";
+import { ReorderPlayerDialog } from "./dialogs/ReorderPlayerDialog";
export const ModalDialogContainer = observer(function ModalDialogContainer() {
// The Protest dialogs aren't here because they require extra information
@@ -29,6 +30,7 @@ export const ModalDialogContainer = observer(function ModalDialogContainer() {
+
>
);
});
diff --git a/src/components/cycleItems/TossupAnswerCycleItem.tsx b/src/components/cycleItems/TossupAnswerCycleItem.tsx
index e276258..8802f88 100644
--- a/src/components/cycleItems/TossupAnswerCycleItem.tsx
+++ b/src/components/cycleItems/TossupAnswerCycleItem.tsx
@@ -38,9 +38,7 @@ export const TossupAnswerCycleItem = observer(function TossupAnswerCycleItem(
}
}
- const text = `${props.buzz.marker.player.name} (${
- props.buzz.marker.player.teamName
- }) ${buzzDescription} on tossup #${props.buzz.tossupIndex + 1} at word ${props.buzz.marker.position + 1}`;
+ const text = `${props.buzz.marker.player.name} (${props.buzz.marker.player.teamName}) ${buzzDescription}`;
return ;
});
diff --git a/src/components/dialogs/ReorderPlayerDialog.tsx b/src/components/dialogs/ReorderPlayerDialog.tsx
new file mode 100644
index 0000000..d1091e1
--- /dev/null
+++ b/src/components/dialogs/ReorderPlayerDialog.tsx
@@ -0,0 +1,249 @@
+import * as React from "react";
+import { observer } from "mobx-react-lite";
+import {
+ Dropdown,
+ IDropdownOption,
+ IDialogContentProps,
+ DialogType,
+ IModalProps,
+ ContextualMenu,
+ Dialog,
+ DialogFooter,
+ PrimaryButton,
+ DefaultButton,
+ Stack,
+ Label,
+ StackItem,
+ IconButton,
+ IIconProps,
+ IStackTokens,
+ DetailsList,
+ SelectionMode,
+ IColumn,
+ CheckboxVisibility,
+ IDragDropEvents,
+} from "@fluentui/react";
+
+import * as ReorderPlayersDialogController from "../../components/dialogs/ReorderPlayersDialogController";
+import { Player } from "../../state/TeamState";
+import { AppState } from "../../state/AppState";
+import { StateContext } from "../../contexts/StateContext";
+import { ReorderPlayersDialogState } from "../../state/ReorderPlayersDialogState";
+
+const content: IDialogContentProps = {
+ type: DialogType.normal,
+ title: "Reorder Players",
+ closeButtonAriaLabel: "Close",
+ showCloseButton: true,
+ styles: {
+ innerContent: {
+ display: "flex",
+ flexDirection: "column",
+ },
+ },
+};
+
+const modalProps: IModalProps = {
+ isBlocking: false,
+ dragOptions: {
+ moveMenuItemText: "Move",
+ closeMenuItemText: "Close",
+ menu: ContextualMenu,
+ },
+ styles: {
+ main: {
+ top: "25vh",
+ },
+ },
+ topOffsetFixed: true,
+};
+
+const buttonTokens: IStackTokens = { childrenGap: 10 };
+
+const dialogBodyTokens: IStackTokens = { childrenGap: 10 };
+
+const columns: IColumn[] = [
+ {
+ key: "name",
+ fieldName: "name",
+ name: "name",
+ minWidth: 30,
+ },
+];
+
+const downButtonProps: IIconProps = {
+ iconName: "ChevronDown",
+};
+
+const upButtonProps: IIconProps = {
+ iconName: "ChevronUp",
+};
+
+const rowStyle: React.CSSProperties = {
+ display: "flex",
+ justifyContent: "space-between",
+ alignItems: "center",
+};
+
+const moveDownClassName = "moveDownButton";
+const moveUpClassName = "moveUpButton";
+
+// TODO: Look into making a DefaultDialog, which handles the footers and default props
+export const ReorderPlayerDialog = observer(function ReorderPlayerDialog(): JSX.Element {
+ const appState: AppState = React.useContext(StateContext);
+
+ return (
+
+ );
+});
+
+const ReorderPlayerDialogBody = observer(function ReorderPlayerDialogBody(): JSX.Element {
+ const appState: AppState = React.useContext(StateContext);
+
+ const [draggedItem, setDraggedItem] = React.useState(undefined);
+ const [autoFocusIndex, setAutoFocusIndex] = React.useState(undefined);
+ const [autoFocusIsUp, setAutoFocusIsUp] = React.useState(false);
+
+ const teamChangeHandler = React.useCallback((ev: React.FormEvent, option?: IDropdownOption) => {
+ if (option?.text != undefined) {
+ ReorderPlayersDialogController.changeTeamName(option.text);
+ setAutoFocusIndex(undefined);
+ }
+ }, []);
+
+ const reorderPlayersDialog: ReorderPlayersDialogState | undefined =
+ appState.uiState.dialogState.reorderPlayersDialog;
+ if (reorderPlayersDialog === undefined) {
+ return <>>;
+ }
+
+ // This makes sure that the focus stays on the button belonging to the player
+ if (autoFocusIndex != undefined) {
+ const className: string = autoFocusIsUp ? moveUpClassName : moveDownClassName;
+ const element = document.getElementsByClassName(className)[autoFocusIndex] as HTMLButtonElement;
+ if (element) {
+ element.focus();
+ }
+
+ setAutoFocusIndex(undefined);
+ }
+
+ const teamOptions: IDropdownOption[] = appState.game.teamNames.map((teamName, index) => {
+ return {
+ key: index,
+ text: teamName,
+ selected: reorderPlayersDialog.teamName === teamName,
+ };
+ });
+
+ const players: Player[] = reorderPlayersDialog.players.filter((p) => p.teamName === reorderPlayersDialog.teamName);
+
+ function renderPlayerRow(player: Player | undefined, index: number | undefined) {
+ if (player == undefined || index == undefined || reorderPlayersDialog == undefined) {
+ return <>>;
+ }
+
+ return (
+
+
+
+
+
+
+ {
+ ReorderPlayersDialogController.moveForward(player);
+ if (index > 1) {
+ setAutoFocusIsUp(true);
+ setAutoFocusIndex(index - 1);
+ }
+ }}
+ />
+
+
+ = players.length - 1}
+ onClick={() => {
+ ReorderPlayersDialogController.moveBackward(player);
+ if (index < players.length - 1) {
+ setAutoFocusIsUp(false);
+ setAutoFocusIndex(index + 1);
+ }
+ }}
+ />
+
+
+
+ );
+ }
+
+ function insertBeforeItem(item: Player) {
+ if (draggedItem == undefined) {
+ return;
+ }
+
+ const locationIndex = players.indexOf(item);
+ const itemIndex = players.indexOf(draggedItem);
+ if (locationIndex !== itemIndex) {
+ ReorderPlayersDialogController.movePlayerToIndex(draggedItem, locationIndex);
+ }
+ }
+
+ const dragDropEvents: IDragDropEvents = {
+ canDrag: () => true,
+ canDrop: () => true,
+ onDragStart: (item?: Player) => {
+ setDraggedItem(item);
+ },
+ onDragEnd: () => {
+ setDraggedItem(undefined);
+ },
+ onDrop: (item?: Player) => {
+ if (draggedItem && item) {
+ insertBeforeItem(item);
+ setDraggedItem(undefined);
+ }
+ },
+ };
+
+ return (
+
+
+
+
+
+
+
+
+
+
+
+ );
+});
diff --git a/src/components/dialogs/ReorderPlayersDialogController.ts b/src/components/dialogs/ReorderPlayersDialogController.ts
new file mode 100644
index 0000000..e819668
--- /dev/null
+++ b/src/components/dialogs/ReorderPlayersDialogController.ts
@@ -0,0 +1,46 @@
+import { Player } from "../../state/TeamState";
+import { AppState } from "../../state/AppState";
+import { GameState } from "../../state/GameState";
+import { ReorderPlayersDialogState } from "../../state/ReorderPlayersDialogState";
+
+export function changeTeamName(newName: string): void {
+ const appState: AppState = AppState.instance;
+ const reorderPlayersDialog: ReorderPlayersDialogState | undefined =
+ appState.uiState.dialogState.reorderPlayersDialog;
+ if (reorderPlayersDialog == undefined) {
+ return;
+ }
+
+ reorderPlayersDialog.setTeamName(newName);
+}
+
+export function hideDialog(): void {
+ const appState: AppState = AppState.instance;
+ appState.uiState.dialogState.hideReorderPlayersDialog();
+}
+
+export function moveBackward(player: Player): void {
+ AppState.instance.uiState.dialogState.reorderPlayersDialog?.movePlayerBackward(player);
+}
+
+export function moveForward(player: Player): void {
+ AppState.instance.uiState.dialogState.reorderPlayersDialog?.movePlayerForward(player);
+}
+
+export function movePlayerToIndex(player: Player, index: number): void {
+ AppState.instance.uiState.dialogState.reorderPlayersDialog?.movePlayerToIndex(player, index);
+}
+
+export function submit(): void {
+ const appState: AppState = AppState.instance;
+ const game: GameState = appState.game;
+ const reorderPlayersDialogState: ReorderPlayersDialogState | undefined =
+ appState.uiState.dialogState.reorderPlayersDialog;
+ if (reorderPlayersDialogState == undefined) {
+ return;
+ }
+
+ game.setPlayers(reorderPlayersDialogState.players);
+
+ hideDialog();
+}
diff --git a/src/state/DialogState.ts b/src/state/DialogState.ts
index 6548eea..8ca0fa0 100644
--- a/src/state/DialogState.ts
+++ b/src/state/DialogState.ts
@@ -6,6 +6,7 @@ import { IGameFormat } from "./IGameFormat";
import { IMessageDialogState, MessageDialogType } from "./IMessageDialogState";
import { RenamePlayerDialogState } from "./RenamePlayerDialogState";
import { Player } from "./TeamState";
+import { ReorderPlayersDialogState } from "./ReorderPlayersDialogState";
export class DialogState {
@ignore
@@ -32,6 +33,9 @@ export class DialogState {
@ignore
public renamePlayerDialog: RenamePlayerDialogState | undefined;
+ @ignore
+ public reorderPlayersDialog: ReorderPlayersDialogState | undefined;
+
constructor() {
makeAutoObservable(this);
@@ -43,6 +47,7 @@ export class DialogState {
this.messageDialog = undefined;
this.newGameDialogVisible = false;
this.renamePlayerDialog = undefined;
+ this.reorderPlayersDialog = undefined;
}
public hideAddQuestionsDialog(): void {
@@ -77,6 +82,10 @@ export class DialogState {
this.renamePlayerDialog = undefined;
}
+ public hideReorderPlayersDialog(): void {
+ this.reorderPlayersDialog = undefined;
+ }
+
public showAddQuestionsDialog(): void {
this.addQuestions = new AddQuestionDialogState();
}
@@ -101,6 +110,10 @@ export class DialogState {
this.renamePlayerDialog = new RenamePlayerDialogState(player);
}
+ public showReorderPlayersDialog(players: Player[]): void {
+ this.reorderPlayersDialog = new ReorderPlayersDialogState(players);
+ }
+
public showOKMessageDialog(title: string, message: string, onOK?: () => void): void {
this.messageDialog = {
title,
diff --git a/src/state/ReorderPlayersDialogState.ts b/src/state/ReorderPlayersDialogState.ts
new file mode 100644
index 0000000..a706fde
--- /dev/null
+++ b/src/state/ReorderPlayersDialogState.ts
@@ -0,0 +1,85 @@
+import { makeAutoObservable } from "mobx";
+import { Player } from "./TeamState";
+
+export class ReorderPlayersDialogState {
+ public teamName: string;
+
+ public players: Player[];
+
+ constructor(players: Player[]) {
+ makeAutoObservable(this);
+
+ this.players = players;
+ this.teamName = players.length > 0 ? players[0].teamName : "";
+ }
+
+ public setTeamName(newTeam: string): void {
+ this.teamName = newTeam;
+ }
+
+ public movePlayerBackward(player: Player): void {
+ let nextTeammateIndex = -1;
+ for (let i = this.players.length - 1; i >= 0; i--) {
+ const currentPlayer: Player = this.players[i];
+ if (player === currentPlayer) {
+ if (nextTeammateIndex === -1) {
+ // Current player is in front, we can't move them
+ return;
+ }
+
+ this.players[i] = this.players[nextTeammateIndex];
+ this.players[nextTeammateIndex] = player;
+ return;
+ }
+
+ if (this.players[i].teamName === player.teamName) {
+ nextTeammateIndex = i;
+ }
+ }
+ }
+
+ public movePlayerForward(player: Player): void {
+ let previousTeammateIndex = -1;
+ for (let i = 0; i < this.players.length; i++) {
+ const currentPlayer: Player = this.players[i];
+ if (player === currentPlayer) {
+ if (previousTeammateIndex === -1) {
+ // Current player is in front, we can't move them
+ return;
+ }
+
+ this.players[i] = this.players[previousTeammateIndex];
+ this.players[previousTeammateIndex] = player;
+ return;
+ }
+
+ if (this.players[i].teamName === player.teamName) {
+ previousTeammateIndex = i;
+ }
+ }
+ }
+
+ public movePlayerToIndex(player: Player, index: number): void {
+ if (index < 0) {
+ return;
+ }
+
+ const teamName: string = player.teamName;
+ let sameTeamCount = -1;
+ for (let i = 0; i < this.players.length; i++) {
+ const currentPlayer: Player = this.players[i];
+ if (currentPlayer.teamName === teamName) {
+ sameTeamCount++;
+ }
+
+ if (sameTeamCount === index) {
+ let newPlayers = this.players.filter((p) => p !== player);
+ newPlayers = newPlayers.slice(0, i).concat(player).concat(newPlayers.slice(i));
+ this.players = newPlayers;
+ return;
+ }
+ }
+
+ // Index is beyond the number of players in the team. Treat it as a no-op
+ }
+}
diff --git a/tests/ReorderPlayersDialogControllerTests.ts b/tests/ReorderPlayersDialogControllerTests.ts
new file mode 100644
index 0000000..284f85b
--- /dev/null
+++ b/tests/ReorderPlayersDialogControllerTests.ts
@@ -0,0 +1,244 @@
+import { assert, expect } from "chai";
+
+import * as ReorderPlayersDialogController from "src/components/dialogs/ReorderPlayersDialogController";
+import { AppState } from "src/state/AppState";
+import { GameState } from "src/state/GameState";
+import { PacketState, Tossup } from "src/state/PacketState";
+import { Player } from "src/state/TeamState";
+import { ReorderPlayersDialogState } from "src/state/ReorderPlayersDialogState";
+
+const defaultPacket: PacketState = new PacketState();
+defaultPacket.setTossups([
+ new Tossup("first q", "first a"),
+ new Tossup("second q", "second a"),
+ new Tossup("third q", "third a"),
+]);
+
+const defaultTeamNames: string[] = ["First Team", "Team2"];
+
+const firstTeamPlayers: Player[] = [
+ new Player("Alex", defaultTeamNames[0], true),
+ new Player("Anna", defaultTeamNames[0], true),
+ new Player("Ashok", defaultTeamNames[0], false),
+];
+
+const secondTeamPlayers: Player[] = [
+ new Player("Bob", defaultTeamNames[1], true),
+ new Player("Anna", defaultTeamNames[1], true),
+];
+
+const defaultExistingPlayers: Player[] = [
+ firstTeamPlayers[0],
+ firstTeamPlayers[1],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ firstTeamPlayers[2],
+];
+
+function initializeApp(players?: Player[]): { appState: AppState; players: Player[] } {
+ const gameState: GameState = new GameState();
+ gameState.loadPacket(defaultPacket);
+
+ players = players ?? defaultExistingPlayers;
+ gameState.addPlayers(players);
+
+ AppState.resetInstance();
+ const appState: AppState = AppState.instance;
+ appState.game = gameState;
+ appState.uiState.dialogState.showReorderPlayersDialog(players);
+ return { appState, players };
+}
+
+function getReorderPlayersDialogState(appState: AppState): ReorderPlayersDialogState {
+ if (appState.uiState.dialogState.reorderPlayersDialog == undefined) {
+ assert.fail("PendingNewPlayer should not be undefined");
+ }
+
+ return appState.uiState.dialogState.reorderPlayersDialog;
+}
+
+describe("ReorderPlayersDialogControllerTests", () => {
+ it("hideDialog", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.hideDialog();
+
+ expect(appState.uiState.dialogState.reorderPlayersDialog).to.be.undefined;
+ });
+ describe("changeTeamName", () => {
+ it("change to both teams", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.changeTeamName(defaultTeamNames[1]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.teamName).to.equal(defaultTeamNames[1]);
+ ReorderPlayersDialogController.changeTeamName(defaultTeamNames[0]);
+ expect(dialog.teamName).to.equal(defaultTeamNames[0]);
+ });
+ });
+ it("submit", () => {
+ const { appState, players } = initializeApp();
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+ dialog.movePlayerBackward(players[0]);
+
+ ReorderPlayersDialogController.submit();
+
+ expect(appState.uiState.dialogState.reorderPlayersDialog).to.be.undefined;
+ expect(appState.game.players).to.be.deep.equal([
+ firstTeamPlayers[1],
+ firstTeamPlayers[0],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ firstTeamPlayers[2],
+ ]);
+ });
+ describe("moveBackward", () => {
+ it("Move backwards from the end is no-op", () => {
+ const { appState, players } = initializeApp();
+ ReorderPlayersDialogController.moveBackward(players[players.length - 1]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players[players.length - 1]).to.equal(players[players.length - 1]);
+ expect(dialog.players).to.be.deep.equal(players);
+ });
+ it("Move backwards from front moves player back", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveBackward(firstTeamPlayers[0]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players[0]).to.equal(firstTeamPlayers[1]);
+ expect(dialog.players[1]).to.equal(firstTeamPlayers[0]);
+ expect(dialog.players.slice(2)).to.be.deep.equal(defaultExistingPlayers.slice(2));
+ });
+ it("Move backwards for second team", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveBackward(secondTeamPlayers[0]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[0],
+ firstTeamPlayers[1],
+ secondTeamPlayers[1],
+ secondTeamPlayers[0],
+ firstTeamPlayers[2],
+ ]);
+ });
+ it("Move backwards with gap swaps correctly", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveBackward(firstTeamPlayers[1]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[0],
+ firstTeamPlayers[2],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ firstTeamPlayers[1],
+ ]);
+ });
+ });
+ describe("moveForward", () => {
+ it("Move forwards from 0 is no-op", () => {
+ const { appState, players } = initializeApp();
+ ReorderPlayersDialogController.moveForward(players[0]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players[0]).to.equal(players[0]);
+ expect(dialog.players).to.be.deep.equal(players);
+ });
+ it("Move forwards from 1 swaps player to front", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveForward(firstTeamPlayers[1]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players[0]).to.equal(firstTeamPlayers[1]);
+ expect(dialog.players[1]).to.equal(firstTeamPlayers[0]);
+ expect(dialog.players.slice(2)).to.be.deep.equal(defaultExistingPlayers.slice(2));
+ });
+ it("Move forwards for second team", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveForward(secondTeamPlayers[1]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[0],
+ firstTeamPlayers[1],
+ secondTeamPlayers[1],
+ secondTeamPlayers[0],
+ firstTeamPlayers[2],
+ ]);
+ });
+ it("Move forwards with gap swaps correctly", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.moveForward(firstTeamPlayers[2]);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[0],
+ firstTeamPlayers[2],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ firstTeamPlayers[1],
+ ]);
+ });
+ });
+
+ // move ToIndex tests
+ describe("movePlayerToIndex", () => {
+ it("movePlayerToIndex to same index is no-op", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[1], 1);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal(defaultExistingPlayers);
+ });
+ it("movePlayerToIndex to negative index is no-op", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[1], -1);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal(defaultExistingPlayers);
+ });
+ it("movePlayerToIndex to overly large index is no-op", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[1], firstTeamPlayers.length);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal(defaultExistingPlayers);
+ });
+ it("movePlayerToIndex next player swap", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[0], 1);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players[0]).to.equal(firstTeamPlayers[1]);
+ expect(dialog.players[1]).to.equal(firstTeamPlayers[0]);
+ expect(dialog.players.slice(2)).to.be.deep.equal(defaultExistingPlayers.slice(2));
+ });
+ it("movePlayerToIndex first to last player swap", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[0], 2);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[1],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ firstTeamPlayers[2],
+ firstTeamPlayers[0],
+ ]);
+ });
+ it("movePlayerToIndex last to first player swap", () => {
+ const { appState } = initializeApp();
+ ReorderPlayersDialogController.movePlayerToIndex(firstTeamPlayers[2], 0);
+ const dialog: ReorderPlayersDialogState = getReorderPlayersDialogState(appState);
+
+ expect(dialog.players).to.be.deep.equal([
+ firstTeamPlayers[2],
+ firstTeamPlayers[0],
+ firstTeamPlayers[1],
+ secondTeamPlayers[0],
+ secondTeamPlayers[1],
+ ]);
+ });
+ });
+});