Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions internal/client/screens/localmoveinput_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -167,6 +167,38 @@ func TestLocalMoveInput_HandleMsg_MouseClick_ConvertsToSAN(t *testing.T) {
}
}

func TestLocalMoveInput_HandleMsg_MouseClick_AccountsForBoardTopY(t *testing.T) {
// Screens that draw a title above the board (puzzle, replay-branch) render
// the board starting at screen row 2. The click mapping must subtract that
// offset, otherwise every click lands one or more ranks too high.
game := chess.NewGame()
board := render.NewBoard(game.Position(), false)
li := NewLocalMoveInput(false)
li.SetBoardOriginY(2)

// e2 is at y=18 when the board top is row 0; with a top offset of 2 the same
// square is drawn at y=20.
msg := tea.MouseMsg{X: 26, Y: 20, Action: tea.MouseActionPress, Button: tea.MouseButtonLeft}
if _, handled, _ := li.HandleMsg(msg, board, game); !handled {
t.Fatalf("expected handled=true for click on piece")
}
if !li.hasSelected {
t.Fatalf("expected hasSelected=true after clicking own piece at the offset board")
}
if li.selectedSq != chess.E2 {
t.Fatalf("expected selectedSq=E2, got %v", li.selectedSq)
}

// Regression guard: the un-offset coordinate (y=18) must no longer resolve to
// E2 when the board is shifted down — it maps two screen rows higher.
li2 := NewLocalMoveInput(false)
li2.SetBoardOriginY(2)
stale := tea.MouseMsg{X: 26, Y: 18, Action: tea.MouseActionPress, Button: tea.MouseButtonLeft}
if _, _, _ = li2.HandleMsg(stale, board, game); li2.hasSelected && li2.selectedSq == chess.E2 {
t.Fatalf("y=18 should not select E2 when board top is offset by 2")
}
}

func TestLocalMoveInput_HandleMsg_FlippedBoard_MapsSquaresCorrectly(t *testing.T) {
game := chess.NewGame()
board := render.NewBoard(game.Position(), true)
Expand Down
6 changes: 4 additions & 2 deletions internal/client/screens/puzzle.go
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package screens

Check failure on line 1 in internal/client/screens/puzzle.go

View workflow job for this annotation

GitHub Actions / coverage

File test coverage below threshold

File test coverage below threshold: coverage: 51.1% (159/311); threshold: 60%

import (
"fmt"
Expand Down Expand Up @@ -516,8 +516,10 @@
var sb strings.Builder
sb.WriteString(puzzleTitleStyle.Render("Puzzle Mode"))
sb.WriteString("\n\n")
// Title on line 0, blank on line 1, so the board's top cell is on line 2.
m.input.SetBoardOriginY(2)
// Title on line 0, blank on line 1, board starts at line 2.
if m.input != nil {
m.input.SetBoardOriginY(2)
}
boardView := m.board.View()
right := m.rightPanel()
sb.WriteString(lipgloss.JoinHorizontal(lipgloss.Top, boardView, " ", right))
Expand Down
2 changes: 2 additions & 0 deletions internal/client/screens/replay.go
Original file line number Diff line number Diff line change
Expand Up @@ -426,6 +426,8 @@ func (m *ReplayModel) View() string {
if m.atBranchTip() {
inputY := 2 + m.board.CellRows()*8 + 2
m.input.SetPromoPopupY(inputY)
// Title on line 0, blank on line 1, board starts at line 2.
m.input.SetBoardOriginY(2)
sb.WriteString(m.input.View(m.board, m.branchGame))
} else {
sb.WriteString(replayStepStyle.Render(" (navigate to tip to play)"))
Expand Down
Loading