|
| 1 | +package tui_test |
| 2 | + |
| 3 | +import ( |
| 4 | + "os" |
| 5 | + "path/filepath" |
| 6 | + "strings" |
| 7 | + "testing" |
| 8 | + "time" |
| 9 | + |
| 10 | + "github.com/stretchr/testify/require" |
| 11 | + |
| 12 | + "github.com/docker/docker-agent/pkg/tui/messages" |
| 13 | + "github.com/docker/docker-agent/pkg/tui/styles" |
| 14 | + "github.com/docker/docker-agent/pkg/tui/tuitest" |
| 15 | +) |
| 16 | + |
| 17 | +// TestTheme_HotReload is the regression net for the theme watcher wiring |
| 18 | +// (issue #3501): editing the active custom theme file while the TUI runs |
| 19 | +// must hot-reload it without a restart. It drives the full loop — switch to |
| 20 | +// a user theme, edit its file on disk, and wait for the watcher to emit |
| 21 | +// ThemeFileChangedMsg, which surfaces as the "Theme hot-reloaded" |
| 22 | +// notification. No LLM call is made, so the cassette is empty. |
| 23 | +func TestTheme_HotReload(t *testing.T) { |
| 24 | + // The theme registry is process-global; restore the default so other |
| 25 | + // tests never see this test's theme. Registered before newTUI so that |
| 26 | + // (LIFO) it runs only after the program stopped rendering. |
| 27 | + t.Cleanup(func() { styles.ApplyThemeRef(styles.DefaultThemeRef) }) |
| 28 | + d := newTUI(t, "testdata/basic.yaml", 120, 40) |
| 29 | + |
| 30 | + // isolateState (inside newTUI) redirected the data dir to a temp dir, so |
| 31 | + // this writes the user theme where the running TUI expects it. |
| 32 | + themesDir := styles.ThemesDir() |
| 33 | + require.NoError(t, os.MkdirAll(themesDir, 0o755)) |
| 34 | + themePath := filepath.Join(themesDir, "hotreload.yaml") |
| 35 | + require.NoError(t, os.WriteFile(themePath, []byte("version: 1\nname: Hot Reload Before\n"), 0o644)) |
| 36 | + |
| 37 | + // Switching themes re-targets the watcher onto the new theme's file. |
| 38 | + d.Send(messages.ChangeThemeMsg{ThemeRef: "user:hotreload"}). |
| 39 | + WaitFor(tuitest.Contains("Theme changed to Hot Reload Before")) |
| 40 | + |
| 41 | + // The watcher arms asynchronously (ThemeChangedMsg is sequenced after the |
| 42 | + // notification above) and debounces events for 500ms, so a single write |
| 43 | + // could slip in before fsnotify is attached. Re-edit the file on every |
| 44 | + // poll — like a user tweaking colors — until the reload lands. The 700ms |
| 45 | + // interval exceeds the debounce so rewrites cannot starve the timer. |
| 46 | + require.Eventually(t, func() bool { |
| 47 | + require.NoError(t, os.WriteFile(themePath, []byte("version: 1\nname: Hot Reload After\n"), 0o644)) |
| 48 | + return strings.Contains(d.Frame(), "Theme hot-reloaded") |
| 49 | + }, 15*time.Second, 700*time.Millisecond, "editing the theme file should hot-reload it") |
| 50 | + |
| 51 | + require.Eventually(t, func() bool { |
| 52 | + return styles.CurrentTheme().Name == "Hot Reload After" |
| 53 | + }, 3*time.Second, 50*time.Millisecond, "styles.CurrentTheme() should pick up the edited theme") |
| 54 | +} |
0 commit comments