- internal/test/e2e/: FrameCapture, Harness, ElementAssertions, helpers - internal/editor/logic.go: Add done channel and Done() method for graceful shutdown - internal/editor/mock_setup.go: Mock filesystem for tests - internal/browser/: Browser layout and search logic - internal/io/: Worker pool for async tasks - Update architecture docs and spec
124 lines
2.8 KiB
Go
124 lines
2.8 KiB
Go
package e2e
|
|
|
|
import (
|
|
"sync"
|
|
"time"
|
|
|
|
"pad/internal/editor"
|
|
"pad/internal/ui"
|
|
)
|
|
|
|
// Harness orchestrates the test environment for e2e tests.
|
|
type Harness struct {
|
|
logic *editor.Logic
|
|
capture *FrameCapture
|
|
logicWg sync.WaitGroup
|
|
frameReceiverDone chan struct{}
|
|
wg sync.WaitGroup
|
|
}
|
|
|
|
// HarnessOption configures the test harness.
|
|
type HarnessOption func(*Harness)
|
|
|
|
// NewHarness creates a test harness with the given options.
|
|
func NewHarness(opts ...HarnessOption) *Harness {
|
|
h := &Harness{
|
|
logic: editor.NewLogic(),
|
|
capture: NewFrameCapture(),
|
|
frameReceiverDone: make(chan struct{}),
|
|
}
|
|
|
|
for _, opt := range opts {
|
|
opt(h)
|
|
}
|
|
|
|
return h
|
|
}
|
|
|
|
// Run starts the logic goroutine and frame capture.
|
|
func (h *Harness) Run() {
|
|
h.logicWg.Add(1)
|
|
h.wg.Add(1)
|
|
go func() {
|
|
defer h.wg.Done()
|
|
defer h.logicWg.Done()
|
|
h.logic.Run()
|
|
}()
|
|
|
|
h.wg.Add(1)
|
|
go func() {
|
|
defer h.wg.Done()
|
|
for {
|
|
select {
|
|
case frame := <-h.logic.FrameChan():
|
|
h.capture.CaptureFrame(frame)
|
|
case <-h.frameReceiverDone:
|
|
return
|
|
}
|
|
}
|
|
}()
|
|
}
|
|
|
|
// SendConfig simulates a window config event (resize).
|
|
func (h *Harness) SendConfig(width, height int) {
|
|
h.logic.ConfigChan() <- editor.ConfigEvent{
|
|
PixelWidth: width,
|
|
PixelHeight: height,
|
|
}
|
|
}
|
|
|
|
// SendScale simulates a scale factor change.
|
|
func (h *Harness) SendScale(scale float32) {
|
|
h.logic.ConfigChan() <- editor.ScaleEvent{Scale: scale}
|
|
}
|
|
|
|
// SendInput simulates user input events.
|
|
func (h *Harness) SendInput(events []ui.InputEvent) {
|
|
h.logic.InputChan() <- events
|
|
}
|
|
|
|
// SendSearchQuery simulates a search query update.
|
|
func (h *Harness) SendSearchQuery(query string) {
|
|
h.logic.SearchQueryChan() <- query
|
|
}
|
|
|
|
// GetFrames returns all captured frames.
|
|
func (h *Harness) GetFrames() [][]ui.Element {
|
|
return h.capture.GetFrames()
|
|
}
|
|
|
|
// FrameCount returns the number of frames captured so far.
|
|
func (h *Harness) FrameCount() int {
|
|
return h.capture.FrameCount()
|
|
}
|
|
|
|
// WaitForFrameCount blocks until at least N frames are captured.
|
|
func (h *Harness) WaitForFrameCount(count int, timeout time.Duration) ([][]ui.Element, error) {
|
|
return h.capture.WaitForFrameCount(count, timeout)
|
|
}
|
|
|
|
// WaitForFrame blocks until at least one frame is captured.
|
|
func (h *Harness) WaitForFrame(timeout time.Duration) ([][]ui.Element, error) {
|
|
return h.capture.WaitForFrame(timeout)
|
|
}
|
|
|
|
// Cleanup stops all goroutines.
|
|
func (h *Harness) Cleanup() {
|
|
// 1. Signal logic to stop sending frames
|
|
h.logic.Done()
|
|
|
|
// 2. Wait for logic goroutine to fully exit (no more frameChan sends)
|
|
h.logicWg.Wait()
|
|
|
|
// 3. Now safe to stop the frame receiver
|
|
close(h.frameReceiverDone)
|
|
|
|
// 4. Wait for frame receiver to finish
|
|
h.wg.Wait()
|
|
|
|
h.capture.Close()
|
|
}
|
|
|
|
// DefaultTimeout is the default timeout for waiting operations.
|
|
const DefaultTimeout = 5 * time.Second
|