Pad/internal/test/e2e/assertions.go
Greg Pomerantz 6a43e3c0db Add e2e test harness for testing editor logic without Gio display
- 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
2026-05-31 08:56:30 -04:00

358 lines
8.7 KiB
Go

package e2e
import (
"fmt"
"reflect"
"testing"
"pad/internal/ui"
)
// ElementAssertions provides fluent assertions on element slices.
type ElementAssertions struct {
elements []ui.Element
t testing.TB
failed bool
}
// NewElementAssertions creates assertions for a frame.
func NewElementAssertions(t testing.TB, elements []ui.Element) *ElementAssertions {
return &ElementAssertions{
elements: elements,
t: t,
failed: false,
}
}
// HasElementCount asserts the number of elements.
func (ea *ElementAssertions) HasElementCount(count int) *ElementAssertions {
if len(ea.elements) != count {
ea.t.Errorf("expected %d elements, got %d", count, len(ea.elements))
ea.failed = true
}
return ea
}
// HasElementWithID asserts an element exists with the given ID.
func (ea *ElementAssertions) HasElementWithID(id string) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if idElem, ok := elem.(interface{ ID() string }); ok {
if idElem.ID() == id {
found = true
break
}
}
}
if !found {
ea.t.Errorf("expected element with ID %q, not found", id)
ea.failed = true
}
return ea
}
// HasElementOfType asserts an element of the given type exists.
func (ea *ElementAssertions) HasElementOfType(t reflect.Type) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if reflect.TypeOf(elem) == t {
found = true
break
}
}
if !found {
ea.t.Errorf("expected element of type %v, not found", t)
ea.failed = true
}
return ea
}
// HasLabelWithText asserts a Label element contains the given text.
func (ea *ElementAssertions) HasLabelWithText(text string) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if label, ok := elem.(ui.Label); ok {
if label.Text == text {
found = true
break
}
}
}
if !found {
ea.t.Errorf("expected Label with text %q, not found", text)
ea.failed = true
}
return ea
}
// HasListViewWithItemCount asserts a ListView has the expected number of items.
func (ea *ElementAssertions) HasListViewWithItemCount(count int) *ElementAssertions {
var listView *ui.ListView
for _, elem := range ea.elements {
if lv, ok := elem.(ui.ListView); ok {
listView = &lv
break
}
}
if listView == nil {
ea.t.Error("expected ListView element, not found")
ea.failed = true
return ea
}
if len(listView.Items) != count {
ea.t.Errorf("expected ListView with %d items, got %d", count, len(listView.Items))
ea.failed = true
}
return ea
}
// HasListViewWithItems asserts a ListView has the expected items.
func (ea *ElementAssertions) HasListViewWithItems(expected []string) *ElementAssertions {
var listView *ui.ListView
for _, elem := range ea.elements {
if lv, ok := elem.(ui.ListView); ok {
listView = &lv
break
}
}
if listView == nil {
ea.t.Error("expected ListView element, not found")
ea.failed = true
return ea
}
for _, expItem := range expected {
found := false
for _, item := range listView.Items {
if item.Text == expItem {
found = true
break
}
}
if !found {
ea.t.Errorf("expected ListView item %q, not found", expItem)
ea.failed = true
}
}
return ea
}
// HasElementInRegion asserts an element exists within the given region bounds.
func (ea *ElementAssertions) HasElementInRegion(region ui.Region) *ElementAssertions {
found := false
for _, elem := range ea.elements {
elemRegion := elem.Region()
if elemRegion.X >= region.X &&
elemRegion.Y >= region.Y &&
elemRegion.X+elemRegion.W <= region.X+region.W &&
elemRegion.Y+elemRegion.H <= region.Y+region.H {
found = true
break
}
}
if !found {
ea.t.Errorf("expected element in region %+v, not found", region)
ea.failed = true
}
return ea
}
// HasVisibleElement asserts at least one visible element exists.
func (ea *ElementAssertions) HasVisibleElement() *ElementAssertions {
found := false
for _, elem := range ea.elements {
if elem.Visible() {
found = true
break
}
}
if !found {
ea.t.Error("expected at least one visible element, none found")
ea.failed = true
}
return ea
}
// HasButtonWithText asserts a Button element with the given text exists.
func (ea *ElementAssertions) HasButtonWithText(text string) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if btn, ok := elem.(ui.Button); ok {
if btn.Text == text {
found = true
break
}
}
}
if !found {
ea.t.Errorf("expected Button with text %q, not found", text)
ea.failed = true
}
return ea
}
// HasTextFieldWithID asserts a TextField with the given ID exists.
func (ea *ElementAssertions) HasTextFieldWithID(id string) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if tf, ok := elem.(ui.TextField); ok {
if tf.ID() == id {
found = true
break
}
}
}
if !found {
ea.t.Errorf("expected TextField with ID %q, not found", id)
ea.failed = true
}
return ea
}
// HasIconWithName asserts an Icon with the given name exists.
func (ea *ElementAssertions) HasIconWithName(name string) *ElementAssertions {
found := false
for _, elem := range ea.elements {
if icon, ok := elem.(ui.Icon); ok {
if icon.Name == name {
found = true
break
}
}
}
if !found {
ea.t.Errorf("expected Icon with name %q, not found", name)
ea.failed = true
}
return ea
}
// GetElementByID returns the element with the given ID, or nil if not found.
func (ea *ElementAssertions) GetElementByID(id string) ui.Element {
for _, elem := range ea.elements {
if idElem, ok := elem.(interface{ ID() string }); ok {
if idElem.ID() == id {
return elem
}
}
}
return nil
}
// GetElementByType returns the first element of the given type, or nil if not found.
func (ea *ElementAssertions) GetElementByType(t reflect.Type) ui.Element {
for _, elem := range ea.elements {
if reflect.TypeOf(elem) == t {
return elem
}
}
return nil
}
// GetListItems returns all list item texts from the first ListView found.
func (ea *ElementAssertions) GetListItems() []string {
for _, elem := range ea.elements {
if lv, ok := elem.(ui.ListView); ok {
items := make([]string, len(lv.Items))
for i, item := range lv.Items {
items[i] = item.Text
}
return items
}
}
return nil
}
// GetLabelTexts returns all label texts from the frame.
func (ea *ElementAssertions) GetLabelTexts() []string {
var texts []string
for _, elem := range ea.elements {
if label, ok := elem.(ui.Label); ok {
texts = append(texts, label.Text)
}
}
return texts
}
// GetButtonTexts returns all button texts from the frame.
func (ea *ElementAssertions) GetButtonTexts() []string {
var texts []string
for _, elem := range ea.elements {
if btn, ok := elem.(ui.Button); ok {
texts = append(texts, btn.Text)
}
}
return texts
}
// GetElementRegions returns all element regions from the frame.
func (ea *ElementAssertions) GetElementRegions() []ui.Region {
regions := make([]ui.Region, len(ea.elements))
for i, elem := range ea.elements {
regions[i] = elem.Region()
}
return regions
}
// Failed returns true if any assertion has failed.
func (ea *ElementAssertions) Failed() bool {
return ea.failed
}
// --- Helper functions ---
// regionsOverlap checks if two regions overlap.
func regionsOverlap(a, b ui.Region) bool {
return !(a.X+a.W <= b.X || b.X+b.W <= a.X ||
a.Y+a.H <= b.Y || b.Y+b.H <= a.Y)
}
// AssertNoOverlappingElements asserts that no elements in the frame overlap.
func AssertNoOverlappingElements(t testing.TB, frame []ui.Element) {
for i := 0; i < len(frame); i++ {
for j := i + 1; j < len(frame); j++ {
regionA := frame[i].Region()
regionB := frame[j].Region()
if regionsOverlap(regionA, regionB) {
t.Errorf("elements %d (%T) and %d (%T) overlap: %+v vs %+v",
i, frame[i], j, frame[j], regionA, regionB)
}
}
}
}
// AssertElementPositions asserts that elements are positioned within expected bounds.
func AssertElementPositions(t testing.TB, frame []ui.Element, bounds ui.Region) {
for i, elem := range frame {
region := elem.Region()
if region.X < bounds.X || region.Y < bounds.Y ||
region.X+region.W > bounds.X+bounds.W ||
region.Y+region.H > bounds.Y+bounds.H {
t.Errorf("element %d (%T) is outside expected bounds: %+v vs %+v",
i, elem, region, bounds)
}
}
}
// AssertFrameStructure asserts the basic structure of a frame.
func AssertFrameStructure(t testing.TB, frame []ui.Element, expectedCount int) {
if len(frame) != expectedCount {
t.Errorf("expected %d elements, got %d", expectedCount, len(frame))
}
}
// PrintFrame prints all elements in a frame for debugging.
func PrintFrame(frame []ui.Element) string {
var result string
for i, elem := range frame {
region := elem.Region()
result += fmt.Sprintf(" [%d] %T: region=%+v visible=%v\n",
i, elem, region, elem.Visible())
}
return result
}