fix: spectator board rendering
This commit is contained in:
@@ -96,6 +96,9 @@ static void test_human_game_journey(void) {
|
||||
resume(&api, player_tokens[0], "player1");
|
||||
resume(&api, player_tokens[1], "player2");
|
||||
resume(&api, spectator_token, "spectator");
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, spectator_token);
|
||||
assert(response.status == 200U && strstr(response.body, "\"phase\":\"lobby\"") != NULL &&
|
||||
strstr(response.body, "\"viewer\":\"spectator\"") != NULL);
|
||||
const uint32_t game_id = application.lifecycle.game.state.game_id;
|
||||
char config[96];
|
||||
snprintf(config, sizeof(config), "{\"token\":\"%s\",\"gameId\":%u,\"mode\":\"human\"}",
|
||||
@@ -105,14 +108,21 @@ static void test_human_game_journey(void) {
|
||||
command(&api, HTTP_API_ROUTE_START, player_tokens[0], game_id);
|
||||
assert(application.lifecycle.game.state.phase == PHASE_IN_PROGRESS);
|
||||
|
||||
response = call(&api, HTTP_API_ROUTE_JOIN, HTTP_API_POST,
|
||||
"{\"name\":\"Новый зритель\",\"requestedRole\":\"spectator\"}", NULL);
|
||||
assert(response.status == 200U && strstr(response.body, "\"role\":\"spectator\"") != NULL);
|
||||
char in_progress_spectator_token[33];
|
||||
token_from_response(&response, in_progress_spectator_token);
|
||||
resume(&api, in_progress_spectator_token, "spectator");
|
||||
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, player_tokens[0]);
|
||||
assert(response.status == 200U && board_start(response.body, 0)[0] <= '3');
|
||||
assert_hidden(response.body, 1U);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, player_tokens[1]);
|
||||
assert(response.status == 200U);
|
||||
assert_hidden(response.body, 0U);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, spectator_token);
|
||||
assert(response.status == 200U);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, in_progress_spectator_token);
|
||||
assert(response.status == 200U && strstr(response.body, "\"viewer\":\"spectator\"") != NULL);
|
||||
assert_hidden(response.body, 0U);
|
||||
assert_hidden(response.body, 1U);
|
||||
|
||||
@@ -124,7 +134,7 @@ static void test_human_game_journey(void) {
|
||||
while (application.lifecycle.game.state.phase == PHASE_IN_PROGRESS) {
|
||||
const coordinate_t target = first_cell(&application.lifecycle.game.state.boards[winner ^ 1U], CELL_SHIP);
|
||||
shot(&api, player_tokens[winner], game_id, target);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, spectator_token);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, in_progress_spectator_token);
|
||||
saw_sunk = saw_sunk || strchr(board_start(response.body, winner ^ 1U), '4') != NULL;
|
||||
}
|
||||
assert(saw_sunk);
|
||||
@@ -133,11 +143,21 @@ static void test_human_game_journey(void) {
|
||||
assert(application.lifecycle.game.state.statistics[winner].hits == 20U);
|
||||
assert(application.lifecycle.game.state.statistics[winner].ships_sunk == 10U);
|
||||
assert(application.lifecycle.game.state.statistics[first_player].misses == 1U);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, spectator_token);
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, in_progress_spectator_token);
|
||||
assert(response.status == 200U && strstr(response.body, "\"winner\":") != NULL);
|
||||
assert(strstr(response.body, "\"statistics\":[") != NULL);
|
||||
assert(strchr(board_start(response.body, 0U), '1') != NULL || strchr(board_start(response.body, 1U), '1') != NULL);
|
||||
|
||||
response = call(&api, HTTP_API_ROUTE_JOIN, HTTP_API_POST,
|
||||
"{\"name\":\"Финишный зритель\",\"requestedRole\":\"spectator\"}", NULL);
|
||||
assert(response.status == 200U && strstr(response.body, "\"role\":\"spectator\"") != NULL);
|
||||
char finished_spectator_token[33];
|
||||
token_from_response(&response, finished_spectator_token);
|
||||
resume(&api, finished_spectator_token, "spectator");
|
||||
response = call(&api, HTTP_API_ROUTE_STATE, HTTP_API_GET, NULL, finished_spectator_token);
|
||||
assert(response.status == 200U && strstr(response.body, "\"phase\":\"finished\"") != NULL &&
|
||||
strstr(response.body, "\"viewer\":\"spectator\"") != NULL);
|
||||
|
||||
resume(&api, player_tokens[0], "player1");
|
||||
resume(&api, player_tokens[1], "player2");
|
||||
command(&api, HTTP_API_ROUTE_REMATCH, player_tokens[0], game_id);
|
||||
@@ -149,6 +169,30 @@ static void test_human_game_journey(void) {
|
||||
assert(game_lifecycle_disconnect(&application.lifecycle, 1U) == LIFECYCLE_RESULT_OK);
|
||||
command(&api, HTTP_API_ROUTE_ABORT, player_tokens[0], application.lifecycle.game.state.game_id);
|
||||
assert(application.lifecycle.game.state.phase == PHASE_LOBBY);
|
||||
|
||||
char recovery[80];
|
||||
snprintf(recovery, sizeof(recovery), "{\"token\":\"%s\",\"gameId\":%u}", in_progress_spectator_token,
|
||||
(unsigned int)application.lifecycle.game.state.game_id);
|
||||
response = call(&api, HTTP_API_ROUTE_PROFILE_RESET, HTTP_API_POST, recovery, NULL);
|
||||
assert(response.status == 200U);
|
||||
response = call(&api, HTTP_API_ROUTE_JOIN, HTTP_API_POST,
|
||||
"{\"name\":\"После профиля\",\"requestedRole\":\"spectator\"}", NULL);
|
||||
assert(response.status == 200U && strstr(response.body, "\"role\":\"spectator\"") != NULL);
|
||||
char recovery_spectator_token[33];
|
||||
token_from_response(&response, recovery_spectator_token);
|
||||
|
||||
snprintf(recovery, sizeof(recovery), "{\"token\":\"%s\",\"gameId\":%u}", recovery_spectator_token,
|
||||
(unsigned int)application.lifecycle.game.state.game_id);
|
||||
response = call(&api, HTTP_API_ROUTE_LEAVE, HTTP_API_POST, recovery, NULL);
|
||||
assert(response.status == 200U);
|
||||
response = call(&api, HTTP_API_ROUTE_JOIN, HTTP_API_POST,
|
||||
"{\"name\":\"После выхода\",\"requestedRole\":\"spectator\"}", NULL);
|
||||
assert(response.status == 200U && strstr(response.body, "\"role\":\"spectator\"") != NULL);
|
||||
|
||||
command(&api, HTTP_API_ROUTE_RESET, player_tokens[0], application.lifecycle.game.state.game_id);
|
||||
response = call(&api, HTTP_API_ROUTE_JOIN, HTTP_API_POST,
|
||||
"{\"name\":\"После сброса\",\"requestedRole\":\"spectator\"}", NULL);
|
||||
assert(response.status == 200U && strstr(response.body, "\"role\":\"spectator\"") != NULL);
|
||||
}
|
||||
|
||||
static void test_atomic_two_player_lobby(void) {
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
const assert = require('node:assert/strict');
|
||||
const fs = require('node:fs');
|
||||
const path = require('node:path');
|
||||
const test = require('node:test');
|
||||
|
||||
const app = fs.readFileSync(path.join(__dirname, '../../data/app.js'), 'utf8');
|
||||
const css = fs.readFileSync(path.join(__dirname, '../../data/styles.css'), 'utf8');
|
||||
|
||||
test('spectator state selects a public player board by default and retains only spectator board keys', () => {
|
||||
assert.match(app, /const boardKeys = Object\.freeze\(\{ own: 'own', opponent: 'opponent', player1: 'player1', player2: 'player2' \}\)/);
|
||||
assert.match(app, /const validBoardKeys = role === 'spectator' \? \[boardKeys\.player1, boardKeys\.player2\] : \[boardKeys\.own, boardKeys\.opponent\]/);
|
||||
assert.match(app, /if \(!validBoardKeys\.includes\(activeBoard\)\) activeBoard = validBoardKeys\[0\]/);
|
||||
assert.match(app, /key: boardKeys\.player1/);
|
||||
assert.match(app, /key: boardKeys\.player2/);
|
||||
});
|
||||
|
||||
test('spectator boards have working tabs and no player firing controls', () => {
|
||||
assert.match(app, /tab\.addEventListener\('click', \(\) => \{ activeBoard = definition\.key; renderGame\(\); \}\)/);
|
||||
assert.match(app, /ui\.shotControls\.hidden = !isPlayer\(\)/);
|
||||
assert.match(app, /const watching = role === 'spectator'/);
|
||||
assert.match(app, /Наблюдаем за боем · Ходит \$\{turnPlayer\}/);
|
||||
assert.match(app, /watching \? 'Наблюдаем за боем' : 'Ждём соперника…'/);
|
||||
assert.match(css, /\.board\[hidden\]\s*\{\s*display:\s*none;/);
|
||||
assert.match(css, /\.board\[hidden\]\s*\{\s*display:\s*block;/);
|
||||
});
|
||||
|
||||
test('WebSocket and HTTP state updates share spectator role and board-tab reconciliation', () => {
|
||||
assert.match(app, /async function pollState\(\)[\s\S]*?acceptState\(payload\);/);
|
||||
assert.match(app, /if \(message\.type === 'state'\) \{\s*acceptState\(message\);/);
|
||||
assert.match(app, /role = payload\.viewer;\s*const validBoardKeys = role === 'spectator'/);
|
||||
});
|
||||
Reference in New Issue
Block a user