28f0a0d8e2
Item 4 of the test-suite review: - Delete TestEmitWithNoObserversIsNoop (no assertion; ranging a nil slice cannot panic) and TestStoreReturnsWiredStore (a getter returning its own field). - Collapse the four TestFilteredJobIndexes* tests into one table-driven TestFilteredJobIndexes, matching TestFilterValue above it. - Replace the TestMainViewBuilds smoke test with TestMainViewRecordStartupAddsHistoryRow, which calls the recordStartup closure for both wordings run.go selects between and asserts the rows reach the History table through its own cell callbacks. Keeps the unique coverage the review identified and adds the !windowShown branch. Item 5 is declined with measurements: the three RunJob tests cost 0.14 s combined, so merging them saves ~90 ms while forcing their three fixtures (including the only Manual trigger) into one. The runner package's runtime is the two timeout tests, not subprocess spawns. go vet and go test -race pass for src/app and src/ui. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
151 lines
4.9 KiB
Go
151 lines
4.9 KiB
Go
package ui
|
|
|
|
import (
|
|
"path/filepath"
|
|
"testing"
|
|
"time"
|
|
|
|
"gitea.mixdep.ru/mix/gosentry/src/app"
|
|
"gitea.mixdep.ru/mix/gosentry/src/domain"
|
|
"gitea.mixdep.ru/mix/gosentry/src/storage"
|
|
|
|
"fyne.io/fyne/v2"
|
|
"fyne.io/fyne/v2/container"
|
|
"fyne.io/fyne/v2/test"
|
|
"fyne.io/fyne/v2/widget"
|
|
)
|
|
|
|
// newTestStore builds a Store rooted in a temp directory. It is separate from
|
|
// newTestService so tests that need a non-default Config (or their own jobs)
|
|
// can adjust it before handing it to app.NewService.
|
|
func newTestStore(t *testing.T) *storage.Store {
|
|
t.Helper()
|
|
dir := t.TempDir()
|
|
return &storage.Store{
|
|
Paths: storage.Paths{
|
|
ExecutablePath: filepath.Join(dir, "gosentry"),
|
|
AppDir: dir,
|
|
ConfigPath: filepath.Join(dir, "gosentry.json"),
|
|
JobsDir: dir,
|
|
JobsPath: filepath.Join(dir, "jobs.json"),
|
|
LogsDir: filepath.Join(dir, "logs"),
|
|
},
|
|
Config: domain.Config{
|
|
JobsFile: "jobs.json",
|
|
LogsDir: "logs",
|
|
MaxLogFiles: 100,
|
|
MaxLogAgeDays: 30,
|
|
ExecutionMode: domain.ExecutionModeParallel,
|
|
OverlapPolicy: domain.OverlapPolicySkip,
|
|
DefaultTimeoutSeconds: 30,
|
|
KeepRunningInTray: true,
|
|
NotifyOnFailure: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func newTestService(t *testing.T) *app.Service {
|
|
t.Helper()
|
|
return app.NewService(newTestStore(t), nil)
|
|
}
|
|
|
|
// TestMainViewFitsTheDefaultWindowSize is the regression guard for F1: the
|
|
// assembled content must fit within the window size the app asks for, so Fyne
|
|
// never silently widens the window past it. The store's ConfigPath is
|
|
// deliberately long so the test also covers F3 — the config path label must
|
|
// not grow the Settings tab's minimum width with it.
|
|
func TestMainViewFitsTheDefaultWindowSize(t *testing.T) {
|
|
testApp := test.NewApp()
|
|
defer testApp.Quit()
|
|
|
|
w := testApp.NewWindow("test")
|
|
defer w.Close()
|
|
|
|
store := newTestStore(t)
|
|
store.Paths.ConfigPath = filepath.Join(t.TempDir(), "a-deliberately-long-directory-name-to-stress-the-config-path-label", "gosentry.json")
|
|
svc := app.NewService(store, nil)
|
|
defer svc.Stop()
|
|
|
|
content, _ := newMainView(w, svc)
|
|
min := content.MinSize()
|
|
if min.Width > defaultWindowWidth || min.Height > defaultWindowHeight {
|
|
t.Errorf("content.MinSize() = %v, want within %vx%v", min, defaultWindowWidth, defaultWindowHeight)
|
|
}
|
|
}
|
|
|
|
// historyTable returns the History tab's table. It is the only widget.Table the
|
|
// main view builds, so the search does not need to know the tab order.
|
|
func historyTable(t *testing.T, content fyne.CanvasObject) *widget.Table {
|
|
t.Helper()
|
|
tabs, ok := content.(*container.AppTabs)
|
|
if !ok {
|
|
t.Fatal("main view is not the expected AppTabs container")
|
|
}
|
|
for _, item := range tabs.Items {
|
|
found := findFirst(item.Content, func(o fyne.CanvasObject) bool {
|
|
_, ok := o.(*widget.Table)
|
|
return ok
|
|
})
|
|
if found != nil {
|
|
return found.(*widget.Table)
|
|
}
|
|
}
|
|
t.Fatal("main view has no history table")
|
|
return nil
|
|
}
|
|
|
|
// TestMainViewRecordStartupAddsHistoryRow covers the second return value of
|
|
// newMainView. run.go calls it once per launch with a different windowShown
|
|
// flag depending on whether the app started into the tray, and that call is the
|
|
// only thing that puts the startup receipt into History — so both the wording
|
|
// and the fact that the table is redrawn are worth pinning. Building the full
|
|
// tab set and setting it as the window content is a side benefit: no other test
|
|
// assembles all three tabs together.
|
|
func TestMainViewRecordStartupAddsHistoryRow(t *testing.T) {
|
|
testApp := test.NewApp()
|
|
defer testApp.Quit()
|
|
|
|
w := testApp.NewWindow("test")
|
|
defer w.Close()
|
|
|
|
svc := newTestService(t)
|
|
defer svc.Stop()
|
|
|
|
content, recordStartup := newMainView(w, svc)
|
|
w.SetContent(content)
|
|
|
|
table := historyTable(t, content)
|
|
if rows, _ := table.Length(); rows != 0 {
|
|
t.Fatalf("history rows before startup = %d, want 0", rows)
|
|
}
|
|
|
|
recordStartup(1500*time.Millisecond, true)
|
|
recordStartup(20*time.Millisecond, false)
|
|
|
|
rows, _ := table.Length()
|
|
if rows != 2 {
|
|
t.Fatalf("history rows after two startup records = %d, want 2", rows)
|
|
}
|
|
|
|
// Read the rows back through the table's own cell callbacks, which is what
|
|
// the redraw does; a value only in the events slice would not prove the
|
|
// table was refreshed with it.
|
|
cell := table.CreateCell()
|
|
cellText := func(row, col int) string {
|
|
table.UpdateCell(widget.TableCellID{Row: row, Col: col}, cell)
|
|
return cell.(*widget.Label).Text
|
|
}
|
|
if got := cellText(0, 2); got != "Application" {
|
|
t.Errorf("startup row job = %q, want %q", got, "Application")
|
|
}
|
|
if got := cellText(0, 3); got != "Started" {
|
|
t.Errorf("startup row state = %q, want %q", got, "Started")
|
|
}
|
|
if got := cellText(0, 4); got != "Window shown in 1.5s" {
|
|
t.Errorf("windowed startup detail = %q, want %q", got, "Window shown in 1.5s")
|
|
}
|
|
if got := cellText(1, 4); got != "Started in tray in 20ms" {
|
|
t.Errorf("tray startup detail = %q, want %q", got, "Started in tray in 20ms")
|
|
}
|
|
}
|