import gleam/option.{None, Some} import gleam/string import gleeunit import glint import glint/check import glint/lsp/diagnostics import glint/lsp/hover import glint/pipeline import glint/position import glint/value pub fn main() -> Nil { gleeunit.main() } pub fn hello_loads_test() { let source = " type Mode { Dev Prod } type Config { Config( name: String, mode: Mode, port: Int, ) } pub let config = Config( name: \"hello\", mode: Dev, port: 3000, ) " let assert Ok(checked) = pipeline.load(source) let assert "Config" = check.type_to_string(checked.config_type) let assert True = string.contains(value.to_glint(checked.config), "\"hello\"") let assert True = string.contains(value.to_json(checked.config), "hello") } pub fn app_example_test() { let source = " type LogLevel { Debug Info } type Database { Database( host: String, port: Int, password: Option(String), ) } type Config { Config( log_level: LogLevel, database: Database, origins: List(String), ) } let database = Database( host: \"localhost\", port: 5432, password: None, ) pub let config = Config( log_level: Info, database: database, origins: [\"https://example.com\"], ) " let assert Ok(checked) = glint.load(source) let assert value.VVariant("Config", fields) = checked.config let assert True = list_has_tag(fields, "log_level") } pub fn type_mismatch_test() { let source = " type Config { Config(port: Int) } pub let config = Config(port: \"nope\") " let assert Error(msg) = glint.load(source) let assert True = string.contains(msg, "type mismatch") } pub fn missing_config_test() { let source = " type Mode { Dev } let mode = Dev " let assert Error(msg) = glint.load(source) let assert True = string.contains(msg, "pub let config") } pub fn unknown_constructor_test() { let source = " type Config { Config(name: String) } pub let config = Config(name: \"x\", extra: 1) " let assert Error(msg) = glint.load(source) let assert True = string.contains(msg, "unknown field") } pub fn comments_and_trailing_commas_test() { let source = " // leading comment type Mode { Dev Prod } type Config { Config( mode: Mode, port: Int, ) } pub let config = Config( mode: Dev, port: 1_000, ) " let assert Ok(_) = glint.load(source) } fn list_has_tag(fields: List(#(String, value.Value)), label: String) -> Bool { case fields { [] -> False [#(l, _), ..rest] -> case l == label { True -> True False -> list_has_tag(rest, label) } } } pub fn host_accessors_test() { let source = " type Mode { Dev Prod } type Server { Server(host: String, port: Int) } type Config { Config( name: String, mode: Mode, server: Server, ) } pub let config = Config( name: \"hello\", mode: Dev, server: Server(host: \"localhost\", port: 3000), ) " let assert Ok(checked) = glint.load(source) let cfg = checked.config let assert "hello" = glint.string(cfg, "name") let assert "Dev" = glint.unit(cfg, "mode") let assert 3000 = glint.int(cfg, "server.port") let assert "localhost" = glint.string(cfg, "server.host") } // ── Position / diagnostics ────────────────────────────────────────── pub fn offset_to_position_test() { let source = "abc\ndef" let assert position.Position(0, 0) = position.offset_to_position(source, 0) let assert position.Position(0, 2) = position.offset_to_position(source, 2) let assert position.Position(1, 0) = position.offset_to_position(source, 4) let assert position.Position(1, 2) = position.offset_to_position(source, 6) } pub fn range_from_offsets_test() { let source = "let x = 1" let range = position.range_from_offsets(source, 0, 3) let assert position.Position(0, 0) = range.start let assert position.Position(0, 3) = range.end } pub fn valid_source_no_diagnostics_test() { let source = " type Config { Config(port: Int) } pub let config = Config(port: 1) " let assert [] = diagnostics.analyse(source) } pub fn lex_error_has_range_test() { // `@` is not a valid token let source = "let x = @" let diags = diagnostics.analyse(source) let assert [d] = diags let assert True = string.contains(d.message, "lex error") // Range should not be the default empty only — character should land near `@` let assert True = d.range.start.line >= 0 let assert True = d.range.start.character >= 0 } pub fn parse_error_has_range_test() { // Missing `=` after name let source = "let x 1" let diags = diagnostics.analyse(source) let assert [d] = diags let assert True = string.contains(d.message, "parse error") let assert True = d.range.start.character > 0 || d.range.end.character > d.range.start.character } pub fn check_error_diagnostic_test() { let source = " type Config { Config(port: Int) } pub let config = Config(port: \"nope\") " let diags = diagnostics.analyse(source) let assert [d] = diags let assert True = string.contains(d.message, "type error") } // ── Hover ─────────────────────────────────────────────────────────── pub fn position_to_offset_test() { let source = "abc\ndef" let assert 0 = position.position_to_offset(source, position.Position(0, 0)) let assert 2 = position.position_to_offset(source, position.Position(0, 2)) let assert 4 = position.position_to_offset(source, position.Position(1, 0)) let assert 6 = position.position_to_offset(source, position.Position(1, 2)) // Past end of line clamps to before newline let assert 3 = position.position_to_offset(source, position.Position(0, 99)) } pub fn hover_builtin_test() { let source = "type Config { Config(name: String) }\npub let config = Config(name: \"x\")" // Hover on `String` in the field type let assert Some(h) = hover.at(source, 0, 30) let assert True = string.contains(h.contents.value, "String") let assert True = string.contains(h.contents.value, "Built-in") } pub fn hover_variable_test() { let source = "type Mode { Dev }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" // Find line with `config` binding — line 2 (0-based), character of `c` in config let assert Some(h) = hover.at(source, 2, 8) let assert True = string.contains(h.contents.value, "config") let assert True = string.contains(h.contents.value, "Config") } pub fn hover_constructor_test() { // `Dev` starts at character 30 on line 2: // "pub let config = Config(mode: Dev)" let source = "type Mode { Dev Prod }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" let assert Some(h) = hover.at(source, 2, 30) let assert True = string.contains(h.contents.value, "Dev") let assert True = string.contains(h.contents.value, "Mode") } pub fn hover_type_test() { let source = "type Mode { Dev }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" // `Mode` in `type Mode` let assert Some(h) = hover.at(source, 0, 5) let assert True = string.contains(h.contents.value, "type") let assert True = string.contains(h.contents.value, "Mode") } pub fn hover_whitespace_none_test() { let source = "type Mode { Dev }\n" let assert None = hover.at(source, 0, 4) }