initial commit

This commit is contained in:
2026-07-23 21:23:01 +00:00
commit c6c309f941
25 changed files with 6825 additions and 0 deletions
+80
View File
@@ -0,0 +1,80 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('Audio Module', function()
local audio
before_each(function()
audio = require('src.audio.init')
end)
it('should create purr sound', function()
local sound = audio.createPurrSound()
assert.are_equal('source', sound.type)
assert.is_true(sound.spatial)
assert.are_equal(5.0, sound.maxDistance)
end)
it('should create meow sound', function()
local sound = audio.createMeowSound()
assert.are_equal('source', sound.type)
assert.is_true(sound.spatial)
assert.are_equal(3.0, sound.maxDistance)
end)
it('should create click sound', function()
local sound = audio.createClickSound()
assert.are_equal('source', sound.type)
assert.is_true(sound.spatial)
assert.are_equal(2.0, sound.maxDistance)
end)
it('should create ambient sound', function()
local sound = audio.createAmbientSound()
assert.are_equal('source', sound.type)
assert.is_false(sound.spatial)
assert.is_true(sound.looping)
end)
it('should play purr at position', function()
audio.playPurr({ 1, 2, 3 })
end)
it('should play meow at position', function()
audio.playMeow({ 1, 2, 3 })
end)
it('should play click sound', function()
audio.playClick()
end)
it('should handle nil position in playPurr', function()
audio.playPurr(nil)
end)
it('should handle nil position in playMeow', function()
audio.playMeow(nil)
end)
it('should handle nil sounds', function()
audio.sounds = {}
audio.playPurr({ 1, 2, 3 })
audio.playMeow({ 1, 2, 3 })
end)
it('should handle nil sounds.purr', function()
audio.sounds.purr = nil
audio.playPurr({ 1, 2, 3 })
end)
it('should handle nil sounds.meow', function()
audio.sounds.meow = nil
audio.playMeow({ 1, 2, 3 })
end)
it('should handle nil sounds.click', function()
audio.sounds.click = nil
audio.playClick()
end)
end)
+155
View File
@@ -0,0 +1,155 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('Cat Module', function()
local cat
setup(function()
cat = require('src.cat.init')
end)
it('should create cat with default personality', function()
local c = cat.createCat(1, 'Test Cat', nil)
assert.are.equal(1, c.id)
assert.are.equal('Test Cat', c.name)
assert.are.equal('social', c.personality.id)
end)
it('should create cat with custom personality', function()
local personality = { id = 'lone_wolf', color = { 0.5, 0.5, 0.6 } }
local c = cat.createCat(2, 'Lone Cat', personality)
assert.are.equal('lone_wolf', c.personality.id)
assert.are.equal(0.5, c.personality.color[1])
end)
it('should update cat needs over time', function()
local c = cat.createCat(1, 'Test Cat', nil)
local oldComfort = c.needs.comfort
c:update(1.0)
assert.is_true(c.needs.comfort < oldComfort)
end)
it('should transition to sleep state', function()
local c = cat.createCat(1, 'Test Cat', nil)
c.needs.comfort = 0.1
c:update(1.0)
assert.is_true(c.isSleeping)
assert.are.equal('sleep', c.state)
end)
it('should wake up after sleep timer', function()
local c = cat.createCat(1, 'Test Cat', nil)
c.needs.comfort = 0.1
c.isSleeping = true
c.sleepTimer = 0.5
c:update(1.0)
assert.is_false(c.isSleeping)
assert.are.equal('idle', c.state)
end)
it('should move toward target position', function()
local c = cat.createCat(1, 'Test Cat', nil)
c.targetPosition = { x = 1, y = 0, z = 1 }
local oldX = c.position.x
local oldZ = c.position.z
c.state = 'moving'
c:update(0.1)
assert.is_true(c.position.x ~= oldX or c.position.z ~= oldZ)
end)
it('should handle zero distance movement', function()
local c = cat.createCat(1, 'Test Cat', nil)
c.targetPosition = { x = 0, y = 0, z = 0 }
c.position = { x = 0, y = 0, z = 0 }
c.state = 'moving'
c:update(0.1)
assert.is_true(true) -- Should not crash
end)
it('should seek furniture based on needs', function()
mocks.setupFilesystem()
require('src.core')
local c = cat.createCat(1, 'Test Cat', nil)
c.needs.comfort = 0.1
c:aiBehavior()
-- Should set targetPosition if furniture found
end)
it('should check personality compatibility', function()
mocks.setupFilesystem()
require('src.core')
local c1 = cat.createCat(1, 'Test Cat 1', { id = 'social', compatibility = { ['social'] = 0.5 } })
local c2 = cat.createCat(2, 'Test Cat 2', { id = 'social', compatibility = { ['social'] = 0.5 } })
c1.position = { x = 0, y = 0, z = 0 }
c2.position = { x = 0.5, y = 0, z = 0 }
c1:updateNeeds()
assert.is_true(c1.needs.social >= 0.7)
end)
it('should handle incompatible cats', function()
mocks.setupFilesystem()
require('src.core')
local c1 = cat.createCat(1, 'Test Cat 1', { id = 'lone_wolf', compatibility = { ['social'] = -0.5 } })
local c2 = cat.createCat(2, 'Test Cat 2', { id = 'social' })
c1.position = { x = 0, y = 0, z = 0 }
c2.position = { x = 0.5, y = 0, z = 0 }
local oldSocial = c1.needs.social
c1:updateNeeds()
assert.is_true(c1.needs.social < oldSocial)
end)
it('should pet cat and increase mood', function()
local c = cat.createCat(1, 'Test Cat', nil)
local oldMood = c.mood
c:pet()
assert.is_true(c.mood > oldMood)
end)
it('should serialize cat', function()
local c = cat.createCat(1, 'Test Cat', nil)
local serialized = c:serialize()
assert.are.equal(1, serialized.id)
assert.are.equal('Test Cat', serialized.name)
assert.are.equal('social', serialized.personalityId)
end)
it('should deserialize cat', function()
mocks.setupFilesystem()
require('src.core')
local original = cat.createCat(1, 'Test Cat', nil)
local serialized = original:serialize()
local deserialized = cat.deserialize(serialized)
assert.are.equal(original.id, deserialized.id)
assert.are.equal(original.name, deserialized.name)
end)
it('should handle nil personality in deserialize', function()
local data = {
id = 1,
name = 'Test',
personalityId = 'invalid',
position = { x = 0, y = 0, z = 0 }
}
local c = cat.deserialize(data)
assert.are_not.equal(nil, c)
assert.are.equal('social', c.personality.id)
end)
it('should get cat animation', function()
local c = cat.createCat(1, 'Test Cat', nil)
local anim = cat.getCatAnimation(c, 0)
assert.are.equal('table', type(anim))
assert.are.equal('number', type(anim.bounce))
end)
it('should handle nil cat in animation', function()
local anim = cat.getCatAnimation(nil, 0)
assert.are.equal(0, anim.bounce)
end)
it('should handle nil input in updateCats', function()
cat.updateCats(0.1)
assert.is_true(true) -- Should not crash
end)
end)
+160
View File
@@ -0,0 +1,160 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('Core Module', function()
local core
setup(function()
mocks.setupFilesystem()
core = require('src.core')
end)
it('should initialize game state', function()
core.init()
assert.are.equal(0, core.state.gameTime)
assert.are.equal(2, #core.state.cats)
assert.are.equal(0, #core.state.furniture)
end)
it('should spawn cats', function()
local initialCount = #core.state.cats
core.spawnCat()
assert.are.equal(initialCount + 1, #core.state.cats)
end)
it('should generate cat names', function()
local name = core.generateCatName()
assert.are_not.equal(nil, name:find(' '))
end)
it('should remove cats', function()
local catId = core.state.cats[1].id
local result = core.removeCat(catId)
assert.is_true(result)
assert.are.equal(1, #core.state.cats)
end)
it('should add furniture', function()
local furn = core.addFurniture('bed', { x = 0, y = 0, z = 0 })
assert.are_not.equal(nil, furn)
end)
it('should reject invalid furniture type', function()
local furn = core.addFurniture('invalid_type', { x = 0, y = 0, z = 0 })
assert.is_false(furn)
end)
it('should save game', function()
local result = core.saveGame()
assert.is_true(result)
end)
it('should load game', function()
core.saveGame()
local result = core.loadGame()
assert.is_true(result)
end)
it('should handle missing save file', function()
local result = core.loadGame()
assert.is_false(result)
end)
it('should toggle pause', function()
core.togglePause()
assert.is_true(core.state.isPaused)
core.togglePause()
assert.is_false(core.state.isPaused)
end)
it('should get happiness percentage', function()
local happiness = core.getHappinessPercentage()
assert.is_true(happiness >= 0)
assert.is_true(happiness <= 100)
end)
it('should calculate happiness with zero cats', function()
core.state.cats = {}
core.calculateHouseHappiness()
assert.are.equal(0, core.state.house.totalHappiness)
end)
it('should check cat departures', function()
local cat = utils.createMockCat({ needs = { comfort = 0.05, hunger = 0.5 } })
table.insert(core.state.cats, cat)
local initialCount = #core.state.cats
core.checkCatDepartures()
assert.is_true(#core.state.cats < initialCount)
end)
it('should handle nil cat in update', function()
core.state.cats = { nil }
core.update(0.1)
end)
it('should handle nil cat in happiness calculation', function()
core.state.cats = { nil }
core.calculateHouseHappiness()
end)
it('should handle nil cat in removeCat', function()
local result = core.removeCat(999)
assert.is_false(result)
end)
it('should handle nil core in update', function()
_G.core = nil
core.update(0.1)
end)
it('should handle nil cat module', function()
_G.cat = nil
core.update(0.1)
end)
it('should handle nil ui module', function()
_G.ui = nil
core.update(0.1)
end)
it('should handle nil voxel module', function()
_G.voxel = nil
core.update(0.1)
end)
it('should handle nil audio module', function()
_G.audio = nil
core.update(0.1)
end)
it('should handle nil house module', function()
_G.house = nil
core.update(0.1)
end)
it('should handle empty furniture list', function()
core.state.furniture = {}
core.calculateHouseHappiness()
end)
it('should handle nil needs in cat', function()
local cat = utils.createMockCat()
cat.needs = nil
table.insert(core.state.cats, cat)
core.calculateHouseHappiness()
end)
it('should handle nil personality in cat', function()
local cat = utils.createMockCat()
cat.personality = nil
table.insert(core.state.cats, cat)
cat:updateNeeds()
end)
it('should handle nil furniture in cat ai behavior', function()
core.state.furniture = nil
local cat = utils.createMockCat()
cat:aiBehavior()
end)
end)
+124
View File
@@ -0,0 +1,124 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('House Module', function()
local house
before_each(function()
house = require('src.house.init')
end)
it('should create furniture', function()
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
local furn = house.createFurniture(type, { x = 0, y = 0, z = 0 })
assert.are.equal('bed', furn.type.id)
assert.are.equal(1, furn.upgradeLevel)
end)
it('should upgrade furniture', function()
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
local furn = house.createFurniture(type, { x = 0, y = 0, z = 0 })
local oldContribution = furn.happinessContribution
furn:upgrade()
assert.are.equal(2, furn.upgradeLevel)
assert.is_true(furn.happinessContribution > oldContribution)
end)
it('should limit furniture upgrades to 3', function()
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
local furn = house.createFurniture(type, { x = 0, y = 0, z = 0 })
furn:upgrade()
furn:upgrade()
local result = furn:upgrade()
assert.is_false(result)
assert.are.equal(3, furn.upgradeLevel)
end)
it('should serialize furniture', function()
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
local furn = house.createFurniture(type, { x = 0, y = 0, z = 0 })
local serialized = furn:serialize()
assert.are.equal('bed', serialized.typeId)
assert.are.equal(1, serialized.upgradeLevel)
end)
it('should deserialize furniture', function()
mocks.setupFilesystem()
require('src.core')
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
local furn = house.createFurniture(type, { x = 0, y = 0, z = 0 })
local serialized = furn:serialize()
local deserialized = house.deserializeFurniture(serialized)
assert.are.equal('bed', deserialized.type.id)
assert.are.equal(1, deserialized.upgradeLevel)
end)
it('should check furniture placement bounds', function()
local canPlace, reason = house.canPlaceFurniture({ x = 0, y = 0, z = 0 }, { 1, 1, 1 })
assert.is_true(canPlace)
end)
it('should reject furniture out of bounds', function()
local canPlace, reason = house.canPlaceFurniture({ x = 100, y = 0, z = 100 }, { 1, 1, 1 })
assert.is_false(canPlace)
assert.are.equal('out of bounds', reason)
end)
it('should detect furniture collision', function()
mocks.setupFilesystem()
require('src.core')
local type = { id = 'bed', name = 'Bed', baseHappiness = 0.7, size = { 1, 0.3, 1 } }
house.createFurniture(type, { x = 0, y = 0, z = 0 })
local canPlace, reason = house.canPlaceFurniture({ x = 0, y = 0, z = 0 }, { 1, 1, 1 })
assert.is_false(canPlace)
assert.are.equal('collision', reason)
end)
it('should place furniture', function()
mocks.setupFilesystem()
require('src.core')
local furn = house.placeFurniture('bed', { x = 0, y = 0, z = 0 })
assert.are_not.equal(nil, furn)
end)
it('should reject invalid furniture type', function()
local furn = house.placeFurniture('invalid_type', { x = 0, y = 0, z = 0 })
assert.are.equal(nil, furn)
end)
it('should detect collision with furniture', function()
local result = house.checkCollision({ x = 0, y = 0, z = 0 }, { 1, 1, 1 })
assert.is_false(result)
end)
it('should interact with furniture', function()
local furn = house.interactWithFurniture({ x = 0, y = 0, z = 0 })
assert.are.equal(nil, furn)
end)
it('should handle nil furniture type', function()
local furn = house.createFurniture(nil, { x = 0, y = 0, z = 0 })
assert.are.equal(nil, furn)
end)
it('should handle nil data in deserialize', function()
local furn = house.deserializeFurniture(nil)
assert.are.equal(nil, furn)
end)
it('should handle nil core in canPlaceFurniture', function()
local canPlace, reason = house.canPlaceFurniture({ x = 0, y = 0, z = 0 }, { 1, 1, 1 })
assert.is_false(canPlace)
end)
it('should handle nil core in checkCollision', function()
local result = house.checkCollision({ x = 0, y = 0, z = 0 }, { 1, 1, 1 })
assert.is_false(result)
end)
it('should handle nil core in interactWithFurniture', function()
local furn = house.interactWithFurniture({ x = 0, y = 0, z = 0 })
assert.are.equal(nil, furn)
end)
end)
+146
View File
@@ -0,0 +1,146 @@
describe('JSON Module', function()
local json
setup(function()
json = require('src.json')
end)
it('should encode strings', function()
assert.are.equal('"hello"', json.encode('hello'))
assert.are.equal('"hello world"', json.encode('hello world'))
end)
it('should encode numbers', function()
assert.are.equal('123', json.encode(123))
assert.are.equal('12.5', json.encode(12.5))
assert.are.equal('-42', json.encode(-42))
end)
it('should encode booleans', function()
assert.are.equal('true', json.encode(true))
assert.are.equal('false', json.encode(false))
end)
it('should encode null', function()
assert.are.equal('null', json.encode(nil))
end)
it('should encode arrays', function()
local arr = { 1, 2, 3 }
local encoded = json.encode(arr)
assert.are.equal('{ 1, 2, 3 }', encoded)
end)
it('should encode objects', function()
local obj = { name = 'test', value = 42 }
local encoded = json.encode(obj)
assert.are_not.equal(nil, encoded:find('name'))
assert.are_not.equal(nil, encoded:find('value'))
end)
it('should decode strings', function()
local decoded = json.decode('"hello"')
assert.are.equal('hello', decoded)
end)
it('should decode numbers', function()
local decoded = json.decode('123')
assert.are.equal(123, decoded)
local decoded2 = json.decode('12.5')
assert.are.equal(12.5, decoded2)
end)
it('should decode booleans', function()
local decoded = json.decode('true')
assert.are.equal(true, decoded)
local decoded2 = json.decode('false')
assert.are.equal(false, decoded2)
end)
it('should decode null', function()
local decoded = json.decode('null')
assert.are.equal(nil, decoded)
end)
it('should decode arrays', function()
local decoded = json.decode('{ 1, 2, 3 }')
assert.are.equal(1, decoded[1])
assert.are.equal(2, decoded[2])
assert.are.equal(3, decoded[3])
end)
it('should decode objects', function()
local decoded = json.decode('{ "name": "test", "value": 42 }')
assert.are.equal('test', decoded.name)
assert.are.equal(42, decoded.value)
end)
it('should handle round-trip encoding/decoding', function()
local original = {
name = 'Test Cat',
age = 3,
active = true,
tags = { 'cute', 'playful' },
stats = { comfort = 0.8, hunger = 0.6 }
}
local encoded = json.encode(original)
local decoded = json.decode(encoded)
assert.are.equal(original.name, decoded.name)
assert.are.equal(original.age, decoded.age)
assert.are.equal(original.active, decoded.active)
assert.are.equal(original.tags[1], decoded.tags[1])
assert.are.equal(original.stats.comfort, decoded.stats.comfort)
end)
it('should handle escaped characters in strings', function()
local original = 'hello\nworld\t"quoted"'
local encoded = json.encode(original)
local decoded = json.decode(encoded)
assert.are.equal(original, decoded)
end)
it('should handle empty arrays', function()
local original = {}
local encoded = json.encode(original)
local decoded = json.decode(encoded)
assert.are.equal(0, #decoded)
end)
it('should handle nested structures', function()
local original = {
cats = {
{ name = 'Cat1', personality = { id = 'social' } },
{ name = 'Cat2', personality = { id = 'lone_wolf' } }
}
}
local encoded = json.encode(original)
local decoded = json.decode(encoded)
assert.are.equal('Cat1', decoded.cats[1].name)
assert.are.equal('social', decoded.cats[1].personality.id)
end)
it('should handle non-string keys by converting to strings', function()
local original = { [123] = 'value', [true] = 'bool' }
local encoded = json.encode(original)
local decoded = json.decode(encoded)
assert.are.equal('value', decoded['123'])
assert.are.equal('bool', decoded['true'])
end)
it('should handle NaN and infinity as null', function()
assert.are.equal('null', json.encode(0/0))
assert.are.equal('null', json.encode(math.huge))
assert.are.equal('null', json.encode(-math.huge))
end)
it('should return error for invalid JSON', function()
local decoded, err = json.decode('{ invalid }')
assert.are_not.equal(nil, err)
assert.are.equal(nil, decoded)
end)
it('should return error for non-string input', function()
local decoded, err = json.encode({ function() end })
assert.are.equal('null', decoded)
end)
end)
+98
View File
@@ -0,0 +1,98 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('UI Module', function()
local ui
local core
before_each(function()
ui = require('src.ui.init')
mocks.setupFilesystem()
core = require('src.core')
end)
it('should render HUD', function()
local pass = utils.createMockPass()
ui.renderHUD(pass)
end)
it('should render HUD with zero cats', function()
core.state.cats = {}
local pass = utils.createMockPass()
ui.renderHUD(pass)
end)
it('should render inventory', function()
local pass = utils.createMockPass()
ui.renderInventory(pass, 1280, 720, 0.72)
end)
it('should render cat log', function()
local pass = utils.createMockPass()
ui.renderCatLog(pass, 1280, 0.72)
end)
it('should render placement preview', function()
local pass = utils.createMockPass()
ui.renderPlacementPreview(pass, 1280, 720)
end)
it('should get world ray', function()
local start, endPos = ui.getWorldRay(1280, 720)
assert.are.equal('table', type(start))
assert.are.equal('table', type(endPos))
end)
it('should handle mouse press in inventory', function()
ui.showInventory = true
ui.selectedFurniture = nil
ui.handleMousePress(1000, 500, 1)
end)
it('should handle mouse press on close button', function()
ui.showInventory = true
ui.handleMousePress(1200, 300, 1)
assert.is_false(ui.showInventory)
end)
it('should toggle inventory', function()
ui.toggleInventory()
assert.is_true(ui.showInventory)
ui.toggleInventory()
assert.is_false(ui.showInventory)
end)
it('should toggle cat log', function()
ui.toggleCatLog()
assert.is_false(ui.showCatLog)
ui.toggleCatLog()
assert.is_true(ui.showCatLog)
end)
it('should handle nil core in renderHUD', function()
_G.core = nil
local pass = utils.createMockPass()
ui.renderHUD(pass)
end)
it('should handle nil pass in renderHUD', function()
ui.renderHUD(nil)
end)
it('should handle nil pass in renderInventory', function()
ui.renderInventory(nil, 1280, 720, 0.72)
end)
it('should handle nil pass in renderCatLog', function()
ui.renderCatLog(nil, 1280, 0.72)
end)
it('should handle nil pass in renderPlacementPreview', function()
ui.renderPlacementPreview(nil, 1280, 720)
end)
it('should handle nil core in handleMousePress', function()
ui.handleMousePress(100, 100, 1)
end)
end)
+101
View File
@@ -0,0 +1,101 @@
local test_helper = require('test.test_helper')
local mocks = test_helper.mocks
local utils = test_helper.utils
describe('Voxel Module', function()
local voxel
before_each(function()
-- Mock LOVR
_G.lovr = mocks.lovr
voxel = require('src.voxel.init')
end)
it('should create voxel texture', function()
local color = { 1, 0, 0 }
local texture = voxel.createVoxelTexture(color)
assert.are.equal('texture', texture.type)
assert.are.equal(64, texture.image.width)
assert.are.equal(64, texture.image.height)
end)
it('should initialize textures for furniture', function()
mocks.setupFilesystem()
require('src.core')
voxel.init()
assert.are_not.equal(nil, voxel.textures['bed'])
assert.are_not.equal(nil, voxel.textures['cat_tree'])
end)
it('should create cube mesh', function()
local mesh = voxel.cubeMesh
assert.are.equal('mesh', mesh.type)
assert.are.equal(36, #mesh.indices) -- 12 triangles
end)
it('should render world', function()
mocks.setupFilesystem()
require('src.core')
local pass = utils.createMockPass()
voxel.renderWorld(pass)
-- Should draw floor and walls
end)
it('should render furniture', function()
mocks.setupFilesystem()
require('src.core')
local pass = utils.createMockPass()
voxel.renderFurniture(pass)
end)
it('should render cats', function()
mocks.setupFilesystem()
require('src.core')
local pass = utils.createMockPass()
voxel.renderCats(pass)
end)
it('should raycast furniture', function()
local start = { 0, 0, 0 }
local direction = { 0, 0, -1 }
local maxDistance = 10
local result = voxel.raycast(start, direction, maxDistance)
assert.are.equal('table', type(result))
end)
it('should raycast cats', function()
local start = { 0, 0, 0 }
local direction = { 0, 0, -1 }
local maxDistance = 10
local result = voxel.raycast(start, direction, maxDistance)
assert.are.equal('table', type(result))
end)
it('should handle nil inputs', function()
local result = voxel.raycast(nil, nil, nil)
assert.are.equal('table', type(result))
assert.are.equal(0, #result)
end)
it('should handle zero distance direction', function()
local start = { 0, 0, 0 }
local direction = { 0, 0, 0 }
local maxDistance = 10
local result = voxel.raycast(start, direction, maxDistance)
assert.are.equal('table', type(result))
end)
it('should sort raycast results by distance', function()
local start = { 0, 0, 0 }
local direction = { 0, 0, -1 }
local maxDistance = 10
local result = voxel.raycast(start, direction, maxDistance)
for i = 2, #result do
assert.is_true(result[i].distance >= result[i-1].distance)
end
end)
end)