package appimport ()// TestEngine encapsulates the methods required to load components and manage// asynchronous events within a unit test environment. It simulates the UI engine's// behavior, enabling comprehensive unit testing of UI components.typeTestEngineinterface {// Load initializes the test engine with the specified component, preparing it // for unit testing. It returns an error if the component cannot be // integrated or if the engine fails to initialize properly.Load(Composer) error// ConsumeNext advances the test engine's state by processing the next // operation in the dispatch queue. It allows for fine-grained control over // the sequence of operations during unit testing.ConsumeNext()// ConsumeAll advances the test engine's state by executing all pending // dispatched, deferred, and asynchronous operations. This ensures that the // component's state is fully updated, allowing for accurate assertions and // verifications in test scenarios.ConsumeAll()Start(framerate int)}// NewTestEngine creates and returns a new instance of test engine configured// for unit testing.func () TestEngine { , := url.Parse("/") := makeRequestPage(, nil) := makeRouter()returnnewEngine(context.Background(), &,nil, &,map[string]ActionHandler{"/test": func( Context, Action) {}, }, )}// Match compares the expected UI element with another UI element at a specified// location in a UI tree. It is the preferred function for matching UI elements// in tests due to its simplified usage.//// Example usage adapted for Match function://// tree := app.Div().Body(// app.H2().Body(app.Text("foo")),// app.P().Body(app.Text("bar")),// )//// err := app.Match(app.Div(), tree)// // err == nil if the root matches a Div element//// err := app.Match(app.H3(), tree, 0)// // err != nil because the first child is not an H3 element but a H2.//// err = app.Match(app.Text("bar"), tree, 1, 0)// // err == nil if the text of the first child of the second element is "bar"func ( UI, UI, ...int) error {returnTestMatch(, TestUIDescriptor{Path: TestPath(...),Expected: , })}// TestUIDescriptor describes a UI element and its hierarchical location// relative to parent elements for the purpose of testing.typeTestUIDescriptorstruct {// Path represents the sequence of child indices to navigate through the UI // tree to reach the element to be tested. An empty path implies the root. Path []int// Expected is the UI element that is expected to be found at the location // specified by Path. The comparison behavior varies depending on the type // of element; simple text elements are compared by text value, HTML // elements by attributes and event handlers, and components by the values // of their exported fields. Expected UI}// TestPath is a utility function that constructs a path, represented as a slice// of integers, for use in a TestUIDescriptor.func ( ...int) []int {return}// TestMatch searches for a UI element within a tree as described by a// TestUIDescriptor and verifies if it matches the Expected element. It returns// an error if the match is unsuccessful or if the path is invalid. Prefer using// the Match function for a simpler API.func ( UI, TestUIDescriptor) error {iflen(.Path) != 0 { := .Path[0]switch root := .(type) {caseHTML: := .body()if < 0 || >= len() {returnerrors.New("element to match is out of range").WithTag("type", reflect.TypeOf(.Expected)).WithTag("parent-type", reflect.TypeOf()).WithTag("parent-children-count", len()).WithTag("index", ) } .Path = .Path[1:]return ([], )caseComposer:if != 0 {returnerrors.New("element to match is out of range").WithTag("type", reflect.TypeOf(.Expected)).WithTag("parent-type", reflect.TypeOf()).WithTag("parent-children-count", 1).WithTag("index", ) } .Path = .Path[1:]return (.root(), ) } }returnmatch(, )}func match( UI, TestUIDescriptor) error {if , := reflect.TypeOf(.Expected), reflect.TypeOf(); != {returnerrors.New("types are not matching").WithTag("type", ).WithTag("expected-type", ) }switch .Expected.(type) {case *text:returnmatchText(.(*text), )caseHTML:returnmatchHTML(.(HTML), )caseComposer:returnmatchComponent(.(Composer), )case *raw:returnmatchRaw(.(*raw), )default:returnerrors.New("unsupported element").WithTag("type", reflect.TypeOf()) }}func matchText( *text, TestUIDescriptor) error { := := .Expected.(*text)if .value != .value {returnerrors.New("text does not match").WithTag("type", reflect.TypeOf()).WithTag("expected-value", .value).WithTag("current-value", .value) }returnnil}func matchHTML( HTML, TestUIDescriptor) error { := := .Expected.(HTML)if , := reflect.TypeOf(), reflect.TypeOf(); != || .Tag() != .Tag() {returnerrors.New("types are not matching").WithTag("type", ).WithTag("expected-type", ) }if := matchHTMLAttributes(.attrs(), .attrs()); != nil {returnerrors.New("attributes does not match").WithTag("type", reflect.TypeOf()).Wrap() }if := matchHTMLEventHandlers(.events(), .events()); != nil {returnerrors.New("event handlers does not match").WithTag("type", reflect.TypeOf()).Wrap() }returnnil}func matchHTMLAttributes(, attributes) error {for , := range { , := []if ! {returnerrors.New("expected attribute not found").WithTag("name", ) }if != {returnerrors.New("value does not match").WithTag("name", ).WithTag("value", ).WithTag("expected-value", ) } }for , := range {if , := []; ! {returnerrors.New("attribute is not expected").WithTag("name", ).WithTag("value", ) } }returnnil}func matchHTMLEventHandlers(, eventHandlers) error {for := range {if , := []; ! {returnerrors.New("expected event handler not found").WithTag("event", ) } }for := range {if , := []; ! {returnerrors.New("event handler is not expected").WithTag("event", ) } }returnnil}func matchComponent( Composer, TestUIDescriptor) error { := reflect.Indirect(reflect.ValueOf()) := reflect.Indirect(reflect.ValueOf(.Expected))for := 0; < .NumField(); ++ { := .Field() := .Field()if !.CanSet() {continue }if , := .Interface().(Compo); {continue }if !reflect.DeepEqual(.Interface(), .Interface()) {returnerrors.New("field are not matching").WithTag("type", reflect.TypeOf()).WithTag("field", .Type().Field().Name).WithTag("value", .Interface()).WithTag("expected-value", .Interface()) } }returnnil}func matchRaw( *raw, TestUIDescriptor) error { := := .Expected.(*raw)if .value != .value {returnerrors.New("the raw html element is not matching with the descriptor").WithTag("type", reflect.TypeOf()).WithTag("reason", "unexpected value").WithTag("expected-value", .value).WithTag("current-value", .value) }returnnil}
The pages are generated with Goldsv0.8.7. (GOOS=linux GOARCH=amd64)
Golds is a Go 101 project developed by Tapir Liu.
PR and bug reports are welcome and can be submitted to the issue list.
Please follow @zigo_101 (reachable from the left QR code) to get the latest news of Golds.