Files
LearnGO/go/pkg/mod/github.com/mattn/go-runewidth@v0.0.13/runewidth_test.go
T
2024-09-19 21:38:24 -04:00

427 lines
11 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// +build !js,!appengine
package runewidth
import (
"crypto/sha256"
"fmt"
"os"
"sort"
"testing"
"unicode/utf8"
)
var _ sort.Interface = (*table)(nil) // ensure that type "table" does implement sort.Interface
func init() {
os.Setenv("RUNEWIDTH_EASTASIAN", "")
handleEnv()
}
func (t table) Len() int {
return len(t)
}
func (t table) Less(i, j int) bool {
return t[i].first < t[j].first
}
func (t *table) Swap(i, j int) {
(*t)[i], (*t)[j] = (*t)[j], (*t)[i]
}
type tableInfo struct {
tbl table
name string
wantN int
wantSHA string
}
var tables = []tableInfo{
{private, "private", 137468, "a4a641206dc8c5de80bd9f03515a54a706a5a4904c7684dc6a33d65c967a51b2"},
{nonprint, "nonprint", 2143, "288904683eb225e7c4c0bd3ee481b53e8dace404ec31d443afdbc4d13729fe95"},
{combining, "combining", 465, "3cce13deb5e23f9f7327f2b1ef162328285a7dcf277a98302a8f7cdd43971268"},
{doublewidth, "doublewidth", 182440, "3d16eda8650dc2c92d6318d32f0b4a74fda5a278db2d4544b1dd65863394823c"},
{ambiguous, "ambiguous", 138739, "d05e339a10f296de6547ff3d6c5aee32f627f6555477afebd4a3b7e3cf74c9e3"},
{emoji, "emoji", 3535, "9ec17351601d49c535658de8d129c1d0ccda2e620669fc39a2faaee7dedcef6d"},
{narrow, "narrow", 111, "fa897699c5e3cd9141c638d539331b0bdd508b874e22996c5e929767d455fc5a"},
{neutral, "neutral", 27333, "5455f5e75c307f70b4e9b2384dc5a8bcd91a4c5e2b24b2b185dfad4d860ee5c2"},
}
func TestTableChecksums(t *testing.T) {
for _, ti := range tables {
gotN := 0
buf := make([]byte, utf8.MaxRune+1)
for r := rune(0); r <= utf8.MaxRune; r++ {
if inTable(r, ti.tbl) {
gotN++
buf[r] = 1
}
}
gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
if gotN != ti.wantN || gotSHA != ti.wantSHA {
t.Errorf("table = %s,\n\tn = %d want %d,\n\tsha256 = %s want %s", ti.name, gotN, ti.wantN, gotSHA, ti.wantSHA)
}
}
}
func TestRuneWidthChecksums(t *testing.T) {
var testcases = []struct {
name string
eastAsianWidth bool
wantSHA string
}{
{"ea-no", false, "4eb632b105d3b2c800dda9141381d0b8a95250a3a5c7f1a5ca2c4d4daaa85234"},
{"ea-yes", true, "c2ddc3bdf42d81d4c23050e21eda46eb639b38b15322d35e8eb6c26f3b83ce92"},
}
for _, testcase := range testcases {
c := NewCondition()
c.EastAsianWidth = testcase.eastAsianWidth
buf := make([]byte, utf8.MaxRune+1)
for r := rune(0); r <= utf8.MaxRune; r++ {
buf[r] = byte(c.RuneWidth(r))
}
gotSHA := fmt.Sprintf("%x", sha256.Sum256(buf))
if gotSHA != testcase.wantSHA {
t.Errorf("TestRuneWidthChecksums = %s,\n\tsha256 = %s want %s",
testcase.name, gotSHA, testcase.wantSHA)
}
}
}
func checkInterval(first, last rune) bool {
return first >= 0 && first <= utf8.MaxRune &&
last >= 0 && last <= utf8.MaxRune &&
first <= last
}
func isCompact(t *testing.T, ti *tableInfo) bool {
tbl := ti.tbl
for i := range tbl {
e := tbl[i]
if !checkInterval(e.first, e.last) { // sanity check
t.Errorf("table invalid: table = %s index = %d %v", ti.name, i, e)
return false
}
if i+1 < len(tbl) && e.last+1 >= tbl[i+1].first { // can be combined into one entry
t.Errorf("table not compact: table = %s index = %d %v %v", ti.name, i, e, tbl[i+1])
return false
}
}
return true
}
func TestSorted(t *testing.T) {
for _, ti := range tables {
if !sort.IsSorted(&ti.tbl) {
t.Errorf("table not sorted: %s", ti.name)
}
if !isCompact(t, &ti) {
t.Errorf("table not compact: %s", ti.name)
}
}
}
var runewidthtests = []struct {
in rune
out int
eaout int
nseout int
}{
{'世', 2, 2, 2},
{'界', 2, 2, 2},
{'セ', 1, 1, 1},
{'カ', 1, 1, 1},
{'イ', 1, 1, 1},
{'☆', 1, 2, 2}, // double width in ambiguous
{'☺', 1, 1, 2},
{'☻', 1, 1, 2},
{'♥', 1, 2, 2},
{'♦', 1, 1, 2},
{'♣', 1, 2, 2},
{'♠', 1, 2, 2},
{'♂', 1, 2, 2},
{'♀', 1, 2, 2},
{'♪', 1, 2, 2},
{'♫', 1, 1, 2},
{'☼', 1, 1, 2},
{'↕', 1, 2, 2},
{'‼', 1, 1, 2},
{'↔', 1, 2, 2},
{'\x00', 0, 0, 0},
{'\x01', 0, 0, 0},
{'\u0300', 0, 0, 0},
{'\u2028', 0, 0, 0},
{'\u2029', 0, 0, 0},
{'a', 1, 1, 1}, // ASCII classified as "na" (narrow)
{'⟦', 1, 1, 1}, // non-ASCII classified as "na" (narrow)
{'👁', 1, 1, 2},
}
func TestRuneWidth(t *testing.T) {
c := NewCondition()
c.EastAsianWidth = false
for _, tt := range runewidthtests {
if out := c.RuneWidth(tt.in); out != tt.out {
t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=false)", tt.in, out, tt.out)
}
}
c.EastAsianWidth = true
for _, tt := range runewidthtests {
if out := c.RuneWidth(tt.in); out != tt.eaout {
t.Errorf("RuneWidth(%q) = %d, want %d (EastAsianWidth=true)", tt.in, out, tt.eaout)
}
}
c.StrictEmojiNeutral = false
for _, tt := range runewidthtests {
if out := c.RuneWidth(tt.in); out != tt.nseout {
t.Errorf("RuneWidth(%q) = %d, want %d (StrictEmojiNeutral=false)", tt.in, out, tt.eaout)
}
}
}
var isambiguouswidthtests = []struct {
in rune
out bool
}{
{'世', false},
{'■', true},
{'界', false},
{'○', true},
{'㈱', false},
{'①', true},
{'②', true},
{'③', true},
{'④', true},
{'⑤', true},
{'⑥', true},
{'⑦', true},
{'⑧', true},
{'⑨', true},
{'⑩', true},
{'⑪', true},
{'⑫', true},
{'⑬', true},
{'⑭', true},
{'⑮', true},
{'⑯', true},
{'⑰', true},
{'⑱', true},
{'⑲', true},
{'⑳', true},
{'☆', true},
}
func TestIsAmbiguousWidth(t *testing.T) {
for _, tt := range isambiguouswidthtests {
if out := IsAmbiguousWidth(tt.in); out != tt.out {
t.Errorf("IsAmbiguousWidth(%q) = %v, want %v", tt.in, out, tt.out)
}
}
}
var stringwidthtests = []struct {
in string
out int
eaout int
}{
{"■㈱の世界①", 10, 12},
{"スター☆", 7, 8},
{"つのだ☆HIRO", 11, 12},
}
func TestStringWidth(t *testing.T) {
c := NewCondition()
c.EastAsianWidth = false
for _, tt := range stringwidthtests {
if out := c.StringWidth(tt.in); out != tt.out {
t.Errorf("StringWidth(%q) = %d, want %d", tt.in, out, tt.out)
}
}
c.EastAsianWidth = true
for _, tt := range stringwidthtests {
if out := c.StringWidth(tt.in); out != tt.eaout {
t.Errorf("StringWidth(%q) = %d, want %d (EA)", tt.in, out, tt.eaout)
}
}
}
func TestStringWidthInvalid(t *testing.T) {
s := "こんにちわ\x00世界"
if out := StringWidth(s); out != 14 {
t.Errorf("StringWidth(%q) = %d, want %d", s, out, 14)
}
}
func TestTruncateSmaller(t *testing.T) {
s := "あいうえお"
expected := "あいうえお"
if out := Truncate(s, 10, "..."); out != expected {
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
}
}
func TestTruncate(t *testing.T) {
s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
out := Truncate(s, 80, "...")
if out != expected {
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
}
width := StringWidth(out)
if width != 79 {
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 79, width)
}
}
func TestTruncateFit(t *testing.T) {
s := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
expected := "aあいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおお..."
out := Truncate(s, 80, "...")
if out != expected {
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
}
width := StringWidth(out)
if width != 80 {
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
}
}
func TestTruncateJustFit(t *testing.T) {
s := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
expected := "あいうえおあいうえおえおおおおおおおおおおおおおおおおおおおおおおおおおおおおお"
out := Truncate(s, 80, "...")
if out != expected {
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
}
width := StringWidth(out)
if width != 80 {
t.Errorf("width of Truncate(%q) should be %d, but %d", s, 80, width)
}
}
func TestWrap(t *testing.T) {
s := `東京特許許可局局長はよく柿喰う客だ/東京特許許可局局長はよく柿喰う客だ
123456789012345678901234567890
END`
expected := `東京特許許可局局長はよく柿喰う
客だ/東京特許許可局局長はよく
柿喰う客だ
123456789012345678901234567890
END`
if out := Wrap(s, 30); out != expected {
t.Errorf("Wrap(%q) = %q, want %q", s, out, expected)
}
}
func TestTruncateNoNeeded(t *testing.T) {
s := "あいうえおあい"
expected := "あいうえおあい"
if out := Truncate(s, 80, "..."); out != expected {
t.Errorf("Truncate(%q) = %q, want %q", s, out, expected)
}
}
var isneutralwidthtests = []struct {
in rune
out bool
}{
{'→', false},
{'┊', false},
{'┈', false},
{'', false},
{'└', false},
{'⣀', true},
{'⣀', true},
}
func TestIsNeutralWidth(t *testing.T) {
for _, tt := range isneutralwidthtests {
if out := IsNeutralWidth(tt.in); out != tt.out {
t.Errorf("IsNeutralWidth(%q) = %v, want %v", tt.in, out, tt.out)
}
}
}
func TestFillLeft(t *testing.T) {
s := "あxいうえお"
expected := " あxいうえお"
if out := FillLeft(s, 15); out != expected {
t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
}
}
func TestFillLeftFit(t *testing.T) {
s := "あいうえお"
expected := "あいうえお"
if out := FillLeft(s, 10); out != expected {
t.Errorf("FillLeft(%q) = %q, want %q", s, out, expected)
}
}
func TestFillRight(t *testing.T) {
s := "あxいうえお"
expected := "あxいうえお "
if out := FillRight(s, 15); out != expected {
t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
}
}
func TestFillRightFit(t *testing.T) {
s := "あいうえお"
expected := "あいうえお"
if out := FillRight(s, 10); out != expected {
t.Errorf("FillRight(%q) = %q, want %q", s, out, expected)
}
}
func TestEnv(t *testing.T) {
old := os.Getenv("RUNEWIDTH_EASTASIAN")
defer os.Setenv("RUNEWIDTH_EASTASIAN", old)
os.Setenv("RUNEWIDTH_EASTASIAN", "0")
handleEnv()
if w := RuneWidth('│'); w != 1 {
t.Errorf("RuneWidth('│') = %d, want %d", w, 1)
}
}
func TestZeroWidthJoiner(t *testing.T) {
c := NewCondition()
var tests = []struct {
in string
want int
}{
{"👩", 2},
{"👩‍", 2},
{"👩‍🍳", 2},
{"‍🍳", 2},
{"👨‍👨", 2},
{"👨‍👨‍👧", 2},
{"🏳️‍🌈", 1},
{"あ👩‍🍳い", 6},
{"あ‍🍳い", 6},
{"あ‍い", 4},
}
for _, tt := range tests {
if got := c.StringWidth(tt.in); got != tt.want {
t.Errorf("StringWidth(%q) = %d, want %d", tt.in, got, tt.want)
}
}
}