test(daemon): repair vacuous assertion and undeferred cleanup in notices tests
model_test.go's degenerate-dimensions test asserted frame dims are never negative, which Render's max(_, 0) clamping already guarantees by construction; assert the expected clamped dimensions per case instead. notices_overlay_test.go's q/Ctrl-C close test called release() as the loop body's final statement, so a require failure on the 'q' iteration would Goexit the test, skip the 0x03 iteration, and leak the blocking PTY. Move each iteration into t.Run with a deferred release().
- commit
- ca926196a4bb024b5937cb40b96cfa1a48e8c352
- author
- brice · · 2026-07-20 13:41 UTC
- parent
- 211b2e1
- signature
- GPG Verified
CD88F9E564E0102646FB6077A1A07C7C7C3E349D· arch-gpg Verified for the committer email
2 files · +36 -19
M internal/usecase/daemon/notices_overlay_test.go +17 -14
| @@ -1,6 +1,7 @@ | |||
| 1 | 1 | package daemon | |
| 2 | 2 | ||
| 3 | 3 | import ( | |
| 4 | + | "fmt" | |
| 4 | 5 | "testing" | |
| 5 | 6 | "time" | |
| 6 | 7 | ||
| @@ -72,20 +73,22 @@ func TestNoticesJKNavigatesSelection(t *testing.T) { | |||
| 72 | 73 | ||
| 73 | 74 | func TestNoticesQAndCtrlCCloseImmediatelyAndClearState(t *testing.T) { | |
| 74 | 75 | for _, key := range []byte{'q', 0x03} { | |
| 75 | - | p, release := newBlockingPTY(t) | |
| 76 | - | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 77 | - | d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)}) | |
| 78 | - | ||
| 79 | - | d.enterNotices(sess, ac) | |
| 80 | - | awaitFrame(t, sends, ports.MsgOutput) | |
| 81 | - | require.True(t, ac.overlays.noticesActive()) | |
| 82 | - | ||
| 83 | - | d.handleInput(sess, ac, []byte{key}) | |
| 84 | - | awaitFrame(t, sends, ports.MsgOutput) | |
| 85 | - | ||
| 86 | - | require.False(t, ac.overlays.noticesActive()) | |
| 87 | - | require.Nil(t, ac.overlays.noticesOverlay) | |
| 88 | - | release() | |
| 76 | + | t.Run(fmt.Sprintf("key=%q", key), func(t *testing.T) { | |
| 77 | + | p, release := newBlockingPTY(t) | |
| 78 | + | defer release() | |
| 79 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 80 | + | d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)}) | |
| 81 | + | ||
| 82 | + | d.enterNotices(sess, ac) | |
| 83 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 84 | + | require.True(t, ac.overlays.noticesActive()) | |
| 85 | + | ||
| 86 | + | d.handleInput(sess, ac, []byte{key}) | |
| 87 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 88 | + | ||
| 89 | + | require.False(t, ac.overlays.noticesActive()) | |
| 90 | + | require.Nil(t, ac.overlays.noticesOverlay) | |
| 91 | + | }) | |
| 89 | 92 | } | |
| 90 | 93 | } | |
| 91 | 94 | ||
| @@ -1,6 +1,7 @@ | |||||
| 1 | package daemon | 1 | package daemon | ||
| 2 | 2 | ||||
| 3 | import ( | 3 | import ( | ||
| 4 | + | "fmt" | |||
| 4 | "testing" | 5 | "testing" | ||
| 5 | "time" | 6 | "time" | ||
| 6 | 7 | ||||
| @@ -72,20 +73,22 @@ func TestNoticesJKNavigatesSelection(t *testing.T) { | |||||
| 72 | 73 | ||||
| 73 | func TestNoticesQAndCtrlCCloseImmediatelyAndClearState(t *testing.T) { | 74 | func TestNoticesQAndCtrlCCloseImmediatelyAndClearState(t *testing.T) { | ||
| 74 | for _, key := range []byte{'q', 0x03} { | 75 | for _, key := range []byte{'q', 0x03} { | ||
| 75 | - | p, release := newBlockingPTY(t) | 76 | + | t.Run(fmt.Sprintf("key=%q", key), func(t *testing.T) { |
| 76 | - | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | 77 | + | p, release := newBlockingPTY(t) |
| 77 | - | d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)}) | 78 | + | defer release() |
| 78 | - | 79 | + | d, sess, ac, sends := newManualSessionWithPTYs(t, p) | |
| 79 | - | d.enterNotices(sess, ac) | 80 | + | d.notices.record(domain.Notification{Code: domain.NoticePaneSpawn, Message: "m", Time: time.Unix(1, 0)}) |
| 80 | - | awaitFrame(t, sends, ports.MsgOutput) | 81 | + | |
| 81 | - | require.True(t, ac.overlays.noticesActive()) | 82 | + | d.enterNotices(sess, ac) |
| 82 | - | 83 | + | awaitFrame(t, sends, ports.MsgOutput) | |
| 83 | - | d.handleInput(sess, ac, []byte{key}) | 84 | + | require.True(t, ac.overlays.noticesActive()) |
| 84 | - | awaitFrame(t, sends, ports.MsgOutput) | 85 | + | |
| 85 | - | 86 | + | d.handleInput(sess, ac, []byte{key}) | |
| 86 | - | require.False(t, ac.overlays.noticesActive()) | 87 | + | awaitFrame(t, sends, ports.MsgOutput) |
| 87 | - | require.Nil(t, ac.overlays.noticesOverlay) | 88 | + | |
| 88 | - | release() | 89 | + | require.False(t, ac.overlays.noticesActive()) |
| 90 | + | require.Nil(t, ac.overlays.noticesOverlay) | |||
| 91 | + | }) | |||
| 89 | } | 92 | } | ||
| 90 | } | 93 | } | ||
| 91 | 94 | ||||
M internal/usecase/notices/model_test.go +19 -5
| @@ -157,11 +157,25 @@ func TestRenderEmptyHistoryShowsPlaceholderNotBlank(t *testing.T) { | |||
| 157 | 157 | ||
| 158 | 158 | func TestRenderDegenerateDimensionsDoesNotPanic(t *testing.T) { | |
| 159 | 159 | m := New([]domain.Notification{{Message: "x", Time: time.Unix(0, 0)}}, time.Unix(0, 0)) | |
| 160 | - | sizes := []domain.Size{{Cols: 0, Rows: 0}, {Cols: -1, Rows: 5}, {Cols: 5, Rows: -1}, {Cols: 0, Rows: 5}, {Cols: 1, Rows: 1}} | |
| 161 | - | for _, size := range sizes { | |
| 162 | - | frame := m.Render(size, testStyles()) | |
| 163 | - | if frame.Width < 0 || frame.Height < 0 { | |
| 164 | - | t.Fatalf("Render(%+v) produced negative frame dims", size) | |
| 160 | + | tests := []struct { | |
| 161 | + | size domain.Size | |
| 162 | + | wantWidth int | |
| 163 | + | wantHeight int | |
| 164 | + | }{ | |
| 165 | + | // Render clamps each negative dimension to 0 independently via | |
| 166 | + | // max(inner.Cols, 0) / max(inner.Rows, 0); it never clamps the pair | |
| 167 | + | // together, so a negative Cols with a valid Rows still yields a | |
| 168 | + | // zero-width, full-height frame (and vice versa). | |
| 169 | + | {domain.Size{Cols: 0, Rows: 0}, 0, 0}, | |
| 170 | + | {domain.Size{Cols: -1, Rows: 5}, 0, 5}, | |
| 171 | + | {domain.Size{Cols: 5, Rows: -1}, 5, 0}, | |
| 172 | + | {domain.Size{Cols: 0, Rows: 5}, 0, 5}, | |
| 173 | + | {domain.Size{Cols: 1, Rows: 1}, 1, 1}, | |
| 174 | + | } | |
| 175 | + | for _, tt := range tests { | |
| 176 | + | frame := m.Render(tt.size, testStyles()) | |
| 177 | + | if frame.Width != tt.wantWidth || frame.Height != tt.wantHeight { | |
| 178 | + | t.Fatalf("Render(%+v) = frame{Width: %d, Height: %d}, want {Width: %d, Height: %d}", tt.size, frame.Width, frame.Height, tt.wantWidth, tt.wantHeight) | |
| 165 | 179 | } | |
| 166 | 180 | } | |
| 167 | 181 | } | |
| @@ -157,11 +157,25 @@ func TestRenderEmptyHistoryShowsPlaceholderNotBlank(t *testing.T) { | |||||
| 157 | 157 | ||||
| 158 | func TestRenderDegenerateDimensionsDoesNotPanic(t *testing.T) { | 158 | func TestRenderDegenerateDimensionsDoesNotPanic(t *testing.T) { | ||
| 159 | m := New([]domain.Notification{{Message: "x", Time: time.Unix(0, 0)}}, time.Unix(0, 0)) | 159 | m := New([]domain.Notification{{Message: "x", Time: time.Unix(0, 0)}}, time.Unix(0, 0)) | ||
| 160 | - | sizes := []domain.Size{{Cols: 0, Rows: 0}, {Cols: -1, Rows: 5}, {Cols: 5, Rows: -1}, {Cols: 0, Rows: 5}, {Cols: 1, Rows: 1}} | 160 | + | tests := []struct { |
| 161 | - | for _, size := range sizes { | 161 | + | size domain.Size |
| 162 | - | frame := m.Render(size, testStyles()) | 162 | + | wantWidth int |
| 163 | - | if frame.Width < 0 || frame.Height < 0 { | 163 | + | wantHeight int |
| 164 | - | t.Fatalf("Render(%+v) produced negative frame dims", size) | 164 | + | }{ |
| 165 | + | // Render clamps each negative dimension to 0 independently via | |||
| 166 | + | // max(inner.Cols, 0) / max(inner.Rows, 0); it never clamps the pair | |||
| 167 | + | // together, so a negative Cols with a valid Rows still yields a | |||
| 168 | + | // zero-width, full-height frame (and vice versa). | |||
| 169 | + | {domain.Size{Cols: 0, Rows: 0}, 0, 0}, | |||
| 170 | + | {domain.Size{Cols: -1, Rows: 5}, 0, 5}, | |||
| 171 | + | {domain.Size{Cols: 5, Rows: -1}, 5, 0}, | |||
| 172 | + | {domain.Size{Cols: 0, Rows: 5}, 0, 5}, | |||
| 173 | + | {domain.Size{Cols: 1, Rows: 1}, 1, 1}, | |||
| 174 | + | } | |||
| 175 | + | for _, tt := range tests { | |||
| 176 | + | frame := m.Render(tt.size, testStyles()) | |||
| 177 | + | if frame.Width != tt.wantWidth || frame.Height != tt.wantHeight { | |||
| 178 | + | t.Fatalf("Render(%+v) = frame{Width: %d, Height: %d}, want {Width: %d, Height: %d}", tt.size, frame.Width, frame.Height, tt.wantWidth, tt.wantHeight) | |||
| 165 | } | 179 | } | ||
| 166 | } | 180 | } | ||
| 167 | } | 181 | } | ||