create endgame play section

This commit is contained in:
2026-08-11 21:52:33 +02:00
parent ca51546be0
commit 88bb016bac
13 changed files with 594 additions and 26 deletions
+76
View File
@@ -0,0 +1,76 @@
defmodule Chesstrainer.EndgamesTest do
use Chesstrainer.DataCase
alias Chesstrainer.Endgames
alias Chesstrainer.Endgames.Endgame
describe "create_endgame/1 FEN validation" do
test "accepts a standard valid FEN" do
attrs = %{
fen: "8/8/3k4/8/8/3K1R2/8/8 w - - 0 1",
color: :white,
key: "KR v K",
result: :win,
rating: 1500
}
assert {:ok, %Endgame{}} = Endgames.create_endgame(attrs)
end
test "rejects a FEN with fullmove number zero" do
attrs = %{
fen: "6k1/5p2/6p1/8/7p/8/6PP/6K1 b - - 0 0",
color: :black,
key: "KPPP v KPP",
result: :win,
rating: 1500
}
assert {:error, changeset} = Endgames.create_endgame(attrs)
assert [msg] = errors_on(changeset).fen
assert msg =~ "fullmove number"
end
test "rejects a FEN with a zero digit in a rank" do
attrs = %{
fen: "4k3/8/8/8/8/8/8/0K7 w - - 0 1",
color: :white,
key: "KQ v K",
result: :win,
rating: 1500
}
assert {:error, changeset} = Endgames.create_endgame(attrs)
assert [msg] = errors_on(changeset).fen
assert msg =~ "board must be 8 ranks"
end
test "rejects a FEN with the wrong number of fields" do
attrs = %{
fen: "8/8/3k4/8/8/3K1R2/8/8",
color: :white,
key: "KQ v K",
result: :win,
rating: 1500
}
assert {:error, changeset} = Endgames.create_endgame(attrs)
assert [msg] = errors_on(changeset).fen
assert msg =~ "6 space-separated"
end
test "rejects a FEN with an unknown active color" do
attrs = %{
fen: "8/8/3k4/8/8/3K1R2/8/8 x - - 0 1",
color: :white,
key: "KQ v K",
result: :win,
rating: 1500
}
assert {:error, changeset} = Endgames.create_endgame(attrs)
assert [msg] = errors_on(changeset).fen
assert msg =~ "active color"
end
end
end
@@ -0,0 +1,115 @@
defmodule ChesstrainerWeb.EndgameLive.PlayTest do
use ChesstrainerWeb.ConnCase
import Phoenix.LiveViewTest
import Chesstrainer.EndgamesFixtures
defp assert_init_board(view, expected_fen, expected_orientation, expected_player_color) do
%{proxy: {ref, _topic, _}} = view
assert_receive {^ref, {:push_event, "init_board", payload}}, 100
assert payload.fen == expected_fen
assert payload.orientation == expected_orientation
assert payload.player_color == expected_player_color
end
describe "mount" do
test "renders the chessboard hook div and pushes init_board", %{conn: conn} do
endgame = endgame_fixture(color: :white)
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
assert has_element?(view, "#endgame-board")
assert_init_board(view, endgame.fen, "white", "white")
end
test "pushes black orientation when the endgame color is black", %{conn: conn} do
endgame = endgame_fixture(color: :black)
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
assert_init_board(view, endgame.fen, "black", "black")
end
test "shows endgame metadata in the header", %{conn: conn} do
endgame = endgame_fixture(key: "KQ v K", color: :white, result: :win, rating: 1800)
{:ok, _view, html} = live(conn, ~p"/endgames/#{endgame}/play")
assert html =~ "KQ v K"
assert html =~ "white to move"
assert html =~ "win"
assert html =~ "1800"
end
test "raises when the endgame does not exist", %{conn: conn} do
assert_raise Ecto.NoResultsError, fn ->
live(conn, ~p"/endgames/00000000-0000-0000-0000-000000000000/play")
end
end
end
describe "chess_fen_invalid" do
test "shows a flash when the chessground hook reports an unparseable FEN", %{conn: conn} do
endgame = endgame_fixture()
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
view
|> render_hook("chess_fen_invalid", %{
"fen" => endgame.fen,
"message" => "Invalid FEN: move number must be a positive integer"
})
html = render(view)
assert html =~ "Endgame FEN is invalid"
assert html =~ "move number must be a positive integer"
end
end
describe "endgame_move_played" do
test "appends the SAN move and updates last_san / last_from_to", %{conn: conn} do
endgame = endgame_fixture()
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
view
|> render_hook("endgame_move_played", %{
"from" => "d1",
"to" => "d7",
"san" => "Qd7+",
"fen" => endgame.fen,
"endgame_id" => endgame.id
})
html = render(view)
assert html =~ "Qd7+"
assert html =~ "d1 ➔ d7"
end
end
describe "reset" do
test "clears move list and re-pushes init_board", %{conn: conn} do
endgame = endgame_fixture()
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
view
|> render_hook("endgame_move_played", %{
"from" => "d1",
"to" => "d7",
"san" => "Qd7+",
"fen" => endgame.fen,
"endgame_id" => endgame.id
})
assert render(view) =~ "Qd7+"
view |> element("button", "Reset") |> render_click()
assert render(view) =~ "No Moves"
assert_init_board(view, endgame.fen, "white", "white")
end
end
end
@@ -0,0 +1,28 @@
defmodule ChesstrainerWeb.EndgameLive.PlayerIndexTest do
use ChesstrainerWeb.ConnCase
import Phoenix.LiveViewTest
import Chesstrainer.EndgamesFixtures
test "lists endgames with a play link per row", %{conn: conn} do
endgame = endgame_fixture()
other = endgame_fixture()
{:ok, _view, html} = live(conn, ~p"/endgames")
assert html =~ "Endgames"
assert html =~ endgame.key
assert html =~ other.key
assert html =~ ~p"/endgames/#{endgame}/play"
assert html =~ ~p"/endgames/#{other}/play"
end
test "navigating to a play page renders the board", %{conn: conn} do
endgame = endgame_fixture()
{:ok, view, _html} = live(conn, ~p"/endgames/#{endgame}/play")
assert has_element?(view, "#endgame-board")
assert has_element?(view, ~s{a[href="/endgames"]})
end
end
+44
View File
@@ -0,0 +1,44 @@
defmodule Chesstrainer.EndgamesFixtures do
@moduledoc """
This module defines test helpers for creating
entities via the `Chesstrainer.Endgames` context.
"""
@doc """
Generate a unique endgame FEN.
Endgames have a unique constraint on :fen, so tests need
distinct FENs when inserting more than one. Each call
shifts the white king's file to avoid FEN collisions.
"""
def unique_endgame_fen do
# White king on rank 1 in columns b..g, leaving 1-6 empty squares
# before and 6-1 after. Both before_king and after_king are valid
# single digits (1-8), and the rank sums to 8.
offset = System.unique_integer([:positive]) |> rem(6)
before_king = offset + 1
after_king = 7 - before_king
"4k3/8/8/8/8/8/8/#{before_king}K#{after_king} w - - 0 1"
end
@doc """
Generate an endgame.
A simple K v K position with a unique FEN, color,
key, rating, and result.
"""
def endgame_fixture(attrs \\ %{}) do
{:ok, endgame} =
attrs
|> Enum.into(%{
fen: unique_endgame_fen(),
key: "KQ v K",
color: :white,
result: :win,
rating: 1500
})
|> Chesstrainer.Endgames.create_endgame()
endgame
end
end