21 - Gameplay Test

Let's expand our test to play the game. We're going to check the bird can fly past 2 pipes, then crash into one, ending up with a score of 2.

We pass an initRandom field into testSprite. This ensures device.random() calls always produce the same result, in this case 0.5, then 0.5, then 0. With this we can ensure our bird knows what height to fly at.

We create a function keepBirdInMiddle which is going to keep clicking the screen when the bird's y position is too low, to keep the bird jumping at the right height.

We can access the bird Texture through the getTexture("bird") call in order to read its y value. Here "bird" is the testId prop which we need to pass into the t.image Texture in our Bird Sprite. The testId prop can be passed into any Texture to reference it in a test.

We loop through the game's frames with jumpToFrame, which will keep running until a condition returns either a Texture or true. When that exits (bird hit a pipe!) we confirm the score shows as expected.

Lastly we can even confirm the sound effect played as expected after we crashed.

BackNext
1import { testSprite } from "@replay/test";
2import { Game, gameProps } from "..";
3import { pipeGap } from "../pipe";
4import { birdHeight } from "../bird";
5
6test("Can reach a score of 2", async () => {
7 const initInputs = {
8 pointer: {
9 pressed: false,
10 numberPressed: 0,
11 justPressed: false,
12 justReleased: false,
13 x: 0,
14 y: 0,
15 },
16 keysDown: {},
17 keysJustPressed: {},
18 };
19 const mainMenuText = "Start";
20
21 const {
22 nextFrame,
23 updateInputs,
24 getByText,
25 getTexture,
26 jumpToFrame,
27 audio,
28 resolvePromises,
29 } = testSprite(Game(gameProps), gameProps, {
30 initInputs,
31 // First two pipes will have gap in middle, third pipe lower down
32 initRandom: [0.5, 0.5, 0],
33 });
34
35 await resolvePromises();
36 nextFrame();
37
38 expect(getByText(mainMenuText).length).toBe(1);
39
40 updateInputs({
41 pointer: {
42 pressed: false,
43 numberPressed: 1,
44 justPressed: false,
45 justReleased: true,
46 x: 0,
47 y: 0,
48 },
49 keysDown: {},
50 keysJustPressed: {},
51 });
52 nextFrame();
53
54 updateInputs(initInputs);
55 nextFrame();
56
57 // Main menu gone, game has started
58 expect(getByText(mainMenuText).length).toBe(0);
59
60 // Keeps the bird hovering in the middle to pass the first 2 pipes
61 function keepBirdInMiddle() {
62 if (getTexture("bird").props.y < -pipeGap / 2 + birdHeight + 20) {
63 updateInputs({
64 pointer: {
65 pressed: true,
66 numberPressed: 1,
67 justPressed: true,
68 justReleased: false,
69 x: 0,
70 y: 0,
71 },
72 keysDown: {},
73 keysJustPressed: {},
74 });
75 nextFrame();
76
77 updateInputs(initInputs);
78 nextFrame();
79 }
80
81 nextFrame();
82 }
83
84 await jumpToFrame(() => {
85 keepBirdInMiddle();
86
87 // Exit when main menu appears again
88 return getByText(mainMenuText).length > 0;
89 });
90
91 getByText("Score: 2");
92 getByText("High score: 2");
93
94 expect(audio.play).toBeCalledWith("boop.wav");
95 expect(audio.play).toBeCalledTimes(1);
96});
97