livetest

package
v0.11.1 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 19, 2026 License: MIT Imports: 6 Imported by: 0

Documentation

Overview

Package livetest makes live action handlers unit-testable.

Testing a handler used to mean a real HTTP server, a cookie exchange, and hand-parsed SSE frames, so handlers went untested. This package removes the transport: NewSession mints a session with a Recorder attached in place of a browser, Invoke runs a registered handler the way an incoming action would, and the Recorder holds every patch the handler produced.

srv := live.New()
defer srv.Close()
registerHandlers(srv) // the code under test

sess, rec := livetest.NewSession(t, srv)
if err := livetest.Invoke(t, sess, "add-todo", "todos", addDetail{Text: "milk"}); err != nil {
    t.Fatal(err)
}
if _, ok := rec.Last("todos", "items"); !ok {
    t.Error("add-todo did not patch the items prop")
}

Everything is synchronous: a Send inside a handler has reached the recorder by the time Invoke returns, so assertions need no waiting and no polling.

Example

The shape of a handler test: register the code under test, invoke the action, assert on the patches — no HTTP server, no SSE parsing.

package main

import (
	"fmt"
	"testing"

	"github.com/bmartel/alacris-go/live"
	"github.com/bmartel/alacris-go/live/livetest"
)

func main() {
	t := &testing.T{} // in a real test this is the *testing.T you were given

	srv := live.New()
	defer srv.Close()
	live.On(srv, "add-todo", func(c *live.Ctx, d struct {
		Text string `json:"text"`
	}) error {
		c.Session.Element("todos").Set("items", []string{d.Text})
		return nil
	})

	sess, rec := livetest.NewSession(t, srv)
	_ = livetest.Invoke(t, sess, "add-todo", "todos", map[string]string{"text": "milk"})

	p, ok := rec.Last("todos", "items")
	fmt.Println(ok, p.Key)
}
Output:
true items

Index

Examples

Constants

This section is empty.

Variables

This section is empty.

Functions

func Invoke

func Invoke(tb testing.TB, s *live.Session, action, element string, detail any) error

Invoke runs the handler registered for action, session-scoped or server-wide, with detail as the event's payload — the same code path an incoming action takes, minus the transport. element becomes Ctx.Element, which is what Ctx.Handle addresses; leave it empty when the handler does not use it.

detail may be nil, a json.RawMessage, or any value to marshal.

func RenderSession

func RenderSession(tb testing.TB, srv *live.Server) *live.Session

RenderSession mints a session the way the application's render handler would, with nothing attached yet — the state a page is in between render and the browser connecting. Register OnOpen on it, then Attach. The session is closed when the test ends.

Types

type Recorder

type Recorder struct {
	// contains filtered or unexported fields
}

A Recorder holds the patches a session sent, standing in for the browser.

Reads are synchronous with the handlers that produced the patches: whatever a handler sent before returning is visible to the next Patches, Frames or Last call. Like a browser, the recorder must keep up — a session drops a subscriber more than 16 frames behind, so read (or batch) between bursts.

func Attach

func Attach(tb testing.TB, sess *live.Session) *Recorder

Attach connects a Recorder to the session in place of a browser: OnOpen runs, anything buffered since the render is recorded, and every later Send is visible to the recorder's reads. The recorder detaches when the test ends.

func NewSession

func NewSession(tb testing.TB, srv *live.Server) (*live.Session, *Recorder)

NewSession mints a session on srv the way a page render would — cookie exchange included, on a synthetic request — and attaches a Recorder in place of a browser. The session is closed when the test ends.

To exercise OnOpen the way a real page does, keep the real order: mint with RenderSession, register OnOpen, then Attach.

func (*Recorder) Frames

func (r *Recorder) Frames() [][]live.Patch

Frames returns every frame so far, in delivery order. Patches batched together arrive as one frame, so frame boundaries are the "lands in one paint" contract Batch makes.

func (*Recorder) Last

func (r *Recorder) Last(elementID, key string) (live.Patch, bool)

Last returns the most recent patch for one element and key — the value the page would be showing — and whether any patch matched at all.

func (*Recorder) Patches

func (r *Recorder) Patches() []live.Patch

Patches returns every patch so far, flattened, in order.

func (*Recorder) Reset

func (r *Recorder) Reset()

Reset forgets everything recorded so far, so the next assertion reads only what the next action produced.

func (*Recorder) String

func (r *Recorder) String() string

String summarises the recording, for t.Log and failure messages.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL