Gleam-inspired typed configuration language (POC)
0

Configure Feed

Select the types of activity you want to include in your feed.

glint / test / glint_test.gleam
7.5 kB 316 lines
1import gleam/option.{None, Some} 2import gleam/string 3import gleeunit 4import glint 5import glint/check 6import glint/lsp/diagnostics 7import glint/lsp/hover 8import glint/pipeline 9import glint/position 10import glint/value 11 12pub fn main() -> Nil { 13 gleeunit.main() 14} 15 16pub fn hello_loads_test() { 17 let source = 18 " 19type Mode { 20 Dev 21 Prod 22} 23 24type Config { 25 Config( 26 name: String, 27 mode: Mode, 28 port: Int, 29 ) 30} 31 32pub let config = Config( 33 name: \"hello\", 34 mode: Dev, 35 port: 3000, 36) 37" 38 39 let assert Ok(checked) = pipeline.load(source) 40 let assert "Config" = check.type_to_string(checked.config_type) 41 let assert True = 42 string.contains(value.to_glint(checked.config), "\"hello\"") 43 let assert True = string.contains(value.to_json(checked.config), "hello") 44} 45 46pub fn app_example_test() { 47 let source = 48 " 49type LogLevel { 50 Debug 51 Info 52} 53 54type Database { 55 Database( 56 host: String, 57 port: Int, 58 password: Option(String), 59 ) 60} 61 62type Config { 63 Config( 64 log_level: LogLevel, 65 database: Database, 66 origins: List(String), 67 ) 68} 69 70let database = Database( 71 host: \"localhost\", 72 port: 5432, 73 password: None, 74) 75 76pub let config = Config( 77 log_level: Info, 78 database: database, 79 origins: [\"https://example.com\"], 80) 81" 82 83 let assert Ok(checked) = glint.load(source) 84 let assert value.VVariant("Config", fields) = checked.config 85 let assert True = list_has_tag(fields, "log_level") 86} 87 88pub fn type_mismatch_test() { 89 let source = 90 " 91type Config { 92 Config(port: Int) 93} 94 95pub let config = Config(port: \"nope\") 96" 97 let assert Error(msg) = glint.load(source) 98 let assert True = string.contains(msg, "type mismatch") 99} 100 101pub fn missing_config_test() { 102 let source = 103 " 104type Mode { 105 Dev 106} 107 108let mode = Dev 109" 110 let assert Error(msg) = glint.load(source) 111 let assert True = string.contains(msg, "pub let config") 112} 113 114pub fn unknown_constructor_test() { 115 let source = 116 " 117type Config { 118 Config(name: String) 119} 120 121pub let config = Config(name: \"x\", extra: 1) 122" 123 let assert Error(msg) = glint.load(source) 124 let assert True = string.contains(msg, "unknown field") 125} 126 127pub fn comments_and_trailing_commas_test() { 128 let source = 129 " 130// leading comment 131type Mode { 132 Dev 133 Prod 134} 135 136type Config { 137 Config( 138 mode: Mode, 139 port: Int, 140 ) 141} 142 143pub let config = Config( 144 mode: Dev, 145 port: 1_000, 146) 147" 148 let assert Ok(_) = glint.load(source) 149} 150 151fn list_has_tag(fields: List(#(String, value.Value)), label: String) -> Bool { 152 case fields { 153 [] -> False 154 [#(l, _), ..rest] -> 155 case l == label { 156 True -> True 157 False -> list_has_tag(rest, label) 158 } 159 } 160} 161 162pub fn host_accessors_test() { 163 let source = 164 " 165type Mode { 166 Dev 167 Prod 168} 169 170type Server { 171 Server(host: String, port: Int) 172} 173 174type Config { 175 Config( 176 name: String, 177 mode: Mode, 178 server: Server, 179 ) 180} 181 182pub let config = Config( 183 name: \"hello\", 184 mode: Dev, 185 server: Server(host: \"localhost\", port: 3000), 186) 187" 188 let assert Ok(checked) = glint.load(source) 189 let cfg = checked.config 190 let assert "hello" = glint.string(cfg, "name") 191 let assert "Dev" = glint.unit(cfg, "mode") 192 let assert 3000 = glint.int(cfg, "server.port") 193 let assert "localhost" = glint.string(cfg, "server.host") 194} 195 196// ── Position / diagnostics ────────────────────────────────────────── 197 198pub fn offset_to_position_test() { 199 let source = "abc\ndef" 200 let assert position.Position(0, 0) = position.offset_to_position(source, 0) 201 let assert position.Position(0, 2) = position.offset_to_position(source, 2) 202 let assert position.Position(1, 0) = position.offset_to_position(source, 4) 203 let assert position.Position(1, 2) = position.offset_to_position(source, 6) 204} 205 206pub fn range_from_offsets_test() { 207 let source = "let x = 1" 208 let range = position.range_from_offsets(source, 0, 3) 209 let assert position.Position(0, 0) = range.start 210 let assert position.Position(0, 3) = range.end 211} 212 213pub fn valid_source_no_diagnostics_test() { 214 let source = 215 " 216type Config { 217 Config(port: Int) 218} 219 220pub let config = Config(port: 1) 221" 222 let assert [] = diagnostics.analyse(source) 223} 224 225pub fn lex_error_has_range_test() { 226 // `@` is not a valid token 227 let source = "let x = @" 228 let diags = diagnostics.analyse(source) 229 let assert [d] = diags 230 let assert True = string.contains(d.message, "lex error") 231 // Range should not be the default empty only — character should land near `@` 232 let assert True = d.range.start.line >= 0 233 let assert True = d.range.start.character >= 0 234} 235 236pub fn parse_error_has_range_test() { 237 // Missing `=` after name 238 let source = "let x 1" 239 let diags = diagnostics.analyse(source) 240 let assert [d] = diags 241 let assert True = string.contains(d.message, "parse error") 242 let assert True = 243 d.range.start.character > 0 || d.range.end.character > d.range.start.character 244} 245 246pub fn check_error_diagnostic_test() { 247 let source = 248 " 249type Config { 250 Config(port: Int) 251} 252 253pub let config = Config(port: \"nope\") 254" 255 let diags = diagnostics.analyse(source) 256 let assert [d] = diags 257 let assert True = string.contains(d.message, "type error") 258} 259 260// ── Hover ─────────────────────────────────────────────────────────── 261 262pub fn position_to_offset_test() { 263 let source = "abc\ndef" 264 let assert 0 = 265 position.position_to_offset(source, position.Position(0, 0)) 266 let assert 2 = 267 position.position_to_offset(source, position.Position(0, 2)) 268 let assert 4 = 269 position.position_to_offset(source, position.Position(1, 0)) 270 let assert 6 = 271 position.position_to_offset(source, position.Position(1, 2)) 272 // Past end of line clamps to before newline 273 let assert 3 = 274 position.position_to_offset(source, position.Position(0, 99)) 275} 276 277pub fn hover_builtin_test() { 278 let source = "type Config { Config(name: String) }\npub let config = Config(name: \"x\")" 279 // Hover on `String` in the field type 280 let assert Some(h) = hover.at(source, 0, 30) 281 let assert True = string.contains(h.contents.value, "String") 282 let assert True = string.contains(h.contents.value, "Built-in") 283} 284 285pub fn hover_variable_test() { 286 let source = 287 "type Mode { Dev }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" 288 // Find line with `config` binding — line 2 (0-based), character of `c` in config 289 let assert Some(h) = hover.at(source, 2, 8) 290 let assert True = string.contains(h.contents.value, "config") 291 let assert True = string.contains(h.contents.value, "Config") 292} 293 294pub fn hover_constructor_test() { 295 // `Dev` starts at character 30 on line 2: 296 // "pub let config = Config(mode: Dev)" 297 let source = 298 "type Mode { Dev Prod }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" 299 let assert Some(h) = hover.at(source, 2, 30) 300 let assert True = string.contains(h.contents.value, "Dev") 301 let assert True = string.contains(h.contents.value, "Mode") 302} 303 304pub fn hover_type_test() { 305 let source = 306 "type Mode { Dev }\ntype Config { Config(mode: Mode) }\npub let config = Config(mode: Dev)" 307 // `Mode` in `type Mode` 308 let assert Some(h) = hover.at(source, 0, 5) 309 let assert True = string.contains(h.contents.value, "type") 310 let assert True = string.contains(h.contents.value, "Mode") 311} 312 313pub fn hover_whitespace_none_test() { 314 let source = "type Mode { Dev }\n" 315 let assert None = hover.at(source, 0, 4) 316}