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

textlab_test.gno

4.90 Kb · 146 lines
  1package textlab
  2
  3import (
  4	"testing"
  5
  6	"gno.land/p/moul/x/daily/levenshtein/v0"
  7	"gno.land/p/moul/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}