From 22793faf25329ddf5bf5131371dde618facf2087 Mon Sep 17 00:00:00 2001 From: Radoslav Dimitrov Date: Mon, 26 Oct 2026 11:31:17 +0200 Subject: [PATCH] test(mecatui): make template clock refresh test deterministic TestStatusLine_Scenario4_DebounceTemplateRefreshAndClock relied on a real 1s ticker and a 1.5s deadline. Unchanged renders are not published, so when the first tick rendered the same HH:MM:SS as the submit-time render, the refresh was dropped and the next tick missed the deadline. This flaked under -race on loaded CI runners. Split newTemplateSourceWithTicks out of newTemplateSource so the test can drive interval ticks and the submit-time clock by hand. Co-Authored-By: Claude Opus 5.5 --- cmd/mecatui/customization/lifecycle_test.go | 19 +++++++++++-------- cmd/mecatui/customization/template.go | 14 +++++++++----- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/cmd/mecatui/customization/lifecycle_test.go b/cmd/mecatui/customization/lifecycle_test.go index dd985815b1..8d447cd595 100644 --- a/cmd/mecatui/customization/lifecycle_test.go +++ b/cmd/mecatui/customization/lifecycle_test.go @@ -25,18 +25,21 @@ func TestStatusLine_Scenario4_DebounceTemplateRefreshAndClock(t *testing.T) { t.Fatalf("command footer = %q, want latest refresh", got) } - templates := NewTemplateSource(TemplateSet{Footer: SurfaceTemplates{Full: `
{{.Clock.Now.Format "15:04:05"}}
`}}, time.Second) + // Interval ticks carry the clock value; drive them by hand so the refresh + // never lands in the same wall-clock second as the first render. + ticks := make(chan time.Time) + templates := newTemplateSourceWithTicks(TemplateSet{Footer: SurfaceTemplates{Full: ``}}, ticks) t.Cleanup(func() { _ = templates.Close(context.Background()) }) + templates.now = func() time.Time { return time.Date(2026, time.August, 28, 12, 0, 0, 0, time.UTC) } templates.Submit(Input{Terminal: Terminal{FooterAvailCols: 80}}) waitStatusChange(t, templates) - first := statusSurfaceText(templates.Latest().Footer) - select { - case <-templates.Changed(): - case <-time.After(1500 * time.Millisecond): - t.Fatal("template clock did not refresh on its interval") + if got := statusSurfaceText(templates.Latest().Footer); got != "12:00:00" { + t.Fatalf("template footer = %q, want submit-time clock", got) } - if got := statusSurfaceText(templates.Latest().Footer); got == first { - t.Fatalf("template clock did not advance: %q", got) + ticks <- time.Date(2026, time.August, 28, 12, 0, 1, 0, time.UTC) + waitStatusChange(t, templates) + if got := statusSurfaceText(templates.Latest().Footer); got != "12:00:01" { + t.Fatalf("template footer = %q, want interval-tick clock", got) } } diff --git a/cmd/mecatui/customization/template.go b/cmd/mecatui/customization/template.go index f195155d8b..6d3b423b71 100644 --- a/cmd/mecatui/customization/template.go +++ b/cmd/mecatui/customization/template.go @@ -33,16 +33,20 @@ func newTemplateSource(templates TemplateSet, interval time.Duration) *statusLin ticker = time.NewTicker(interval) ticks = ticker.C } + s := newTemplateSourceWithTicks(templates, ticks) + if ticker != nil { + s.stopTicker = ticker.Stop + } + return s +} + +func newTemplateSourceWithTicks(templates TemplateSet, ticks <-chan time.Time) *statusLineSource { defaults := defaultTemplateSet() header := compileVariants(templates.Header, defaults.Header) footer := compileVariants(templates.Footer, defaults.Footer) - s := newSource(ticks, func(ctx context.Context, input Input) Result { + return newSource(ticks, func(ctx context.Context, input Input) Result { return renderTemplates(ctx, header, footer, input) }) - if ticker != nil { - s.stopTicker = ticker.Stop - } - return s } type templateVariants struct{ full, compact, minimal statusTemplate }