1package devtools23import (4 "testing"56 "gno.land/p/moul/fifo/v0"7 "gno.land/r/moul/config/v1"8 "gno.land/p/nt/uassert/v0"9)1011// reset puts the realm back to a just-deployed state. Realm globals live for12// the whole test binary and gno runs every Example AFTER every Test, so without13// this the pinned output would depend on which tests ran before it.14func reset() { notes = fifo.New(maxNotes) }1516func TestAdd(cur realm, t *testing.T) {17 reset()1819 Add(cross(cur), "hello")20 uassert.Equal(t, 1, notes.Size())21 uassert.Equal(t, "hello", notes.Get(0).(string))2223 uassert.AbortsWithMessage(t, cur, "empty note", func() { Add(cross(cur), " ") })24 uassert.AbortsContains(t, cur, "note too long", func() {25 long := ""26 for i := 0; i < 201; i++ {27 long += "x"28 }29 Add(cross(cur), long)30 })31}3233// TestAddEscapes is the guard that matters most in a realm anyone can write to:34// a note is a string a caller typed, and it lands in a markdown list. Without35// ui.Inline the first line below would close the list item and render as a36// heading, and the second would become a link.37func TestAddEscapes(cur realm, t *testing.T) {38 reset()3940 Add(cross(cur), "# not a heading")41 Add(cross(cur), "[not](https://evil.example.com)")4243 for _, got := range []string{notes.Get(0).(string), notes.Get(1).(string)} {44 uassert.False(t, got == "# not a heading", "markdown survived unescaped")45 uassert.False(t, got == "[not](https://evil.example.com)", "a link survived unescaped")46 }47 uassert.True(t, len(notes.Get(0).(string)) > len("# not a heading"),48 "escaping should have added characters")49}5051// TestNotesAreBounded pins the reason fifo is here rather than a slice: storage52// is paid for and never refunded, so a list anyone can append to needs a53// ceiling in the type, not in a comment.54func TestNotesAreBounded(cur realm, t *testing.T) {55 reset()5657 for i := 0; i < maxNotes+5; i++ {58 Add(cross(cur), "note "+string(rune('a'+i)))59 }60 uassert.Equal(t, maxNotes, notes.Size(), "the list stops growing at maxNotes")61 uassert.Equal(t, maxNotes, notes.MaxSize())62}6364// TestAddHonoursThePause is the whole point of importing r/moul/config: this65// realm can be stopped from outside, by a transaction against another realm,66// with no redeploy here. It cannot set that from this package (config has its67// own authority), so it asserts the wiring: Add goes through the pause check68// and passes while nothing is set.69func TestAddHonoursThePause(cur realm, t *testing.T) {70 reset()71 uassert.False(t, config.IsPausedFor(realmPath), "nothing configured means running")72 Add(cross(cur), "allowed while running")73 uassert.Equal(t, 1, notes.Size())74}7576func TestReset(cur realm, t *testing.T) {77 reset()78 Add(cross(cur), "one")79 Reset(cross(cur))80 uassert.Equal(t, 0, notes.Size())81}8283// ExampleRender pins the root view. It is free of the height and the chain id84// on purpose: those live under :chain, so this one is stable.85func ExampleRender() {86 reset()87 print(Render(""))88 // Output:89 // # allinone: devtools90 //91 // Eight packages, used together rather than one at a time. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/devtools/v0)92 //93 // ## Notes94 //95 // *no notes yet*96 //97 // - [add a note](/r/moul/x/allinone/devtools/v0$help&func=Add&body=hello)98 // - [clear them](/r/moul/x/allinone/devtools/v0$help&func=Reset)99 // - [live chain values](/r/moul/x/allinone/devtools/v0:chain)100 // - [toggle the debug panel](/r/moul/x/allinone/devtools/v0:?debug=1)101 //102 // ## What each package does here103 //104 // | package | what it contributed |105 // | --- | --- |106 // | `r/moul/config` | the notice block above, the pause guard on `Add`, and which explorer to link |107 // | `p/moul/mygnoscan` | the explorer routes, so a link survives a route change |108 // | `p/moul/debug` | the `?debug=1` panel, empty otherwise |109 // | `p/moul/realmpath` | splitting the render path into a view and a query |110 // | `p/moul/kit/ui` | escaping a caller's note, and this table |111 // | `p/moul/md` | every heading, link and list on the page |112 // | `p/moul/txlink` | the call links above, relative to this realm |113 // | `p/moul/fifo` | the note list, bounded so storage cannot grow forever |114}115Add(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string}, body string)
Render(path string) string
Reset(cur interface {.seal func(); Address func() .uverse.address; IsCode func() bool; IsCurrent func() bool; IsEphemeral func() bool; IsUser func() bool; IsUserCall func() bool; IsUserRun func() bool; PkgPath func() string; Previous func() .uverse.realm; String func() string; Sub func(string) .uverse.realm; Subpath func() string})
Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.
vm/qrender output, sanitized (docs/render-security.md) and displayed in an empty-sandbox iframe — scripts, forms and popups cannot run. Links stay inert in-preview; right-click to open.