import gleam/list import gleeunit import gleeunit/should import string_width.{Piece, Size, dimensions, line as sw} pub fn main() { let _ = echo string_width.get_terminal_size() as "Terminal size" gleeunit.main() } fn swg(str: String) { let options = string_width.new() |> string_width.mode_2027_ext string_width.line_with(str, options) } fn sww(str: String) { let options = string_width.new() |> string_width.ambiguous_as_wide string_width.line_with(str, options) } fn swc(str: String) { let options = string_width.new() |> string_width.count_ansi_codes string_width.line_with(str, options) } pub fn main_test() { sw("abcde") |> should.equal(5) sw("古池や") |> should.equal(6) sw("あいうabc") |> should.equal(9) sw("±") |> should.equal(1) sw("ノード.js") |> should.equal(9) sw("你好") |> should.equal(4) sw("안녕하세요") |> should.equal(10) } pub fn docs_test() { sw("äöüè") |> should.equal(4) sw("안녕하세요") |> should.equal(10) sw("123|") sw("🏳️‍⚧️") |> should.equal(2) swg("🏳️‍⚧️") |> should.equal(2) sw("👩‍👩‍👦‍👦") |> should.equal(8) swg("👩‍👩‍👦‍👦") |> should.equal(2) sw("\u{1B}[31mhello\u{1B}[39m") |> should.equal(5) dimensions("안녕하세요") |> should.equal(Size(1, 10)) dimensions("hello,\n안녕하세요") |> should.equal(Size(2, 10)) } pub fn dimensions_test() { dimensions("") |> should.equal(Size(0, 0)) dimensions("\n") |> should.equal(Size(1, 0)) dimensions("\nhello") |> should.equal(Size(2, 5)) dimensions("hello,\n안녕하세요\n") |> should.equal(Size(2, 10)) } pub fn dimensions_with_tab_offset_test() { let options = string_width.new() |> string_width.at_tab_offset(3) string_width.dimensions_with("", options) |> should.equal(Size(0, 0)) } pub fn pedantic_test() { // these are the test cases I had for v3. // I wanna document what other programs do, even if I think they are buggy. // sindre, fstw; vscode; gnome-terminal, kitty, contour, foot (off), foot (on) // 123456789 sindre fstw | vscode gnome-terminal kitty foot(0) foot(1) contour // let sw = fn(str, opts) { sw(io.debug(str), opts) } // 🏳️‍⚧️ 2 2 | 2 2 4 3 2 2 sw("🏳️‍⚧️") |> should.equal(2) swg("🏳️‍⚧️") |> should.equal(2) // 👩‍👩‍👧‍👦 2 2 | 2 8 8 8 2 2 sw("👩‍👩‍👦‍👦") |> should.equal(8) swg("👩‍👩‍👦‍👦") |> should.equal(2) // র্য 1 2 | 1 2 2 2 2 1 sw("র্য") |> should.equal(2) // despair1 // র‍্য 1 3 | 2 2 2 2 2 2 sw("র‍্য") |> should.equal(2) // despair2 // अनुच्छेद 4 5 | 5 5 5 5 5 5 sw("अनुच्छेद") |> should.equal(5) // despair3 // ශ්ර 2 2 | 2.5 2 2 2 2 2 sw("ශ්ර") |> should.equal(2) swg("ශ්ර") |> should.equal(2) // ශ්‍ර 2 3 | 1.5 2 2 2 2 1 sw("ශ්‍ර") |> should.equal(2) swg("ශ්‍ර") |> should.equal(2) // क्षि 1 2 | 2 3 2 3 2 1 sw("क्षि") |> should.equal(3) // despair4 // ↔️ 2 2 | 1 1 2 1 1 1 sw("↔️") |> should.equal(1) swg("↔️") |> should.equal(2) // VS 16 forces double-width // 葛󠄀 2 2 | 2 2 2 2 2 2 sw("葛󠄀") |> should.equal(2) swg("葛󠄀") |> should.equal(2) // Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞ 6 6 | 6 6 6 6 6 6 sw("Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞") |> should.equal(6) swg("Z͑ͫ̓ͪ̂ͫ̽͏̴̙̤̞͉͚̯̞̠͍A̴̵̜̰͔ͫ͗͢L̠ͨͧͩ͘G̴̻͈͍͔̹̑͗̎̅͛́Ǫ̵̹̻̝̳͂̌̌͘!͖̬̰̙̗̿̋ͥͥ̂ͣ̐́́͜͞") |> should.equal(6) // バ| :L 1 2 | 2 2 2 2 1 sw("バ") |> should.equal(2) // despair5 } pub fn ambigous_test() { sw("⛣") |> should.equal(1) sww("⛣") |> should.equal(2) sw("★") |> should.equal(1) sww("★") |> should.equal(2) sw("“") |> should.equal(1) sww("“") |> should.equal(2) sw("あいう★") |> should.equal(7) sww("あいう★") |> should.equal(8) sw("★あいう") |> should.equal(7) sww("★あいう") |> should.equal(8) } pub fn v5v7_test() { // these are examples in https://github.com/sindresorhus/string-width/issues/56 sw("◼") |> should.equal(1) sw("⚠") |> should.equal(1) sw("✔") |> should.equal(1) sw("♥") |> should.equal(1) sw("♀") |> should.equal(1) sw("♂") |> should.equal(1) sw("\u{21a9}") |> should.equal(1) sw("\u{2194}") |> should.equal(1) sw("\u{2197}") |> should.equal(1) sw("✔") |> should.equal(1) sw("\u{2709}") |> should.equal(1) sw("\u{26a0}") |> should.equal(1) } pub fn dakuten_test() { // https://github.com/sindresorhus/string-width/issues/55 sw("バ") |> should.equal(2) sw("パ") |> should.equal(2) } pub fn ansi_test() { sw("\u{1B}[31m\u{1B}[39m") |> should.equal(0) swc("\u{1B}[31m\u{1B}[39m") |> should.equal(8) sw("\u{1B}]8;;https://github.com\u{0007}Click\u{1B}]8;;\u{0007}") |> should.equal(5) } pub fn default_emoji_presentation_test() { // ⌚ sw("\u{231A}") |> should.equal(2) } pub fn default_text_presentation_emoji_test() { // ↔️ default text presentation rendered as emoji sw:2 sw("\u{2194}") |> should.equal(1) // I do not know currently how to handle this properly. // sw("\u{2194}\u{FE0F}") |> should.equal(2) } pub fn emoji_modifier_base_test() { // 👩 sw("\u{1F469}") |> should.equal(2) } pub fn emoji_with_modifier_test() { sw("\u{1F469}\u{1F3FF}") |> should.equal(4) } pub fn variation_selector_test() { sw("\u{845B}\u{E0100}") |> should.equal(2) sw("A\u{FE0F}") |> should.equal(1) sw("\u{FE0F}") |> should.equal(0) } pub fn thai_test() { sw("_\u{0E34}") |> should.equal(1) sw("ปฏัก") |> should.equal(3) } pub fn control_characters_test() { sw("\u{0}") |> should.equal(0) sw("\u{1f}") |> should.equal(0) sw("\u{7f}") |> should.equal(0) sw("\u{86}") |> should.equal(0) sw("\u{9f}") |> should.equal(0) sw("\u{1b}") |> should.equal(0) } pub fn combining_characters_test() { sw("x\u{0300}") |> should.equal(1) sw("\u{0300}\u{0301}") |> should.equal(0) sw("e\u{0301}e") |> should.equal(2) sw("x\u{036f}") |> should.equal(1) sw("\u{036f}\u{036f}") |> should.equal(0) } pub fn zwj_test() { sw("👶") |> should.equal(2) sw("👶🏽") |> should.equal(4) sw("👩‍👩‍👦‍👦") |> should.equal(8) sw("👨‍❤️‍💋‍👨") |> should.equal(7) } pub fn zwj_cluster_test() { swg("👶") |> should.equal(2) swg("👶🏽") |> should.equal(2) swg("👩‍👩‍👦‍👦") |> should.equal(2) swg("👨‍❤️‍💋‍👨") |> should.equal(2) swg("\u{1F469}\u{1F3FF}") |> should.equal(2) swg("👩‍🎓") |> should.equal(2) } pub fn zero_width_test() { sw("\u{200b}") |> should.equal(0) sw("x\u{200b}x") |> should.equal(2) sw("\u{200c}") |> should.equal(0) sw("x\u{200c}x") |> should.equal(2) sw("\u{200d}") |> should.equal(0) sw("x\u{200d}x") |> should.equal(2) sw("\u{feff}") |> should.equal(0) sw("x\u{feff}x") |> should.equal(2) } pub fn variation_selectors_test() { // regional indicator symbol A with variation selector sw("\u{1f1e6}\u{fe0f}") |> should.equal(1) sw("A\u{fe0f}") |> should.equal(1) sw("\u{fe0f}") |> should.equal(0) } pub fn edge_cases_test() { sw("") |> should.equal(0) sw("\u{200b}\u{200b}") |> should.equal(0) sw("x\u{200b}x\u{200b}") |> should.equal(2) sw("x\u{0300}x\u{0300}") |> should.equal(2) // 😀 with variation selector sw("😀\u{fe0f}") |> should.equal(2) sw("👩‍🎓") |> should.equal(4) // combining diacritical marks extended sw("x\u{1ab0}x\u{1ab0}") |> should.equal(2) // combining diacritical marks supplement sw("x\u{1dc0}x\u{1dc0}") |> should.equal(2) // combining diacritical marks for symbols sw("x\u{20d0}x\u{20d0}") |> should.equal(2) // combining half marks sw("x\u{fe20}x\u{fe20}") |> should.equal(2) } pub fn ignores_default_ignorable_test() { // word joiner sw("\u{2060}") |> should.equal(0) // function application sw("\u{2061}") |> should.equal(0) // invisible times sw("\u{2062}") |> should.equal(0) // invisible separator sw("\u{2063}") |> should.equal(0) // invisible plus sw("\u{2064}") |> should.equal(0) // zero-width no-break space sw("\u{feff}") |> should.equal(0) sw("x\u{2060}x") |> should.equal(2) sw("x\u{2061}x") |> should.equal(2) sw("x\u{2062}x") |> should.equal(2) sw("x\u{2063}x") |> should.equal(2) sw("x\u{2064}x") |> should.equal(2) sw("x\u{feff}x") |> should.equal(2) } pub fn fold_identity_test() { let reducer = fn(acc, chr, _) { acc <> chr } let do_test = fn(input, options) { string_width.fold_raw(input, options, "", reducer) |> should.equal(input) } let default = string_width.new() let clusters = default |> string_width.mode_2027 do_test("äöüè", default) do_test("안녕하세요", default) do_test("🏳️‍⚧️", default) do_test("🏳️‍⚧️", clusters) do_test("👩‍👩‍👦‍👦", default) do_test("wobble\u{1B}[31mhello\u{1B}[39mwibble", default) } pub fn tab_test() { sw("\t") |> should.equal(8) sw("asdf\t") |> should.equal(8) sw("1234567\t") |> should.equal(8) sw("12345678\t") |> should.equal(16) sw("wibble\twobble") |> should.equal(14) } pub fn tab_offset_test() { let sw = fn(str, offset) { let options = string_width.new() |> string_width.at_tab_offset(offset) string_width.line_with(str, options) } sw("\t", 3) |> should.equal(5) sw("12345\t678", 3) |> should.equal(16) } pub fn tab_fold_test() { string_width.fold("hello\tworld", [], list.prepend) |> list.reverse |> should.equal([ Piece("h", 0, 0, 1), Piece("e", 0, 1, 1), Piece("l", 0, 2, 1), Piece("l", 0, 3, 1), Piece("o", 0, 4, 1), Piece("\t", 0, 5, 3), Piece("w", 0, 8, 1), Piece("o", 0, 9, 1), Piece("r", 0, 10, 1), Piece("l", 0, 11, 1), Piece("d", 0, 12, 1), ]) }