PathrockNetwork Gno Explorer
HomeBlocksTransactionsTokensRealmsPackagesValidatorsAnalytics

PathrockNetwork Gno Explorer — an independent explorer for Gno.land Mainnet (gnoland-1), operated by PathrockNetwork. Not an official Gno.land service.

gnowebarchive RPC

gno.land/p/moul/x/social/threads/v0

Package
Open in gnoweb ↗

Overview

Kind
Pure package
Name
v0
Namespace
moul / x / social / threads
Files
4 (README)(gnomod.toml)
Exported functions
n/a — not supported for pure packages by the node (vm/qfuncs)
Module
gno.land/p/moul/x/social/threads/v0
gno
0.9

Files (4)

  • README.mdmarkdown
  • gnomod.tomltoml
  • threads.gnogno
  • threads_test.gnogno
threads_test.gnogno
1package threads23import (4	"strings"5	"testing"67	"gno.land/p/nt/testutils/v0"8	"gno.land/p/nt/uassert/v0"9	"gno.land/p/nt/urequire/v0"10)1112var (13	alice = testutils.TestAddress("alice")14	bob   = testutils.TestAddress("bob")15	carol = testutils.TestAddress("carol")1617	home = "gno.land/r/moul/home"18)1920func TestValidPage(t *testing.T) {21	tests := []struct {22		page string23		want bool24	}{25		{"gno.land/r/moul/home", true},26		{"gno.land/r/gov/dao:proposal/42", true},27		{"a/b", true},28		{"", false},29		{"nopath", false},30		{"/leading", false},31		{"trailing/", false},32		{"double//slash", false},33		{"gno.land/r/Moul/home", false},34		{"gno.land/r/moul/home?x=1", false},35		{"gno.land/r/moul/" + strings.Repeat("x", MaxPageLen), false},36	}37	for _, tt := range tests {38		uassert.Equal(t, tt.want, ValidPage(tt.page), tt.page)39	}40}4142func TestValidBody(t *testing.T) {43	tests := []struct {44		name string45		body string46		want bool47	}{48		{"plain", "hello", true},49		{"newlines and tabs are content", "a\nb\tc", true},50		{"markdown is allowed and escaped later", "[x](y) | z", true},51		{"empty", "", false},52		{"blank", "   \n ", false},53		{"too long", strings.Repeat("x", MaxBodyLen+1), false},54		{"control character", "a\x01b", false},55		{"delete character", "a\x7fb", false},56	}57	for _, tt := range tests {58		uassert.Equal(t, tt.want, ValidBody(tt.body), tt.name)59	}60}6162func TestPostRejectsWhatItCannotStore(t *testing.T) {63	b := NewBoard()6465	_, err := b.Post("nopath", alice, "hi", 100)66	uassert.ErrorIs(t, err, ErrBadPage)6768	_, err = b.Post(home, alice, "", 100)69	uassert.ErrorIs(t, err, ErrBadBody)7071	uassert.Equal(t, 0, b.Len())72	uassert.Equal(t, 0, b.Pages())73}7475func TestReplyCountsPeopleAndNotMessages(t *testing.T) {76	b := NewBoard()77	id, err := b.Post(home, alice, "root", 100)78	urequire.NoError(t, err)7980	// A new address is the mint signal, exactly once.81	isNew, err := b.Reply(id, bob, "first", 101)82	urequire.NoError(t, err)83	uassert.True(t, isNew, "bob is a new replier")8485	isNew, err = b.Reply(id, bob, "again", 102)86	urequire.NoError(t, err)87	uassert.False(t, isNew, "bob replying twice is not two people")8889	// The author replying to their own thread never counts.90	isNew, err = b.Reply(id, alice, "mine", 103)91	urequire.NoError(t, err)92	uassert.False(t, isNew, "the author is not an audience")9394	isNew, err = b.Reply(id, carol, "hello", 104)95	urequire.NoError(t, err)96	uassert.True(t, isNew, "carol is a new replier")9798	th, ok := b.Get(id)99	urequire.True(t, ok, "thread exists")100	uassert.Equal(t, 4, len(th.Replies))101	uassert.Equal(t, 2, th.Repliers())102	uassert.True(t, th.HasReplied(bob))103	uassert.False(t, th.HasReplied(alice), "the author is never in the replier set")104}105106func TestReplyToNothing(t *testing.T) {107	b := NewBoard()108	_, err := b.Reply(42, bob, "hi", 100)109	uassert.ErrorIs(t, err, ErrNoThread)110}111112func TestPinOnlyEverMovesForward(t *testing.T) {113	b := NewBoard()114	id, _ := b.Post(home, alice, "root", 100)115116	urequire.NoError(t, b.Pin(id, 1000))117	th, _ := b.Get(id)118	uassert.True(t, th.Pinned(999))119	uassert.False(t, th.Pinned(1000), "the pin is exclusive at its own height")120121	// A cheaper pin cannot cut an expensive one short.122	uassert.ErrorIs(t, b.Pin(id, 500), ErrPinIsPast)123	uassert.ErrorIs(t, b.Pin(id, 1000), ErrPinIsPast)124	uassert.Equal(t, int64(1000), th.PinnedUntil)125126	urequire.NoError(t, b.Pin(id, 1200))127	uassert.Equal(t, int64(1200), th.PinnedUntil)128}129130func TestListIsPinnedThenNewest(t *testing.T) {131	b := NewBoard()132	first, _ := b.Post(home, alice, "first", 100)133	second, _ := b.Post(home, bob, "second", 101)134	third, _ := b.Post(home, carol, "third", 102)135136	// Newest first, nothing pinned.137	got := b.List(home, 200, 0)138	urequire.Equal(t, 3, len(got))139	uassert.Equal(t, uint64(third), uint64(got[0].ID))140	uassert.Equal(t, uint64(second), uint64(got[1].ID))141	uassert.Equal(t, uint64(first), uint64(got[2].ID))142143	// Pinning the oldest moves it to the top and leaves the rest in order.144	urequire.NoError(t, b.Pin(first, 500))145	got = b.List(home, 200, 0)146	uassert.Equal(t, uint64(first), uint64(got[0].ID))147	uassert.Equal(t, uint64(third), uint64(got[1].ID))148	uassert.Equal(t, uint64(second), uint64(got[2].ID))149150	// The pin expires on its own, with nothing to sweep.151	got = b.List(home, 500, 0)152	uassert.Equal(t, uint64(third), uint64(got[0].ID))153154	// limit truncates after the partition, so a pin is never cut off.155	got = b.List(home, 200, 1)156	urequire.Equal(t, 1, len(got))157	uassert.Equal(t, uint64(first), uint64(got[0].ID))158}159160func TestPagesAreIndependent(t *testing.T) {161	b := NewBoard()162	b.Post(home, alice, "on home", 100)163	b.Post("gno.land/r/moul/blog", bob, "on the blog", 101)164165	uassert.Equal(t, 2, b.Len())166	uassert.Equal(t, 2, b.Pages())167	uassert.Equal(t, 1, b.PageLen(home))168	uassert.Equal(t, 0, b.PageLen("gno.land/r/nobody/here"))169	uassert.Equal(t, 0, len(b.List("gno.land/r/nobody/here", 100, 0)))170}171172func TestRecentCrossesPages(t *testing.T) {173	b := NewBoard()174	b.Post(home, alice, "one", 100)175	b.Post("gno.land/r/moul/blog", bob, "two", 101)176	third, _ := b.Post(home, carol, "three", 102)177178	got := b.Recent(2)179	urequire.Equal(t, 2, len(got))180	uassert.Equal(t, uint64(third), uint64(got[0].ID), "newest first")181}182183func TestBlockEscapesTheBodyItShows(t *testing.T) {184	b := NewBoard()185	id, _ := b.Post(home, alice, "a | pipe and [a link](x)", 100)186187	out := Block("gno.land/r/moul/x/social/threads/v0", home, b.List(home, 200, 10), 1, 200)188189	// The assertion has to be on what IS there, not on what is not: an190	// escaped cell contains the backslash form, and a row that silently lost191	// the body entirely would satisfy any "does not contain" check.192	uassert.True(t, strings.Contains(out, `a \| pipe and \[a link\]\(x\)`),193		"the body is cut and escaped once, pipe included: "+out)194	uassert.False(t, strings.Contains(out, "| a | pipe"), "a raw pipe would open a column: "+out)195	uassert.True(t, strings.Contains(out, ThreadURL("gno.land/r/moul/x/social/threads/v0", id)))196	uassert.True(t, strings.Contains(out, "func=Post"), "the block offers the transaction")197}198199func TestBlockOnAnEmptyPageSaysSo(t *testing.T) {200	out := Block("gno.land/r/moul/x/social/threads/v0", home, nil, 0, 200)201	uassert.True(t, strings.Contains(out, "No discussion yet."), out)202}203204func TestURLsStripTheChainDomain(t *testing.T) {205	uassert.Equal(t, "/r/moul/x/social/threads/v0", RealmURL("gno.land/r/moul/x/social/threads/v0"))206	uassert.Equal(t, "/r/a/b:thread/7", ThreadURL("gno.land/r/a/b", 7))207	uassert.Equal(t, "/r/a/b:page/gno.land/r/moul/home", PageURL("gno.land/r/a/b", home))208}209

Functions

not supported for pure packages by the node (vm/qfuncs)

Signatures reconstructed verbatim from vm/qfuncs — interface params keep their inline definitions.