// Package e2e provides end-to-end testing utilities for the Pad editor. // It allows testing the logic layer without requiring a Gio display. package e2e import ( "sync" "time" "pad/internal/ui" ) // FrameCapture receives frames from the logic layer without Gio. type FrameCapture struct { frames [][]ui.Element mu sync.Mutex closed chan struct{} } // NewFrameCapture creates a new frame capture instance. func NewFrameCapture() *FrameCapture { return &FrameCapture{ frames: make([][]ui.Element, 0), closed: make(chan struct{}), } } // CaptureFrame receives a frame and stores it for assertion. func (fc *FrameCapture) CaptureFrame(elements []ui.Element) { fc.mu.Lock() defer fc.mu.Unlock() // Copy to avoid mutation issues cp := make([]ui.Element, len(elements)) copy(cp, elements) fc.frames = append(fc.frames, cp) } // GetFrames returns all captured frames (thread-safe). func (fc *FrameCapture) GetFrames() [][]ui.Element { fc.mu.Lock() defer fc.mu.Unlock() result := make([][]ui.Element, len(fc.frames)) for i, frame := range fc.frames { result[i] = make([]ui.Element, len(frame)) copy(result[i], frame) } return result } // FrameCount returns the number of frames captured so far. func (fc *FrameCapture) FrameCount() int { fc.mu.Lock() defer fc.mu.Unlock() return len(fc.frames) } // WaitForFrame blocks until at least one frame is captured or timeout. func (fc *FrameCapture) WaitForFrame(timeout time.Duration) ([][]ui.Element, error) { return fc.WaitForFrameCount(1, timeout) } // WaitForFrameCount blocks until at least N frames are captured or timeout. func (fc *FrameCapture) WaitForFrameCount(count int, timeout time.Duration) ([][]ui.Element, error) { deadline := time.Now().Add(timeout) for { fc.mu.Lock() n := len(fc.frames) fc.mu.Unlock() if n >= count { return fc.GetFrames(), nil } if time.Now().After(deadline) { return nil, ErrTimeout } time.Sleep(10 * time.Millisecond) } } // Close signals the capture to stop. func (fc *FrameCapture) Close() { close(fc.closed) } // ErrTimeout is returned when a wait operation times out. var ErrTimeout = errTimeout{} type errTimeout struct{} func (errTimeout) Error() string { return "timeout waiting for frames" } // IsTimeout returns true if the error is a timeout error. func IsTimeout(err error) bool { _, ok := err.(errTimeout) return ok }