diff --git a/jaws.go b/jaws.go index 0601930..04d55cf 100644 --- a/jaws.go +++ b/jaws.go @@ -3,6 +3,7 @@ package jaws import ( "html/template" "io" + "net/http" "sync" "time" @@ -49,6 +50,7 @@ type ( With = pkg.With Session = pkg.Session Tag = pkg.Tag + TestRequest = pkg.TestRequest ) var ( @@ -236,3 +238,7 @@ func NewUiText(vp Setter[string]) *UiText { func NewUiTr(innerHTML HTMLGetter) *UiTr { return pkg.NewUiTr(innerHTML) } + +func NewTestRequest(jw *Jaws, hr *http.Request) (tr *TestRequest) { + return pkg.NewTestRequest(jw, hr) +} diff --git a/jaws/clickhandler_test.go b/jaws/clickhandler_test.go index 29552c2..268efea 100644 --- a/jaws/clickhandler_test.go +++ b/jaws/clickhandler_test.go @@ -23,7 +23,7 @@ var _ ClickHandler = (*testJawsClick)(nil) func Test_clickHandlerWapper_JawsEvent(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tjc := &testJawsClick{ @@ -37,16 +37,16 @@ func Test_clickHandlerWapper_JawsEvent(t *testing.T) { t.Errorf("Request.Div() = %q, want %q", got, want) } - rq.inCh <- wsMsg{Data: "text", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "text", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } - rq.inCh <- wsMsg{Data: "adam", Jid: 1, What: what.Click} + rq.InCh <- wsMsg{Data: "adam", Jid: 1, What: what.Click} select { case <-th.C: th.Timeout() diff --git a/jaws/element_test.go b/jaws/element_test.go index dbd01c2..9cfb5eb 100644 --- a/jaws/element_test.go +++ b/jaws/element_test.go @@ -66,12 +66,12 @@ func (tss *testUi) JawsUpdate(e *Element) { func TestElement_helpers(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{} e := rq.NewElement(tss) - is.Equal(e.Jaws, rq.jw.Jaws) + is.Equal(e.Jaws, rq.Jaws) is.Equal(e.Request, rq.Request) is.Equal(e.Session(), nil) e.Set("foo", "bar") // no session, so no effect @@ -80,7 +80,7 @@ func TestElement_helpers(t *testing.T) { func TestElement_Tag(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{} @@ -96,7 +96,7 @@ func TestElement_Tag(t *testing.T) { func TestElement_Queued(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{ @@ -181,13 +181,12 @@ func TestElement_Queued(t *testing.T) { time.Sleep(time.Millisecond) } } - th.Equal(tss.updateCalled, int32(1)) th.Equal(tss.renderCalled, int32(2)) } func TestElement_ReplacePanicsOnMissingId(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() defer func() { if x := recover(); x == nil { @@ -202,7 +201,7 @@ func TestElement_ReplacePanicsOnMissingId(t *testing.T) { func TestElement_maybeDirty(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{s: "foo"} e := rq.NewElement(tss) @@ -231,7 +230,7 @@ var _ ClickHandler = testClickHandler{} func TestElement_ApplyGetter(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{s: "foo"} @@ -250,7 +249,7 @@ func TestElement_ApplyGetter(t *testing.T) { func TestElement_JawsInit(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{s: "foo"} diff --git a/jaws/handler_test.go b/jaws/handler_test.go index c89c94b..1c4d7a6 100644 --- a/jaws/handler_test.go +++ b/jaws/handler_test.go @@ -8,7 +8,7 @@ import ( func TestHandler_ServeHTTP(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() dot := Tag("123") diff --git a/jaws/jawsevent_test.go b/jaws/jawsevent_test.go index 0a191aa..4af7a00 100644 --- a/jaws/jawsevent_test.go +++ b/jaws/jawsevent_test.go @@ -58,7 +58,7 @@ var _ UI = (*testJawsEvent)(nil) func Test_JawsEvent_ClickUnhandled(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() msgCh := make(chan string, 1) @@ -68,7 +68,7 @@ func Test_JawsEvent_ClickUnhandled(t *testing.T) { id := rq.Register(zomgItem, je, "attr1", []string{"attr2"}, template.HTMLAttr("attr3"), []template.HTMLAttr{"attr4"}) je.clickerr = ErrEventUnhandled - rq.inCh <- wsMsg{Data: "name", Jid: id, What: what.Click} + rq.InCh <- wsMsg{Data: "name", Jid: id, What: what.Click} select { case <-th.C: th.Timeout() @@ -82,7 +82,7 @@ func Test_JawsEvent_ClickUnhandled(t *testing.T) { func Test_JawsEvent_AllUnhandled(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() msgCh := make(chan string, 1) @@ -93,7 +93,7 @@ func Test_JawsEvent_AllUnhandled(t *testing.T) { je.clickerr = ErrEventUnhandled je.eventerr = ErrEventUnhandled - rq.inCh <- wsMsg{Data: "name", Jid: id, What: what.Click} + rq.InCh <- wsMsg{Data: "name", Jid: id, What: what.Click} select { case <-th.C: th.Timeout() @@ -126,7 +126,7 @@ func (t *testJawsEventHandler) JawsEvent(e *Element, wht what.What, val string) func Test_JawsEvent_ExtraHandler(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() msgCh := make(chan string, 1) @@ -139,7 +139,7 @@ func Test_JawsEvent_ExtraHandler(t *testing.T) { th.NoErr(je.JawsRender(elem, &sb, nil)) th.Equal(sb.String(), "
tjEH
") - rq.inCh <- wsMsg{Data: "name", Jid: 1, What: what.Click} + rq.InCh <- wsMsg{Data: "name", Jid: 1, What: what.Click} select { case <-th.C: th.Timeout() diff --git a/jaws/jawsjaws_test.go b/jaws/jawsjaws_test.go index baea00d..75d36df 100644 --- a/jaws/jawsjaws_test.go +++ b/jaws/jawsjaws_test.go @@ -178,6 +178,8 @@ func TestJaws_BroadcastFullClosesChannel(t *testing.T) { go func() { select { + case <-t.Context().Done(): + close(failCh) case <-th.C: close(failCh) case <-subCh2: @@ -521,18 +523,20 @@ func TestJaws_GenerateHeadHTML(t *testing.T) { func TestJaws_TemplateLookuper(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + tj := newTestJaws() + defer tj.Close() + rq := NewTestRequest(tj.Jaws, nil) defer rq.Close() th.Equal(rq.Jaws.LookupTemplate("nosuchtemplate"), nil) - th.Equal(rq.Jaws.LookupTemplate("testtemplate"), rq.jw.testtmpl) - rq.Jaws.RemoveTemplateLookuper(rq.jw.testtmpl) + th.Equal(rq.Jaws.LookupTemplate("testtemplate"), tj.testtmpl) + rq.Jaws.RemoveTemplateLookuper(tj.testtmpl) th.Equal(rq.Jaws.LookupTemplate("testtemplate"), nil) } func TestJaws_JsCall(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := &testUi{} @@ -548,7 +552,7 @@ func TestJaws_JsCall(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: got := msg.Format() th.Equal(got, "Call\tJid.1\tsomefn=1.3\n") } diff --git a/jaws/jsvar_test.go b/jaws/jsvar_test.go index d3afd51..96c1681 100644 --- a/jaws/jsvar_test.go +++ b/jaws/jsvar_test.go @@ -43,11 +43,11 @@ func (tl *testLocker) Unlock() { func Test_JsVar_JawsRender(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() nextJid = 0 - rq.jw.AddTemplateLookuper(template.Must(template.New("jsvartemplate").Parse(`{{$.JsVar "` + varname + `" .Dot}}`))) + rq.Jaws.AddTemplateLookuper(template.Must(template.New("jsvartemplate").Parse(`{{$.JsVar "` + varname + `" .Dot}}`))) var mu deadlock.RWMutex var val valtype @@ -88,7 +88,7 @@ func Test_JsVar_Update(t *testing.T) { var val valtype dot := NewJsVar(&mu, &val) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() elem := rq.NewElement(dot) @@ -107,7 +107,7 @@ func Test_JsVar_Update(t *testing.T) { select { case <-th.C: th.Timeout() - case gotMsg := <-rq.outCh: + case gotMsg := <-rq.OutCh: wantMsg := wsMsg{ Data: "={\"String\":\"x\",\"Number\":2}", Jid: 1, @@ -166,7 +166,7 @@ func Test_JsVar_Event(t *testing.T) { select { case <-th.C: th.Timeout() - case rq1.inCh <- wsMsg{Jid: 1, What: what.Set, Data: "={\"String\":\"y\",\"Number\":3}"}: + case rq1.InCh <- wsMsg{Jid: 1, What: what.Set, Data: "={\"String\":\"y\",\"Number\":3}"}: } select { @@ -180,7 +180,7 @@ func Test_JsVar_Event(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() after, found := strings.CutPrefix(s, "Set\tJid.1\t=") th.Equal(found, true) @@ -197,7 +197,7 @@ func Test_JsVar_Event(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq2.outCh: + case msg := <-rq2.OutCh: s := msg.Format() after, found := strings.CutPrefix(s, "Set\tJid.2\t=") th.Equal(found, true) @@ -214,13 +214,13 @@ func Test_JsVar_Event(t *testing.T) { select { case <-th.C: th.Timeout() - case rq1.inCh <- wsMsg{Jid: 1, What: what.Set, Data: "=1"}: + case rq1.InCh <- wsMsg{Jid: 1, What: what.Set, Data: "=1"}: } select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if !strings.Contains(s, "jq: expected") { th.Error(s) @@ -230,7 +230,7 @@ func Test_JsVar_Event(t *testing.T) { func Test_JsVar_PanicsOnWrongType(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() defer func() { if x := recover(); x == nil { @@ -255,7 +255,7 @@ var _ JsVarMaker = &testJsVarMaker{} func Test_JsVar_JsVarMaker(t *testing.T) { nextJid = 0 th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() err := rq.JsVar("foo", &testJsVarMaker{}) th.NoErr(err) @@ -286,7 +286,7 @@ var _ SetPather = &testJsVarPathSetter{} func Test_JsVar_PathSetter_SetPather(t *testing.T) { nextJid = 0 th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() var mu deadlock.Mutex @@ -302,7 +302,7 @@ func Test_JsVar_PathSetter_SetPather(t *testing.T) { func Test_JsVar_Unchanged(t *testing.T) { nextJid = 0 th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() var mu deadlock.Mutex diff --git a/jaws/namedbool_test.go b/jaws/namedbool_test.go index 60c62db..5190768 100644 --- a/jaws/namedbool_test.go +++ b/jaws/namedbool_test.go @@ -12,7 +12,7 @@ func TestNamedBool(t *testing.T) { nba.Add("1", "one") nb := nba.data[0] - rq := newTestRequest() + rq := newTestRequest(t) e := rq.NewElement(NewUiCheckbox(nb)) defer rq.Close() diff --git a/jaws/namedboolarray_test.go b/jaws/namedboolarray_test.go index af690e6..6db85b9 100644 --- a/jaws/namedboolarray_test.go +++ b/jaws/namedboolarray_test.go @@ -78,7 +78,7 @@ func Test_NamedBoolArray(t *testing.T) { (nba.data)[1].Set(true) is.Equal(nba.IsChecked("2"), true) - rq := newTestRequest() + rq := newTestRequest(t) e := rq.NewElement(NewUiSelect(nba)) defer rq.Close() diff --git a/jaws/request.go b/jaws/request.go index 6526492..fc94b5a 100644 --- a/jaws/request.go +++ b/jaws/request.go @@ -395,6 +395,13 @@ func (rq *Request) NewElement(ui UI) *Element { return rq.newElementLocked(ui) } +func (rq *Request) GetElementByJid(jid Jid) (e *Element) { + rq.mu.RLock() + defer rq.mu.RUnlock() + e = rq.getElementByJidLocked(jid) + return +} + func (rq *Request) getElementByJidLocked(jid Jid) (elem *Element) { for _, e := range rq.elems { if e.Jid() == jid { diff --git a/jaws/request_test.go b/jaws/request_test.go index d214973..3d1c916 100644 --- a/jaws/request_test.go +++ b/jaws/request_test.go @@ -5,12 +5,14 @@ import ( "context" "errors" "fmt" + "log/slog" "strconv" "strings" "sync/atomic" "testing" "time" + "github.com/linkdata/deadlock" "github.com/linkdata/jaws/jid" "github.com/linkdata/jaws/what" ) @@ -29,7 +31,7 @@ func fillWsCh(ch chan wsMsg) { func TestRequest_Registrations(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() x := &testUi{} @@ -90,19 +92,19 @@ document.getElementById("Jid.1")?.classList?.remove("cls"); func TestRequest_SendArrivesOk(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() x := &testUi{} jid := rq.Register(x) - elem := rq.getElementByJid(jid) + elem := rq.GetElementByJid(jid) is.True(elem != nil) - rq.jw.Broadcast(Message{Dest: x, What: what.Inner, Data: "bar"}) + rq.Jaws.Broadcast(Message{Dest: x, What: what.Inner, Data: "bar"}) select { case <-time.NewTimer(time.Hour).C: is.Error("timeout") - case msg := <-rq.outCh: - elem := rq.getElementByJid(jid) + case msg := <-rq.OutCh: + elem := rq.GetElementByJid(jid) is.True(elem != nil) if elem != nil { is.Equal(msg, wsMsg{Jid: elem.jid, Data: "bar", What: what.Inner}) @@ -111,7 +113,7 @@ func TestRequest_SendArrivesOk(t *testing.T) { } func TestRequest_SetContext(t *testing.T) { - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() type testKey string rq.SetContext(func(oldctx context.Context) (newctx context.Context) { @@ -124,23 +126,23 @@ func TestRequest_SetContext(t *testing.T) { func TestRequest_OutboundRespectsContextDone(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() var callCount int32 x := &testUi{} rq.Register(x, func(e *Element, evt what.What, val string) error { atomic.AddInt32(&callCount, 1) - rq.cancel() + rq.cancel(nil) return errors.New(val) }) - fillWsCh(rq.outCh) - rq.jw.Broadcast(Message{Dest: x, What: what.Hook, Data: "bar"}) + fillWsCh(rq.OutCh) + rq.Jaws.Broadcast(Message{Dest: x, What: what.Hook, Data: "bar"}) select { case <-th.C: th.Equal(int(atomic.LoadInt32(&callCount)), 0) th.Timeout() - case <-rq.jw.Done(): + case <-rq.Jaws.Done(): th.Fatal("jaws done too soon") case <-rq.ctx.Done(): } @@ -148,7 +150,7 @@ func TestRequest_OutboundRespectsContextDone(t *testing.T) { th.Equal(int(atomic.LoadInt32(&callCount)), 1) select { - case <-rq.jw.Done(): + case <-rq.Jaws.Done(): th.Fatal("jaws done too soon") default: } @@ -156,7 +158,7 @@ func TestRequest_OutboundRespectsContextDone(t *testing.T) { func TestRequest_Trigger(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() gotFooCall := make(chan struct{}) gotEndCall := make(chan struct{}) @@ -176,11 +178,11 @@ func TestRequest_Trigger(t *testing.T) { }) // broadcasts from ourselves should not invoke fn - rq.jw.Broadcast(Message{Dest: endItem, What: what.Input, Data: ""}) // to know when to stop + rq.Jaws.Broadcast(Message{Dest: endItem, What: what.Input, Data: ""}) // to know when to stop select { case <-th.C: th.Timeout() - case s := <-rq.outCh: + case s := <-rq.OutCh: th.Fatal(s) case <-gotFooCall: th.Fatal("gotFooCall") @@ -188,21 +190,21 @@ func TestRequest_Trigger(t *testing.T) { } // global broadcast should invoke fn - rq.jw.Broadcast(Message{Dest: fooItem, What: what.Input, Data: "bar"}) + rq.Jaws.Broadcast(Message{Dest: fooItem, What: what.Input, Data: "bar"}) select { case <-th.C: th.Timeout() - case s := <-rq.outCh: + case s := <-rq.OutCh: th.Fatal(s) case <-gotFooCall: } // fn returning error should send an danger alert message - rq.jw.Broadcast(Message{Dest: errItem, What: what.Input, Data: "omg"}) + rq.Jaws.Broadcast(Message{Dest: errItem, What: what.Input, Data: "omg"}) select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: th.Equal(msg.Format(), (&wsMsg{ Data: "danger\nomg", Jid: jid.Jid(0), @@ -213,7 +215,7 @@ func TestRequest_Trigger(t *testing.T) { func TestRequest_EventFnQueue(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() // calls to slow event functions queue up and are executed in order @@ -224,120 +226,153 @@ func TestRequest_EventFnQueue(t *testing.T) { rq.Register(sleepItem, func(e *Element, evt what.What, val string) error { count := int(atomic.AddInt32(&callCount, 1)) if val != strconv.Itoa(count) { - t.Logf("val=%s, count=%d, cap=%d", val, count, cap(rq.outCh)) + t.Logf("val=%s, count=%d, cap=%d", val, count, cap(rq.OutCh)) th.Fail() } if count == 1 { close(firstDoneCh) } for atomic.LoadInt32(&sleepDone) == 0 { - time.Sleep(time.Millisecond) + select { + case <-t.Context().Done(): + return nil + default: + time.Sleep(time.Millisecond) + } } return nil }) - for i := 0; i < cap(rq.outCh); i++ { - rq.jw.Broadcast(Message{Dest: sleepItem, What: what.Input, Data: strconv.Itoa(i + 1)}) + for i := 0; i < cap(rq.OutCh); i++ { + rq.Jaws.Broadcast(Message{Dest: sleepItem, What: what.Input, Data: strconv.Itoa(i + 1)}) } select { case <-th.C: th.Timeout() - case <-rq.doneCh: + case <-rq.DoneCh: th.Fatal("doneCh") case <-firstDoneCh: } th.Equal(atomic.LoadInt32(&callCount), int32(1)) atomic.StoreInt32(&sleepDone, 1) - th.Equal(rq.panicVal, nil) + th.Equal(rq.PanicVal, nil) - for int(atomic.LoadInt32(&callCount)) < cap(rq.outCh) { + for int(atomic.LoadInt32(&callCount)) < cap(rq.OutCh) { select { case <-th.C: - t.Logf("callCount=%d, cap=%d", atomic.LoadInt32(&callCount), cap(rq.outCh)) - th.Equal(rq.panicVal, nil) + t.Logf("callCount=%d, cap=%d", atomic.LoadInt32(&callCount), cap(rq.OutCh)) + th.Equal(rq.PanicVal, nil) th.Timeout() default: time.Sleep(time.Millisecond) } } - th.Equal(atomic.LoadInt32(&callCount), int32(cap(rq.outCh))) + th.Equal(atomic.LoadInt32(&callCount), int32(cap(rq.OutCh))) } func TestRequest_EventFnQueueOverflowPanicsWithNoLogger(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) var wait int32 bombItem := &testUi{} rq.Register(bombItem, func(e *Element, evt what.What, val string) error { - time.Sleep(time.Millisecond * time.Duration(atomic.AddInt32(&wait, 1))) + delay := 1 << atomic.AddInt32(&wait, 1) + select { + case <-t.Context().Done(): + case <-time.NewTimer(time.Millisecond * time.Duration(min(1000, delay))).C: + } return nil }) - rq.expectPanic = true - rq.jw.Logger = nil + rq.ExpectPanic = true + rq.Jaws.Logger = nil jid := jidForTag(rq.Request, bombItem) for { select { - case <-rq.doneCh: - th.True(rq.panicked) - th.True(strings.Contains(rq.panicVal.(error).Error(), "eventCallCh is full sending")) + case <-rq.DoneCh: + if t.Context().Err() == nil { + th.True(rq.Panicked) + txt := fmt.Sprint(rq.PanicVal) + if !strings.Contains(txt, "eventCallCh is full sending") { + t.Log(log.String()) + t.Errorf("unexpected panic value %q", txt) + } + } else { + t.Log(log.String()) + t.Error("test timed out before event channel full") + } return case <-th.C: th.Timeout() - case rq.inCh <- wsMsg{Jid: jid, What: what.Input}: + case rq.InCh <- wsMsg{Jid: jid, What: what.Input}: } } } func TestRequest_IgnoresIncomingMsgsDuringShutdown(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) + + waitms := 1000 + if deadlock.Debug { + waitms *= 10 + } var spewState int32 var callCount int32 spewItem := &testUi{} rq.Register(spewItem, func(e *Element, evt what.What, val string) error { atomic.AddInt32(&callCount, 1) - if len(rq.outCh) < cap(rq.outCh) { - rq.jw.Broadcast(Message{Dest: spewItem, What: what.Input}) - } else { - atomic.StoreInt32(&spewState, 1) - for atomic.LoadInt32(&spewState) == 1 { + if len(rq.OutCh) < cap(rq.OutCh) { + rq.Jaws.Broadcast(Message{Dest: spewItem, What: what.Input}) + return errors.New("chunks") + } + atomic.StoreInt32(&spewState, 1) + for atomic.LoadInt32(&spewState) == 1 { + select { + case <-t.Context().Done(): + atomic.StoreInt32(&spewState, 3) + default: time.Sleep(time.Millisecond) } } + atomic.StoreInt32(&spewState, 3) return errors.New("chunks") }) fooItem := &testUi{} rq.Register(fooItem) - rq.jw.Broadcast(Message{Dest: spewItem, What: what.Input}) + rq.Jaws.Broadcast(Message{Dest: spewItem, What: what.Input}) // wait for the event fn to be in hold state waited := 0 - for waited < 1000 && atomic.LoadInt32(&spewState) == 0 { + for waited < waitms && atomic.LoadInt32(&spewState) == 0 { time.Sleep(time.Millisecond) waited++ } th.Equal(atomic.LoadInt32(&spewState), int32(1)) - th.Equal(cap(rq.outCh), len(rq.outCh)) - th.True(waited < 1000) + th.Equal(cap(rq.OutCh), len(rq.OutCh)) + th.True(waited < waitms) - rq.cancel() + rq.cancel(nil) // rq should now be in shutdown phase draining channels // while waiting for the event fn to return - for i := 0; i < cap(rq.outCh)*2; i++ { + for i := 0; i < cap(rq.OutCh)*2; i++ { select { - case <-rq.doneCh: + case <-rq.DoneCh: th.Fatal() case <-th.C: th.Timeout() @@ -345,8 +380,8 @@ func TestRequest_IgnoresIncomingMsgsDuringShutdown(t *testing.T) { rq.Jaws.Broadcast(Message{Dest: rq}) } select { - case rq.inCh <- wsMsg{}: - case <-rq.doneCh: + case rq.InCh <- wsMsg{}: + case <-rq.DoneCh: th.Fatal() case <-th.C: th.Timeout() @@ -357,14 +392,17 @@ func TestRequest_IgnoresIncomingMsgsDuringShutdown(t *testing.T) { atomic.StoreInt32(&spewState, 2) select { - case <-rq.doneCh: + case <-rq.DoneCh: + th.True(atomic.LoadInt32(&spewState) == 3) th.True(atomic.LoadInt32(&callCount) > 1) case <-th.C: + t.Logf("timeout callcount %v, spewState %v", atomic.LoadInt32(&callCount), atomic.LoadInt32(&spewState)) + t.Log(log.String()) th.Timeout() } // log data should contain message that we were unable to deliver error - th.True(strings.Contains(rq.jw.log.String(), "outboundMsgCh full sending event")) + th.True(strings.Contains(log.String(), "outboundMsgCh full sending event")) } func TestRequest_Alert(t *testing.T) { @@ -378,14 +416,14 @@ func TestRequest_Alert(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if s != "Alert\t\t\"info\\n\\nnot\\tescaped\"\n" { t.Errorf("%q", s) } } select { - case s := <-rq2.outCh: + case s := <-rq2.OutCh: t.Errorf("%q", s) default: } @@ -402,14 +440,14 @@ func TestRequest_Redirect(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if s != "Redirect\t\t\"some-url\"\n" { t.Errorf("%q", s) } } select { - case s := <-rq2.outCh: + case s := <-rq2.OutCh: t.Errorf("%q", s) default: } @@ -424,7 +462,7 @@ func TestRequest_AlertError(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\n<html>\\nshould-be-escaped\"\n" { t.Errorf("%q", s) @@ -466,7 +504,7 @@ func TestRequest_DeleteByTag(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if s != "Delete\tJid.1\t\"\"\n" { t.Errorf("%q", s) @@ -476,7 +514,7 @@ func TestRequest_DeleteByTag(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if s != "Delete\tJid.3\t\"\"\n" { t.Errorf("%q", s) @@ -486,7 +524,7 @@ func TestRequest_DeleteByTag(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq2.outCh: + case msg := <-rq2.OutCh: s := msg.Format() if s != "Delete\tJid.4\t\"\"\n" { t.Errorf("%q", s) @@ -496,7 +534,7 @@ func TestRequest_DeleteByTag(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq2.outCh: + case msg := <-rq2.OutCh: s := msg.Format() if s != "Delete\tJid.6\t\"\"\n" { t.Errorf("%q", s) @@ -519,7 +557,7 @@ func TestRequest_HTMLIdBroadcast(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq1.outCh: + case msg := <-rq1.OutCh: s := msg.Format() if s != "Inner\tfooId\t\"inner\"\n" { t.Errorf("%q", s) @@ -528,7 +566,7 @@ func TestRequest_HTMLIdBroadcast(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq2.outCh: + case msg := <-rq2.OutCh: s := msg.Format() if s != "Inner\tfooId\t\"inner\"\n" { t.Errorf("%q", s) @@ -545,7 +583,7 @@ func jidForTag(rq *Request, tag any) jid.Jid { func TestRequest_ConnectFn(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() th.Equal(rq.GetConnectFn(), nil) @@ -561,7 +599,7 @@ func TestRequest_ConnectFn(t *testing.T) { func TestRequest_Dirty(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss1 := &testUi{s: "foo1"} @@ -588,8 +626,10 @@ func TestRequest_Dirty(t *testing.T) { func TestRequest_UpdatePanicLogs(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) tss := &testUi{ updateFn: func(e *Element) { @@ -600,9 +640,9 @@ func TestRequest_UpdatePanicLogs(t *testing.T) { select { case <-th.C: th.Timeout() - case <-rq.doneCh: + case <-rq.DoneCh: } - if s := rq.jw.log.String(); !strings.Contains(s, "wildpanic") { + if s := log.String(); !strings.Contains(s, "wildpanic") { t.Error(s) } } @@ -610,7 +650,7 @@ func TestRequest_UpdatePanicLogs(t *testing.T) { func TestRequest_IncomingRemove(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tss := newTestSetter("") @@ -619,17 +659,17 @@ func TestRequest_IncomingRemove(t *testing.T) { select { case <-th.C: th.Timeout() - case rq.inCh <- wsMsg{What: what.Remove, Data: "Jid.1"}: + case rq.InCh <- wsMsg{What: what.Remove, Data: "Jid.1"}: } - elem := rq.getElementByJid(1) + elem := rq.GetElementByJid(1) for elem != nil { select { case <-th.C: th.Timeout() default: time.Sleep(time.Millisecond) - elem = rq.getElementByJid(1) + elem = rq.GetElementByJid(1) } } } @@ -637,7 +677,7 @@ func TestRequest_IncomingRemove(t *testing.T) { func TestRequest_IncomingClick(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() tjc1 := &testJawsClick{ @@ -656,7 +696,7 @@ func TestRequest_IncomingClick(t *testing.T) { select { case <-th.C: th.Timeout() - case rq.inCh <- wsMsg{What: what.Click, Data: "name\tJid.1\tJid.2"}: + case rq.InCh <- wsMsg{What: what.Click, Data: "name\tJid.1\tJid.2"}: } select { @@ -676,7 +716,7 @@ func TestRequest_IncomingClick(t *testing.T) { func TestRequest_CustomErrors(t *testing.T) { th := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() cause := newErrNoWebSocketRequest(rq.Request) err := newErrPendingCancelledLocked(rq.Request, cause) diff --git a/jaws/sessioner_test.go b/jaws/sessioner_test.go index 7bf0149..6721718 100644 --- a/jaws/sessioner_test.go +++ b/jaws/sessioner_test.go @@ -8,7 +8,7 @@ import ( func TestJaws_Session(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() dot := Tag("123") diff --git a/jaws/template_test.go b/jaws/template_test.go index a0a056c..111de17 100644 --- a/jaws/template_test.go +++ b/jaws/template_test.go @@ -7,7 +7,7 @@ import ( ) func TestTemplate_Missing(t *testing.T) { - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() err := rq.Template("missingtemplate", nil, nil) @@ -21,7 +21,7 @@ func TestTemplate_Missing(t *testing.T) { func TestTemplate_String(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() dot := 123 @@ -31,7 +31,7 @@ func TestTemplate_String(t *testing.T) { } func TestTemplate_Calls_Dot_Updater(t *testing.T) { - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() dot := &testUi{} tmpl := NewTemplate("testtemplate", dot) diff --git a/jaws/testhelper_test.go b/jaws/testhelper_test.go index f02ae4a..3b1746b 100644 --- a/jaws/testhelper_test.go +++ b/jaws/testhelper_test.go @@ -1,20 +1,70 @@ package jaws import ( + "bytes" + "fmt" "reflect" + "regexp" + "runtime" + "sort" "testing" "time" + + "github.com/linkdata/deadlock" ) +func printGoroutineOrigins(t *testing.T) { + t.Helper() + buf := make([]byte, 1<<20) + n := runtime.Stack(buf, true) + buf = buf[:n] + + lines := bytes.Split(buf, []byte("\n")) + re := regexp.MustCompile(`\t(.*?):(\d+) \+0x`) + counts := make(map[string]int) + + for _, line := range lines { + m := re.FindSubmatch(line) + if len(m) == 3 { + loc := fmt.Sprintf("%s:%s", m[1], m[2]) + counts[loc]++ + } + } + + // Convert to slice for sorting + type pair struct { + loc string + count int + } + var items []pair + for k, v := range counts { + if v > 1 { // omit entries with only one goroutine + items = append(items, pair{k, v}) + } + } + + sort.Slice(items, func(i, j int) bool { + return items[i].count > items[j].count + }) + + for _, item := range items { + t.Logf("%-50s %4d goroutines\n", item.loc, item.count) + } +} + type testHelper struct { *time.Timer *testing.T } func newTestHelper(t *testing.T) (th *testHelper) { + seconds := 3 + if deadlock.Debug { + seconds *= 10 + } th = &testHelper{ T: t, - Timer: time.NewTimer(time.Second * 3), + Timer: time.NewTimer(time.Second * time.Duration(seconds)), } t.Cleanup(th.Cleanup) return @@ -47,7 +97,8 @@ func (th *testHelper) NoErr(err error) { func (th *testHelper) Timeout() { th.Helper() - th.Fatal("timeout") + printGoroutineOrigins(th.T) + th.Fatalf("timeout") } func Test_testHelper(t *testing.T) { diff --git a/jaws/testjaws_test.go b/jaws/testjaws_test.go index 91f44b4..46f4d63 100644 --- a/jaws/testjaws_test.go +++ b/jaws/testjaws_test.go @@ -2,12 +2,10 @@ package jaws import ( "bytes" - "context" "html/template" "log/slog" "net/http" - "net/http/httptest" - "strings" + "testing" "time" ) @@ -37,98 +35,15 @@ func newTestJaws() (tj *testJaws) { return } -type testRequest struct { - hr *http.Request - rr *httptest.ResponseRecorder - jw *testJaws - readyCh chan struct{} - doneCh chan struct{} - inCh chan wsMsg - outCh chan wsMsg - bcastCh chan Message - ctx context.Context - cancel context.CancelFunc - expectPanic bool - panicked bool - panicVal any - *Request - RequestWriter +func (tj *testJaws) newRequest(hr *http.Request) (tr *TestRequest) { + return NewTestRequest(tj.Jaws, hr) } -func (tj *testJaws) newRequest(hr *http.Request) (tr *testRequest) { - if hr == nil { - hr = httptest.NewRequest(http.MethodGet, "/", nil) - } - ctx, cancel := context.WithTimeout(context.Background(), time.Hour) - hr = hr.WithContext(ctx) - rr := httptest.NewRecorder() - rr.Body = &bytes.Buffer{} - rq := tj.NewRequest(hr) - if rq == nil || tj.UseRequest(rq.JawsKey, hr) != rq { - panic("failed to create or use jaws.Request") - } - bcastCh := tj.subscribe(rq, 64) - for i := 0; i <= cap(tj.subCh); i++ { - tj.subCh <- subscription{} // ensure subscription is processed - } - - tr = &testRequest{ - hr: hr, - rr: rr, - jw: tj, - readyCh: make(chan struct{}), - doneCh: make(chan struct{}), - inCh: make(chan wsMsg), - outCh: make(chan wsMsg, cap(bcastCh)), - bcastCh: bcastCh, - ctx: ctx, - cancel: cancel, - Request: rq, - RequestWriter: rq.Writer(rr), - } - - go func() { - defer func() { - if tr.expectPanic { - if tr.panicVal = recover(); tr.panicVal != nil { - tr.panicked = true - } - } - close(tr.doneCh) - }() - close(tr.readyCh) - tr.process(tr.bcastCh, tr.inCh, tr.outCh) // usubs from bcase, closes outCh - tr.jw.recycle(tr.Request) - }() - - return -} - -func (tr *testRequest) BodyString() string { - return tr.rr.Body.String() -} - -func (tr *testRequest) BodyHTML() template.HTML { - return template.HTML(strings.TrimSpace(tr.BodyString())) -} - -func (tr *testRequest) Close() { - tr.cancel() - tr.jw.Close() -} - -func (tr *testRequest) Write(buf []byte) (int, error) { - return tr.rr.Write(buf) -} - -func (tr *testRequest) getElementByJid(jid Jid) (e *Element) { - tr.Request.mu.RLock() - e = tr.Request.getElementByJidLocked(jid) - tr.Request.mu.RUnlock() - return -} - -func newTestRequest() (tr *testRequest) { +func newTestRequest(t *testing.T) (tr *TestRequest) { tj := newTestJaws() - return tj.newRequest(nil) + if t != nil { + t.Helper() + t.Cleanup(tj.Close) + } + return NewTestRequest(tj.Jaws, nil) } diff --git a/jaws/testpage_test.go b/jaws/testpage_test.go index f0888e3..82eef84 100644 --- a/jaws/testpage_test.go +++ b/jaws/testpage_test.go @@ -72,9 +72,9 @@ type testPage struct { TheDot any } -func newTestPage(tr *testRequest) *testPage { +func newTestPage(tr *TestRequest) *testPage { testDate, _ := time.Parse(ISO8601, "1901-02-03") - tr.jw.AddTemplateLookuper(template.Must(template.New("nested").Parse(testPageNestedTmplText))) + tr.Jaws.AddTemplateLookuper(template.Must(template.New("nested").Parse(testPageNestedTmplText))) tmpl := template.Must(template.New("normal").Parse(testPageTmplText)) tp := &testPage{ diff --git a/jaws/testrequest.go b/jaws/testrequest.go new file mode 100644 index 0000000..4dbf659 --- /dev/null +++ b/jaws/testrequest.go @@ -0,0 +1,81 @@ +package jaws + +import ( + "bytes" + "html/template" + "net/http" + "net/http/httptest" + "strings" +) + +type TestRequest struct { + *Request + *httptest.ResponseRecorder + RequestWriter + ReadyCh chan struct{} + DoneCh chan struct{} + InCh chan wsMsg + OutCh chan wsMsg + BcastCh chan Message + ExpectPanic bool + Panicked bool + PanicVal any +} + +// NewTestRequest creates a TestRequest for use when testing. +// Passing nil for hr will create a "GET /" request with no body. +// +// If NewRequest() or UseRequest() fails, it returns nil. +func NewTestRequest(jw *Jaws, hr *http.Request) (tr *TestRequest) { + if hr == nil { + hr = httptest.NewRequest(http.MethodGet, "/", nil) + } + rr := httptest.NewRecorder() + rr.Body = &bytes.Buffer{} + rq := jw.NewRequest(hr) + if rq != nil && jw.UseRequest(rq.JawsKey, hr) == rq { + bcastCh := jw.subscribe(rq, 64) + for i := 0; i <= cap(jw.subCh); i++ { + jw.subCh <- subscription{} // ensure subscription is processed + } + + tr = &TestRequest{ + ReadyCh: make(chan struct{}), + DoneCh: make(chan struct{}), + InCh: make(chan wsMsg), + OutCh: make(chan wsMsg, cap(bcastCh)), + BcastCh: bcastCh, + Request: rq, + RequestWriter: rq.Writer(rr), + ResponseRecorder: rr, + } + + go func() { + defer func() { + if tr.ExpectPanic { + if tr.PanicVal = recover(); tr.PanicVal != nil { + tr.Panicked = true + } + } + close(tr.DoneCh) + }() + close(tr.ReadyCh) + tr.process(tr.BcastCh, tr.InCh, tr.OutCh) // unsubs from bcast, closes outCh + jw.recycle(tr.Request) + }() + } + + return +} + +func (tr *TestRequest) Close() { + close(tr.InCh) +} + +func (tr *TestRequest) BodyString() string { + return strings.TrimSpace(tr.Body.String()) +} + +func (tr *TestRequest) BodyHTML() template.HTML { + return template.HTML(tr.BodyString()) /* #nosec G203 */ +} diff --git a/jaws/ui_test.go b/jaws/ui_test.go index 494268f..cc79a5a 100644 --- a/jaws/ui_test.go +++ b/jaws/ui_test.go @@ -37,7 +37,7 @@ func TestRequest_NewElement_DebugPanicsIfNotComparable(t *testing.T) { }() nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() rq.NewElement(notHashableUI) @@ -51,7 +51,7 @@ func (testStringer) String() string { return "foo" } func TestRequest_JawsRender_DebugOutput(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() rq.Jaws.Debug = true @@ -72,7 +72,7 @@ func TestRequest_JawsRender_DebugOutput(t *testing.T) { func TestRequest_InsideTemplate(t *testing.T) { var buf bytes.Buffer - tr := newTestRequest() + tr := newTestRequest(t) defer tr.Close() tp := newTestPage(tr) err := tp.render(&buf) @@ -85,7 +85,7 @@ func TestRequest_InsideTemplate(t *testing.T) { } func BenchmarkPageRender(b *testing.B) { - tr := newTestRequest() + tr := newTestRequest(nil) defer tr.Close() tp := newTestPage(tr) @@ -97,7 +97,7 @@ func BenchmarkPageRender(b *testing.B) { } func BenchmarkPageUpdate(b *testing.B) { - tr := newTestRequest() + tr := newTestRequest(nil) defer tr.Close() tp := newTestPage(tr) var buf bytes.Buffer diff --git a/jaws/uia_test.go b/jaws/uia_test.go index 801d2ba..d1fbd2c 100644 --- a/jaws/uia_test.go +++ b/jaws/uia_test.go @@ -52,7 +52,7 @@ func TestRequest_A(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() rq.A(tt.args.innerHTML, tt.args.params...) if got := rq.BodyHTML(); !reflect.DeepEqual(got, tt.want) { diff --git a/jaws/uibutton_test.go b/jaws/uibutton_test.go index 594cc29..fb2e780 100644 --- a/jaws/uibutton_test.go +++ b/jaws/uibutton_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Button(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `` rq.Button("inner") diff --git a/jaws/uicheckbox_test.go b/jaws/uicheckbox_test.go index 46edd29..b537889 100644 --- a/jaws/uicheckbox_test.go +++ b/jaws/uicheckbox_test.go @@ -10,7 +10,7 @@ import ( func TestRequest_Checkbox(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter(true) @@ -21,7 +21,7 @@ func TestRequest_Checkbox(t *testing.T) { } val := false - rq.inCh <- wsMsg{Data: "false", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "false", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -31,7 +31,7 @@ func TestRequest_Checkbox(t *testing.T) { t.Error(ts.Get(), "!=", val) } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -42,7 +42,7 @@ func TestRequest_Checkbox(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Value\tJid.1\t\"true\"\n" { t.Errorf("%q", s) @@ -55,11 +55,11 @@ func TestRequest_Checkbox(t *testing.T) { t.Error("SetCount", ts.SetCount()) } - rq.inCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nstrconv.ParseBool: parsing "omg": invalid syntax\"\n" { t.Errorf("wrong Alert: %q", s) @@ -67,11 +67,11 @@ func TestRequest_Checkbox(t *testing.T) { } ts.err = errors.New("meh") - rq.inCh <- wsMsg{Data: "true", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "true", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) diff --git a/jaws/uicontainer_test.go b/jaws/uicontainer_test.go index 0bd3d01..4573052 100644 --- a/jaws/uicontainer_test.go +++ b/jaws/uicontainer_test.go @@ -58,7 +58,7 @@ func TestRequest_Container(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() err := rq.Container("div", tt.args.c, tt.args.params...) if err != nil { diff --git a/jaws/uidate_test.go b/jaws/uidate_test.go index c861411..de30cab 100644 --- a/jaws/uidate_test.go +++ b/jaws/uidate_test.go @@ -12,7 +12,7 @@ import ( func TestRequest_Date(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter(time.Now()) @@ -23,7 +23,7 @@ func TestRequest_Date(t *testing.T) { } val, _ := time.Parse(ISO8601, "1970-02-03") - rq.inCh <- wsMsg{Data: val.Format(ISO8601), Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: val.Format(ISO8601), Jid: 1, What: what.Input} tmr := time.NewTimer(testTimeout) defer tmr.Stop() select { @@ -35,7 +35,7 @@ func TestRequest_Date(t *testing.T) { t.Error(ts.Get(), "!=", val) } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -46,7 +46,7 @@ func TestRequest_Date(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != fmt.Sprintf("Value\tJid.1\t\"%s\"\n", val.Format(ISO8601)) { t.Error("wrong Value") @@ -59,11 +59,11 @@ func TestRequest_Date(t *testing.T) { t.Error("SetCount", ts.SetCount()) } - rq.inCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nparsing time "omg" as "2006-01-02": cannot parse "omg" as "2006"\"\n" { t.Errorf("wrong Alert: %q", s) @@ -71,11 +71,11 @@ func TestRequest_Date(t *testing.T) { } ts.err = errors.New("meh") - rq.inCh <- wsMsg{Data: val.Format(ISO8601), Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: val.Format(ISO8601), Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) diff --git a/jaws/uidiv_test.go b/jaws/uidiv_test.go index 48c4fb7..95ddc02 100644 --- a/jaws/uidiv_test.go +++ b/jaws/uidiv_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Div(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `
inner
` rq.Div("inner") diff --git a/jaws/uiimg_test.go b/jaws/uiimg_test.go index edeecf3..341c7cf 100644 --- a/jaws/uiimg_test.go +++ b/jaws/uiimg_test.go @@ -8,7 +8,7 @@ import ( func TestRequest_Img(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter("image.png") @@ -25,7 +25,7 @@ func TestRequest_Img(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "SAttr\tJid.1\t\"src\\nimage2.jpg\"\n" { t.Error(strconv.Quote(s)) diff --git a/jaws/uilabel_test.go b/jaws/uilabel_test.go index 0bc0fa8..db60b72 100644 --- a/jaws/uilabel_test.go +++ b/jaws/uilabel_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Label(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `` rq.Label("inner") diff --git a/jaws/uili_test.go b/jaws/uili_test.go index 0c1c675..979f143 100644 --- a/jaws/uili_test.go +++ b/jaws/uili_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Li(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `
  • inner
  • ` rq.Li("inner") diff --git a/jaws/uinumber_test.go b/jaws/uinumber_test.go index e45cd92..a93a675 100644 --- a/jaws/uinumber_test.go +++ b/jaws/uinumber_test.go @@ -11,7 +11,7 @@ import ( func TestRequest_Number(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter(float64(1.2)) @@ -22,7 +22,7 @@ func TestRequest_Number(t *testing.T) { } val := float64(2.3) - rq.inCh <- wsMsg{Data: fmt.Sprint(val), Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: fmt.Sprint(val), Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -32,7 +32,7 @@ func TestRequest_Number(t *testing.T) { t.Error(ts.Get(), "!=", val) } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -43,7 +43,7 @@ func TestRequest_Number(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != fmt.Sprintf("Value\tJid.1\t\"%v\"\n", val) { t.Error("wrong Value") @@ -56,11 +56,11 @@ func TestRequest_Number(t *testing.T) { t.Error("SetCount", ts.SetCount()) } - rq.inCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nstrconv.ParseFloat: parsing "omg": invalid syntax\"\n" { t.Errorf("wrong Alert: %q", s) @@ -68,11 +68,11 @@ func TestRequest_Number(t *testing.T) { } ts.err = errors.New("meh") - rq.inCh <- wsMsg{Data: fmt.Sprint(val), Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: fmt.Sprint(val), Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) diff --git a/jaws/uioption_test.go b/jaws/uioption_test.go index 48f5c89..363e5e3 100644 --- a/jaws/uioption_test.go +++ b/jaws/uioption_test.go @@ -8,7 +8,7 @@ import ( func TestUiOption(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() nba := NewNamedBoolArray() @@ -30,7 +30,7 @@ func TestUiOption(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "RAttr\tJid.1\t\"selected\"\n" { t.Errorf("%q", s) @@ -42,7 +42,7 @@ func TestUiOption(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "SAttr\tJid.1\t\"selected\\n\"\n" { t.Errorf("%q", s) diff --git a/jaws/uipassword_test.go b/jaws/uipassword_test.go index f8b9ea3..8e42e7d 100644 --- a/jaws/uipassword_test.go +++ b/jaws/uipassword_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Password(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter("") want := `` diff --git a/jaws/uiradio_test.go b/jaws/uiradio_test.go index 1e0c715..c0c4039 100644 --- a/jaws/uiradio_test.go +++ b/jaws/uiradio_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Radio(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter(true) diff --git a/jaws/uiradiogroup_test.go b/jaws/uiradiogroup_test.go index d088e9d..50e9c17 100644 --- a/jaws/uiradiogroup_test.go +++ b/jaws/uiradiogroup_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_RadioGroup(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() nba := NewNamedBoolArray() diff --git a/jaws/uirange_test.go b/jaws/uirange_test.go index 59e9e9a..8e93bee 100644 --- a/jaws/uirange_test.go +++ b/jaws/uirange_test.go @@ -10,7 +10,7 @@ import ( func TestRequest_Range(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ts := newTestSetter(float64(1)) @@ -19,7 +19,7 @@ func TestRequest_Range(t *testing.T) { if got := rq.BodyString(); got != want { t.Errorf("Request.Range() = %q, want %q", got, want) } - rq.inCh <- wsMsg{Data: "2.1", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "2.1", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -29,7 +29,7 @@ func TestRequest_Range(t *testing.T) { t.Error(ts.Get()) } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -38,7 +38,7 @@ func TestRequest_Range(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Value\tJid.1\t\"2.3\"\n" { t.Error(s) @@ -52,11 +52,11 @@ func TestRequest_Range(t *testing.T) { } ts.err = errors.New("meh") - rq.inCh <- wsMsg{Data: "3.4", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "3.4", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) diff --git a/jaws/uiregister_test.go b/jaws/uiregister_test.go index d29faf3..96242d9 100644 --- a/jaws/uiregister_test.go +++ b/jaws/uiregister_test.go @@ -8,12 +8,12 @@ import ( func TestRequestWriter_Register(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() item := &testUi{} jid := rq.Register(item) th.Equal(jid, Jid(1)) th.Equal(atomic.LoadInt32(&item.updateCalled), int32(1)) - e := rq.getElementByJid(jid) + e := rq.GetElementByJid(jid) th.NoErr(e.JawsRender(nil, nil)) } diff --git a/jaws/uiselect_test.go b/jaws/uiselect_test.go index aab2df2..521fcde 100644 --- a/jaws/uiselect_test.go +++ b/jaws/uiselect_test.go @@ -32,7 +32,7 @@ func (ts *testNamedBoolArray) JawsSet(e *Element, val string) (err error) { func TestRequest_Select(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() a := &testNamedBoolArray{ @@ -56,7 +56,7 @@ func TestRequest_Select(t *testing.T) { t.Error("2 is checked") } - rq.inCh <- wsMsg{Data: "2", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "2", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -71,9 +71,13 @@ func TestRequest_Select(t *testing.T) { } select { - case s := <-rq.outCh: - t.Errorf("%q", s) - default: + case <-th.C: + th.Timeout() + case msg := <-rq.OutCh: + s := msg.Format() + if s != "Value\tJid.1\t\"2\"\n" { + t.Errorf("wrong Value %q", s) + } } a.Set("2", false) @@ -82,10 +86,10 @@ func TestRequest_Select(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Value\tJid.1\t\"\"\n" { - t.Error("wrong Value") + t.Errorf("wrong Value %q", s) } } @@ -96,15 +100,23 @@ func TestRequest_Select(t *testing.T) { t.Error("2 is checked") } + a.mu.Lock() a.err = errors.New("meh") - rq.inCh <- wsMsg{Data: "1", Jid: 1, What: what.Input} + a.mu.Unlock() + rq.InCh <- wsMsg{Data: "1", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) + select { + case msg := <-rq.OutCh: + s := msg.Format() + t.Errorf("queued msg: %q", s) + default: + } } } diff --git a/jaws/uispan_test.go b/jaws/uispan_test.go index 41d8ca6..c16551a 100644 --- a/jaws/uispan_test.go +++ b/jaws/uispan_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Span(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `inner` rq.Span("inner") diff --git a/jaws/uitbody_test.go b/jaws/uitbody_test.go index cbd2db7..b64d4fc 100644 --- a/jaws/uitbody_test.go +++ b/jaws/uitbody_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Tbody(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `` rq.Tbody(&testContainer{}) diff --git a/jaws/uitd_test.go b/jaws/uitd_test.go index 0bc350b..952effc 100644 --- a/jaws/uitd_test.go +++ b/jaws/uitd_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Td(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `inner` rq.Td("inner") diff --git a/jaws/uitemplate_test.go b/jaws/uitemplate_test.go index e614c31..a8c209c 100644 --- a/jaws/uitemplate_test.go +++ b/jaws/uitemplate_test.go @@ -1,7 +1,9 @@ package jaws import ( + "bytes" "html/template" + "log/slog" "reflect" "strings" "testing" @@ -16,15 +18,17 @@ func TestRequest_TemplateMissingJid(t *testing.T) { t.Skip("debug tag not set") } nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() - rq.jw.AddTemplateLookuper(template.Must(template.New("badtesttemplate").Parse(`{{with $.Dot}}
    {{.}}
    {{end}}`))) + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) + rq.Jaws.AddTemplateLookuper(template.Must(template.New("badtesttemplate").Parse(`{{with $.Dot}}
    {{.}}
    {{end}}`))) if e := rq.Template("badtesttemplate", nil, nil); e != nil { t.Error(e) } - if !strings.Contains(rq.jw.log.String(), "WARN") || !strings.Contains(rq.jw.log.String(), "badtesttemplate") { + if !strings.Contains(log.String(), "WARN") || !strings.Contains(log.String(), "badtesttemplate") { t.Error("expected WARN in the log") - t.Log(rq.jw.log.String()) + t.Log(log.String()) } } @@ -33,15 +37,17 @@ func TestRequest_TemplateJidInsideIf(t *testing.T) { t.Skip("debug tag not set") } nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() - rq.jw.AddTemplateLookuper(template.Must(template.New("iftesttemplate").Parse(`{{with $.Dot}}{{if true}}
    {{.}}
    {{end}}{{end}}`))) + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) + rq.Jaws.AddTemplateLookuper(template.Must(template.New("iftesttemplate").Parse(`{{with $.Dot}}{{if true}}
    {{.}}
    {{end}}{{end}}`))) if e := rq.Template("iftesttemplate", nil, nil); e != nil { t.Error(e) } - if strings.Contains(rq.jw.log.String(), "WARN") && strings.Contains(rq.jw.log.String(), "iftesttemplate") { + if strings.Contains(log.String(), "WARN") && strings.Contains(log.String(), "iftesttemplate") { t.Error("found WARN in the log") - t.Log(rq.jw.log.String()) + t.Log(log.String()) } } @@ -50,15 +56,17 @@ func TestRequest_TemplateMissingJidButHasHTMLTag(t *testing.T) { t.Skip("debug tag not set") } nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() - rq.jw.AddTemplateLookuper(template.Must(template.New("badtesttemplate").Parse(`{{with $.Dot}}
    {{.}}
    {{end}}`))) + var log bytes.Buffer + rq.Jaws.Logger = slog.New(slog.NewTextHandler(&log, nil)) + rq.Jaws.AddTemplateLookuper(template.Must(template.New("badtesttemplate").Parse(`{{with $.Dot}}
    {{.}}
    {{end}}`))) if e := rq.Template("badtesttemplate", nil, nil); e != nil { t.Error(e) } - if strings.Contains(rq.jw.log.String(), "WARN") { + if strings.Contains(log.String(), "WARN") { t.Error("expected no WARN in the log") - t.Log(rq.jw.log.String()) + t.Log(log.String()) } } @@ -106,7 +114,7 @@ func TestRequest_Template(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() if tt.errtxt != "" { defer func() { @@ -155,15 +163,15 @@ var _ ClickHandler = &templateDot{} func TestRequest_Template_Event(t *testing.T) { is := newTestHelper(t) - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() dot := &templateDot{clickedCh: make(chan struct{})} rq.Template("testtemplate", dot) - rq.jw.Broadcast(Message{ + rq.Jaws.Broadcast(Message{ Dest: dot, What: what.Update, }) - rq.jw.Broadcast(Message{ + rq.Jaws.Broadcast(Message{ Dest: dot, What: what.Click, Data: "foo", diff --git a/jaws/uitext_test.go b/jaws/uitext_test.go index 0170ce6..1726381 100644 --- a/jaws/uitext_test.go +++ b/jaws/uitext_test.go @@ -10,7 +10,7 @@ import ( func TestRequest_Text(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ss := newTestSetter("foo") @@ -19,7 +19,7 @@ func TestRequest_Text(t *testing.T) { if got := rq.BodyString(); got != want { t.Errorf("Request.Text() = %q, want %q", got, want) } - rq.inCh <- wsMsg{Data: "bar", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "bar", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -29,7 +29,7 @@ func TestRequest_Text(t *testing.T) { t.Error(ss.Get()) } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -38,7 +38,7 @@ func TestRequest_Text(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Value\tJid.1\t\"quux\"\n" { t.Error("wrong Value") @@ -52,11 +52,11 @@ func TestRequest_Text(t *testing.T) { } ss.err = errors.New("meh") - rq.inCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "omg", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Alert\t\t\"danger\\nmeh\"\n" { t.Errorf("wrong Alert: %q", s) diff --git a/jaws/uitextarea_test.go b/jaws/uitextarea_test.go index 0c78540..fe4ef77 100644 --- a/jaws/uitextarea_test.go +++ b/jaws/uitextarea_test.go @@ -9,7 +9,7 @@ import ( func TestRequest_Textarea(t *testing.T) { th := newTestHelper(t) nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() ss := newTestSetter("foo") @@ -18,7 +18,7 @@ func TestRequest_Textarea(t *testing.T) { if got := rq.BodyString(); got != want { t.Errorf("Request.Textarea() = %q, want %q", got, want) } - rq.inCh <- wsMsg{Data: "bar", Jid: 1, What: what.Input} + rq.InCh <- wsMsg{Data: "bar", Jid: 1, What: what.Input} select { case <-th.C: th.Timeout() @@ -28,7 +28,7 @@ func TestRequest_Textarea(t *testing.T) { t.Fail() } select { - case s := <-rq.outCh: + case s := <-rq.OutCh: t.Errorf("%q", s) default: } @@ -37,7 +37,7 @@ func TestRequest_Textarea(t *testing.T) { select { case <-th.C: th.Timeout() - case msg := <-rq.outCh: + case msg := <-rq.OutCh: s := msg.Format() if s != "Value\tJid.1\t\"quux\"\n" { t.Fail() diff --git a/jaws/uitr_test.go b/jaws/uitr_test.go index 3e87fad..1be3f27 100644 --- a/jaws/uitr_test.go +++ b/jaws/uitr_test.go @@ -6,7 +6,7 @@ import ( func TestRequest_Tr(t *testing.T) { nextJid = 0 - rq := newTestRequest() + rq := newTestRequest(t) defer rq.Close() want := `inner` rq.Tr("inner") diff --git a/jaws_test.go b/jaws_test.go index a5602e7..b6e47eb 100644 --- a/jaws_test.go +++ b/jaws_test.go @@ -93,45 +93,49 @@ func maybeFatal(t *testing.T, err error) { } } -func TestNewTemplate(t *testing.T) { - jw, err := jaws.New() - maybeFatal(t, err) - defer jw.Close() - - jw.AddTemplateLookuper(template.Must(template.New("nested").Parse(testPageNestedTmplText))) - jw.AddTemplateLookuper(template.Must(template.New("normal").Parse(testPageTmplText))) - - hr := httptest.NewRequest(http.MethodGet, "/", nil) - rq := jw.NewRequest(hr) - jw.UseRequest(rq.JawsKey, hr) - var sb strings.Builder - rqwr := rq.Writer(&sb) - - var mu sync.RWMutex - vbool := true - vtime, _ := time.Parse(jaws.ISO8601, "1901-02-03") - vnumber := float64(1.2) - vstring := "bar" - nba := jaws.NewNamedBoolArray() - - tp := &testPage{ - TheBool: jaws.Bind(&mu, &vbool), - TheContainer: &testContainer{}, - TheTime: jaws.Bind(&mu, &vtime), - TheNumber: jaws.Bind(&mu, &vnumber), - TheString: jaws.Bind(&mu, &vstring), - TheSelector: nba, - TheDot: jaws.Tag("dot"), - } +var onlyOnce sync.Once - tmpl := jaws.NewTemplate("normal", tp) - elem := rq.NewElement(tmpl) - err = tmpl.JawsRender(elem, rqwr, nil) - maybeFatal(t, err) - - if sb.String() != testPageWant { - t.Errorf("\n got: %q\nwant: %q\n", sb.String(), testPageWant) - } +func TestNewTemplate(t *testing.T) { + onlyOnce.Do(func() { + jw, err := jaws.New() + maybeFatal(t, err) + defer jw.Close() + + jw.AddTemplateLookuper(template.Must(template.New("nested").Parse(testPageNestedTmplText))) + jw.AddTemplateLookuper(template.Must(template.New("normal").Parse(testPageTmplText))) + + hr := httptest.NewRequest(http.MethodGet, "/", nil) + rq := jw.NewRequest(hr) + jw.UseRequest(rq.JawsKey, hr) + var sb strings.Builder + rqwr := rq.Writer(&sb) + + var mu sync.RWMutex + vbool := true + vtime, _ := time.Parse(jaws.ISO8601, "1901-02-03") + vnumber := float64(1.2) + vstring := "bar" + nba := jaws.NewNamedBoolArray() + + tp := &testPage{ + TheBool: jaws.Bind(&mu, &vbool), + TheContainer: &testContainer{}, + TheTime: jaws.Bind(&mu, &vtime), + TheNumber: jaws.Bind(&mu, &vnumber), + TheString: jaws.Bind(&mu, &vstring), + TheSelector: nba, + TheDot: jaws.Tag("dot"), + } + + tmpl := jaws.NewTemplate("normal", tp) + elem := rq.NewElement(tmpl) + err = tmpl.JawsRender(elem, rqwr, nil) + maybeFatal(t, err) + + if s := sb.String(); s != testPageWant { + t.Errorf("\n got: %q\nwant: %q\n", s, testPageWant) + } + }) } func TestJsVar(t *testing.T) { @@ -191,5 +195,16 @@ func TestNewUi(t *testing.T) { jaws.NewUiTd(htmlGetter) jaws.NewUiText(jaws.Bind(&mu, &vstring)) jaws.NewUiTr(htmlGetter) +} +func TestNewTestRequest(t *testing.T) { + jw, err := jaws.New() + maybeFatal(t, err) + defer jw.Close() + go jw.Serve() + if tr := jaws.NewTestRequest(jw, nil); tr == nil { + t.Fatal("got nil") + } else { + tr.Close() + } } diff --git a/jawstree/tree.go b/jawstree/tree.go index 18a2cf4..4c4fa96 100644 --- a/jawstree/tree.go +++ b/jawstree/tree.go @@ -3,6 +3,7 @@ package jawstree import ( "fmt" "io" + "strconv" "github.com/linkdata/jaws" ) @@ -37,3 +38,13 @@ func (t *Tree) JawsRender(e *jaws.Element, w io.Writer, params []any) (err error } return } + +func (t *Tree) JawsUpdate(elem *jaws.Element) { + var b []byte + b = append(b, `{"tree":`...) + b = strconv.AppendQuote(b, t.id) + b = append(b, `,"data":`...) + b = t.JsVar.Ptr.marshalJSON(b) + b = append(b, `}`...) + elem.Jaws.JsCall(t.Tag, "jawstreeSet", string(b)) +} diff --git a/jawstree/tree_test.go b/jawstree/tree_test.go index d51685b..cb42f54 100644 --- a/jawstree/tree_test.go +++ b/jawstree/tree_test.go @@ -3,7 +3,6 @@ package jawstree import ( "encoding/json" "net/http" - "net/http/httptest" "os" "reflect" "strings" @@ -25,11 +24,12 @@ func TestTree(t *testing.T) { maybeError(t, err) defer jw.Close() - err = jw.Setup(http.DefaultServeMux.Handle, "/", Setup) + mux := http.NewServeMux() + err = jw.Setup(mux.Handle, "/", Setup) maybeError(t, err) - rq := jw.NewRequest(httptest.NewRequest("GET", "/", nil)) - rq = rq.Jaws.UseRequest(rq.JawsKey, httptest.NewRequest("GET", "/", nil)) + go jw.Serve() + rq := jaws.NewTestRequest(jw, nil) root, err := os.OpenRoot(".") maybeError(t, err) @@ -78,5 +78,26 @@ func TestTree(t *testing.T) { t.Fatal("selection mismatch") } + changed[0].Disabled = true + tree.JawsUpdate(elem) + select { + case <-t.Context().Done(): + case msg := <-rq.OutCh: + if s := string(rootnode.marshalJSON(nil)); !strings.Contains(msg.Data, s) { + t.Log(msg.Data) + t.Error("msg data did not contain our JSON") + } + if !strings.Contains(msg.Data, `"selectable":false`) { + t.Error("msg data did not contain selectable:false") + } + } + rootnode.JawsPathSet(elem, changed[0].ID+".selected", "false") + select { + case <-t.Context().Done(): + case msg := <-rq.OutCh: + if s := "jawstreeSetPath={\"tree\":\"tree\",\"id\":\"children.1.children.1\",\"set\":false}"; msg.Data != s { + t.Errorf("unexpected data: %q", msg.Data) + } + } }