From 4b2ce146011d4c7376e0387fefce6111363aa077 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Tue, 14 Nov 2023 22:52:58 -0800 Subject: [PATCH 01/14] Add tests for GoEngine.place() --- src/__tests__/GoEngine_place.test.ts | 113 +++++++++++++++++++++++++++ 1 file changed, 113 insertions(+) create mode 100644 src/__tests__/GoEngine_place.test.ts diff --git a/src/__tests__/GoEngine_place.test.ts b/src/__tests__/GoEngine_place.test.ts new file mode 100644 index 00000000..a30af53a --- /dev/null +++ b/src/__tests__/GoEngine_place.test.ts @@ -0,0 +1,113 @@ +import { GoEngine } from "../GoEngine"; +import { GobanMoveError } from "../GobanError"; + +test("GoEngine.place()", () => { + // Basic test to make sure it's working + + const engine = new GoEngine({}); + + engine.place(16, 3); + engine.place(3, 2); + engine.place(15, 16); + engine.place(14, 2); + engine.place(2, 15); + engine.place(16, 14); + engine.place(15, 4); + + expect(engine.board).toEqual([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); +}); + +test("stone on top of stone", () => { + const engine = new GoEngine({ width: 3, height: 3 }); + + engine.place(1, 1); + + expect(() => engine.place(1, 1)).toThrow( + new GobanMoveError(0, 1, "B2", "stone_already_placed_here"), + ); +}); + +test("capture", () => { + const engine = new GoEngine({ width: 2, height: 2 }); + + engine.place(0, 1); + engine.place(0, 0); + + expect(engine.place(1, 0)).toBe(1); + expect(engine.board).toEqual([ + [0, 1], + [1, 0], + ]); +}); + +test("ko", () => { + const engine = new GoEngine({ + width: 4, + height: 3, + initial_state: { + black: "baabbc", + white: "cadbcc", + }, + }); + + /* A B C D + * 3 . X O . + * 2 X . . O + * 1 . X O . + */ + + engine.place(2, 1); + engine.place(1, 1); + + expect(() => engine.place(2, 1, true)).toThrow( + new GobanMoveError(0, 2, "C2", "illegal_ko_move"), + ); +}); + +test("superko", () => { + const engine = new GoEngine({ + rules: "chinese", + initial_state: { + black: "dabbcbdbccadbdcd", + white: "baeaabebacdcecddaebecede", + }, + }); + + /* A B C D E F + * 19 . O . X O . + * 18 O X X X O . + * 17 O . X O O . + * 16 X X X O . . + * 15 O O O O . . + * 14 . . . . . . + */ + + engine.place(-1, -1); + engine.place(2, 0); + engine.place(0, 0); + engine.place(1, 0); + engine.place(-1, -1); + expect(() => engine.place(2, 0, true, true)).toThrow( + new GobanMoveError(0, 5, "C19", "illegal_board_repetition"), + ); +}); From db0c280d0a1ba0a2d214c479eb767477e3f6f83f Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Wed, 15 Nov 2023 02:49:01 -0800 Subject: [PATCH 02/14] Add more tests, and create a dedicated test suite for place() --- src/__tests__/GoEngine_place.test.ts | 251 +++++++++++++++++---------- src/__tests__/GoEngine_sgf.test.ts | 6 + 2 files changed, 161 insertions(+), 96 deletions(-) diff --git a/src/__tests__/GoEngine_place.test.ts b/src/__tests__/GoEngine_place.test.ts index a30af53a..eaf79c5f 100644 --- a/src/__tests__/GoEngine_place.test.ts +++ b/src/__tests__/GoEngine_place.test.ts @@ -1,113 +1,172 @@ import { GoEngine } from "../GoEngine"; import { GobanMoveError } from "../GobanError"; +import { JGOFIntersection } from "../JGOF"; + +describe("GoEngine.place()", () => { + test("Basic test to make sure it's working", () => { + const engine = new GoEngine({}); + + engine.place(16, 3); + engine.place(3, 2); + engine.place(15, 16); + engine.place(14, 2); + engine.place(2, 15); + engine.place(16, 14); + engine.place(15, 4); + + expect(engine.board).toEqual([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + }); -test("GoEngine.place()", () => { - // Basic test to make sure it's working - - const engine = new GoEngine({}); - - engine.place(16, 3); - engine.place(3, 2); - engine.place(15, 16); - engine.place(14, 2); - engine.place(2, 15); - engine.place(16, 14); - engine.place(15, 4); - - expect(engine.board).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); -}); + test("stone on top of stone", () => { + const engine = new GoEngine({ width: 3, height: 3 }); -test("stone on top of stone", () => { - const engine = new GoEngine({ width: 3, height: 3 }); + engine.place(1, 1); - engine.place(1, 1); + expect(() => engine.place(1, 1)).toThrow( + new GobanMoveError(0, 1, "B2", "stone_already_placed_here"), + ); + }); - expect(() => engine.place(1, 1)).toThrow( - new GobanMoveError(0, 1, "B2", "stone_already_placed_here"), - ); -}); + test("capture", () => { + const engine = new GoEngine({ width: 2, height: 2 }); -test("capture", () => { - const engine = new GoEngine({ width: 2, height: 2 }); + engine.place(0, 1); + engine.place(0, 0); - engine.place(0, 1); - engine.place(0, 0); + expect(engine.place(1, 0)).toBe(1); + expect(engine.board).toEqual([ + [0, 1], + [1, 0], + ]); + }); - expect(engine.place(1, 0)).toBe(1); - expect(engine.board).toEqual([ - [0, 1], - [1, 0], - ]); -}); + test("ko", () => { + const engine = new GoEngine({ + width: 4, + height: 3, + initial_state: { + black: "baabbc", + white: "cadbcc", + }, + }); + + /* A B C D + * 3 . X O . + * 2 X . . O + * 1 . X O . + */ + + engine.place(2, 1); + engine.place(1, 1); + + expect(() => engine.place(2, 1, true)).toThrow( + new GobanMoveError(0, 2, "C2", "illegal_ko_move"), + ); + }); + + test("superko", () => { + const engine = new GoEngine({ + rules: "chinese", + initial_state: { + black: "dabbcbdbccadbdcd", + white: "baeaabebacdcecddaebecede", + }, + }); + + /* A B C D E F + * 19 . O . X O . + * 18 O X X X O . + * 17 O . X O O . + * 16 X X X O . . + * 15 O O O O . . + * 14 . . . . . . + */ + + engine.place(-1, -1); + engine.place(2, 0); + engine.place(0, 0); + engine.place(1, 0); + engine.place(-1, -1); + expect(() => engine.place(2, 0, true, true)).toThrow( + new GobanMoveError(0, 5, "C19", "illegal_board_repetition"), + ); + }); -test("ko", () => { - const engine = new GoEngine({ - width: 4, - height: 3, - initial_state: { - black: "baabbc", - white: "cadbcc", - }, + test("suicide", () => { + const engine = new GoEngine({ + width: 2, + height: 2, + initial_state: { + black: "", + white: "abba", + }, + }); + + /* A B + * 2 . O + * 1 O . + */ + + expect(() => engine.place(0, 0)).toThrow( + new GobanMoveError(0, 0, "A2", "move_is_suicidal"), + ); }); - /* A B C D - * 3 . X O . - * 2 X . . O - * 1 . X O . - */ + test("Self capture allowed (ing)", () => { + const goban_callback = { + set: jest.fn(), + }; + + const engine = new GoEngine( + { + width: 2, + height: 2, + initial_state: { + black: "", + white: "abba", + }, + rules: "ing", + }, + goban_callback as any, + ); + + /* A B + * 2 . O + * 1 O . + */ + goban_callback.set.mockClear(); + expect(engine.place(0, 0)).toBe(1); + expect(goban_callback.set).toBeCalledWith(0, 0, 0); + }); - engine.place(2, 1); - engine.place(1, 1); + test("removed_stones parameter", () => { + const engine = new GoEngine({ width: 2, height: 2 }); - expect(() => engine.place(2, 1, true)).toThrow( - new GobanMoveError(0, 2, "C2", "illegal_ko_move"), - ); -}); + engine.place(0, 1); + engine.place(0, 0); -test("superko", () => { - const engine = new GoEngine({ - rules: "chinese", - initial_state: { - black: "dabbcbdbccadbdcd", - white: "baeaabebacdcecddaebecede", - }, + const removed_stones: JGOFIntersection[] = []; + expect(engine.place(1, 0, false, false, false, false, false, removed_stones)).toBe(1); + expect(removed_stones).toEqual([{ x: 0, y: 0 }]); }); - - /* A B C D E F - * 19 . O . X O . - * 18 O X X X O . - * 17 O . X O O . - * 16 X X X O . . - * 15 O O O O . . - * 14 . . . . . . - */ - - engine.place(-1, -1); - engine.place(2, 0); - engine.place(0, 0); - engine.place(1, 0); - engine.place(-1, -1); - expect(() => engine.place(2, 0, true, true)).toThrow( - new GobanMoveError(0, 5, "C19", "illegal_board_repetition"), - ); }); diff --git a/src/__tests__/GoEngine_sgf.test.ts b/src/__tests__/GoEngine_sgf.test.ts index 116ff1c8..78e06b55 100644 --- a/src/__tests__/GoEngine_sgf.test.ts +++ b/src/__tests__/GoEngine_sgf.test.ts @@ -54,6 +54,12 @@ const SGF_TEST_CASES: Array = [ id: "cgoban_tree3", size: 7, }, + { + template: "(;GM[1]FF[4]CA[UTF-8]_MOVES_)", + moves: ";B[aa];W[aa]", + id: "invalid move - stone on top of stone", + size: 3, + }, ]; function rmNewlines(txt: string): string { From e02d34afc868d1a3f3e08bcda4a768501b188516 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Wed, 15 Nov 2023 20:13:37 -0800 Subject: [PATCH 03/14] Add some cur_move tests. --- src/__tests__/GoEngine_moves.test.ts | 39 ++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) create mode 100644 src/__tests__/GoEngine_moves.test.ts diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts new file mode 100644 index 00000000..cf6db1ea --- /dev/null +++ b/src/__tests__/GoEngine_moves.test.ts @@ -0,0 +1,39 @@ +import { GoEngine } from "../GoEngine"; + +test("cur_review_move", () => { + const engine = new GoEngine({}); + const on_cur_review_move = jest.fn(); + engine.addListener("cur_review_move", on_cur_review_move); + + expect(engine.cur_review_move).toBeUndefined(); + + engine.place(0, 0); + + expect(engine.cur_review_move).not.toBe(engine.cur_move); + + engine.setAsCurrentReviewMove(); + + expect(engine.cur_review_move).toBe(engine.cur_move); + expect(on_cur_review_move).toBeCalledTimes(1); + + on_cur_review_move.mockClear(); + engine.setAsCurrentReviewMove(); + + // the signal shouldn't be emitted if the value doesn't actually change + expect(on_cur_review_move).not.toBeCalled(); +}); + +test("cur_move", () => { + const engine = new GoEngine({}); + const on_cur_move = jest.fn(); + engine.addListener("cur_move", on_cur_move); + + expect(engine.cur_move.x).toBe(-1); + expect(engine.cur_move.y).toBe(-1); + + engine.place(2, 3); + + expect(engine.cur_move.x).toBe(2); + expect(engine.cur_move.y).toBe(3); + expect(on_cur_move).toBeCalledTimes(1); +}); From 5acdc1ac33170359d630fbeaf7c4313b4f0a7511 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Thu, 16 Nov 2023 20:22:26 -0800 Subject: [PATCH 04/14] Add GoEngine groups test. --- src/__tests__/GoEngine_groups.test.ts | 56 +++++++++++++++++++++++++++ 1 file changed, 56 insertions(+) create mode 100644 src/__tests__/GoEngine_groups.test.ts diff --git a/src/__tests__/GoEngine_groups.test.ts b/src/__tests__/GoEngine_groups.test.ts new file mode 100644 index 00000000..f3ae0e55 --- /dev/null +++ b/src/__tests__/GoEngine_groups.test.ts @@ -0,0 +1,56 @@ +import { GoEngine } from "../GoEngine"; +import { makeMatrix } from "../GoMath"; + +test("toggleMetagroupRemoval", () => { + const engine = new GoEngine({ + width: 4, + height: 4, + initial_state: { black: "aabbdd", white: "cacbcccd" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + engine.toggleMetaGroupRemoval(0, 0); + + expect(on_removal_updated).toBeCalledTimes(1); + + expect(engine.removal).toEqual([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]); + + engine.toggleMetaGroupRemoval(0, 0); + + expect(engine.removal).toEqual(makeMatrix(4, 4)); +}); + +test("toggleMetagroupRemoval out-of-bounds", () => { + const engine = new GoEngine({ + width: 4, + height: 4, + initial_state: { black: "aabbdd", white: "cacbcccd" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + expect(engine.toggleMetaGroupRemoval(0, 4)).toEqual([[0, []]]); + expect(on_removal_updated).toBeCalledTimes(0); +}); From 2d4511bb0dd4074d4a12d706f8c417aaa4785d65 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sat, 18 Nov 2023 14:16:23 -0800 Subject: [PATCH 05/14] Add toggle empty area test. --- src/__tests__/GoEngine_groups.test.ts | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/__tests__/GoEngine_groups.test.ts b/src/__tests__/GoEngine_groups.test.ts index f3ae0e55..5fff37ea 100644 --- a/src/__tests__/GoEngine_groups.test.ts +++ b/src/__tests__/GoEngine_groups.test.ts @@ -54,3 +54,27 @@ test("toggleMetagroupRemoval out-of-bounds", () => { expect(engine.toggleMetaGroupRemoval(0, 4)).toEqual([[0, []]]); expect(on_removal_updated).toBeCalledTimes(0); }); + +test("toggleMetagroupRemoval empty area", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + initial_state: { black: "aabb", white: "cacb" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + expect(engine.toggleMetaGroupRemoval(0, 1)).toEqual([ + [1, [{ x: 0, y: 1 }]], + [0, []], + ]); + expect(on_removal_updated).toBeCalledTimes(1); +}); From 5a0dff30ca2b3f3f9a38b890699e72d5fa779b86 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sat, 18 Nov 2023 14:29:51 -0800 Subject: [PATCH 06/14] add last official move tests. --- src/__tests__/GoEngine_moves.test.ts | 34 ++++++++++++++++++++++++++++ 1 file changed, 34 insertions(+) diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts index cf6db1ea..bf6fb842 100644 --- a/src/__tests__/GoEngine_moves.test.ts +++ b/src/__tests__/GoEngine_moves.test.ts @@ -37,3 +37,37 @@ test("cur_move", () => { expect(engine.cur_move.y).toBe(3); expect(on_cur_move).toBeCalledTimes(1); }); + +describe("setLastOfficialMove", () => { + test("cur_move on trunk", () => { + const engine = new GoEngine({}); + const on_last_official_move = jest.fn(); + engine.addListener("last_official_move", on_last_official_move); + + expect(engine.last_official_move).toBe(engine.cur_move); + + engine.place(10, 10, false, false, false, false, true /* isTrunkMove */); + + expect(on_last_official_move).not.toBeCalled(); + expect(engine.last_official_move).not.toBe(engine.cur_move); + + engine.setLastOfficialMove(); + + expect(engine.last_official_move).toBe(engine.cur_move); + expect(on_last_official_move).toBeCalledTimes(1); + + on_last_official_move.mockClear(); + + engine.setLastOfficialMove(); + // nothing changed, so no message is emitted + expect(on_last_official_move).toBeCalledTimes(0); + }); + + test("cur_move not on trunk is an error", () => { + const engine = new GoEngine({}); + + // isTrunkMove is false by default + engine.place(10, 10); + expect(() => engine.setLastOfficialMove()).toThrow("non-trunk move"); + }); +}); From e55065080a18530f0b8179427f010a8558041b82 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sun, 19 Nov 2023 07:39:14 -0800 Subject: [PATCH 07/14] Add a rules test --- src/__tests__/GoEngine_rules.test.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 src/__tests__/GoEngine_rules.test.ts diff --git a/src/__tests__/GoEngine_rules.test.ts b/src/__tests__/GoEngine_rules.test.ts new file mode 100644 index 00000000..5a34988a --- /dev/null +++ b/src/__tests__/GoEngine_rules.test.ts @@ -0,0 +1,12 @@ +import { GoEngine } from "../GoEngine"; + +test("Korean is almost the same as Japanese", () => { + // https://forums.online-go.com/t/just-a-brief-question/3564/10 + const korean_config = new GoEngine({ rules: "korean" }).config; + const japanese_config = new GoEngine({ rules: "japanese" }).config; + + delete korean_config.rules; + delete japanese_config.rules; + + expect(korean_config).toEqual(japanese_config); +}); From ea3accc464d3e83958d67f46dc0a4f5c103f84c4 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sun, 19 Nov 2023 07:45:57 -0800 Subject: [PATCH 08/14] Add config.moves test --- src/__tests__/GoEngine_moves.test.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts index bf6fb842..340ca519 100644 --- a/src/__tests__/GoEngine_moves.test.ts +++ b/src/__tests__/GoEngine_moves.test.ts @@ -71,3 +71,16 @@ describe("setLastOfficialMove", () => { expect(() => engine.setLastOfficialMove()).toThrow("non-trunk move"); }); }); + +test("config.moves", () => { + const moves = [ + { x: 0, y: 0 }, + { x: 1, y: 1 }, + ]; + const engine = new GoEngine({ width: 2, height: 2, moves: moves }); + + expect(engine.board).toEqual([ + [1, 0], + [0, 2], + ]); +}); From 7206a47d2cb2696d697cd0868f84e4491c93c40c Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Sun, 19 Nov 2023 07:50:50 -0800 Subject: [PATCH 09/14] Add config.moves tests. --- src/__tests__/GoEngine_moves.test.ts | 37 +++++++++++++++++++--------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts index 340ca519..0fc0a252 100644 --- a/src/__tests__/GoEngine_moves.test.ts +++ b/src/__tests__/GoEngine_moves.test.ts @@ -72,15 +72,30 @@ describe("setLastOfficialMove", () => { }); }); -test("config.moves", () => { - const moves = [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ]; - const engine = new GoEngine({ width: 2, height: 2, moves: moves }); - - expect(engine.board).toEqual([ - [1, 0], - [0, 2], - ]); +describe("config.moves", () => { + test("two good moves", () => { + const moves = [ + { x: 0, y: 0 }, + { x: 1, y: 1 }, + ]; + const engine = new GoEngine({ width: 2, height: 2, moves: moves }); + + expect(engine.board).toEqual([ + [1, 0], + [0, 2], + ]); + }); + + test("one illegal move", () => { + const moves = [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ]; + const engine = new GoEngine({ width: 2, height: 2, moves: moves }); + + expect(engine.board).toEqual([ + [0, 0], + [0, 0], + ]); + }); }); From 2c9f9e315aa0014fe12cb3178776191701751f2a Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Wed, 22 Nov 2023 22:51:18 -0500 Subject: [PATCH 10/14] Add move_tree tests. --- src/__tests__/GoEngine_moves.test.ts | 46 ++++++++++++++++++++++++++++ 1 file changed, 46 insertions(+) diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts index 0fc0a252..9e19eb1c 100644 --- a/src/__tests__/GoEngine_moves.test.ts +++ b/src/__tests__/GoEngine_moves.test.ts @@ -99,3 +99,49 @@ describe("config.moves", () => { ]); }); }); + +describe("config.move_tree", () => { + test("move_tree but not starting with pass", () => { + const move_tree = { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }; + + // Personally I don't think this should throw - it would be nice if we could just pass in + // a move_tree, but not moves and moves could be inferred by traversing trunk. + expect(() => new GoEngine({ width: 2, height: 2, move_tree })).toThrow("Node mismatch"); + }); + + test("move_tree with two trunk moves", () => { + const move_tree = { + x: -1, + y: -1, + trunk_next: { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }, + }; + + const engine = new GoEngine({ width: 2, height: 2, move_tree }); + + expect(engine.board).toEqual([ + [0, 0], + [0, 0], + ]); + + engine.jumpToOfficialMoveNumber(2); + + expect(engine.board).toEqual([ + [1, 0], + [0, 2], + ]); + }); +}); From 8032a0bae86c289200632b9ad63e5e69055a55d6 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Thu, 23 Nov 2023 11:22:14 -0500 Subject: [PATCH 11/14] Add tests for boardMatriciesAreTheSame and puzzle placement --- src/__tests__/GoEngine_board.test.ts | 0 src/__tests__/GoEngine_moves.test.ts | 27 +++++++++++++++++++++++++++ src/__tests__/GobanCanvas.test.ts | 19 +++++++++++++++++++ 3 files changed, 46 insertions(+) create mode 100644 src/__tests__/GoEngine_board.test.ts diff --git a/src/__tests__/GoEngine_board.test.ts b/src/__tests__/GoEngine_board.test.ts new file mode 100644 index 00000000..e69de29b diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts index 9e19eb1c..1d257d96 100644 --- a/src/__tests__/GoEngine_moves.test.ts +++ b/src/__tests__/GoEngine_moves.test.ts @@ -145,3 +145,30 @@ describe("config.move_tree", () => { ]); }); }); + +test("followPath", () => { + const engine = new GoEngine({ width: 4, height: 2 }); + engine.followPath(10, "aabacada"); + expect(engine.board).toEqual([ + [1, 2, 1, 2], + [0, 0, 0, 0], + ]); + expect(engine.cur_move.move_number).toBe(4); +}); + +test("deleteCurMove", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + }); + + engine.followPath(0, "aabacada"); + + expect(engine.cur_move.x).toBe(3); + expect(engine.cur_move.move_number).toBe(4); + + engine.deleteCurMove(); + + expect(engine.cur_move.x).toBe(2); + expect(engine.cur_move.move_number).toBe(3); +}); diff --git a/src/__tests__/GobanCanvas.test.ts b/src/__tests__/GobanCanvas.test.ts index e29814ba..320d7589 100644 --- a/src/__tests__/GobanCanvas.test.ts +++ b/src/__tests__/GobanCanvas.test.ts @@ -430,4 +430,23 @@ describe("onTap", () => { expect(goban.engine.estimateScore).toBeCalledTimes(0); expect(mock_score_estimate.handleClick).toBeCalledTimes(1); }); + + test("puzzle mode", () => { + const goban = new GobanCanvas( + basic3x3Config({ + mode: "puzzle", + getPuzzlePlacementSetting: () => ({ mode: "setup", color: 1 }), + }), + ); + goban.enableStonePlacement(); + const canvas = document.getElementById("board-canvas") as HTMLCanvasElement; + + simulateMouseClick(canvas, { x: 0, y: 0 }); + + expect(goban.engine.board).toEqual([ + [1, 0, 0], + [0, 0, 0], + [0, 0, 0], + ]); + }); }); From 09e2e245180cbf716468ffa928537d294ab289b7 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Thu, 23 Nov 2023 11:30:39 -0500 Subject: [PATCH 12/14] Consolidate GoEngine tests --- src/__tests__/GoEngine.test.ts | 641 +++++++++++++++++++++++++++++++++ 1 file changed, 641 insertions(+) create mode 100644 src/__tests__/GoEngine.test.ts diff --git a/src/__tests__/GoEngine.test.ts b/src/__tests__/GoEngine.test.ts new file mode 100644 index 00000000..3d61f255 --- /dev/null +++ b/src/__tests__/GoEngine.test.ts @@ -0,0 +1,641 @@ +import { GoEngine } from "../GoEngine"; +import { movesFromBoardState } from "../test_utils"; +import { GobanMoveError } from "../GobanError"; +import { JGOFIntersection } from "../JGOF"; +import { makeMatrix } from "../GoMath"; + +test("boardMatriciesAreTheSame", () => { + const engine = new GoEngine({}); + const a = [ + [1, 2], + [3, 4], + ]; + const b = [ + [1, 2], + [3, 4], + ]; + const c = [ + [1, 1], + [1, 1], + ]; + const d = [ + [1, 2, 5], + [3, 4, 6], + ]; + expect(engine.boardMatriciesAreTheSame(a, b)).toBe(true); + expect(engine.boardMatriciesAreTheSame(a, c)).toBe(false); + expect(engine.boardMatriciesAreTheSame(a, d)).toBe(false); +}); + +describe("computeScore", () => { + test("GoEngine defaults", () => { + const engine = new GoEngine({}); + expect(engine.computeScore()).toEqual({ + black: { + handicap: 0, + komi: 0, + prisoners: 0, + scoring_positions: "", + stones: 0, + territory: 0, + total: 0, + }, + white: { + handicap: 0, + komi: 6.5, + prisoners: 0, + scoring_positions: "", + stones: 0, + territory: 0, + total: 6.5, + }, + }); + }); + + test("GoEngine defaults", () => { + const engine = new GoEngine({}); + expect(engine.computeScore()).toEqual({ + black: { + handicap: 0, + komi: 0, + prisoners: 0, + scoring_positions: "", + stones: 0, + territory: 0, + total: 0, + }, + white: { + handicap: 0, + komi: 6.5, + prisoners: 0, + scoring_positions: "", + stones: 0, + territory: 0, + total: 6.5, + }, + }); + }); + + test("Japanese handicap", () => { + const engine = new GoEngine({ rules: "japanese", handicap: 4 }); + expect(engine.computeScore()).toEqual({ + black: expect.objectContaining({ + handicap: 0, + komi: 0, + territory: 357, + total: 357, + }), + white: expect.objectContaining({ + handicap: 4, + komi: 0.5, + territory: 0, + total: 0.5, + }), + }); + }); + + test("AGA handicap - white is given compensation ", () => { + const engine = new GoEngine({ rules: "aga", handicap: 4 }); + + // From the AGA Concise rules of Go: + // + // If the players have agreed to use area counting to score the game, + // White receives an additional point of compensation for each Black + // handicap stone after the first. + expect(engine.computeScore().white).toEqual( + expect.objectContaining({ + komi: 0.5, + handicap: 3, + total: 3.5, + }), + ); + }); + + test("Both sides have territory", () => { + const board = [ + [0, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 0], + ]; + const engine = new GoEngine({ width: 4, height: 4, moves: movesFromBoardState(board) }); + + expect(engine.computeScore()).toEqual({ + black: expect.objectContaining({ + scoring_positions: "aaabacad", + stones: 0, + territory: 4, + total: 4, + }), + white: expect.objectContaining({ + komi: 6.5, + scoring_positions: "dadbdcdd", + stones: 0, + territory: 4, + total: 10.5, + }), + }); + }); + + test("Both sides have territory (Chinese)", () => { + const board = [ + [0, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 0], + ]; + const engine = new GoEngine({ + width: 4, + height: 4, + moves: movesFromBoardState(board), + rules: "chinese", + }); + + expect(engine.computeScore()).toEqual({ + black: expect.objectContaining({ + scoring_positions: "aaabacadbabbbcbd", + stones: 4, + territory: 4, + total: 8, + }), + white: expect.objectContaining({ + komi: 7.5, + scoring_positions: "dadbdcddcacbcccd", + stones: 4, + territory: 4, + total: 15.5, + }), + }); + }); + + test("Removed stones", () => { + const board = [ + [2, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 0], + [0, 1, 2, 1], + ]; + const engine = new GoEngine({ + width: 4, + height: 4, + moves: movesFromBoardState(board), + rules: "chinese", + removed: "aadd", + }); + + expect(engine.computeScore()).toEqual({ + black: expect.objectContaining({ + prisoners: 0, + scoring_positions: "aaabacadbabbbcbd", + stones: 4, + territory: 4, + total: 8, + }), + white: expect.objectContaining({ + prisoners: 0, + komi: 7.5, + scoring_positions: "dadbdcddcacbcccd", + stones: 4, + territory: 4, + total: 15.5, + }), + }); + }); +}); + +describe("rules", () => { + test("Korean is almost the same as Japanese", () => { + // https://forums.online-go.com/t/just-a-brief-question/3564/10 + const korean_config = new GoEngine({ rules: "korean" }).config; + const japanese_config = new GoEngine({ rules: "japanese" }).config; + + delete korean_config.rules; + delete japanese_config.rules; + + expect(korean_config).toEqual(japanese_config); + }); +}); + +describe("GoEngine.place()", () => { + test("Basic test to make sure it's working", () => { + const engine = new GoEngine({}); + + engine.place(16, 3); + engine.place(3, 2); + engine.place(15, 16); + engine.place(14, 2); + engine.place(2, 15); + engine.place(16, 14); + engine.place(15, 4); + + expect(engine.board).toEqual([ + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], + [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], + ]); + }); + + test("stone on top of stone", () => { + const engine = new GoEngine({ width: 3, height: 3 }); + + engine.place(1, 1); + + expect(() => engine.place(1, 1)).toThrow( + new GobanMoveError(0, 1, "B2", "stone_already_placed_here"), + ); + }); + + test("capture", () => { + const engine = new GoEngine({ width: 2, height: 2 }); + + engine.place(0, 1); + engine.place(0, 0); + + expect(engine.place(1, 0)).toBe(1); + expect(engine.board).toEqual([ + [0, 1], + [1, 0], + ]); + }); + + test("ko", () => { + const engine = new GoEngine({ + width: 4, + height: 3, + initial_state: { + black: "baabbc", + white: "cadbcc", + }, + }); + + /* A B C D + * 3 . X O . + * 2 X . . O + * 1 . X O . + */ + + engine.place(2, 1); + engine.place(1, 1); + + expect(() => engine.place(2, 1, true)).toThrow( + new GobanMoveError(0, 2, "C2", "illegal_ko_move"), + ); + }); + + test("superko", () => { + const engine = new GoEngine({ + rules: "chinese", + initial_state: { + black: "dabbcbdbccadbdcd", + white: "baeaabebacdcecddaebecede", + }, + }); + + /* A B C D E F + * 19 . O . X O . + * 18 O X X X O . + * 17 O . X O O . + * 16 X X X O . . + * 15 O O O O . . + * 14 . . . . . . + */ + + engine.place(-1, -1); + engine.place(2, 0); + engine.place(0, 0); + engine.place(1, 0); + engine.place(-1, -1); + expect(() => engine.place(2, 0, true, true)).toThrow( + new GobanMoveError(0, 5, "C19", "illegal_board_repetition"), + ); + }); + + test("suicide", () => { + const engine = new GoEngine({ + width: 2, + height: 2, + initial_state: { + black: "", + white: "abba", + }, + }); + + /* A B + * 2 . O + * 1 O . + */ + + expect(() => engine.place(0, 0)).toThrow( + new GobanMoveError(0, 0, "A2", "move_is_suicidal"), + ); + }); + + test("Self capture allowed (ing)", () => { + const goban_callback = { + set: jest.fn(), + }; + + const engine = new GoEngine( + { + width: 2, + height: 2, + initial_state: { + black: "", + white: "abba", + }, + rules: "ing", + }, + goban_callback as any, + ); + + /* A B + * 2 . O + * 1 O . + */ + goban_callback.set.mockClear(); + expect(engine.place(0, 0)).toBe(1); + expect(goban_callback.set).toBeCalledWith(0, 0, 0); + }); + + test("removed_stones parameter", () => { + const engine = new GoEngine({ width: 2, height: 2 }); + + engine.place(0, 1); + engine.place(0, 0); + + const removed_stones: JGOFIntersection[] = []; + expect(engine.place(1, 0, false, false, false, false, false, removed_stones)).toBe(1); + expect(removed_stones).toEqual([{ x: 0, y: 0 }]); + }); +}); + +describe("moves", () => { + test("cur_review_move", () => { + const engine = new GoEngine({}); + const on_cur_review_move = jest.fn(); + engine.addListener("cur_review_move", on_cur_review_move); + + expect(engine.cur_review_move).toBeUndefined(); + + engine.place(0, 0); + + expect(engine.cur_review_move).not.toBe(engine.cur_move); + + engine.setAsCurrentReviewMove(); + + expect(engine.cur_review_move).toBe(engine.cur_move); + expect(on_cur_review_move).toBeCalledTimes(1); + + on_cur_review_move.mockClear(); + engine.setAsCurrentReviewMove(); + + // the signal shouldn't be emitted if the value doesn't actually change + expect(on_cur_review_move).not.toBeCalled(); + }); + + test("cur_move", () => { + const engine = new GoEngine({}); + const on_cur_move = jest.fn(); + engine.addListener("cur_move", on_cur_move); + + expect(engine.cur_move.x).toBe(-1); + expect(engine.cur_move.y).toBe(-1); + + engine.place(2, 3); + + expect(engine.cur_move.x).toBe(2); + expect(engine.cur_move.y).toBe(3); + expect(on_cur_move).toBeCalledTimes(1); + }); + + describe("setLastOfficialMove", () => { + test("cur_move on trunk", () => { + const engine = new GoEngine({}); + const on_last_official_move = jest.fn(); + engine.addListener("last_official_move", on_last_official_move); + + expect(engine.last_official_move).toBe(engine.cur_move); + + engine.place(10, 10, false, false, false, false, true /* isTrunkMove */); + + expect(on_last_official_move).not.toBeCalled(); + expect(engine.last_official_move).not.toBe(engine.cur_move); + + engine.setLastOfficialMove(); + + expect(engine.last_official_move).toBe(engine.cur_move); + expect(on_last_official_move).toBeCalledTimes(1); + + on_last_official_move.mockClear(); + + engine.setLastOfficialMove(); + // nothing changed, so no message is emitted + expect(on_last_official_move).toBeCalledTimes(0); + }); + + test("cur_move not on trunk is an error", () => { + const engine = new GoEngine({}); + + // isTrunkMove is false by default + engine.place(10, 10); + expect(() => engine.setLastOfficialMove()).toThrow("non-trunk move"); + }); + }); + + describe("config.moves", () => { + test("two good moves", () => { + const moves = [ + { x: 0, y: 0 }, + { x: 1, y: 1 }, + ]; + const engine = new GoEngine({ width: 2, height: 2, moves: moves }); + + expect(engine.board).toEqual([ + [1, 0], + [0, 2], + ]); + }); + + test("one illegal move", () => { + const moves = [ + { x: 0, y: 0 }, + { x: 0, y: 0 }, + ]; + const engine = new GoEngine({ width: 2, height: 2, moves: moves }); + + expect(engine.board).toEqual([ + [0, 0], + [0, 0], + ]); + }); + }); + + describe("config.move_tree", () => { + test("move_tree but not starting with pass", () => { + const move_tree = { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }; + + // Personally I don't think this should throw - it would be nice if we could just pass in + // a move_tree, but not moves and moves could be inferred by traversing trunk. + expect(() => new GoEngine({ width: 2, height: 2, move_tree })).toThrow("Node mismatch"); + }); + + test("move_tree with two trunk moves", () => { + const move_tree = { + x: -1, + y: -1, + trunk_next: { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }, + }; + + const engine = new GoEngine({ width: 2, height: 2, move_tree }); + + expect(engine.board).toEqual([ + [0, 0], + [0, 0], + ]); + + engine.jumpToOfficialMoveNumber(2); + + expect(engine.board).toEqual([ + [1, 0], + [0, 2], + ]); + }); + }); + + test("followPath", () => { + const engine = new GoEngine({ width: 4, height: 2 }); + engine.followPath(10, "aabacada"); + expect(engine.board).toEqual([ + [1, 2, 1, 2], + [0, 0, 0, 0], + ]); + expect(engine.cur_move.move_number).toBe(4); + }); + + test("deleteCurMove", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + }); + + engine.followPath(0, "aabacada"); + + expect(engine.cur_move.x).toBe(3); + expect(engine.cur_move.move_number).toBe(4); + + engine.deleteCurMove(); + + expect(engine.cur_move.x).toBe(2); + expect(engine.cur_move.move_number).toBe(3); + }); +}); + +describe("groups", () => { + test("toggleMetagroupRemoval", () => { + const engine = new GoEngine({ + width: 4, + height: 4, + initial_state: { black: "aabbdd", white: "cacbcccd" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + engine.toggleMetaGroupRemoval(0, 0); + + expect(on_removal_updated).toBeCalledTimes(1); + + expect(engine.removal).toEqual([ + [1, 0, 0, 0], + [0, 1, 0, 0], + [0, 0, 0, 0], + [0, 0, 0, 0], + ]); + + engine.toggleMetaGroupRemoval(0, 0); + + expect(engine.removal).toEqual(makeMatrix(4, 4)); + }); + + test("toggleMetagroupRemoval out-of-bounds", () => { + const engine = new GoEngine({ + width: 4, + height: 4, + initial_state: { black: "aabbdd", white: "cacbcccd" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + expect(engine.toggleMetaGroupRemoval(0, 4)).toEqual([[0, []]]); + expect(on_removal_updated).toBeCalledTimes(0); + }); + + test("toggleMetagroupRemoval empty area", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + initial_state: { black: "aabb", white: "cacb" }, + }); + + /* A B C D + * 4 x . o . + * 3 . x o . + * 2 . . o . + * 1 . . o x + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + + expect(engine.toggleMetaGroupRemoval(0, 1)).toEqual([ + [1, [{ x: 0, y: 1 }]], + [0, []], + ]); + expect(on_removal_updated).toBeCalledTimes(1); + }); +}); From be08d1bef19de0b59474b922ea84ce09872061ff Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Thu, 23 Nov 2023 11:51:43 -0500 Subject: [PATCH 13/14] Delete unused tests. --- src/__tests__/GoEngine.test.ts | 79 +++++++++ src/__tests__/GoEngine_board.test.ts | 0 src/__tests__/GoEngine_computeScore.test.ts | 187 -------------------- src/__tests__/GoEngine_groups.test.ts | 80 --------- src/__tests__/GoEngine_moves.test.ts | 174 ------------------ src/__tests__/GoEngine_place.test.ts | 172 ------------------ src/__tests__/GoEngine_rules.test.ts | 12 -- 7 files changed, 79 insertions(+), 625 deletions(-) delete mode 100644 src/__tests__/GoEngine_board.test.ts delete mode 100644 src/__tests__/GoEngine_computeScore.test.ts delete mode 100644 src/__tests__/GoEngine_groups.test.ts delete mode 100644 src/__tests__/GoEngine_moves.test.ts delete mode 100644 src/__tests__/GoEngine_place.test.ts delete mode 100644 src/__tests__/GoEngine_rules.test.ts diff --git a/src/__tests__/GoEngine.test.ts b/src/__tests__/GoEngine.test.ts index 3d61f255..f4990d2f 100644 --- a/src/__tests__/GoEngine.test.ts +++ b/src/__tests__/GoEngine.test.ts @@ -530,6 +530,48 @@ describe("moves", () => { [0, 2], ]); }); + + test("showNext", () => { + const move_tree = { + x: -1, + y: -1, + trunk_next: { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }, + }; + + const engine = new GoEngine({ width: 2, height: 2, move_tree }); + + expect(engine.cur_move.move_number).toBe(0); + expect(engine.showNext()).toBe(true); + expect(engine.cur_move.move_number).toBe(1); + }); + + test("showNextTrunk", () => { + const move_tree = { + x: -1, + y: -1, + trunk_next: { + x: 0, + y: 0, + trunk_next: { + x: 1, + y: 1, + }, + }, + }; + + const engine = new GoEngine({ width: 2, height: 2, move_tree }); + + expect(engine.cur_move.move_number).toBe(0); + expect(engine.showNextTrunk()).toBe(true); + expect(engine.cur_move.move_number).toBe(1); + }); }); test("followPath", () => { @@ -638,4 +680,41 @@ describe("groups", () => { ]); expect(on_removal_updated).toBeCalledTimes(1); }); + + test("clearRemoved", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + initial_state: { black: "aabb", white: "cacb" }, + removed: "aabb", + }); + + /* A B C D + * 2 x . o . + * 1 . x o . + */ + + const on_removal_updated = jest.fn(); + engine.addListener("stone-removal.updated", on_removal_updated); + engine.clearRemoved(); + + expect(on_removal_updated).toBeCalledTimes(1); + expect(engine.removal).toEqual(makeMatrix(4, 2)); + }); + + test("clearRemoved", () => { + const engine = new GoEngine({ + width: 4, + height: 2, + initial_state: { black: "aabb", white: "cacb" }, + removed: "aabb", + }); + + /* A B C D + * 2 x . o . + * 1 . x o . + */ + + expect(engine.getStoneRemovalString()).toBe("aabb"); + }); }); diff --git a/src/__tests__/GoEngine_board.test.ts b/src/__tests__/GoEngine_board.test.ts deleted file mode 100644 index e69de29b..00000000 diff --git a/src/__tests__/GoEngine_computeScore.test.ts b/src/__tests__/GoEngine_computeScore.test.ts deleted file mode 100644 index 2f36b29f..00000000 --- a/src/__tests__/GoEngine_computeScore.test.ts +++ /dev/null @@ -1,187 +0,0 @@ -/** - * @jest-environment jsdom - */ - -// ^^ jsdom environment is because getLocation() returns window.location.pathname -// Same about CLIENT. -// -// TODO: move this into a setup-jest.ts file - -(global as any).CLIENT = true; - -import { GoEngine } from "../GoEngine"; -import { movesFromBoardState } from "../test_utils"; - -test("GoEngine defaults", () => { - const engine = new GoEngine({}); - expect(engine.computeScore()).toEqual({ - black: { - handicap: 0, - komi: 0, - prisoners: 0, - scoring_positions: "", - stones: 0, - territory: 0, - total: 0, - }, - white: { - handicap: 0, - komi: 6.5, - prisoners: 0, - scoring_positions: "", - stones: 0, - territory: 0, - total: 6.5, - }, - }); -}); - -test("GoEngine defaults", () => { - const engine = new GoEngine({}); - expect(engine.computeScore()).toEqual({ - black: { - handicap: 0, - komi: 0, - prisoners: 0, - scoring_positions: "", - stones: 0, - territory: 0, - total: 0, - }, - white: { - handicap: 0, - komi: 6.5, - prisoners: 0, - scoring_positions: "", - stones: 0, - territory: 0, - total: 6.5, - }, - }); -}); - -test("Japanese handicap", () => { - const engine = new GoEngine({ rules: "japanese", handicap: 4 }); - expect(engine.computeScore()).toEqual({ - black: expect.objectContaining({ - handicap: 0, - komi: 0, - territory: 357, - total: 357, - }), - white: expect.objectContaining({ - handicap: 4, - komi: 0.5, - territory: 0, - total: 0.5, - }), - }); -}); - -test("AGA handicap - white is given compensation ", () => { - const engine = new GoEngine({ rules: "aga", handicap: 4 }); - - // From the AGA Concise rules of Go: - // - // If the players have agreed to use area counting to score the game, - // White receives an additional point of compensation for each Black - // handicap stone after the first. - expect(engine.computeScore().white).toEqual( - expect.objectContaining({ - komi: 0.5, - handicap: 3, - total: 3.5, - }), - ); -}); - -test("Both sides have territory", () => { - const board = [ - [0, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 0], - ]; - const engine = new GoEngine({ width: 4, height: 4, moves: movesFromBoardState(board) }); - - expect(engine.computeScore()).toEqual({ - black: expect.objectContaining({ - scoring_positions: "aaabacad", - stones: 0, - territory: 4, - total: 4, - }), - white: expect.objectContaining({ - komi: 6.5, - scoring_positions: "dadbdcdd", - stones: 0, - territory: 4, - total: 10.5, - }), - }); -}); - -test("Both sides have territory (Chinese)", () => { - const board = [ - [0, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 0], - ]; - const engine = new GoEngine({ - width: 4, - height: 4, - moves: movesFromBoardState(board), - rules: "chinese", - }); - - expect(engine.computeScore()).toEqual({ - black: expect.objectContaining({ - scoring_positions: "aaabacadbabbbcbd", - stones: 4, - territory: 4, - total: 8, - }), - white: expect.objectContaining({ - komi: 7.5, - scoring_positions: "dadbdcddcacbcccd", - stones: 4, - territory: 4, - total: 15.5, - }), - }); -}); - -test("Removed stones", () => { - const board = [ - [2, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 0], - [0, 1, 2, 1], - ]; - const engine = new GoEngine({ - width: 4, - height: 4, - moves: movesFromBoardState(board), - rules: "chinese", - removed: "aadd", - }); - - expect(engine.computeScore()).toEqual({ - black: expect.objectContaining({ - prisoners: 0, - scoring_positions: "aaabacadbabbbcbd", - stones: 4, - territory: 4, - total: 8, - }), - white: expect.objectContaining({ - prisoners: 0, - komi: 7.5, - scoring_positions: "dadbdcddcacbcccd", - stones: 4, - territory: 4, - total: 15.5, - }), - }); -}); diff --git a/src/__tests__/GoEngine_groups.test.ts b/src/__tests__/GoEngine_groups.test.ts deleted file mode 100644 index 5fff37ea..00000000 --- a/src/__tests__/GoEngine_groups.test.ts +++ /dev/null @@ -1,80 +0,0 @@ -import { GoEngine } from "../GoEngine"; -import { makeMatrix } from "../GoMath"; - -test("toggleMetagroupRemoval", () => { - const engine = new GoEngine({ - width: 4, - height: 4, - initial_state: { black: "aabbdd", white: "cacbcccd" }, - }); - - /* A B C D - * 4 x . o . - * 3 . x o . - * 2 . . o . - * 1 . . o x - */ - - const on_removal_updated = jest.fn(); - engine.addListener("stone-removal.updated", on_removal_updated); - - engine.toggleMetaGroupRemoval(0, 0); - - expect(on_removal_updated).toBeCalledTimes(1); - - expect(engine.removal).toEqual([ - [1, 0, 0, 0], - [0, 1, 0, 0], - [0, 0, 0, 0], - [0, 0, 0, 0], - ]); - - engine.toggleMetaGroupRemoval(0, 0); - - expect(engine.removal).toEqual(makeMatrix(4, 4)); -}); - -test("toggleMetagroupRemoval out-of-bounds", () => { - const engine = new GoEngine({ - width: 4, - height: 4, - initial_state: { black: "aabbdd", white: "cacbcccd" }, - }); - - /* A B C D - * 4 x . o . - * 3 . x o . - * 2 . . o . - * 1 . . o x - */ - - const on_removal_updated = jest.fn(); - engine.addListener("stone-removal.updated", on_removal_updated); - - expect(engine.toggleMetaGroupRemoval(0, 4)).toEqual([[0, []]]); - expect(on_removal_updated).toBeCalledTimes(0); -}); - -test("toggleMetagroupRemoval empty area", () => { - const engine = new GoEngine({ - width: 4, - height: 2, - initial_state: { black: "aabb", white: "cacb" }, - }); - - /* A B C D - * 4 x . o . - * 3 . x o . - * 2 . . o . - * 1 . . o x - */ - - const on_removal_updated = jest.fn(); - engine.addListener("stone-removal.updated", on_removal_updated); - - expect(engine.toggleMetaGroupRemoval(0, 1)).toEqual([ - [1, [{ x: 0, y: 1 }]], - [0, []], - ]); - expect(on_removal_updated).toBeCalledTimes(1); -}); diff --git a/src/__tests__/GoEngine_moves.test.ts b/src/__tests__/GoEngine_moves.test.ts deleted file mode 100644 index 1d257d96..00000000 --- a/src/__tests__/GoEngine_moves.test.ts +++ /dev/null @@ -1,174 +0,0 @@ -import { GoEngine } from "../GoEngine"; - -test("cur_review_move", () => { - const engine = new GoEngine({}); - const on_cur_review_move = jest.fn(); - engine.addListener("cur_review_move", on_cur_review_move); - - expect(engine.cur_review_move).toBeUndefined(); - - engine.place(0, 0); - - expect(engine.cur_review_move).not.toBe(engine.cur_move); - - engine.setAsCurrentReviewMove(); - - expect(engine.cur_review_move).toBe(engine.cur_move); - expect(on_cur_review_move).toBeCalledTimes(1); - - on_cur_review_move.mockClear(); - engine.setAsCurrentReviewMove(); - - // the signal shouldn't be emitted if the value doesn't actually change - expect(on_cur_review_move).not.toBeCalled(); -}); - -test("cur_move", () => { - const engine = new GoEngine({}); - const on_cur_move = jest.fn(); - engine.addListener("cur_move", on_cur_move); - - expect(engine.cur_move.x).toBe(-1); - expect(engine.cur_move.y).toBe(-1); - - engine.place(2, 3); - - expect(engine.cur_move.x).toBe(2); - expect(engine.cur_move.y).toBe(3); - expect(on_cur_move).toBeCalledTimes(1); -}); - -describe("setLastOfficialMove", () => { - test("cur_move on trunk", () => { - const engine = new GoEngine({}); - const on_last_official_move = jest.fn(); - engine.addListener("last_official_move", on_last_official_move); - - expect(engine.last_official_move).toBe(engine.cur_move); - - engine.place(10, 10, false, false, false, false, true /* isTrunkMove */); - - expect(on_last_official_move).not.toBeCalled(); - expect(engine.last_official_move).not.toBe(engine.cur_move); - - engine.setLastOfficialMove(); - - expect(engine.last_official_move).toBe(engine.cur_move); - expect(on_last_official_move).toBeCalledTimes(1); - - on_last_official_move.mockClear(); - - engine.setLastOfficialMove(); - // nothing changed, so no message is emitted - expect(on_last_official_move).toBeCalledTimes(0); - }); - - test("cur_move not on trunk is an error", () => { - const engine = new GoEngine({}); - - // isTrunkMove is false by default - engine.place(10, 10); - expect(() => engine.setLastOfficialMove()).toThrow("non-trunk move"); - }); -}); - -describe("config.moves", () => { - test("two good moves", () => { - const moves = [ - { x: 0, y: 0 }, - { x: 1, y: 1 }, - ]; - const engine = new GoEngine({ width: 2, height: 2, moves: moves }); - - expect(engine.board).toEqual([ - [1, 0], - [0, 2], - ]); - }); - - test("one illegal move", () => { - const moves = [ - { x: 0, y: 0 }, - { x: 0, y: 0 }, - ]; - const engine = new GoEngine({ width: 2, height: 2, moves: moves }); - - expect(engine.board).toEqual([ - [0, 0], - [0, 0], - ]); - }); -}); - -describe("config.move_tree", () => { - test("move_tree but not starting with pass", () => { - const move_tree = { - x: 0, - y: 0, - trunk_next: { - x: 1, - y: 1, - }, - }; - - // Personally I don't think this should throw - it would be nice if we could just pass in - // a move_tree, but not moves and moves could be inferred by traversing trunk. - expect(() => new GoEngine({ width: 2, height: 2, move_tree })).toThrow("Node mismatch"); - }); - - test("move_tree with two trunk moves", () => { - const move_tree = { - x: -1, - y: -1, - trunk_next: { - x: 0, - y: 0, - trunk_next: { - x: 1, - y: 1, - }, - }, - }; - - const engine = new GoEngine({ width: 2, height: 2, move_tree }); - - expect(engine.board).toEqual([ - [0, 0], - [0, 0], - ]); - - engine.jumpToOfficialMoveNumber(2); - - expect(engine.board).toEqual([ - [1, 0], - [0, 2], - ]); - }); -}); - -test("followPath", () => { - const engine = new GoEngine({ width: 4, height: 2 }); - engine.followPath(10, "aabacada"); - expect(engine.board).toEqual([ - [1, 2, 1, 2], - [0, 0, 0, 0], - ]); - expect(engine.cur_move.move_number).toBe(4); -}); - -test("deleteCurMove", () => { - const engine = new GoEngine({ - width: 4, - height: 2, - }); - - engine.followPath(0, "aabacada"); - - expect(engine.cur_move.x).toBe(3); - expect(engine.cur_move.move_number).toBe(4); - - engine.deleteCurMove(); - - expect(engine.cur_move.x).toBe(2); - expect(engine.cur_move.move_number).toBe(3); -}); diff --git a/src/__tests__/GoEngine_place.test.ts b/src/__tests__/GoEngine_place.test.ts deleted file mode 100644 index eaf79c5f..00000000 --- a/src/__tests__/GoEngine_place.test.ts +++ /dev/null @@ -1,172 +0,0 @@ -import { GoEngine } from "../GoEngine"; -import { GobanMoveError } from "../GobanError"; -import { JGOFIntersection } from "../JGOF"; - -describe("GoEngine.place()", () => { - test("Basic test to make sure it's working", () => { - const engine = new GoEngine({}); - - engine.place(16, 3); - engine.place(3, 2); - engine.place(15, 16); - engine.place(14, 2); - engine.place(2, 15); - engine.place(16, 14); - engine.place(15, 4); - - expect(engine.board).toEqual([ - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 2, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 2, 0, 0], - [0, 0, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 1, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0], - ]); - }); - - test("stone on top of stone", () => { - const engine = new GoEngine({ width: 3, height: 3 }); - - engine.place(1, 1); - - expect(() => engine.place(1, 1)).toThrow( - new GobanMoveError(0, 1, "B2", "stone_already_placed_here"), - ); - }); - - test("capture", () => { - const engine = new GoEngine({ width: 2, height: 2 }); - - engine.place(0, 1); - engine.place(0, 0); - - expect(engine.place(1, 0)).toBe(1); - expect(engine.board).toEqual([ - [0, 1], - [1, 0], - ]); - }); - - test("ko", () => { - const engine = new GoEngine({ - width: 4, - height: 3, - initial_state: { - black: "baabbc", - white: "cadbcc", - }, - }); - - /* A B C D - * 3 . X O . - * 2 X . . O - * 1 . X O . - */ - - engine.place(2, 1); - engine.place(1, 1); - - expect(() => engine.place(2, 1, true)).toThrow( - new GobanMoveError(0, 2, "C2", "illegal_ko_move"), - ); - }); - - test("superko", () => { - const engine = new GoEngine({ - rules: "chinese", - initial_state: { - black: "dabbcbdbccadbdcd", - white: "baeaabebacdcecddaebecede", - }, - }); - - /* A B C D E F - * 19 . O . X O . - * 18 O X X X O . - * 17 O . X O O . - * 16 X X X O . . - * 15 O O O O . . - * 14 . . . . . . - */ - - engine.place(-1, -1); - engine.place(2, 0); - engine.place(0, 0); - engine.place(1, 0); - engine.place(-1, -1); - expect(() => engine.place(2, 0, true, true)).toThrow( - new GobanMoveError(0, 5, "C19", "illegal_board_repetition"), - ); - }); - - test("suicide", () => { - const engine = new GoEngine({ - width: 2, - height: 2, - initial_state: { - black: "", - white: "abba", - }, - }); - - /* A B - * 2 . O - * 1 O . - */ - - expect(() => engine.place(0, 0)).toThrow( - new GobanMoveError(0, 0, "A2", "move_is_suicidal"), - ); - }); - - test("Self capture allowed (ing)", () => { - const goban_callback = { - set: jest.fn(), - }; - - const engine = new GoEngine( - { - width: 2, - height: 2, - initial_state: { - black: "", - white: "abba", - }, - rules: "ing", - }, - goban_callback as any, - ); - - /* A B - * 2 . O - * 1 O . - */ - goban_callback.set.mockClear(); - expect(engine.place(0, 0)).toBe(1); - expect(goban_callback.set).toBeCalledWith(0, 0, 0); - }); - - test("removed_stones parameter", () => { - const engine = new GoEngine({ width: 2, height: 2 }); - - engine.place(0, 1); - engine.place(0, 0); - - const removed_stones: JGOFIntersection[] = []; - expect(engine.place(1, 0, false, false, false, false, false, removed_stones)).toBe(1); - expect(removed_stones).toEqual([{ x: 0, y: 0 }]); - }); -}); diff --git a/src/__tests__/GoEngine_rules.test.ts b/src/__tests__/GoEngine_rules.test.ts deleted file mode 100644 index 5a34988a..00000000 --- a/src/__tests__/GoEngine_rules.test.ts +++ /dev/null @@ -1,12 +0,0 @@ -import { GoEngine } from "../GoEngine"; - -test("Korean is almost the same as Japanese", () => { - // https://forums.online-go.com/t/just-a-brief-question/3564/10 - const korean_config = new GoEngine({ rules: "korean" }).config; - const japanese_config = new GoEngine({ rules: "japanese" }).config; - - delete korean_config.rules; - delete japanese_config.rules; - - expect(korean_config).toEqual(japanese_config); -}); From 1c3a4fc66e7c2b72d093112cc6dd9f1814060783 Mon Sep 17 00:00:00 2001 From: "Benjamin P. Jones" Date: Thu, 23 Nov 2023 14:58:10 -0500 Subject: [PATCH 14/14] Clean up test logs. --- src/__tests__/GoEngine.test.ts | 3 +++ src/__tests__/GoEngine_sgf.test.ts | 9 ++++++++- src/__tests__/GobanCanvas.test.ts | 7 +++++++ 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/src/__tests__/GoEngine.test.ts b/src/__tests__/GoEngine.test.ts index f4990d2f..07ad85e7 100644 --- a/src/__tests__/GoEngine.test.ts +++ b/src/__tests__/GoEngine.test.ts @@ -477,12 +477,15 @@ describe("moves", () => { { x: 0, y: 0 }, { x: 0, y: 0 }, ]; + // Placement errors are logged, not thrown + const log_spy = jest.spyOn(console, "log").mockImplementation(() => {}); const engine = new GoEngine({ width: 2, height: 2, moves: moves }); expect(engine.board).toEqual([ [0, 0], [0, 0], ]); + expect(log_spy.mock.calls[0][0].error).toBe("Error placing black at A2 (0, 0)"); }); }); diff --git a/src/__tests__/GoEngine_sgf.test.ts b/src/__tests__/GoEngine_sgf.test.ts index 78e06b55..573629f2 100644 --- a/src/__tests__/GoEngine_sgf.test.ts +++ b/src/__tests__/GoEngine_sgf.test.ts @@ -17,6 +17,7 @@ type SGFTestcase = { moves: string; id: string; size: number; + num_errors?: number; }; const SGF_TEST_CASES: Array = [ @@ -59,6 +60,7 @@ const SGF_TEST_CASES: Array = [ moves: ";B[aa];W[aa]", id: "invalid move - stone on top of stone", size: 3, + num_errors: 1, }, ]; @@ -71,8 +73,10 @@ function rmNewlines(txt: string): string { */ test.each(SGF_TEST_CASES)( "sgf -> parseSGF() -> toSGF() roundtrip (moves only)", - ({ template, moves, id, size }) => { + ({ template, moves, size, num_errors }) => { const sgf = template.replace(/_MOVES_/, moves); + // Placement errors are logged, not thrown + const log_spy = jest.spyOn(console, "log").mockImplementation(() => {}); const goban = new TestGoban({ original_sgf: sgf, removed: "" }); // by default, `edited = true` when `original_sgf` is used, which causes // the moves to be serialized as setup SGF props `AB` & `AW`. @@ -82,6 +86,9 @@ test.each(SGF_TEST_CASES)( const moves_gen = goban.engine.move_tree.toSGF(); expect(rmNewlines(moves_gen)).toBe(rmNewlines(moves)); expect(goban.engine.move_tree.size()).toBe(size); + if (num_errors) { + expect(log_spy).toBeCalledTimes(num_errors); + } }, ); diff --git a/src/__tests__/GobanCanvas.test.ts b/src/__tests__/GobanCanvas.test.ts index 320d7589..5ec65ae8 100644 --- a/src/__tests__/GobanCanvas.test.ts +++ b/src/__tests__/GobanCanvas.test.ts @@ -214,6 +214,8 @@ describe("onTap", () => { const goban = new GobanCanvas(basic3x3Config()); const canvas = document.getElementById("board-canvas") as HTMLCanvasElement; + const log_spy = jest.spyOn(console, "info").mockImplementation(() => {}); + await socket_server.connected; goban.enableStonePlacement(); @@ -232,6 +234,11 @@ describe("onTap", () => { [0, 0, 0], [0, 0, 0], ]); + expect(log_spy).toBeCalledWith( + "Submit button pressed only ", + 40, + "ms after stone was placed, presuming bad click", + ); jest.useRealTimers(); });