The examples below are this package's own black-box tests, so they use the in-package alias @common. Callers import it as "moonbitlang/editor/base/common" @base_common and write @base_common. in their own code.
direction: right
uri: resource identity {
grid-columns: 1
a: Uri / UriError
b: ExtUri (case policy)
c: Posix / Win32 / extpath
}
coords: coordinates {
grid-columns: 1
d: Position / Range
e: LineRange / LineRangeSet
f: OffsetRange
}
life: lifecycle + events {
grid-columns: 1
g: Disposable
h: Emitter
i: MicrotaskEmitter
}
text: text + characters {
grid-columns: 1
j: split_lines / whitespace scan
k: compare_substring (case folding)
l: CharacterClassifier / CharacterSet
m: RTL + full-width probes
}
uri -> coords: "no dependency, one package" {style.stroke-dash: 3}
coords -> life: "" {style.stroke-dash: 3}
life -> text: "" {style.stroke-dash: 3}flowchart LR
subgraph one["1-based, UTF-16 code units"]
P["Position<br>line_number, column"]
R["Range<br>start/end line+column"]
LR["LineRange<br>half-open line span"]
end
subgraph zero["0-based, UTF-16 code units"]
O["OffsetRange<br>start, end_exclusive"]
end
P --- R
R --- LR
one <-->|"TextSnapshot<br>viewer/common/model"| zero///|
test "positions order by line then column" {
let a = @common.Position(1, 5)
let b = @common.Position(2, 1)
debug_inspect(
(a.is_before(b), a.compare(b), a.delta(delta_column=3), a.with_(column=1)),
content=(
#|(
#| true,
#| -1,
#| { line_number: 1, column: 8 },
#| { line_number: 1, column: 1 },
#|)
),
)
}///|
test "range containment, intersection, and touching are distinct" {
let outer = @common.Range(1, 1, 3, 10)
let inner = @common.Range(2, 1, 2, 5)
let touching = @common.Range(3, 10, 4, 1)
debug_inspect(
(
outer.contains_range(inner),
outer.strict_contains_range(inner),
outer.are_intersecting(touching),
outer.are_intersecting_or_touching(touching),
outer.plus_range(touching).to_string(),
),
content=(
#|(true, true, false, true, "[1,1 -> 4,1]")
),
)
}///|
test "LineRange is half-open and OffsetRange normalizes inverted input" {
let lines = @common.LineRange(4, 7)
let inverted = @common.OffsetRange(9, 2)
debug_inspect(
(
lines.length(),
lines.contains(6),
lines.contains(7),
lines.is_empty(),
inverted,
),
content=(
#|(3, true, false, false, { start: 2, end_exclusive: 9 })
),
)
}///|
test "join_many unions sorted range arrays without mutating the inputs" {
let left : Array[@common.LineRange] = [LineRange(1, 3), LineRange(8, 10)]
let right = [@common.LineRange(2, 5)]
let joined = @common.LineRange::join_many([left, right])
debug_inspect(
(joined, left.length(), right.length()),
content=(
#|(
#| [
#| { start_line_number: 1, end_line_number_exclusive: 5 },
#| { start_line_number: 8, end_line_number_exclusive: 10 },
#| ],
#| 2,
#| 1,
#|)
),
)
}///|
test "Uri keeps decoded components and re-encodes on to_string" {
let uri = @common.Uri::parse("https://example.com/a%20b/c.mbt?q=1#frag")
debug_inspect(
(uri.scheme, uri.authority, uri.path, uri.query, uri.fragment),
content=(
#|("https", "example.com", "/a b/c.mbt", "q=1", "frag")
),
)
debug_inspect(
uri.to_string(),
content=(
#|"https://example.com/a%20b/c.mbt?q%3D1#frag"
),
)
}///|
test "a missing scheme is a UriError, not a silent default" {
try @common.Uri::from(scheme="", strict=true) |> ignore catch {
err =>
debug_inspect(
err,
content=(
#|UriError("[UriError]: Scheme is missing: {scheme: \"\", authority: \"\", path: \"\", query: \"\", fragment: \"\"}")
),
)
} noraise {
_ => fail("expected a UriError")
}
}///|
test "the ExtUri policies differ only in path-case handling" {
let lower = @common.Uri::parse("file:///dir/file.mbt")
let upper = @common.Uri::parse("file:///DIR/FILE.MBT")
let biased = @common.ext_uri_biased_ignore_path_case.is_equal(
Some(lower),
Some(upper),
)
debug_inspect(
(
@common.ext_uri.is_equal(Some(lower), Some(upper)),
@common.ext_uri_ignore_path_case.is_equal(Some(lower), Some(upper)),
biased == !@common.is_linux,
),
content=(
#|(false, true, true)
),
)
}///|
test "relative_path is None across schemes" {
let root = @common.Uri::parse("file:///workspace")
let inside = @common.Uri::parse("file:///workspace/src/main.mbt")
let elsewhere = @common.Uri::parse("https://example.com/src/main.mbt")
debug_inspect(
(
@common.ext_uri.relative_path(root, inside),
@common.ext_uri.relative_path(root, elsewhere),
),
content=(
#|(Some("src/main.mbt"), None)
),
)
}///|
test "Posix and Win32 normalize the same input differently" {
let input = "/a/b/../c//d/"
debug_inspect(
(
@common.Posix::normalize(input),
@common.Win32::normalize(input),
@common.Posix::join(["/a", "b", "../c"]),
@common.Posix::relative("/a/b", "/a/c/d"),
@common.Posix::extname("/a/b/file.mbt.md"),
),
content=(
#|("/a/c/d/", "\\a\\c\\d\\", "/a/c", "../c/d", ".md")
),
)
}///|
test "emitter delivers in registration order and disposal is idempotent" {
let seen = []
let emitter : @common.Emitter[Int32] = Emitter()
let first = emitter.event(value => seen.push(("first", value)))
emitter.event(value => seen.push(("second", value))) |> ignore
emitter.fire(1)
first.dispose()
first.dispose()
emitter.fire(2)
debug_inspect(
(seen, emitter.has_listeners()),
content=(
#|([("first", 1), ("second", 1), ("second", 2)], true)
),
)
}///|
test "the default inline scheduler never batches, so merge is a no-op" {
let delivered = []
let emitter : @common.MicrotaskEmitter[Int32] = MicrotaskEmitter(merge=batch => {
batch.fold(init=0, (a, b) => a + b)
})
emitter.event(value => delivered.push(value)) |> ignore
emitter.fire(1)
emitter.fire(2)
debug_inspect(
delivered,
content=(
#|[1, 2]
),
)
}///|
test "a deferring scheduler collapses the batch into one value" {
let delivered = []
let pending : Array[() -> Unit] = []
let emitter : @common.MicrotaskEmitter[Int32] = MicrotaskEmitter(
merge=batch => batch.fold(init=0, (a, b) => a + b),
schedule=run => pending.push(run),
)
emitter.event(value => delivered.push(value)) |> ignore
emitter.fire(1)
emitter.fire(2)
emitter.fire(39)
// Only the queue's first event schedules a flush.
let scheduled = pending.length()
for run in pending {
run()
}
debug_inspect(
(scheduled, delivered),
content=(
#|(1, [42])
),
)
}///|
test "the two escapers differ only on the quote character" {
debug_inspect(
(@common.escape("<a href=\"x\">"), @common.escape_html("<a href=\"x\">")),
content=(
#|("<a href=\"x\">", "<a href="x">")
),
)
}///|
test "line splitting and whitespace scanning use UTF-16 indices" {
debug_inspect(
(
@common.split_lines("a\r\nb\nc\rd"),
@common.first_non_whitespace_index(" let x = 1"),
@common.last_non_whitespace_index("let x = 1 "),
@common.first_non_whitespace_index(" "),
),
content=(
#|(["a", "b", "c", "d"], 3, 8, -1)
),
)
}///|
test "classifier separates ASCII fast path from the sparse map" {
let classifier = @common.CharacterClassifier(0)
classifier.set('(', 1)
classifier.set(0x4E2D, 2)
let set = @common.CharacterSet()
set.add('_')
debug_inspect(
(
classifier.get('('),
classifier.get(0x4E2D),
classifier.get('a'),
set.has('_'),
set.has('-'),
),
content=(
#|(1, 2, 0, true, false)
),
)
}///|
test "character probes operate on UTF-16 code units" {
let emoji = "😀"
debug_inspect(
(
@common.is_high_surrogate(emoji[0].to_int()),
@common.is_low_surrogate(emoji[1].to_int()),
@common.is_full_width_character(0x4E2D),
@common.contains_rtl("hello"),
@common.contains_rtl("שלום"),
),
content=(
#|(true, true, true, false, true)
),
)
}///|
test "substring comparison avoids allocating a folded copy" {
debug_inspect(
(
@common.equals_ignore_case("MoonBit", "moonbit"),
@common.starts_with_ignore_case("README.MBT.MD", "readme"),
@common.compare_substring("abcXX", "abcYY", a_end=3, b_end=3),
),
content=(
#|(true, true, 0)
),
)
}moon test --target js base/common
moon test --target native base/commontype CharacterClassifiertype CharacterSettype Disposabletype Emitter[T]type ExtUritype LineRangeSettype MicrotaskEmitter[T]fn[T] MicrotaskEmitter::MicrotaskEmitter(merge? : (Array[T]) -> T, schedule? : (() -> Unit) -> Unit) -> MicrotaskEmitter[T]type Posixpub struct Uri {
scheme : String
authority : String
path : String
query : String
fragment : String
// private fields
} foo://example.com:8042/over/there?name=ferret#nose
\_/ \______________/\_________/ \_________/ \__/
| | | | |
scheme authority path query fragment
| _____________________|__
/ \ / \
urn:example:animal:ferret:nosetype Win32fn compare(a : String, b : String) -> Intfn compare_substring(a : String, b : String, a_start? : Int, a_end? : Int, b_start? : Int, b_end? : Int) -> Intfn compare_substring_ignore_case(a : String, b : String, a_start? : Int, a_end? : Int, b_start? : Int, b_end? : Int) -> Intfn contains_rtl(text : String) -> Boolfn equals_ignore_case(a : String, b : String) -> Boolfn escape(text : String) -> Stringfn escape_html(text : String) -> Stringfn first_non_whitespace_index(text : String) -> Intfn is_full_width_character(char_code : Int) -> Boolfn is_high_surrogate(char_code : Int) -> Boollet is_linux : Boolfn is_low_surrogate(char_code : Int) -> Boolfn last_non_whitespace_index(text : String) -> Intfn platform_is_win32() -> Boollet schemas_in_memory : Stringfn single_letter_hash(n : Int) -> Stringfn starts_with_ignore_case(str : String, candidate : String) -> BoolInstall
Download zipReadonly MoonBit code viewer harness inspired by Monaco and CodeMirror.