Compare commits
1 Commits
Author | SHA1 | Date | |
---|---|---|---|
75eff6c5ad |
18
game/chat.go
18
game/chat.go
@ -25,12 +25,14 @@ type Chat struct {
|
||||
cursorPos int
|
||||
scrollOffset int
|
||||
userData interface{}
|
||||
input InputHandler
|
||||
}
|
||||
|
||||
func NewChat() *Chat {
|
||||
return &Chat{
|
||||
messages: make([]types.ChatMessage, 0, maxMessages),
|
||||
inputBuffer: make([]rune, 0, runeLimit),
|
||||
input: &RaylibInput{},
|
||||
}
|
||||
}
|
||||
|
||||
@ -153,23 +155,23 @@ func (c *Chat) Update() (string, bool) {
|
||||
c.scrollOffset = clamp(c.scrollOffset-int(wheelMove), 0, maxScroll)
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(rl.KeyT) {
|
||||
if c.input.IsKeyPressed(rl.KeyT) {
|
||||
c.isTyping = true
|
||||
return "", false
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
key := rl.GetCharPressed()
|
||||
key := c.input.GetCharPressed()
|
||||
for key > 0 {
|
||||
if len(c.inputBuffer) < runeLimit {
|
||||
c.inputBuffer = append(c.inputBuffer[:c.cursorPos], append([]rune{key}, c.inputBuffer[c.cursorPos:]...)...)
|
||||
c.cursorPos++
|
||||
}
|
||||
key = rl.GetCharPressed()
|
||||
key = c.input.GetCharPressed()
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(rl.KeyEnter) || rl.IsKeyPressed(rl.KeyKpEnter) {
|
||||
if c.input.IsKeyPressed(rl.KeyEnter) || c.input.IsKeyPressed(rl.KeyKpEnter) {
|
||||
if len(c.inputBuffer) > 0 {
|
||||
message := string(c.inputBuffer)
|
||||
c.inputBuffer = c.inputBuffer[:0]
|
||||
@ -180,21 +182,21 @@ func (c *Chat) Update() (string, bool) {
|
||||
c.isTyping = false
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(rl.KeyEscape) && c.isTyping {
|
||||
if c.input.IsKeyPressed(rl.KeyEscape) && c.isTyping {
|
||||
c.inputBuffer = c.inputBuffer[:0]
|
||||
c.cursorPos = 0
|
||||
c.isTyping = false
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(rl.KeyBackspace) && c.cursorPos > 0 {
|
||||
if c.input.IsKeyPressed(rl.KeyBackspace) && c.cursorPos > 0 {
|
||||
c.inputBuffer = append(c.inputBuffer[:c.cursorPos-1], c.inputBuffer[c.cursorPos:]...)
|
||||
c.cursorPos--
|
||||
}
|
||||
|
||||
if rl.IsKeyPressed(rl.KeyLeft) && c.cursorPos > 0 {
|
||||
if c.input.IsKeyPressed(rl.KeyLeft) && c.cursorPos > 0 {
|
||||
c.cursorPos--
|
||||
}
|
||||
if rl.IsKeyPressed(rl.KeyRight) && c.cursorPos < len(c.inputBuffer) {
|
||||
if c.input.IsKeyPressed(rl.KeyRight) && c.cursorPos < len(c.inputBuffer) {
|
||||
c.cursorPos++
|
||||
}
|
||||
|
||||
|
106
game/chat_test.go
Normal file
106
game/chat_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/testutils"
|
||||
"gitea.boner.be/bdnugget/goonscape/types"
|
||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestChat_AddMessage(t *testing.T) {
|
||||
chat := NewChat()
|
||||
|
||||
// Test adding single message
|
||||
chat.AddMessage(1, "Hello")
|
||||
assert.Equal(t, 1, len(chat.messages))
|
||||
assert.Equal(t, int32(1), chat.messages[0].PlayerID)
|
||||
assert.Equal(t, "Hello", chat.messages[0].Content)
|
||||
|
||||
// Test message limit
|
||||
for i := 0; i < maxMessages+10; i++ {
|
||||
chat.AddMessage(1, "spam")
|
||||
}
|
||||
assert.Equal(t, maxMessages, len(chat.messages))
|
||||
assert.Equal(t, "spam", chat.messages[len(chat.messages)-1].Content)
|
||||
}
|
||||
|
||||
func TestChat_HandleServerMessages(t *testing.T) {
|
||||
chat := NewChat()
|
||||
mockGame := &Game{
|
||||
Player: &types.Player{ID: 1},
|
||||
OtherPlayers: map[int32]*types.Player{
|
||||
2: {ID: 2},
|
||||
},
|
||||
}
|
||||
chat.userData = mockGame
|
||||
|
||||
messages := []*pb.ChatMessage{
|
||||
{
|
||||
PlayerId: 1,
|
||||
Username: "player1",
|
||||
Content: "test1",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
},
|
||||
{
|
||||
PlayerId: 2,
|
||||
Username: "player2",
|
||||
Content: "test2",
|
||||
Timestamp: time.Now().UnixNano(),
|
||||
},
|
||||
}
|
||||
|
||||
chat.HandleServerMessages(messages)
|
||||
assert.Equal(t, 2, len(chat.messages))
|
||||
assert.Equal(t, "test1", chat.messages[0].Content)
|
||||
assert.Equal(t, "test2", chat.messages[1].Content)
|
||||
|
||||
// Test duplicate message prevention
|
||||
chat.HandleServerMessages(messages)
|
||||
assert.Equal(t, 2, len(chat.messages))
|
||||
}
|
||||
|
||||
func TestChat_Update(t *testing.T) {
|
||||
t.Parallel()
|
||||
done := make(chan bool)
|
||||
go func() {
|
||||
game, cleanup := setupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
chat := game.Chat
|
||||
|
||||
// Test starting chat
|
||||
testutils.SimulateKeyPress(rl.KeyT)
|
||||
msg, sent := chat.Update()
|
||||
assert.True(t, chat.isTyping)
|
||||
assert.False(t, sent)
|
||||
assert.Empty(t, msg)
|
||||
|
||||
// Test typing message
|
||||
testutils.SimulateCharInput('h')
|
||||
msg, sent = chat.Update()
|
||||
testutils.SimulateCharInput('i')
|
||||
msg, sent = chat.Update()
|
||||
assert.Equal(t, 2, len(chat.inputBuffer))
|
||||
assert.False(t, sent)
|
||||
assert.Empty(t, msg)
|
||||
|
||||
// Test sending message
|
||||
testutils.SimulateKeyPress(rl.KeyEnter)
|
||||
msg, sent = chat.Update()
|
||||
assert.True(t, sent)
|
||||
assert.Equal(t, "hi", msg)
|
||||
assert.False(t, chat.isTyping)
|
||||
done <- true
|
||||
}()
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
// Test completed successfully
|
||||
case <-time.After(5 * time.Second):
|
||||
t.Fatal("Test timed out")
|
||||
}
|
||||
}
|
@ -22,6 +22,7 @@ type Game struct {
|
||||
QuitChan chan struct{} // Channel to signal shutdown
|
||||
loginScreen *LoginScreen
|
||||
isLoggedIn bool
|
||||
input InputHandler
|
||||
}
|
||||
|
||||
func New() *Game {
|
||||
@ -38,6 +39,7 @@ func New() *Game {
|
||||
Chat: NewChat(),
|
||||
QuitChan: make(chan struct{}),
|
||||
loginScreen: NewLoginScreen(),
|
||||
input: &RaylibInput{},
|
||||
}
|
||||
game.Chat.userData = game
|
||||
return game
|
||||
@ -86,7 +88,7 @@ func (g *Game) Update(deltaTime float32) {
|
||||
}
|
||||
|
||||
// Handle ESC for menu
|
||||
if rl.IsKeyPressed(rl.KeyEscape) {
|
||||
if g.input.IsKeyPressed(rl.KeyEscape) {
|
||||
g.MenuOpen = !g.MenuOpen
|
||||
return
|
||||
}
|
||||
@ -325,13 +327,13 @@ func (g *Game) DrawMenu() {
|
||||
}
|
||||
|
||||
// Check mouse hover
|
||||
mousePoint := rl.GetMousePosition()
|
||||
mousePoint := g.input.GetMousePosition()
|
||||
mouseHover := rl.CheckCollisionPointRec(mousePoint, buttonRect)
|
||||
|
||||
// Draw button
|
||||
if mouseHover {
|
||||
rl.DrawRectangleRec(buttonRect, rl.ColorAlpha(rl.White, 0.3))
|
||||
if rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||||
if g.input.IsMouseButtonPressed(toInt32(rl.MouseLeftButton)) {
|
||||
switch item {
|
||||
case "Resume":
|
||||
g.MenuOpen = false
|
||||
|
106
game/game_test.go
Normal file
106
game/game_test.go
Normal file
@ -0,0 +1,106 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/testutils"
|
||||
"gitea.boner.be/bdnugget/goonscape/types"
|
||||
pb "gitea.boner.be/bdnugget/goonserver/actions"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestGame_HandleInput(t *testing.T) {
|
||||
game := New()
|
||||
game.Player = &types.Player{
|
||||
ID: 1,
|
||||
Speed: 50.0,
|
||||
}
|
||||
|
||||
// Test valid click
|
||||
simulateMouseRay(rl.Ray{
|
||||
Position: rl.Vector3{X: 0, Y: 10, Z: 0},
|
||||
Direction: rl.Vector3{X: 0, Y: -1, Z: 0},
|
||||
})
|
||||
simulateMouseButton(toInt32(rl.MouseLeftButton), true)
|
||||
game.HandleInput()
|
||||
assert.NotEmpty(t, game.Player.TargetPath)
|
||||
|
||||
// Test invalid click (outside map)
|
||||
simulateMouseRay(rl.Ray{
|
||||
Position: rl.Vector3{X: 1000, Y: 10, Z: 1000},
|
||||
Direction: rl.Vector3{X: 0, Y: -1, Z: 0},
|
||||
})
|
||||
simulateMouseButton(toInt32(rl.MouseLeftButton), true)
|
||||
game.HandleInput()
|
||||
assert.Empty(t, game.Player.TargetPath)
|
||||
}
|
||||
|
||||
func TestGame_UpdateCamera(t *testing.T) {
|
||||
game := New()
|
||||
|
||||
// Test zoom limits
|
||||
testutils.SimulateMouseWheel(1.0) // Zoom in
|
||||
testutils.SimulateMouseWheel(1.0)
|
||||
assert.GreaterOrEqual(t, cameraDistance, float32(10.0))
|
||||
|
||||
testutils.SimulateMouseWheel(-1.0) // Zoom out
|
||||
testutils.SimulateMouseWheel(-1.0)
|
||||
assert.LessOrEqual(t, cameraDistance, float32(250.0))
|
||||
|
||||
// Test camera rotation
|
||||
originalYaw := cameraYaw
|
||||
testutils.SimulateKeyDown(rl.KeyRight, true)
|
||||
game.Update(0.1)
|
||||
assert.Greater(t, cameraYaw, originalYaw)
|
||||
|
||||
// Test pitch limits
|
||||
simulateKeyDown(rl.KeyUp, true)
|
||||
for i := 0; i < 100; i++ {
|
||||
game.Update(0.1)
|
||||
}
|
||||
assert.GreaterOrEqual(t, cameraPitch, float32(20.0))
|
||||
assert.LessOrEqual(t, cameraPitch, float32(85.0))
|
||||
}
|
||||
|
||||
func TestGame_ChatIntegration(t *testing.T) {
|
||||
game := New()
|
||||
game.Player = &types.Player{ID: 1}
|
||||
|
||||
// Test chat message to action queue
|
||||
testutils.SimulateKeyPress(rl.KeyT)
|
||||
game.Update(0.1)
|
||||
assert.True(t, game.Chat.isTyping)
|
||||
|
||||
testutils.SimulateCharInput('h')
|
||||
testutils.SimulateCharInput('i')
|
||||
testutils.SimulateKeyPress(rl.KeyEnter)
|
||||
game.Update(0.1)
|
||||
|
||||
assert.Equal(t, 1, len(game.Player.ActionQueue))
|
||||
assert.Equal(t, pb.Action_CHAT, game.Player.ActionQueue[0].Type)
|
||||
assert.Equal(t, "hi", game.Player.ActionQueue[0].ChatMessage)
|
||||
}
|
||||
|
||||
func TestGame_MenuHandling(t *testing.T) {
|
||||
game := New()
|
||||
|
||||
// Test menu toggle
|
||||
assert.False(t, game.MenuOpen)
|
||||
testutils.SimulateKeyPress(rl.KeyEscape)
|
||||
game.Update(0.1)
|
||||
assert.True(t, game.MenuOpen)
|
||||
|
||||
// Test input blocking when menu is open
|
||||
game.Player = &types.Player{ID: 1}
|
||||
testutils.SimulateMouseButton(testutils.ToInt32(rl.MouseLeftButton), true)
|
||||
game.Update(0.1)
|
||||
assert.Empty(t, game.Player.TargetPath)
|
||||
|
||||
// Test menu close
|
||||
testutils.SimulateKeyPress(rl.KeyEscape)
|
||||
game.Update(0.1)
|
||||
assert.False(t, game.MenuOpen)
|
||||
}
|
||||
|
||||
// Add more test helpers as needed...
|
@ -3,16 +3,95 @@ package game
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/mock"
|
||||
"gitea.boner.be/bdnugget/goonscape/types"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
// InputHandler abstracts raylib input functions for testing
|
||||
type InputHandler interface {
|
||||
IsKeyPressed(key int32) bool
|
||||
IsKeyDown(key int32) bool
|
||||
IsMouseButtonPressed(button int32) bool
|
||||
GetMousePosition() rl.Vector2
|
||||
GetMouseRay(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray
|
||||
GetMouseWheelMove() float32
|
||||
GetCharPressed() rune
|
||||
}
|
||||
|
||||
// RaylibInput implements InputHandler using actual raylib functions
|
||||
type RaylibInput struct{}
|
||||
|
||||
func (r *RaylibInput) IsKeyPressed(key int32) bool { return rl.IsKeyPressed(key) }
|
||||
func (r *RaylibInput) IsKeyDown(key int32) bool { return rl.IsKeyDown(key) }
|
||||
func (r *RaylibInput) IsMouseButtonPressed(button int32) bool {
|
||||
return rl.IsMouseButtonPressed(rl.MouseButton(button))
|
||||
}
|
||||
func (r *RaylibInput) GetMousePosition() rl.Vector2 { return rl.GetMousePosition() }
|
||||
func (r *RaylibInput) GetMouseRay(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray {
|
||||
return rl.GetMouseRay(mousePos, camera)
|
||||
}
|
||||
func (r *RaylibInput) GetMouseWheelMove() float32 { return rl.GetMouseWheelMove() }
|
||||
func (r *RaylibInput) GetCharPressed() rune { return rl.GetCharPressed() }
|
||||
|
||||
// MockInput implements InputHandler using our mock functions
|
||||
type MockInput struct{}
|
||||
|
||||
func (m *MockInput) IsKeyPressed(key int32) bool {
|
||||
if mock.IsKeyPressed == nil {
|
||||
return false
|
||||
}
|
||||
return mock.IsKeyPressed(key)
|
||||
}
|
||||
|
||||
func (m *MockInput) IsKeyDown(key int32) bool {
|
||||
if mock.IsKeyDown == nil {
|
||||
return false
|
||||
}
|
||||
return mock.IsKeyDown(key)
|
||||
}
|
||||
|
||||
func (m *MockInput) IsMouseButtonPressed(button int32) bool {
|
||||
if mock.IsMouseButtonPressed == nil {
|
||||
return false
|
||||
}
|
||||
return mock.IsMouseButtonPressed(button)
|
||||
}
|
||||
|
||||
func (m *MockInput) GetMousePosition() rl.Vector2 {
|
||||
if mock.GetMousePosition == nil {
|
||||
return rl.Vector2{}
|
||||
}
|
||||
return mock.GetMousePosition()
|
||||
}
|
||||
|
||||
func (m *MockInput) GetMouseRay(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray {
|
||||
if mock.GetMouseRay == nil {
|
||||
return rl.Ray{}
|
||||
}
|
||||
return mock.GetMouseRay(mousePos, camera)
|
||||
}
|
||||
|
||||
func (m *MockInput) GetMouseWheelMove() float32 {
|
||||
if mock.GetMouseWheelMove == nil {
|
||||
return 0
|
||||
}
|
||||
return mock.GetMouseWheelMove()
|
||||
}
|
||||
|
||||
func (m *MockInput) GetCharPressed() rune {
|
||||
if mock.GetCharPressed == nil {
|
||||
return 0
|
||||
}
|
||||
return mock.GetCharPressed()
|
||||
}
|
||||
|
||||
func (g *Game) GetTileAtMouse() (types.Tile, bool) {
|
||||
if !rl.IsMouseButtonPressed(rl.MouseLeftButton) {
|
||||
if !g.input.IsMouseButtonPressed(toInt32(rl.MouseLeftButton)) {
|
||||
return types.Tile{}, false
|
||||
}
|
||||
mouse := rl.GetMousePosition()
|
||||
ray := rl.GetMouseRay(mouse, g.Camera)
|
||||
mouse := g.input.GetMousePosition()
|
||||
ray := g.input.GetMouseRay(mouse, g.Camera)
|
||||
|
||||
for x := 0; x < types.MapWidth; x++ {
|
||||
for y := 0; y < types.MapHeight; y++ {
|
||||
|
187
game/input_test.go
Normal file
187
game/input_test.go
Normal file
@ -0,0 +1,187 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/testutils"
|
||||
"gitea.boner.be/bdnugget/goonscape/types"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestMouseInput_EdgeCases(t *testing.T) {
|
||||
game, cleanup := setupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
game.Player = &types.Player{ID: 1}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
ray rl.Ray
|
||||
expected bool
|
||||
}{
|
||||
{
|
||||
name: "Click outside map bounds",
|
||||
ray: rl.Ray{
|
||||
Position: rl.Vector3{X: 1000, Y: 10, Z: 1000},
|
||||
Direction: rl.Vector3{X: 0, Y: -1, Z: 0},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Click at map edge",
|
||||
ray: rl.Ray{
|
||||
Position: rl.Vector3{X: float32(types.MapWidth * types.TileSize), Y: 10, Z: 0},
|
||||
Direction: rl.Vector3{X: 0, Y: -1, Z: 0},
|
||||
},
|
||||
expected: false,
|
||||
},
|
||||
{
|
||||
name: "Click on valid tile",
|
||||
ray: rl.Ray{
|
||||
Position: rl.Vector3{X: 32, Y: 10, Z: 32},
|
||||
Direction: rl.Vector3{X: 0, Y: -1, Z: 0},
|
||||
},
|
||||
expected: true,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testutils.ResetMockInput()
|
||||
testutils.SimulateMouseRay(tt.ray)
|
||||
testutils.SimulateMouseButton(testutils.ToInt32(rl.MouseLeftButton), true)
|
||||
tile, clicked := game.GetTileAtMouse()
|
||||
assert.Equal(t, tt.expected, clicked)
|
||||
if tt.expected {
|
||||
assert.NotEmpty(t, tile)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestChat_InputValidation(t *testing.T) {
|
||||
game, cleanup := setupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
game.Player = &types.Player{ID: 1}
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
input []rune
|
||||
expected string
|
||||
}{
|
||||
{
|
||||
name: "Empty message",
|
||||
input: []rune{},
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Message with only spaces",
|
||||
input: []rune(" "),
|
||||
expected: "",
|
||||
},
|
||||
{
|
||||
name: "Very long message",
|
||||
input: []rune(string(make([]rune, runeLimit))),
|
||||
expected: string(make([]rune, runeLimit)),
|
||||
},
|
||||
{
|
||||
name: "Unicode characters",
|
||||
input: []rune("Hello 世界"),
|
||||
expected: "Hello 世界",
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
testutils.ResetMockInput()
|
||||
testutils.SimulateKeyPress(rl.KeyT)
|
||||
game.Update(0.1)
|
||||
|
||||
for _, r := range tt.input {
|
||||
testutils.SimulateCharInput(r)
|
||||
game.Update(0.1)
|
||||
}
|
||||
|
||||
testutils.SimulateKeyPress(rl.KeyEnter)
|
||||
game.Update(0.1)
|
||||
|
||||
if tt.expected != "" {
|
||||
assert.Equal(t, 1, len(game.Player.ActionQueue))
|
||||
assert.Equal(t, tt.expected, game.Player.ActionQueue[0].ChatMessage)
|
||||
} else {
|
||||
assert.Empty(t, game.Player.ActionQueue)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func TestLogin_InputValidation(t *testing.T) {
|
||||
_, cleanup := setupTestEnvironment(t)
|
||||
defer cleanup()
|
||||
|
||||
tests := []struct {
|
||||
name string
|
||||
username string
|
||||
password string
|
||||
expectSuccess bool
|
||||
}{
|
||||
{
|
||||
name: "Valid credentials",
|
||||
username: "validuser",
|
||||
password: "validpass",
|
||||
expectSuccess: true,
|
||||
},
|
||||
{
|
||||
name: "Empty username",
|
||||
username: "",
|
||||
password: "password",
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Empty password",
|
||||
username: "username",
|
||||
password: "",
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Username too long",
|
||||
username: "verylongusername123",
|
||||
password: "password",
|
||||
expectSuccess: false,
|
||||
},
|
||||
{
|
||||
name: "Special characters in username",
|
||||
username: "user@name",
|
||||
password: "password",
|
||||
expectSuccess: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
login := NewLoginScreen()
|
||||
testutils.ResetMockInput()
|
||||
|
||||
// Simulate typing username
|
||||
login.focusedField = 0
|
||||
for _, r := range tt.username {
|
||||
testutils.SimulateCharInput(r)
|
||||
login.Update()
|
||||
}
|
||||
|
||||
// Simulate typing password
|
||||
login.focusedField = 1
|
||||
for _, r := range tt.password {
|
||||
testutils.SimulateCharInput(r)
|
||||
login.Update()
|
||||
}
|
||||
|
||||
// Simulate clicking login button
|
||||
testutils.SimulateMouseClick(400, 365)
|
||||
_, _, _, submitted := login.Update()
|
||||
assert.Equal(t, tt.expectSuccess, submitted)
|
||||
})
|
||||
}
|
||||
}
|
80
game/login_test.go
Normal file
80
game/login_test.go
Normal file
@ -0,0 +1,80 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/testutils"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestLoginScreen_Update(t *testing.T) {
|
||||
login := NewLoginScreen()
|
||||
|
||||
// Test field focus switching
|
||||
simulateMouseClick(400, 215) // Click username field
|
||||
assert.Equal(t, 0, login.focusedField)
|
||||
|
||||
simulateMouseClick(400, 265) // Click password field
|
||||
assert.Equal(t, 1, login.focusedField)
|
||||
|
||||
// Test input length limits
|
||||
login.focusedField = 0
|
||||
for i := 0; i < 20; i++ {
|
||||
simulateCharInput('x')
|
||||
}
|
||||
assert.LessOrEqual(t, len(login.username), 12)
|
||||
|
||||
login.focusedField = 1
|
||||
for i := 0; i < 30; i++ {
|
||||
simulateCharInput('x')
|
||||
}
|
||||
assert.LessOrEqual(t, len(login.password), 20)
|
||||
|
||||
// Test mode switching
|
||||
simulateMouseClick(600, 365) // Click switch mode button
|
||||
assert.True(t, login.isRegistering)
|
||||
simulateMouseClick(600, 365) // Click again
|
||||
assert.False(t, login.isRegistering)
|
||||
|
||||
// Test submission
|
||||
login.username = "test"
|
||||
login.password = "password"
|
||||
testutils.SimulateMousePosition(400, 365)
|
||||
testutils.SimulateMouseButton(testutils.ToInt32(rl.MouseLeftButton), true)
|
||||
username, password, isRegistering, submitted := login.Update()
|
||||
assert.True(t, submitted)
|
||||
assert.Equal(t, "test", username)
|
||||
assert.Equal(t, "password", password)
|
||||
assert.False(t, isRegistering)
|
||||
}
|
||||
|
||||
func TestLoginScreen_ErrorHandling(t *testing.T) {
|
||||
login := NewLoginScreen()
|
||||
|
||||
// Test empty fields
|
||||
login.username = ""
|
||||
login.password = "test"
|
||||
testutils.SimulateMousePosition(400, 365)
|
||||
testutils.SimulateMouseButton(testutils.ToInt32(rl.MouseLeftButton), true)
|
||||
_, _, _, submitted := login.Update()
|
||||
assert.False(t, submitted)
|
||||
assert.Contains(t, login.errorMessage, "username")
|
||||
|
||||
// Test special characters
|
||||
login.username = "test!@#"
|
||||
login.password = "password"
|
||||
testutils.SimulateMousePosition(400, 365)
|
||||
testutils.SimulateMouseButton(testutils.ToInt32(rl.MouseLeftButton), true)
|
||||
_, _, _, submitted = login.Update()
|
||||
assert.False(t, submitted)
|
||||
assert.Contains(t, login.errorMessage, "invalid characters")
|
||||
|
||||
// Test error message display
|
||||
login.SetError("Test error")
|
||||
assert.Equal(t, "Test error", login.errorMessage)
|
||||
}
|
||||
|
||||
func simulateMouseClick(x, y float32) {
|
||||
// Implementation would depend on how raylib is mocked
|
||||
}
|
15
game/mock/raylib.go
Normal file
15
game/mock/raylib.go
Normal file
@ -0,0 +1,15 @@
|
||||
package mock
|
||||
|
||||
import (
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
var (
|
||||
IsKeyPressed func(key int32) bool
|
||||
IsKeyDown func(key int32) bool
|
||||
IsMouseButtonPressed func(button int32) bool
|
||||
GetMousePosition func() rl.Vector2
|
||||
GetMouseRay func(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray
|
||||
GetMouseWheelMove func() float32
|
||||
GetCharPressed func() rune
|
||||
)
|
141
game/test_helpers.go
Normal file
141
game/test_helpers.go
Normal file
@ -0,0 +1,141 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/mock"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
var (
|
||||
mockInput struct {
|
||||
sync.Mutex
|
||||
keyPressed map[int32]bool
|
||||
keyDown map[int32]bool
|
||||
mousePressed map[int32]bool
|
||||
mousePosition rl.Vector2
|
||||
mouseRay rl.Ray
|
||||
mouseWheel float32
|
||||
charPressed rune
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
resetMockInput()
|
||||
setupMockFunctions()
|
||||
}
|
||||
|
||||
func setupMockFunctions() {
|
||||
mock.IsKeyPressed = mockIsKeyPressed
|
||||
mock.IsKeyDown = mockIsKeyDown
|
||||
mock.IsMouseButtonPressed = mockIsMouseButtonPressed
|
||||
mock.GetMousePosition = mockGetMousePosition
|
||||
mock.GetMouseRay = mockGetMouseRay
|
||||
mock.GetMouseWheelMove = mockGetMouseWheelMove
|
||||
mock.GetCharPressed = mockGetCharPressed
|
||||
}
|
||||
|
||||
func resetMockInput() {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
mockInput.keyPressed = make(map[int32]bool)
|
||||
mockInput.keyDown = make(map[int32]bool)
|
||||
mockInput.mousePressed = make(map[int32]bool)
|
||||
mockInput.mousePosition = rl.Vector2{}
|
||||
mockInput.mouseRay = rl.Ray{}
|
||||
mockInput.mouseWheel = 0
|
||||
mockInput.charPressed = 0
|
||||
}
|
||||
|
||||
// Mock input simulation functions
|
||||
func simulateKeyPress(key int32) {
|
||||
mockInput.Lock()
|
||||
mockInput.keyPressed[key] = true
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateKeyDown(key int32, isDown bool) {
|
||||
mockInput.Lock()
|
||||
mockInput.keyDown[key] = isDown
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateMouseButton(button int32, isPressed bool) {
|
||||
mockInput.Lock()
|
||||
mockInput.mousePressed[button] = isPressed
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateMousePosition(x, y float32) {
|
||||
mockInput.Lock()
|
||||
mockInput.mousePosition = rl.Vector2{X: x, Y: y}
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateMouseRay(ray rl.Ray) {
|
||||
mockInput.Lock()
|
||||
mockInput.mouseRay = ray
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateMouseWheel(move float32) {
|
||||
mockInput.Lock()
|
||||
mockInput.mouseWheel = move
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
func simulateCharInput(char rune) {
|
||||
mockInput.Lock()
|
||||
mockInput.charPressed = char
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// Mock raylib functions
|
||||
func mockIsKeyPressed(key int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.keyPressed[key]
|
||||
}
|
||||
|
||||
func mockIsKeyDown(key int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.keyDown[key]
|
||||
}
|
||||
|
||||
func mockIsMouseButtonPressed(button int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mousePressed[button]
|
||||
}
|
||||
|
||||
func mockGetMousePosition() rl.Vector2 {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mousePosition
|
||||
}
|
||||
|
||||
func mockGetMouseRay(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mouseRay
|
||||
}
|
||||
|
||||
func mockGetMouseWheelMove() float32 {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mouseWheel
|
||||
}
|
||||
|
||||
func mockGetCharPressed() rune {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.charPressed
|
||||
}
|
||||
|
||||
// Add more mock implementations...
|
||||
|
||||
// Add this helper function
|
||||
func toInt32(button rl.MouseButton) int32 {
|
||||
return int32(button)
|
||||
}
|
26
game/test_setup.go
Normal file
26
game/test_setup.go
Normal file
@ -0,0 +1,26 @@
|
||||
package game
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/mock"
|
||||
"gitea.boner.be/bdnugget/goonscape/game/testutils"
|
||||
)
|
||||
|
||||
func setupTestEnvironment(t *testing.T) (*Game, func()) {
|
||||
testutils.ResetMockInput()
|
||||
testutils.SetupMockFunctions()
|
||||
|
||||
game := New()
|
||||
game.input = &MockInput{}
|
||||
game.Chat.input = &MockInput{} // Also inject mock input into chat
|
||||
|
||||
// Verify mock setup
|
||||
if mock.IsKeyPressed == nil || mock.GetCharPressed == nil {
|
||||
t.Fatal("Mock functions not properly initialized")
|
||||
}
|
||||
|
||||
return game, func() {
|
||||
testutils.ResetMockInput()
|
||||
}
|
||||
}
|
153
game/testutils/helpers.go
Normal file
153
game/testutils/helpers.go
Normal file
@ -0,0 +1,153 @@
|
||||
package testutils
|
||||
|
||||
import (
|
||||
"sync"
|
||||
|
||||
"gitea.boner.be/bdnugget/goonscape/game/mock"
|
||||
rl "github.com/gen2brain/raylib-go/raylib"
|
||||
)
|
||||
|
||||
var (
|
||||
mockInput struct {
|
||||
sync.Mutex
|
||||
keyPressed map[int32]bool
|
||||
keyDown map[int32]bool
|
||||
mousePressed map[int32]bool
|
||||
mousePosition rl.Vector2
|
||||
mouseRay rl.Ray
|
||||
mouseWheel float32
|
||||
charPressed rune
|
||||
}
|
||||
)
|
||||
|
||||
func init() {
|
||||
ResetMockInput()
|
||||
SetupMockFunctions()
|
||||
}
|
||||
|
||||
// SetupMockFunctions initializes mock functions
|
||||
func SetupMockFunctions() {
|
||||
mock.IsKeyPressed = MockIsKeyPressed
|
||||
mock.IsKeyDown = MockIsKeyDown
|
||||
mock.IsMouseButtonPressed = MockIsMouseButtonPressed
|
||||
mock.GetMousePosition = MockGetMousePosition
|
||||
mock.GetMouseRay = MockGetMouseRay
|
||||
mock.GetMouseWheelMove = MockGetMouseWheelMove
|
||||
mock.GetCharPressed = MockGetCharPressed
|
||||
}
|
||||
|
||||
// ResetMockInput resets all mock input states
|
||||
func ResetMockInput() {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
mockInput.keyPressed = make(map[int32]bool)
|
||||
mockInput.keyDown = make(map[int32]bool)
|
||||
mockInput.mousePressed = make(map[int32]bool)
|
||||
mockInput.mousePosition = rl.Vector2{}
|
||||
mockInput.mouseRay = rl.Ray{}
|
||||
mockInput.mouseWheel = 0
|
||||
mockInput.charPressed = 0
|
||||
}
|
||||
|
||||
// SimulateKeyPress simulates a key press
|
||||
func SimulateKeyPress(key int32) {
|
||||
mockInput.Lock()
|
||||
mockInput.keyPressed[key] = true
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateKeyDown simulates holding a key down
|
||||
func SimulateKeyDown(key int32, isDown bool) {
|
||||
mockInput.Lock()
|
||||
mockInput.keyDown[key] = isDown
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateMouseButton simulates a mouse button press
|
||||
func SimulateMouseButton(button int32, isPressed bool) {
|
||||
mockInput.Lock()
|
||||
mockInput.mousePressed[button] = isPressed
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateMousePosition simulates mouse movement
|
||||
func SimulateMousePosition(x, y float32) {
|
||||
mockInput.Lock()
|
||||
mockInput.mousePosition = rl.Vector2{X: x, Y: y}
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateMouseClick simulates a mouse click at the given position
|
||||
func SimulateMouseClick(x, y float32) {
|
||||
SimulateMousePosition(x, y)
|
||||
SimulateMouseButton(ToInt32(rl.MouseLeftButton), true)
|
||||
}
|
||||
|
||||
// SimulateMouseRay simulates a mouse ray
|
||||
func SimulateMouseRay(ray rl.Ray) {
|
||||
mockInput.Lock()
|
||||
mockInput.mouseRay = ray
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateMouseWheel simulates mouse wheel movement
|
||||
func SimulateMouseWheel(move float32) {
|
||||
mockInput.Lock()
|
||||
mockInput.mouseWheel = move
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// SimulateCharInput simulates character input
|
||||
func SimulateCharInput(char rune) {
|
||||
mockInput.Lock()
|
||||
mockInput.charPressed = char
|
||||
mockInput.Unlock()
|
||||
}
|
||||
|
||||
// Mock raylib functions
|
||||
func MockIsKeyPressed(key int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.keyPressed[key]
|
||||
}
|
||||
|
||||
func MockIsKeyDown(key int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.keyDown[key]
|
||||
}
|
||||
|
||||
func MockIsMouseButtonPressed(button int32) bool {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mousePressed[button]
|
||||
}
|
||||
|
||||
func MockGetMousePosition() rl.Vector2 {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mousePosition
|
||||
}
|
||||
|
||||
func MockGetMouseRay(mousePos rl.Vector2, camera rl.Camera3D) rl.Ray {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mouseRay
|
||||
}
|
||||
|
||||
func MockGetMouseWheelMove() float32 {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.mouseWheel
|
||||
}
|
||||
|
||||
func MockGetCharPressed() rune {
|
||||
mockInput.Lock()
|
||||
defer mockInput.Unlock()
|
||||
return mockInput.charPressed
|
||||
}
|
||||
|
||||
// ToInt32 converts MouseButton to int32
|
||||
func ToInt32(button rl.MouseButton) int32 {
|
||||
return int32(button)
|
||||
}
|
7
go.mod
7
go.mod
@ -8,8 +8,15 @@ require (
|
||||
google.golang.org/protobuf v1.36.3
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||
)
|
||||
|
||||
require (
|
||||
github.com/ebitengine/purego v0.8.2 // indirect
|
||||
github.com/stretchr/testify v1.10.0
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
)
|
||||
|
9
go.sum
9
go.sum
@ -1,12 +1,21 @@
|
||||
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||
github.com/ebitengine/purego v0.8.2 h1:jPPGWs2sZ1UgOSgD2bClL0MJIqu58nOmIcBuXr62z1I=
|
||||
github.com/ebitengine/purego v0.8.2/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ=
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b h1:JJfspevP3YOXcSKVABizYOv++yMpTJIdPUtoDzF/RWw=
|
||||
github.com/gen2brain/raylib-go/raylib v0.0.0-20250109172833-6dbba4f81a9b/go.mod h1:BaY76bZk7nw1/kVOSQObPY1v1iwVE1KHAGMfvI6oK1Q=
|
||||
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||
github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA=
|
||||
github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8 h1:yqrTHse8TCMW1M1ZCP+VAR/l0kKxwaAIqN/il7x4voA=
|
||||
golang.org/x/exp v0.0.0-20250106191152-7588d65b2ba8/go.mod h1:tujkw807nyEEAamNbDrEGzRav+ilXA7PCRAd6xsmwiU=
|
||||
golang.org/x/sys v0.29.0 h1:TPYlXGxvx1MGTn2GiZDhnjPA9wZzZeGKHHmKhHYvgaU=
|
||||
golang.org/x/sys v0.29.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||
google.golang.org/protobuf v1.36.3 h1:82DV7MYdb8anAVi3qge1wSnMDrnKK7ebr+I0hHRN1BU=
|
||||
google.golang.org/protobuf v1.36.3/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojtC/QF5bRE=
|
||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||
|
Loading…
x
Reference in New Issue
Block a user