diff --git a/lib/ui/jsvar.go b/lib/ui/jsvar.go index 27fe3551..0f7a10e3 100644 --- a/lib/ui/jsvar.go +++ b/lib/ui/jsvar.go @@ -293,8 +293,14 @@ type JsVar[T any] struct { dirtyTag any // current dirty tag, set during render; read via JawsGetTag } -// JawsGetPath returns the value at jsPath, logging lookup errors on elem when possible. +// JawsGetPath returns the value at jsPath. +// +// A path containing only empty components returns the same logical root value +// as [JsVar.JawsGet]. Lookup errors are logged on elem when possible. func (jsvar *JsVar[T]) JawsGetPath(elem *jaws.Element, jsPath string) (value any) { + if strings.Trim(jsPath, ".") == "" { + return jsvar.JawsGet(elem) + } value, err := jsvar.getPath(jsPath) if elem != nil { _ = elem.Jaws.Log(err) diff --git a/lib/ui/jsvar_test.go b/lib/ui/jsvar_test.go index 2ff686dc..d7f3f360 100644 --- a/lib/ui/jsvar_test.go +++ b/lib/ui/jsvar_test.go @@ -275,6 +275,44 @@ func TestJsVar_RenderSetAndEvent(t *testing.T) { } } +func TestJsVar_GetPathRootReturnsLogicalValue(t *testing.T) { + value := jsVarNilData{Value: "bound"} + tests := []struct { + name string + ptr *jsVarNilData + }{ + {name: "Bound", ptr: &value}, + {name: "Nil"}, + } + paths := []string{"", ".", "..", "..."} + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + jsvar := NewJsVar(new(sync.Mutex), tt.ptr) + want := jsvar.JawsGet(nil) + for _, jsPath := range paths { + t.Run(fmt.Sprintf("path_%q", jsPath), func(t *testing.T) { + gotValue := jsvar.JawsGetPath(nil, jsPath) + got, ok := gotValue.(jsVarNilData) + if !ok { + t.Fatalf("JawsGetPath(%q) returned %T, want jsVarNilData", jsPath, gotValue) + } + if got != want { + t.Fatalf("JawsGetPath(%q) = %#v, want JawsGet result %#v", jsPath, got, want) + } + }) + } + if tt.ptr != nil { + if got := jsvar.JawsGetPath(nil, "..value.."); got != want.Value { + t.Fatalf("nested JawsGetPath = %#v, want %q", got, want.Value) + } + } else if got := jsvar.JawsGetPath(nil, "value"); got != nil { + t.Fatalf("nested JawsGetPath with nil Ptr = %#v, want nil", got) + } + }) + } +} + func TestJsVar_RenderInitialHTMLAttrRunsOutsideBindingLock(t *testing.T) { _, rq := newCoreRequest(t) @@ -381,6 +419,9 @@ func testJsVarGetPathLoggerReentry(t *testing.T, bindingLocker jsVarTryLocker) { if got := jsvar.JawsGetPath(elem, "text"); got != state.Text { t.Fatalf("successful JawsGetPath = %#v, want %q", got, state.Text) } + if got := jsvar.JawsGetPath(elem, "."); got != state { + t.Fatalf("successful root JawsGetPath = %#v, want %#v", got, state) + } testSyncLogger(t, jw) select { case result := <-logger.results: