Skip to content Search Apps Documentation Source Content File Folder Download Copy Actions State String Boolean Number Struct Map Slice Pointer Function Closure Reference Nil Package Type Interface Unknown

Community realm. Read the code first. Community realm, deployed by its author. Read the code before you interact or send coins.

textlab_test.gno

4.97 Kb · 146 lines
  1package textlab
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/levenshtein/v0"
  7	"gno.land/p/g1magld5ptgzaktqcexxq0pgq5lmdf0kz6re8hlv/x/daily/soundex/v0"
  8	"gno.land/p/nt/uassert/v0"
  9)
 10
 11// TestRomanOf covers the one package here whose input is a number rather than
 12// a word, in both directions plus the two ways it can be neither.
 13func TestRomanOf(t *testing.T) {
 14	tests := []struct{ in, want string }{
 15		{"1987", "MCMLXXXVII"},
 16		{"1", "I"},
 17		{"3999", "MMMCMXCIX"},
 18		{"0", "out of range (1..3999)"},
 19		{"-5", "out of range (1..3999)"},
 20		{"4000", "out of range (1..3999)"},
 21		{"MCMLXXXVII", "1987 (read as roman)"},
 22		{"mcmlxxxvii", "1987 (read as roman)"},
 23		{"knight", "not a number"},
 24	}
 25	for _, tt := range tests {
 26		uassert.Equal(t, tt.want, romanOf(tt.in), "romanOf("+tt.in+")")
 27	}
 28}
 29
 30// TestTheDisagreement is the claim the realm is built to show, pinned in both
 31// directions. Measured 2026-09-23; the numbers in the package doc and on the
 32// page come from here, so if either package changes its mind this fails rather
 33// than the page quietly stating something false.
 34func TestTheDisagreement(t *testing.T) {
 35	// One edit apart, and yet soundex says unrelated: it keys on the first
 36	// letter, and the silent K changes it.
 37	uassert.Equal(t, "K523", soundex.Encode("knight"))
 38	uassert.Equal(t, "N230", soundex.Encode("night"))
 39	uassert.False(t, soundex.Match("knight", "night"), "different soundex codes")
 40	uassert.Equal(t, 1, levenshtein.Distance("knight", "night"))
 41	uassert.Equal(t, 83, levenshtein.Similarity("knight", "night"))
 42
 43	// And the other way: two edits apart, identical codes.
 44	uassert.Equal(t, "R163", soundex.Encode("robert"))
 45	uassert.Equal(t, "R163", soundex.Encode("rupert"))
 46	uassert.True(t, soundex.Match("robert", "rupert"), "same soundex code")
 47	uassert.Equal(t, 2, levenshtein.Distance("robert", "rupert"))
 48}
 49
 50// TestRomanOfDoesNotAbortOnOrdinaryWords is the defect this realm shipped with
 51// until a test found it: romannum.FromRoman PANICS on a non-numeral instead of
 52// returning zero, so the DEFAULT view aborted. "knight" is not a roman numeral,
 53// and neither is most of what anyone would put in the URL.
 54func TestRomanOfDoesNotAbortOnOrdinaryWords(t *testing.T) {
 55	for _, w := range []string{"knight", "night", "a|b", "", "hello world", "MMM!"} {
 56		uassert.Equal(t, "not a number", romanOf(w), "romanOf("+w+")")
 57	}
 58	uassert.True(t, isRomanNumeral("MCMLXXXVII"))
 59	uassert.False(t, isRomanNumeral("KNIGHT"))
 60	uassert.False(t, isRomanNumeral(""))
 61}
 62
 63// TestInputIsBounded pins the ceiling. levenshtein.Matrix is O(len(a)*len(b)),
 64// so an unbounded pair out of a URL is a way to make a query expensive for
 65// whoever serves it.
 66func TestInputIsBounded(t *testing.T) {
 67	long := ""
 68	for i := 0; i < maxInput+1; i++ {
 69		long += "x"
 70	}
 71	uassert.True(t, tooLong(long))
 72	uassert.False(t, tooLong("knight"))
 73
 74	out := Render(long)
 75	uassert.True(t, len(out) < 600, "an over-long input renders a refusal, not a matrix")
 76}
 77
 78// TestTableCellsAreEscaped: the input comes out of a URL and lands in a table
 79// cell, where an unescaped pipe eats a column.
 80func TestTableCellsAreEscaped(t *testing.T) {
 81	out := Render("a|b")
 82	uassert.False(t, contains(out, "| a|b |"), "a raw pipe reached a table cell")
 83}
 84
 85func contains(h, n string) bool {
 86	for i := 0; i+len(n) <= len(h); i++ {
 87		if h[i:i+len(n)] == n {
 88			return true
 89		}
 90	}
 91	return false
 92}
 93
 94// ExampleRender pins the root view, which is the default word and the "try
 95// two" list. Nothing here moves with the chain.
 96func ExampleRender() {
 97	print(Render(""))
 98	// Output:
 99	// # allinone: textlab
100	//
101	// Eight packages over one input. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/textlab/v0)
102	//
103	// ## Input: knight
104	//
105	// | package | answer |
106	// | --- | --- |
107	// | `x/daily/soundex` | K523 |
108	// | `x/daily/rot13` | xavtug |
109	// | `x/daily/piglatin` | ightknay |
110	// | `x/daily/romannum` | not a number |
111	//
112	// ## Try two
113	//
114	// - [knight vs night](/r/moul/x/allinone/textlab/v0:knight/night) (one edit apart, yet soundex says unrelated)
115	// - [robert vs rupert](/r/moul/x/allinone/textlab/v0:robert/rupert) (two edits apart, yet soundex says identical)
116	// - [1987 as a roman numeral](/r/moul/x/allinone/textlab/v0:1987)
117}
118
119// ExampleRender_pair pins the comparison view, which is the realm's whole
120// argument: the same pair, judged opposite ways by the two measures.
121func ExampleRender_pair() {
122	print(Render("knight/night"))
123	// Output:
124	// # allinone: textlab
125	//
126	// Eight packages over one input. [source](https://mygnoscan.moul.p2p.team/realm/r/moul/x/allinone/textlab/v0)
127	//
128	// ## Input: knight
129	//
130	// | package | answer |
131	// | --- | --- |
132	// | `x/daily/soundex` | K523 |
133	// | `x/daily/rot13` | xavtug |
134	// | `x/daily/piglatin` | ightknay |
135	// | `x/daily/romannum` | not a number |
136	//
137	// ## Compared with night
138	//
139	// | measure | answer |
140	// | --- | --- |
141	// | `soundex` of knight | K523 |
142	// | `soundex` of night | N230 |
143	// | sound alike | no |
144	// | `levenshtein` distance | 1 |
145	// | `levenshtein` similarity | 83% |
146}