Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions internal/util/lua_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package util

import (
"testing"

"github.com/stretchr/testify/assert"
)

func TestLuaRuneAt(t *testing.T) {
tests := []struct {
name string
input string
idx int
expected string
}{
{"first", "hello", 0, "h"},
{"middle", "hello", 1, "e"},
{"last", "hello", 4, "o"},
{"past end", "hello", 5, ""},
{"well past end", "hello", 100, ""},
{"empty string", "", 0, ""},
{"multibyte", "h" + precomposedE + "llo", 1, precomposedE},
{"after multibyte", "h" + precomposedE + "llo", 2, "l"},
// only the base rune is returned; combining marks are dropped
{"combining mark", "h" + combiningE + "llo", 1, "e"},
{"after combining mark", "h" + combiningE + "llo", 2, "l"},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, LuaRuneAt(tt.input, tt.idx))
})
}
}

func TestLuaGetLeadingWhitespace(t *testing.T) {
tests := []struct {
name string
input string
expected string
}{
{"spaces", " hello", " "},
{"tabs", "\t\thello", "\t\t"},
{"mixed", " \t hello", " \t "},
{"none", "hello", ""},
{"empty", "", ""},
{"only whitespace", " ", " "},
{"newline is not leading whitespace", "\nhello", ""},
{"trailing whitespace ignored", " hello ", " "},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, LuaGetLeadingWhitespace(tt.input))
})
}
}

func TestLuaIsWordChar(t *testing.T) {
tests := []struct {
name string
input string
expected bool
}{
{"letter", "hello", true},
{"digit", "1abc", true},
{"underscore", "_abc", true},
{"multibyte letter", precomposedE, true},
{"space", " abc", false},
{"tab", "\tabc", false},
{"punctuation", ".abc", false},
{"dash", "-abc", false},
{"empty", "", false},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, LuaIsWordChar(tt.input))
})
}
}
84 changes: 84 additions & 0 deletions internal/util/unicode_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
package util

import (
"testing"

"github.com/stretchr/testify/assert"
)

// A character with an accent may be a single precomposed code point, or a
// base letter followed by one or more combining marks. micro treats a base
// letter plus its combining marks as a single character. The two forms are
// visually identical, so they are named here rather than inlined.
const (
precomposedE = "é" // é
combiningE = "é" // e + combining acute
combiningDoubleE = "é̂" // e + combining acute + circumflex
trebleClef = "\U0001d11e" // 4-byte rune
)

func TestDecodeCharacter(t *testing.T) {
tests := []struct {
name string
input string
r rune
combc []rune
size int
}{
{"ascii", "abc", 'a', nil, 1},
{"two byte rune", "ñb", 'ñ', nil, 2},
{"three byte rune", "€b", '€', nil, 3},
{"four byte rune", trebleClef + "b", '\U0001d11e', nil, 4},
{"precomposed accent", precomposedE + "b", 'é', nil, 2},
{"combining accent", combiningE, 'e', []rune{'́'}, 3},
{"multiple combining", combiningDoubleE, 'e', []rune{'́', '̂'}, 5},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
r, combc, size := DecodeCharacter([]byte(tt.input))
assert.Equal(t, tt.r, r)
assert.Equal(t, tt.combc, combc)
assert.Equal(t, tt.size, size)

r, combc, size = DecodeCharacterInString(tt.input)
assert.Equal(t, tt.r, r)
assert.Equal(t, tt.combc, combc)
assert.Equal(t, tt.size, size)
})
}
}

func TestCharacterCount(t *testing.T) {
tests := []struct {
name string
input string
expected int
}{
{"empty", "", 0},
{"ascii", "hello", 5},
{"precomposed multibyte", "h" + precomposedE + "llo", 5},
{"combining marks are not counted", "h" + combiningE + "llo", 5},
{"only combining", combiningE, 1},
{"cjk", "日本語", 3},
{"astral plane", trebleClef + trebleClef, 2},
{"tabs and spaces", "\t a", 3},
}

for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
assert.Equal(t, tt.expected, CharacterCount([]byte(tt.input)))
assert.Equal(t, tt.expected, CharacterCountInString(tt.input))
})
}
}

func TestRunePos(t *testing.T) {
b := []byte("h" + precomposedE + "llo")

assert.Equal(t, 0, RunePos(b, 0))
assert.Equal(t, 1, RunePos(b, 1))
// the precomposed 'é' occupies two bytes, so byte index 3 is rune index 2
assert.Equal(t, 2, RunePos(b, 3))
assert.Equal(t, 5, RunePos(b, len(b)))
}
Loading