1// +build !js,!appengine
2
3package runewidth
4
5import (
6	"crypto/sha256"
7	"fmt"
8	"os"
9	"sort"
10	"testing"
11	"unicode/utf8"
12)
13
14var _ sort.Interface = (*table)(nil) // ensure that type "table" does implement sort.Interface
15
16func init() {
17	os.Setenv("RUNEWIDTH_EASTASIAN", "")
18	handleEnv()
19}
20
21func (t table) Len() int {
22	return len(t)
23}
24
25func (t table) Less(i, j int) bool {
26	return t[i].first < t[j].first
27}
28
29func (t *table) Swap(i, j int) {
30	(*t)[i], (*t)[j] = (*t)[j], (*t)[i]
31}
32
33type tableInfo struct {
34	tbl     table
35	name    string
36	wantN   int
37	wantSHA string
38}
39
40var tables = []tableInfo{
41	{private, "private", 137468, "a4a641206dc8c5de80bd9f03515a54a706a5a4904c7684dc6a33d65c967a51b2"},
42	{nonprint, "nonprint", 2143, "288904683eb225e7c4c0bd3ee481b53e8dace404ec31d443afdbc4d13729fe95"},
43	{combining, "combining", 465, "3cce13deb5e23f9f7327f2b1ef162328285a7dcf277a98302a8f7cdd43971268"},
44	{doublewidth, "doublewidth", 182440, "3d16eda8650dc2c92d6318d32f0b4a74fda5a278db2d4544b1dd65863394823c"},
45	{ambiguous, "ambiguous", 138739, "d05e339a10f296de6547ff3d6c5aee32f627f6555477afebd4a3b7e3cf74c9e3"},
46	{emoji, "emoji", 3535, "9ec17351601d49c535658de8d129c1d0ccda2e620669fc39a2faaee7dedcef6d"},
47	{notassigned, "notassigned", 10, "68441e98eca1450efbe857ac051fcc872eed347054dfd0bc662d1c4ee021d69f"},
48	{neutral, "neutral", 27333, "5455f5e75c307f70b4e9b2384dc5a8bcd91a4c5e2b24b2b185dfad4d860ee5c2"},
49}
50
51func TestTableChecksums(t *testing.T) {
52	for _, ti := range tables {
53		gotN := 0
54		buf := make([]byte, utf8.MaxRune+1)
55		for r := rune(0); r <= utf8.MaxRune; r++ {
56			if inTable(r, ti.tbl) {
57				gotN++
58				buf[r] = 1
59			}
60		}
61		gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
62		if gotN != ti.wantN || gotSHA != ti.wantSHA {
63			t.Errorf("table = %s,\n\tn = %d want %d,\n\tsha256 = %s want %s", ti.name, gotN, ti.wantN, gotSHA, ti.wantSHA)
64		}
65	}
66}
67
68func checkInterval(first, last rune) bool {
69	return first >= 0 && first <= utf8.MaxRune &&
70		last >= 0 && last <= utf8.MaxRune &&
71		first <= last
72}
73
74func isCompact(t *testing.T, ti *tableInfo) bool {
75	tbl := ti.tbl
76	for i := range tbl {
77		e := tbl[i]
78		if !checkInterval(e.first, e.last) { // sanity check
79			t.Errorf("table invalid: table = %s index = %d %v", ti.name, i, e)
80			return false
81		}
82		if i+1 < len(tbl) && e.last+1 >= tbl[i+1].first { // can be combined into one entry
83			t.Errorf("table not compact: table = %s index = %d %v %v", ti.name, i, e, tbl[i+1])
84			return false
85		}
86	}
87	return true
88}
89
90// This is a utility function in case that a table has changed.
91func printCompactTable(tbl table) {
92	counter := 0
93	printEntry := func(first, last rune) {
94		if counter%3 == 0 {
95			fmt.Printf("\t")
96		}
97		fmt.Printf("{0x%04X, 0x%04X},", first, last)
98		if (counter+1)%3 == 0 {
99			fmt.Printf("\n")
100		} else {
101			fmt.Printf(" ")
102		}
103		counter++
104	}
105
106	sort.Sort(&tbl) // just in case
107	first := rune(-1)
108	for i := range tbl {
109		e := tbl[i]
110		if !checkInterval(e.first, e.last) { // sanity check
111			panic("invalid table")
112		}
113		if first < 0 {
114			first = e.first
115		}
116		if i+1 < len(tbl) && e.last+1 >= tbl[i+1].first { // can be combined into one entry
117			continue
118		}
119		printEntry(first, e.last)
120		first = -1
121	}
122	fmt.Printf("\n\n")
123}
124
125func TestSorted(t *testing.T) {
126	for _, ti := range tables {
127		if !sort.IsSorted(&ti.tbl) {
128			t.Errorf("table not sorted: %s", ti.name)
129		}
130		if !isCompact(t, &ti) {
131			t.Errorf("table not compact: %s", ti.name)
132			//printCompactTable(ti.tbl)
133		}
134	}
135}
136
137var runewidthtests = []struct {
138	in    rune
139	out   int
140	eaout int
141}{
142	{'世', 2, 2},
143	{'界', 2, 2},
144	{'セ', 1, 1},
145	{'カ', 1, 1},
146	{'イ', 1, 1},
147	{'☆', 1, 2}, // double width in ambiguous
148	{'☺', 1, 1},
149	{'☻', 1, 1},
150	{'♥', 1, 2},
151	{'♦', 1, 1},
152	{'♣', 1, 2},
153	{'♠', 1, 2},
154	{'♂', 1, 2},
155	{'♀', 1, 2},
156	{'♪', 1, 2},
157	{'♫', 1, 1},
158	{'☼', 1, 1},
159	{'↕', 1, 2},
160	{'‼', 1, 1},
161	{'↔', 1, 2},
162	{'\x00', 0, 0},
163	{'\x01', 0, 0},
164	{'\u0300', 0, 0},
165	{'\u2028', 0, 0},
166	{'\u2029', 0, 0},
167}
168
169func TestRuneWidth(t *testing.T) {
170	c := NewCondition()
171	c.EastAsianWidth = false
172	for _, tt := range runewidthtests {
173		if out := c.RuneWidth(tt.in); out != tt.out {
174			t.Errorf("RuneWidth(%q) = %d, want %d", tt.in, out, tt.out)
175		}
176	}
177	c.EastAsianWidth = true
178	for _, tt := range runewidthtests {
179		if out := c.RuneWidth(tt.in); out != tt.eaout {
180			t.Errorf("RuneWidth(%q) = %d, want %d", tt.in, out, tt.eaout)
181		}
182	}
183}
184
185var isambiguouswidthtests = []struct {
186	in  rune
187	out bool
188}{
189	{'世', false},
190	{'■', true},
191	{'界', false},
192	{'○', true},
193	{'㈱', false},
194	{'①', true},
195	{'②', true},
196	{'③', true},
197	{'④', true},
198	{'⑤', true},
199	{'⑥', true},
200	{'⑦', true},
201	{'⑧', true},
202	{'⑨', true},
203	{'⑩', true},
204	{'⑪', true},
205	{'⑫', true},
206	{'⑬', true},
207	{'⑭', true},
208	{'⑮', true},
209	{'⑯', true},
210	{'⑰', true},
211	{'⑱', true},
212	{'⑲', true},
213	{'⑳', true},
214	{'☆', true},
215}
216
217func TestIsAmbiguousWidth(t *testing.T) {
218	for _, tt := range isambiguouswidthtests {
219		if out := IsAmbiguousWidth(tt.in); out != tt.out {
220			t.Errorf("IsAmbiguousWidth(%q) = %v, want %v", tt.in, out, tt.out)
221		}
222	}
223}
224
225var stringwidthtests = []struct {
226	in    string
227	out   int
228	eaout int
229}{
230	{"■㈱の世界①", 10, 12},
231	{"スター☆", 7, 8},
232	{"つのだ☆HIRO", 11, 12},
233}
234
235func TestStringWidth(t *testing.T) {
236	c := NewCondition()
237	c.EastAsianWidth = false
238	for _, tt := range stringwidthtests {
239		if out := c.StringWidth(tt.in); out != tt.out {
240			t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.out)
241		}
242	}
243	c.EastAsianWidth = true
244	for _, tt := range stringwidthtests {
245		if out := c.StringWidth(tt.in); out != tt.eaout {
246			t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.eaout)
247		}
248	}
249}
250
251func TestStringWidthInvalid(t *testing.T) {
252	s := "こんにちわ\x00世界"
253	if out := StringWidth(s); out != 14 {
254		t.Errorf("StringWidth(%q) = %d, want %d", s, out, 14)
255	}
256}
257
258func TestTruncateSmaller(t *testing.T) {
259	s := "あいうえお"
260	expected := "あいうえお"
261
262	if out := Truncate(s, 10, "..."); out != expected {
263		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
264	}
265}
266
267func TestTruncate(t *testing.T) {
268	s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
269	expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
270	out := Truncate(s, 80, "...")
271	if out != expected {
272		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
273	}
274	width := StringWidth(out)
275	if width != 79 {
276		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 79, width)
277	}
278}
279
280func TestTruncateFit(t *testing.T) {
281	s := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
282	expected := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
283
284	out := Truncate(s, 80, "...")
285	if out != expected {
286		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
287	}
288	width := StringWidth(out)
289	if width != 80 {
290		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
291	}
292}
293
294func TestTruncateJustFit(t *testing.T) {
295	s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
296	expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
297
298	out := Truncate(s, 80, "...")
299	if out != expected {
300		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
301	}
302	width := StringWidth(out)
303	if width != 80 {
304		t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
305	}
306}
307
308func TestWrap(t *testing.T) {
309	s := `東京特許許可局局長はよく柿喰う客だ/東京特許許可局局長はよく柿喰う客だ
310123456789012345678901234567890
311
312END`
313	expected := `東京特許許可局局長はよく柿喰う
314客だ/東京特許許可局局長はよく
315柿喰う客だ
316123456789012345678901234567890
317
318END`
319
320	if out := Wrap(s, 30); out != expected {
321		t.Errorf("Wrap(%q) = %q, want %q", s, out, expected)
322	}
323}
324
325func TestTruncateNoNeeded(t *testing.T) {
326	s := "あいうえおあい"
327	expected := "あいうえおあい"
328
329	if out := Truncate(s, 80, "..."); out != expected {
330		t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
331	}
332}
333
334var isneutralwidthtests = []struct {
335	in  rune
336	out bool
337}{
338	{'→', false},
339	{'┊', false},
340	{'┈', false},
341	{'~', false},
342	{'└', false},
343	{'⣀', true},
344	{'⣀', true},
345}
346
347func TestIsNeutralWidth(t *testing.T) {
348	for _, tt := range isneutralwidthtests {
349		if out := IsNeutralWidth(tt.in); out != tt.out {
350			t.Errorf("IsNeutralWidth(%q) = %v, want %v", tt.in, out, tt.out)
351		}
352	}
353}
354
355func TestFillLeft(t *testing.T) {
356	s := "あxいうえお"
357	expected := "    あxいうえお"
358
359	if out := FillLeft(s, 15); out != expected {
360		t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
361	}
362}
363
364func TestFillLeftFit(t *testing.T) {
365	s := "あいうえお"
366	expected := "あいうえお"
367
368	if out := FillLeft(s, 10); out != expected {
369		t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
370	}
371}
372
373func TestFillRight(t *testing.T) {
374	s := "あxいうえお"
375	expected := "あxいうえお    "
376
377	if out := FillRight(s, 15); out != expected {
378		t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
379	}
380}
381
382func TestFillRightFit(t *testing.T) {
383	s := "あいうえお"
384	expected := "あいうえお"
385
386	if out := FillRight(s, 10); out != expected {
387		t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
388	}
389}
390
391func TestEnv(t *testing.T) {
392	old := os.Getenv("RUNEWIDTH_EASTASIAN")
393	defer os.Setenv("RUNEWIDTH_EASTASIAN", old)
394
395	os.Setenv("RUNEWIDTH_EASTASIAN", "0")
396	handleEnv()
397
398	if w := RuneWidth('│'); w != 1 {
399		t.Errorf("RuneWidth('│') = %d, want %d", w, 1)
400	}
401}
402
403func TestZeroWidthJointer(t *testing.T) {
404	c := NewCondition()
405	c.ZeroWidthJoiner = true
406
407	var tests = []struct {
408		in   string
409		want int
410	}{
411		{"��", 2},
412		{"��‍", 2},
413		{"��‍��", 2},
414		{"‍��", 2},
415		{"��‍��", 2},
416		{"��‍��‍��", 2},
417		{"��️‍��", 2},
418		{"あ��‍��い", 6},
419		{"あ‍��い", 6},
420		{"あ‍い", 4},
421	}
422
423	for _, tt := range tests {
424		if got := c.StringWidth(tt.in); got != tt.want {
425			t.Errorf("StringWidth(%q) = %d, want %d", tt.in, got, tt.want)
426		}
427	}
428}
429