package page import ( "testing" "rhemawavetech.com/rhemawave/internal/download" ) // Regression: DownloadDone was closed twice when checkState() re-navigated // to HOME on the DOWNLOADING_ASSETS -> READY transition. The new Home page // saw the already-closed MessageChannel and tried to close DownloadDone // again, causing panic: close of closed channel. // // Primary fix: checkState() now skips navigation when transitioning between // states that both map to the HOME page. The downloadDone flag on Home is // defense-in-depth. // TestHomeDownloadDoneGuardPreventsDoubleClose verifies that the per-instance // downloadDone flag prevents closing DownloadDone more than once, even if a // new Home page somehow reaches the close path. func TestHomeDownloadDoneGuardPreventsDoubleClose(t *testing.T) { downloadDone := make(chan struct{}) // First Home closes DownloadDone. h1 := &Home{downloadDone: false} h1.Context = &PageCtx{DownloadDone: downloadDone} if !h1.downloadDone { h1.downloadDone = true close(h1.Context.DownloadDone) } // Second Home with guard already set (simulates defense-in-depth). h2 := &Home{downloadDone: true} h2.Context = &PageCtx{DownloadDone: downloadDone} if !h2.downloadDone { h2.downloadDone = true close(h2.Context.DownloadDone) // would panic without guard } } // TestHomeDownloadDoneGuardUnsetWouldPanic proves that without the // downloadDone guard, closing DownloadDone a second time panics. func TestHomeDownloadDoneGuardUnsetWouldPanic(t *testing.T) { downloadDone := make(chan struct{}) close(downloadDone) // pre-close defer func() { if r := recover(); r == nil { t.Error("expected panic when closing already-closed channel") } }() // New Home with downloadDone=false tries to close an already-closed channel. h := &Home{downloadDone: false} h.Context = &PageCtx{DownloadDone: downloadDone} if !h.downloadDone { h.downloadDone = true close(h.Context.DownloadDone) // panics } } // TestHomeDownloadFailedDoesNotClose verifies that when a download fails, // DownloadDone is NOT closed (the error path should not signal success). func TestHomeDownloadFailedDoesNotClose(t *testing.T) { downloadDone := make(chan struct{}) h := &Home{ downloadFailed: true, downloadDone: false, } h.Context = &PageCtx{ MessageChannel: make(chan download.DownloadStatus), DownloadDone: downloadDone, } // Simulate the Update logic: on failure, downloadDone should not be set // and DownloadDone should not be closed. if h.downloadFailed { // Error path: do not close DownloadDone. } else if !h.downloadDone { h.downloadDone = true close(h.Context.DownloadDone) } select { case <-downloadDone: t.Error("DownloadDone should not be closed on download failure") default: // Correct: channel is still open. } } // TestHomeDownloadDoneAlreadyClosed verifies that a Home page with // downloadDone=true never attempts to close the channel. func TestHomeDownloadDoneAlreadyClosed(t *testing.T) { downloadDone := make(chan struct{}) close(downloadDone) h := &Home{downloadDone: true} h.Context = &PageCtx{DownloadDone: downloadDone} // Guard should prevent any close attempt. if !h.downloadDone { h.downloadDone = true close(h.Context.DownloadDone) } }