minimal tests for input handlers

This commit is contained in:
ad044 2021-02-25 21:47:42 +04:00
parent 851b3ff597
commit 918ee5da13
15 changed files with 435 additions and 83 deletions

View file

@ -13,7 +13,7 @@ import TaKScene from "./scenes/TaKScene";
import ChangeDiscScene from "./scenes/ChangeDiscScene";
import EndScene from "./scenes/EndScene";
import IdleMediaScene from "./scenes/IdleMediaScene";
import KeyPressHandler from "./components/KeyPressHandler";
import InputHandler from "./components/InputHandler";
const App = () => {
const currentScene = useStore((state) => state.currentScene);
@ -43,7 +43,7 @@ const App = () => {
<div id="game-root" className="game">
<span className="canvas">
<Canvas concurrent>
<KeyPressHandler />
<InputHandler />
<Suspense fallback={null}>
{/*<Preloader />*/}
{dispatchScene[currentScene as keyof typeof dispatchScene]}

View file

@ -0,0 +1,88 @@
import * as eventTemplates from "../../core/eventTemplates";
import { getBootSceneContext } from "../../store";
import handleBootSceneInput from "../../core/input-handlers/handleBootSceneInput";
import {
enterLoadData,
enterUserAuthorization,
exitUserAuthorization,
startNewGame,
} from "../../core/eventTemplates";
import {
BootSceneContext,
BootSubscene,
MainMenuComponent,
} from "../../types/types";
it("Checks whether or not the boot scene input handler reacts appropriately for each input", () => {
{
// change main menu active component
const spy = jest.spyOn(eventTemplates, "changeMainMenuComponent");
const testContext = getBootSceneContext();
handleBootSceneInput(testContext, "UP");
expect(spy).toHaveBeenCalled();
}
// select main menu component
{
const testContext = {
...getBootSceneContext(),
activeMainMenuComponent: "authorize_user" as MainMenuComponent,
};
expect(handleBootSceneInput(testContext, "CIRCLE")).toEqual(
enterUserAuthorization
);
}
{
const testContext = {
...getBootSceneContext(),
activeMainMenuComponent: "load_data" as MainMenuComponent,
};
expect(handleBootSceneInput(testContext, "CIRCLE")).toEqual(enterLoadData);
}
{
// change letter in authorize user scene
const spy = jest.spyOn(eventTemplates, "updateAuthorizeUserLetterIdx");
const testContext = {
...getBootSceneContext(),
subscene: "authorize_user" as BootSubscene,
};
handleBootSceneInput(testContext, "RIGHT");
expect(spy).toHaveBeenCalled();
}
{
// start new game
const testContext = {
...getBootSceneContext(),
subscene: "authorize_user" as BootSubscene,
playerName: "チ",
};
expect(handleBootSceneInput(testContext, "START")).toEqual(startNewGame);
}
{
// delete last char if player name is not empty
const spy = jest.spyOn(eventTemplates, "removePlayerNameLastChar");
const testContext = {
...getBootSceneContext(),
subscene: "authorize_user" as BootSubscene,
playerName: "チ",
};
handleBootSceneInput(testContext, "X");
expect(spy).toHaveBeenCalled();
}
{
// go back to main menu if player name is empty
const testContext = {
...getBootSceneContext(),
subscene: "authorize_user" as BootSubscene,
playerName: "",
};
expect(handleBootSceneInput(testContext, "X")).toEqual(
exitUserAuthorization
);
}
});

View file

@ -0,0 +1,38 @@
import * as eventTemplates from "../../core/eventTemplates";
import { getEndSceneContext } from "../../store";
import handleEndSceneInput from "../../core/input-handlers/handleEndSceneInput";
import { EndComponent } from "../../types/types";
it("Checks whether or not the end scene input handler reacts appropriately for each input", () => {
{
// changing end active component
const spy = jest.spyOn(eventTemplates, "changeEndComponent");
const testContext = { ...getEndSceneContext(), selectionVisible: true };
handleEndSceneInput(testContext, "DOWN");
expect(spy).toHaveBeenCalled();
}
// selecting end component
{
const spy = jest.spyOn(eventTemplates, "endGame");
const testContext = { ...getEndSceneContext(), selectionVisible: true };
handleEndSceneInput(testContext, "CIRCLE");
expect(spy).toHaveBeenCalled();
}
{
const spy = jest.spyOn(eventTemplates, "changeSite");
const testContext = {
...getEndSceneContext(),
selectionVisible: true,
activeEndComponent: "continue" as EndComponent,
};
handleEndSceneInput(testContext, "CIRCLE");
expect(spy).toHaveBeenCalled();
}
});

View file

@ -0,0 +1,131 @@
import { getMainSceneContext } from "../../store";
import handleMainSceneInput from "../../core/input-handlers/handleMainSceneInput";
import * as eventTemplates from "../../core/eventTemplates";
import site_a from "../../resources/site_a.json";
import {
enterLevelSelection,
pauseGame,
ripNode,
showAbout,
throwNode,
} from "../../core/eventTemplates";
import { nodeToScene } from "../../helpers/node-helpers";
import { PauseComponent } from "../../types/types";
const expectOr = (...tests: (() => void)[]) => {
try {
tests.shift()!();
} catch (e) {
if (tests.length) expectOr(...tests);
else throw e;
}
};
it("Checks whether or not the main scene input handler reacts appropriately for each input", () => {
{
const spy = jest.spyOn(eventTemplates, "changeNode");
const testContext = getMainSceneContext();
handleMainSceneInput(testContext, "UP");
expect(spy).toHaveBeenCalled();
}
{
// move vertically when utmost up node is currently selected
const spy = jest.spyOn(eventTemplates, "siteMoveVertical");
const testContext = {
...getMainSceneContext(),
activeNode: {
...site_a["04"]["0422"],
matrixIndices: { matrixIdx: 7, rowIdx: 0, colIdx: 0 },
},
};
handleMainSceneInput(testContext, "UP");
expect(spy).toHaveBeenCalled();
}
{
// move horizontally when utmost left node is currently selected
const spy = jest.spyOn(eventTemplates, "siteMoveHorizontal");
const testContext = getMainSceneContext();
handleMainSceneInput(testContext, "LEFT");
expect(spy).toHaveBeenCalled();
}
{
// play throw node/rip node animation and set the scene when the player selects a node
const testContext = getMainSceneContext();
const output = handleMainSceneInput(testContext, "CIRCLE");
expectOr(
() =>
expect(output).toEqual(
ripNode({ currentScene: nodeToScene(testContext.activeNode)! })
),
() =>
expect(output).toEqual(
throwNode({ currentScene: nodeToScene(testContext.activeNode)! })
)
);
}
{
// entering level selection
const testContext = getMainSceneContext();
expect(handleMainSceneInput(testContext, "L2")).toEqual(
enterLevelSelection({ selectedLevel: testContext.level })
);
}
{
// entering pause
const testContext = getMainSceneContext();
expect(handleMainSceneInput(testContext, "TRIANGLE")).toEqual(
pauseGame({ siteRot: [Math.PI / 2, testContext.siteRotY, 0] })
);
}
{
// changing level inside level selection
const spy = jest.spyOn(eventTemplates, "changeSelectedLevel");
const testContext = {
...getMainSceneContext(),
subscene: "level_selection",
};
handleMainSceneInput(testContext, "UP");
expect(spy).toHaveBeenCalled();
}
{
// selecting new level when currently below it
const spy = jest.spyOn(eventTemplates, "selectLevel");
const testContext = {
...getMainSceneContext(),
selectedLevel: 10,
subscene: "level_selection",
};
handleMainSceneInput(testContext, "CIRCLE");
expect(spy).toHaveBeenCalled();
}
{
// changing pause active component
const spy = jest.spyOn(eventTemplates, "changePauseComponent");
const testContext = {
...getMainSceneContext(),
subscene: "pause",
};
handleMainSceneInput(testContext, "UP");
expect(spy).toHaveBeenCalled();
}
{
// selecting pause component
const testContext = {
...getMainSceneContext(),
subscene: "pause",
activePauseComponent: "about" as PauseComponent,
};
expect(handleMainSceneInput(testContext, "CIRCLE")).toEqual(showAbout);
}
});

View file

@ -0,0 +1,72 @@
import * as eventTemplates from "../../core/eventTemplates";
import { getMediaSceneContext } from "../../store";
import handleMediaSceneInput from "../../core/input-handlers/handleMediaSceneInput";
import { exitMedia } from "../../core/eventTemplates";
import {
LeftMediaComponent,
MediaSide,
RightMediaComponent,
} from "../../types/types";
it("Checks whether or not the media scene input handler reacts appropriately for each input", () => {
{
// changing left side media active component
const spy = jest.spyOn(eventTemplates, "changeLeftMediaComponent");
const testContext = getMediaSceneContext();
handleMediaSceneInput(testContext, "DOWN");
expect(spy).toHaveBeenCalled();
}
{
// changing media side from left to right
const spy = jest.spyOn(eventTemplates, "changeMediaSide");
const testContext = getMediaSceneContext();
handleMediaSceneInput(testContext, "RIGHT");
expect(spy).toHaveBeenCalled();
}
{
// exit media
const testContext = {
...getMediaSceneContext(),
activeMediaComponent: "exit" as LeftMediaComponent,
};
expect(handleMediaSceneInput(testContext, "CIRCLE")).toEqual(exitMedia);
}
{
// play media
const spy = jest.spyOn(eventTemplates, "playMedia");
const testContext = getMediaSceneContext();
handleMediaSceneInput(testContext, "CIRCLE");
expect(spy).toHaveBeenCalled();
}
{
// change right side media component
const spy = jest.spyOn(eventTemplates, "changeRightMediaComponent");
const testContext = {
...getMediaSceneContext(),
currentMediaSide: "right" as MediaSide,
};
handleMediaSceneInput(testContext, "DOWN");
expect(spy).toHaveBeenCalled();
}
{
// change from right side to left
const spy = jest.spyOn(eventTemplates, "changeMediaSide");
const testContext = {
...getMediaSceneContext(),
currentMediaSide: "right" as MediaSide,
};
handleMediaSceneInput(testContext, "LEFT");
expect(spy).toHaveBeenCalled();
}
});

View file

@ -0,0 +1,34 @@
import * as eventTemplates from "../../core/eventTemplates";
import { getSsknSceneContext } from "../../store";
import handleSsknSceneInput from "../../core/input-handlers/handleSsknSceneInput";
import { exitSskn } from "../../core/eventTemplates";
import { SsknComponent } from "../../types/types";
it("Checks whether or not the sskn scene input handler reacts appropriately for each input", () => {
{
// changing sskn active component
const spy = jest.spyOn(eventTemplates, "changeSsknComponent");
const testContext = getSsknSceneContext();
handleSsknSceneInput(testContext, "DOWN");
expect(spy).toHaveBeenCalled();
}
{
// select ok
const spy = jest.spyOn(eventTemplates, "upgradeSskn");
const testContext = getSsknSceneContext();
handleSsknSceneInput(testContext, "CIRCLE");
expect(spy).toHaveBeenCalled();
}
{
// select cancel
const testContext = {
...getSsknSceneContext(),
activeSsknComponent: "cancel" as SsknComponent,
};
expect(handleSsknSceneInput(testContext, "CIRCLE")).toEqual(exitSskn);
}
});

View file

@ -9,18 +9,18 @@ import {
useStore,
} from "../store";
import { getKeyCodeAssociation } from "../utils/parseUserInput";
import handleMediaSceneKeyPress from "../core/scene-keypress-handlers/handleMediaSceneKeyPress";
import handleSsknSceneKeyPress from "../core/scene-keypress-handlers/handleSsknSceneKeyPress";
import handleMainSceneKeyPress from "../core/scene-keypress-handlers/handleMainSceneKeyPress";
import handleBootSceneKeyPress from "../core/scene-keypress-handlers/handleBootSceneKeyPress";
import handleMediaSceneInput from "../core/input-handlers/handleMediaSceneInput";
import handleSsknSceneInput from "../core/input-handlers/handleSsknSceneInput";
import handleMainSceneInput from "../core/input-handlers/handleMainSceneInput";
import handleBootSceneInput from "../core/input-handlers/handleBootSceneInput";
import { useFrame } from "react-three-fiber";
import { getRandomIdleLainAnim } from "../helpers/idle-helpers";
import * as audio from "../static/audio/sfx";
import handleEndSceneKeyPress from "../core/scene-keypress-handlers/handleEndSceneKeyPress";
import handleEndSceneInput from "../core/input-handlers/handleEndSceneInput";
import handleEvent from "../core/handleEvent";
import { GameEvent } from "../types/types";
const KeyPressHandler = () => {
const InputHandler = () => {
const scene = useStore((state) => state.currentScene);
const mainSubscene = useStore((state) => state.mainSubscene);
const inputCooldown = useStore((state) => state.inputCooldown);
@ -90,27 +90,27 @@ const KeyPressHandler = () => {
case "main":
return {
contextProvider: getMainSceneContext,
keyPressHandler: handleMainSceneKeyPress,
keyPressHandler: handleMainSceneInput,
};
case "media":
return {
contextProvider: getMediaSceneContext,
keyPressHandler: handleMediaSceneKeyPress,
keyPressHandler: handleMediaSceneInput,
};
case "sskn":
return {
contextProvider: getSsknSceneContext,
keyPressHandler: handleSsknSceneKeyPress,
keyPressHandler: handleSsknSceneInput,
};
case "boot":
return {
contextProvider: getBootSceneContext,
keyPressHandler: handleBootSceneKeyPress,
keyPressHandler: handleBootSceneInput,
};
case "end":
return {
contextProvider: getEndSceneContext,
keyPressHandler: handleEndSceneKeyPress,
keyPressHandler: handleEndSceneInput,
};
case "gate":
case "polytan":
@ -128,8 +128,11 @@ const KeyPressHandler = () => {
if (sceneFns) {
const { contextProvider, keyPressHandler } = sceneFns;
const ctx = contextProvider(keyPress);
const event: GameEvent | undefined = keyPressHandler(ctx as any);
const ctx = contextProvider();
const event: GameEvent | undefined = keyPressHandler(
ctx as any,
keyPress
);
if (event) handleEvent(event);
}
}
@ -148,4 +151,4 @@ const KeyPressHandler = () => {
return null;
};
export default KeyPressHandler;
export default InputHandler;

View file

@ -17,11 +17,11 @@ import {
} from "../eventTemplates";
import { BootSceneContext, GameEvent } from "../../types/types";
const handleBootSceneKeyPress = (
bootSceneContext: BootSceneContext
const handleBootSceneInput = (
bootSceneContext: BootSceneContext,
keyPress: string
): GameEvent | undefined => {
const {
keyPress,
subscene,
activeMainMenuComponent,
activePromptComponent,
@ -73,9 +73,7 @@ const handleBootSceneKeyPress = (
case "authorize_user":
switch (keyPress) {
case "START":
if (playerName.length > 0) {
return startNewGame;
}
if (playerName.length > 0) return startNewGame;
return;
case "X":
if (playerName.length > 0) {
@ -202,4 +200,4 @@ const handleBootSceneKeyPress = (
}
};
export default handleBootSceneKeyPress;
export default handleBootSceneInput;

View file

@ -2,11 +2,11 @@ import { changeEndComponent, changeSite, endGame } from "../eventTemplates";
import { EndSceneContext, GameEvent } from "../../types/types";
import { getCurrentUserState } from "../../store";
const handleEndSceneKeyPress = (
endSceneContext: EndSceneContext
const handleEndSceneInput = (
endSceneContext: EndSceneContext,
keyPress: string
): GameEvent | undefined => {
const {
keyPress,
selectionVisible,
activeEndComponent,
siteSaveState,
@ -49,4 +49,4 @@ const handleEndSceneKeyPress = (
}
};
export default handleEndSceneKeyPress;
export default handleEndSceneInput;

View file

@ -2,6 +2,7 @@ import {
findNode,
getNodeById,
isNodeVisible,
nodeToScene,
unknownNodeTemplate,
} from "../../helpers/node-helpers";
import {
@ -35,8 +36,9 @@ import {
import { GameEvent, MainSceneContext } from "../../types/types";
import { getCurrentUserState } from "../../store";
const handleMainSceneKeyPress = (
mainSceneContext: MainSceneContext
const handleMainSceneInput = (
mainSceneContext: MainSceneContext,
keyPress: string
): GameEvent | undefined => {
const {
subscene,
@ -47,7 +49,6 @@ const handleMainSceneKeyPress = (
siteRotY,
activeNode,
level,
keyPress,
showingAbout,
promptVisible,
activePromptComponent,
@ -189,8 +190,6 @@ const handleMainSceneKeyPress = (
case "CIRCLE":
const eventAnimation = Math.random() < 0.4 ? throwNode : ripNode;
const nodeType = activeNode.type;
if (
activeNode.id === "" ||
!isNodeVisible(activeNode, gameProgress)
@ -202,26 +201,8 @@ const handleMainSceneKeyPress = (
return rejectEvents[Math.floor(Math.random() * 3)];
}
switch (nodeType) {
case 0:
case 2:
case 4:
case 3:
case 5:
return eventAnimation({ currentScene: "media" });
case 6:
if (activeNode.node_name.substr(0, 3) === "TaK") {
return eventAnimation({ currentScene: "tak" });
} else {
return eventAnimation({ currentScene: "media" });
}
case 7:
return eventAnimation({ currentScene: "sskn" });
case 8:
return eventAnimation({ currentScene: "gate" });
case 9:
return eventAnimation({ currentScene: "polytan" });
}
const newScene = nodeToScene(activeNode);
if (newScene) return eventAnimation({ currentScene: newScene });
break;
case "L2":
return enterLevelSelection({ selectedLevel: level });
@ -326,4 +307,4 @@ const handleMainSceneKeyPress = (
}
};
export default handleMainSceneKeyPress;
export default handleMainSceneInput;

View file

@ -15,11 +15,11 @@ import {
RightMediaComponent,
} from "../../types/types";
const handleMediaSceneKeyPress = (
mediaSceneContext: MediaSceneContext
const handleMediaSceneInput = (
mediaSceneContext: MediaSceneContext,
keyPress: string
): GameEvent | undefined => {
const {
keyPress,
activeMediaComponent,
wordPosStateIdx,
activeNode,
@ -139,4 +139,4 @@ const handleMediaSceneKeyPress = (
}
};
export default handleMediaSceneKeyPress;
export default handleMediaSceneInput;

View file

@ -1,15 +1,11 @@
import { changeSsknComponent, exitSskn, upgradeSskn } from "../eventTemplates";
import { GameEvent, SsknSceneContext } from "../../types/types";
const handleSsknSceneKeyPress = (
ssknSceneContext: SsknSceneContext
const handleSsknSceneInput = (
ssknSceneContext: SsknSceneContext,
keyPress: string
): GameEvent | undefined => {
const {
keyPress,
activeSsknComponent,
activeNode,
gameProgress,
} = ssknSceneContext;
const { activeSsknComponent, activeNode } = ssknSceneContext;
switch (keyPress) {
case "UP":
@ -27,4 +23,4 @@ const handleSsknSceneKeyPress = (
}
};
export default handleSsknSceneKeyPress;
export default handleSsknSceneInput;

View file

@ -288,3 +288,26 @@ export const unknownNodeTemplate = {
"3": "",
},
};
export const nodeToScene = (node: NodeData) => {
switch (node.type) {
case 0:
case 2:
case 4:
case 3:
case 5:
return "media";
case 6:
if (node.node_name.substr(0, 3) === "TaK") {
return "tak";
} else {
return "media";
}
case 7:
return "sskn";
case 8:
return "gate";
case 9:
return "polytan";
}
};

View file

@ -346,12 +346,11 @@ const getPromptContext = () => {
};
};
export const getMainSceneContext = (keyPress: string): MainSceneContext => {
export const getMainSceneContext = (): MainSceneContext => {
const state = useStore.getState();
return {
...getPromptContext(),
keyPress: keyPress,
subscene: state.mainSubscene,
selectedLevel: state.selectedLevel,
activePauseComponent: state.activePauseComponent,
@ -366,21 +365,18 @@ export const getMainSceneContext = (keyPress: string): MainSceneContext => {
};
};
export const getSsknSceneContext = (keyPress: string): SsknSceneContext => {
export const getSsknSceneContext = (): SsknSceneContext => {
const state = useStore.getState();
return {
keyPress: keyPress,
activeSsknComponent: state.activeSsknComponent,
activeNode: state.activeNode,
gameProgress: state.gameProgress,
};
};
export const getMediaSceneContext = (keyPress: string): MediaSceneContext => {
export const getMediaSceneContext = (): MediaSceneContext => {
const state = useStore.getState();
return {
keyPress: keyPress,
lastActiveMediaComponents: state.lastActiveMediaComponents,
currentMediaSide: state.currentMediaSide,
activeMediaComponent: state.activeMediaComponent,
@ -391,12 +387,11 @@ export const getMediaSceneContext = (keyPress: string): MediaSceneContext => {
};
};
export const getBootSceneContext = (keyPress: string): BootSceneContext => {
export const getBootSceneContext = (): BootSceneContext => {
const state = useStore.getState();
return {
...getPromptContext(),
keyPress: keyPress,
playerName: state.playerName,
subscene: state.bootSubscene,
activeMainMenuComponent: state.activeMainMenuComponent,
@ -404,11 +399,10 @@ export const getBootSceneContext = (keyPress: string): BootSceneContext => {
};
};
export const getEndSceneContext = (keyPress: string): EndSceneContext => {
export const getEndSceneContext = (): EndSceneContext => {
const state = useStore.getState();
return {
keyPress: keyPress,
activeEndComponent: state.activeEndComponent,
selectionVisible: state.endSceneSelectionVisible,
siteSaveState: state.siteSaveState,

View file

@ -98,7 +98,6 @@ type PromptContext = {
};
export interface MainSceneContext extends PromptContext {
keyPress: string;
activeNode: NodeData;
showingAbout: boolean;
level: number;
@ -113,14 +112,11 @@ export interface MainSceneContext extends PromptContext {
}
export type SsknSceneContext = {
keyPress: string;
activeSsknComponent: SsknComponent;
activeNode: NodeData;
gameProgress: GameProgress;
};
export type MediaSceneContext = {
keyPress: string;
wordPosStateIdx: number;
currentMediaSide: MediaSide;
activeMediaComponent: MediaComponent;
@ -134,7 +130,6 @@ export type MediaSceneContext = {
};
export interface BootSceneContext extends PromptContext {
keyPress: string;
playerName: string;
subscene: BootSubscene;
activeMainMenuComponent: MainMenuComponent;
@ -142,7 +137,6 @@ export interface BootSceneContext extends PromptContext {
}
export type EndSceneContext = {
keyPress: string;
activeEndComponent: EndComponent;
selectionVisible: boolean;
siteSaveState: SiteSaveState;