318 lines
9.6 KiB
Go
318 lines
9.6 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
package webview
|
|
|
|
import (
|
|
"context"
|
|
"strings"
|
|
"testing"
|
|
"time"
|
|
)
|
|
|
|
func newAngularTestHarness(t *testing.T, onMessage func(*fakeCDPTarget, cdpMessage)) (*AngularHelper, *fakeCDPTarget, *CDPClient) {
|
|
t.Helper()
|
|
|
|
server := newFakeCDPServer(t)
|
|
target := server.primaryTarget()
|
|
target.onMessage = onMessage
|
|
|
|
client := newConnectedCDPClient(t, target)
|
|
wv := &Webview{
|
|
client: client,
|
|
ctx: context.Background(),
|
|
timeout: time.Second,
|
|
consoleLogs: make([]ConsoleMessage, 0),
|
|
consoleLimit: 10,
|
|
}
|
|
return NewAngularHelper(wv), target, client
|
|
}
|
|
|
|
func TestAngular_SetTimeout_Good(t *testing.T) {
|
|
ah := NewAngularHelper(&Webview{})
|
|
ah.SetTimeout(5 * time.Second)
|
|
if ah.timeout != 5*time.Second {
|
|
t.Fatalf("SetTimeout = %v, want 5s", ah.timeout)
|
|
}
|
|
}
|
|
|
|
func TestAngular_WaitForAngular_Bad_NotAngular(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyValue(msg.ID, false)
|
|
})
|
|
|
|
if err := ah.WaitForAngular(); err == nil {
|
|
t.Fatal("WaitForAngular succeeded for a non-Angular page")
|
|
}
|
|
}
|
|
|
|
func TestAngular_WaitForAngular_Good(t *testing.T) {
|
|
var evaluateCount int
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
evaluateCount++
|
|
expr, _ := msg.Params["expression"].(string)
|
|
if strings.Contains(expr, "getAllAngularRootElements") || strings.Contains(expr, "[ng-version]") {
|
|
target.replyValue(msg.ID, true)
|
|
return
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.WaitForAngular(); err != nil {
|
|
t.Fatalf("WaitForAngular returned error: %v", err)
|
|
}
|
|
if evaluateCount < 2 {
|
|
t.Fatalf("WaitForAngular made %d evaluate calls, want at least 2", evaluateCount)
|
|
}
|
|
}
|
|
|
|
func TestAngular_waitForZoneStability_Good_FallsBackToPolling(t *testing.T) {
|
|
var calls int
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
calls++
|
|
expr, _ := msg.Params["expression"].(string)
|
|
switch {
|
|
case strings.Contains(expr, "new Promise"):
|
|
target.replyError(msg.ID, "zone probe failed")
|
|
default:
|
|
target.replyValue(msg.ID, true)
|
|
}
|
|
})
|
|
|
|
if err := ah.waitForZoneStability(context.Background()); err != nil {
|
|
t.Fatalf("waitForZoneStability returned error: %v", err)
|
|
}
|
|
if calls < 2 {
|
|
t.Fatalf("waitForZoneStability made %d evaluate calls, want at least 2", calls)
|
|
}
|
|
}
|
|
|
|
func TestAngular_NavigateByRouter_Good(t *testing.T) {
|
|
var expressions []string
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
expr, _ := msg.Params["expression"].(string)
|
|
expressions = append(expressions, expr)
|
|
if strings.Contains(expr, "navigateByUrl") {
|
|
target.replyValue(msg.ID, true)
|
|
return
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.NavigateByRouter("/dashboard"); err != nil {
|
|
t.Fatalf("NavigateByRouter returned error: %v", err)
|
|
}
|
|
if len(expressions) < 2 {
|
|
t.Fatalf("NavigateByRouter made %d evaluate calls, want at least 2", len(expressions))
|
|
}
|
|
}
|
|
|
|
func TestAngular_NavigateByRouter_Bad(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyError(msg.ID, "could not find router")
|
|
})
|
|
|
|
if err := ah.NavigateByRouter("/dashboard"); err == nil {
|
|
t.Fatal("NavigateByRouter succeeded despite evaluation error")
|
|
}
|
|
}
|
|
|
|
func TestAngular_GetComponentProperty_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
expr, _ := msg.Params["expression"].(string)
|
|
if !strings.Contains(expr, `const selector = "app-user";`) {
|
|
t.Fatalf("expression did not quote selector: %s", expr)
|
|
}
|
|
if !strings.Contains(expr, `const propertyName = "displayName";`) {
|
|
t.Fatalf("expression did not quote property name: %s", expr)
|
|
}
|
|
target.replyValue(msg.ID, "Ada")
|
|
})
|
|
|
|
got, err := ah.GetComponentProperty("app-user", "displayName")
|
|
if err != nil {
|
|
t.Fatalf("GetComponentProperty returned error: %v", err)
|
|
}
|
|
if got != "Ada" {
|
|
t.Fatalf("GetComponentProperty = %v, want Ada", got)
|
|
}
|
|
}
|
|
|
|
func TestAngular_SetComponentProperty_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
expr, _ := msg.Params["expression"].(string)
|
|
if !strings.Contains(expr, `component[propertyName] = true;`) {
|
|
t.Fatalf("expression did not set the component property: %s", expr)
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.SetComponentProperty("app-user", "active", true); err != nil {
|
|
t.Fatalf("SetComponentProperty returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAngular_CallComponentMethod_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
expr, _ := msg.Params["expression"].(string)
|
|
if !strings.Contains(expr, `component[methodName](1, "two")`) {
|
|
t.Fatalf("expression did not marshal method args: %s", expr)
|
|
}
|
|
target.replyValue(msg.ID, map[string]any{"ok": true})
|
|
})
|
|
|
|
got, err := ah.CallComponentMethod("app-user", "save", 1, "two")
|
|
if err != nil {
|
|
t.Fatalf("CallComponentMethod returned error: %v", err)
|
|
}
|
|
if gotMap, ok := got.(map[string]any); !ok || gotMap["ok"] != true {
|
|
t.Fatalf("CallComponentMethod = %#v, want ok=true", got)
|
|
}
|
|
}
|
|
|
|
func TestAngular_TriggerChangeDetection_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.TriggerChangeDetection(); err != nil {
|
|
t.Fatalf("TriggerChangeDetection returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAngular_GetService_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyValue(msg.ID, map[string]any{"name": "session"})
|
|
})
|
|
|
|
got, err := ah.GetService("SessionService")
|
|
if err != nil {
|
|
t.Fatalf("GetService returned error: %v", err)
|
|
}
|
|
if gotMap, ok := got.(map[string]any); !ok || gotMap["name"] != "session" {
|
|
t.Fatalf("GetService = %#v, want session map", got)
|
|
}
|
|
}
|
|
|
|
func TestAngular_WaitForComponent_Good(t *testing.T) {
|
|
var calls int
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
calls++
|
|
if calls == 1 {
|
|
target.replyValue(msg.ID, false)
|
|
return
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.WaitForComponent("app-user"); err != nil {
|
|
t.Fatalf("WaitForComponent returned error: %v", err)
|
|
}
|
|
if calls < 2 {
|
|
t.Fatalf("WaitForComponent calls = %d, want at least 2", calls)
|
|
}
|
|
}
|
|
|
|
func TestAngular_DispatchEvent_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
expr, _ := msg.Params["expression"].(string)
|
|
if !strings.Contains(expr, `new CustomEvent(eventName, { bubbles: true, detail: {"count":1} })`) && !strings.Contains(expr, `new CustomEvent(eventName, { bubbles: true, detail: {\"count\":1} })`) {
|
|
t.Fatalf("expression did not dispatch custom event with detail: %s", expr)
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.DispatchEvent("app-user", "count-change", map[string]any{"count": 1}); err != nil {
|
|
t.Fatalf("DispatchEvent returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAngular_GetNgModel_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyValue(msg.ID, "hello")
|
|
})
|
|
|
|
got, err := ah.GetNgModel("input[name=email]")
|
|
if err != nil {
|
|
t.Fatalf("GetNgModel returned error: %v", err)
|
|
}
|
|
if got != "hello" {
|
|
t.Fatalf("GetNgModel = %v, want hello", got)
|
|
}
|
|
}
|
|
|
|
func TestAngular_SetNgModel_Good(t *testing.T) {
|
|
ah, _, _ := newAngularTestHarness(t, func(target *fakeCDPTarget, msg cdpMessage) {
|
|
if msg.Method != "Runtime.evaluate" {
|
|
t.Fatalf("unexpected method %q", msg.Method)
|
|
}
|
|
target.replyValue(msg.ID, true)
|
|
})
|
|
|
|
if err := ah.SetNgModel(`input[name="x"]`, `";window.hacked=true;//`); err != nil {
|
|
t.Fatalf("SetNgModel returned error: %v", err)
|
|
}
|
|
}
|
|
|
|
func TestAngular_copyStringOnlyMap_Good(t *testing.T) {
|
|
tests := []struct {
|
|
name string
|
|
in any
|
|
want map[string]string
|
|
}{
|
|
{name: "map any", in: map[string]any{"a": "1", "b": 2}, want: map[string]string{"a": "1"}},
|
|
{name: "map string", in: map[string]string{"c": "3"}, want: map[string]string{"c": "3"}},
|
|
{name: "nil", in: nil, want: nil},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
got := copyStringOnlyMap(tc.in)
|
|
if len(got) != len(tc.want) {
|
|
t.Fatalf("copyStringOnlyMap len = %d, want %d", len(got), len(tc.want))
|
|
}
|
|
for k, want := range tc.want {
|
|
if got[k] != want {
|
|
t.Fatalf("copyStringOnlyMap[%q] = %q, want %q", k, got[k], want)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
}
|