Skip to content

Commit 2fe69e3

Browse files
authored
test: improve render coverage (#65)
1 parent 44cead3 commit 2fe69e3

2 files changed

Lines changed: 145 additions & 1 deletion

File tree

.github/workflows/ci.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ jobs:
5050
run: |
5151
total=$(go tool cover -func=coverage.out | awk '/^total:/ {gsub("%","",$3); print $3}')
5252
# Current enforced coverage floor. Codex PRs raise this incrementally toward 90%.
53-
min=45.0
53+
min=50.0
5454
awk -v t="$total" -v m="$min" 'BEGIN {
5555
if (t+0 < m+0) {
5656
printf "Coverage %.1f%% is below floor %.1f%%\n", t, m

render/skyline_test.go

Lines changed: 144 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ package render
33
import (
44
"bytes"
55
"math/rand/v2"
6+
"path/filepath"
7+
"regexp"
68
"strings"
79
"testing"
810
"time"
@@ -12,10 +14,16 @@ import (
1214
tea "github.com/charmbracelet/bubbletea"
1315
)
1416

17+
var skylineANSIPattern = regexp.MustCompile(`\x1b\[[0-9;]*m`)
18+
1519
func resetSkylineRNG() {
1620
rng = rand.New(rand.NewPCG(42, 0))
1721
}
1822

23+
func stripSkylineANSI(s string) string {
24+
return skylineANSIPattern.ReplaceAllString(s, "")
25+
}
26+
1927
func TestSkylineFilterCodeFiles(t *testing.T) {
2028
tests := []struct {
2129
name string
@@ -209,6 +217,26 @@ func TestSkylineRenderStaticIncludesTitleAndStats(t *testing.T) {
209217
}
210218
}
211219

220+
func TestSkylineUsesRootBaseNameWhenNameMissing(t *testing.T) {
221+
resetSkylineRNG()
222+
223+
root := t.TempDir()
224+
project := scanner.Project{
225+
Root: root,
226+
Files: []scanner.FileInfo{
227+
{Path: "src/main.go", Ext: ".go", Size: 256},
228+
},
229+
}
230+
231+
var buf bytes.Buffer
232+
Skyline(&buf, project, true)
233+
234+
out := stripSkylineANSI(buf.String())
235+
if !strings.Contains(out, "─── "+filepath.Base(root)+" ───") {
236+
t.Fatalf("expected skyline title to use root basename, got:\n%s", out)
237+
}
238+
}
239+
212240
func TestSkylineAnimationModelUpdateAndView(t *testing.T) {
213241
resetSkylineRNG()
214242

@@ -250,6 +278,122 @@ func TestSkylineAnimationModelUpdateAndView(t *testing.T) {
250278
}
251279
}
252280

281+
func TestAnimationModelInitAndPhaseTransitions(t *testing.T) {
282+
resetSkylineRNG()
283+
284+
m := animationModel{
285+
arranged: []building{{height: 3, char: '▓', color: Cyan, extLabel: ".go", gap: 1}},
286+
width: 20,
287+
leftMargin: 2,
288+
sceneLeft: 1,
289+
sceneRight: 12,
290+
sceneWidth: 11,
291+
maxBuildingHeight: 3,
292+
phase: 1,
293+
visibleRows: 5,
294+
}
295+
296+
if cmd := m.Init(); cmd == nil {
297+
t.Fatal("expected Init to return a tick command")
298+
}
299+
300+
updated, cmd := m.Update(tickMsg(time.Now()))
301+
if cmd == nil {
302+
t.Fatal("expected tick command during rising phase")
303+
}
304+
305+
m1 := updated.(animationModel)
306+
if m1.phase != 2 {
307+
t.Fatalf("expected phase transition to 2, got %d", m1.phase)
308+
}
309+
if m1.frame != 0 {
310+
t.Fatalf("expected frame reset after phase transition, got %d", m1.frame)
311+
}
312+
313+
m1.frame = 39
314+
updated, cmd = m1.Update(tickMsg(time.Now()))
315+
if cmd == nil {
316+
t.Fatal("expected quit command when animation completes")
317+
}
318+
319+
m2 := updated.(animationModel)
320+
if !m2.done {
321+
t.Fatal("expected animation model to be marked done")
322+
}
323+
}
324+
325+
func TestAnimationModelUpdateShootingStarLifecycle(t *testing.T) {
326+
resetSkylineRNG()
327+
328+
m := animationModel{
329+
arranged: []building{{height: 4, char: '▓', color: Cyan, extLabel: ".go", gap: 1}},
330+
width: 20,
331+
leftMargin: 2,
332+
sceneLeft: 3,
333+
sceneRight: 10,
334+
sceneWidth: 7,
335+
maxBuildingHeight: 4,
336+
phase: 2,
337+
frame: 9,
338+
shootingStarActive: false,
339+
}
340+
341+
updated, cmd := m.Update(tickMsg(time.Now()))
342+
if cmd == nil {
343+
t.Fatal("expected tick command in twinkling phase")
344+
}
345+
346+
m1 := updated.(animationModel)
347+
if !m1.shootingStarActive {
348+
t.Fatal("expected shooting star to activate on frame 10")
349+
}
350+
if m1.shootingStarCol != m.sceneLeft {
351+
t.Fatalf("expected shooting star to start at scene left %d, got %d", m.sceneLeft, m1.shootingStarCol)
352+
}
353+
354+
m1.shootingStarCol = m1.sceneRight + 1
355+
updated, cmd = m1.Update(tickMsg(time.Now()))
356+
if cmd == nil {
357+
t.Fatal("expected tick command when advancing active shooting star")
358+
}
359+
360+
m2 := updated.(animationModel)
361+
if m2.shootingStarActive {
362+
t.Fatal("expected shooting star to deactivate after leaving the scene")
363+
}
364+
}
365+
366+
func TestAnimationModelViewRendersLabelsAndShootingStar(t *testing.T) {
367+
resetSkylineRNG()
368+
369+
m := animationModel{
370+
arranged: []building{
371+
{height: 4, char: '▓', color: Cyan, extLabel: ".go", gap: 1},
372+
{height: 4, char: '▒', color: Yellow, extLabel: "A-1", gap: 1},
373+
},
374+
width: 24,
375+
leftMargin: 2,
376+
sceneLeft: 1,
377+
sceneRight: 20,
378+
sceneWidth: 19,
379+
starPositions: [][2]int{{0, 2}},
380+
moonCol: 12,
381+
maxBuildingHeight: 4,
382+
phase: 2,
383+
visibleRows: 6,
384+
shootingStarActive: true,
385+
shootingStarRow: 0,
386+
shootingStarCol: 4,
387+
}
388+
389+
out := stripSkylineANSI(m.View())
390+
for _, want := range []string{".go", "A-1", "★", "◐", "▀"} {
391+
if !strings.Contains(out, want) {
392+
t.Fatalf("expected view to contain %q, got:\n%s", want, out)
393+
}
394+
}
395+
}
396+
253397
func TestSkylineMinMax(t *testing.T) {
254398
tests := []struct {
255399
name string

0 commit comments

Comments
 (0)