parse_test.odin (9318B)
1 package html 2 3 import "core:testing" 4 5 // Expected describes a node in the tree for test assertions. 6 Expected :: struct { 7 kind: Node_Kind, 8 name: string, // for Element 9 text: string, // for Text/Comment/Doctype 10 attrs: []Attr_Expect, // optional 11 } 12 13 Attr_Expect :: struct { 14 name: string, 15 value: string, 16 } 17 18 // collect_dfs collects node indices in depth-first pre-order starting at `root`. 19 collect_dfs :: proc(doc: ^Document, root: Node_Ref) -> []Node_Ref { 20 out: [dynamic]Node_Ref 21 defer delete(out) 22 visit_dfs(doc, root, &out) 23 return out[:] 24 } 25 26 visit_dfs :: proc(doc: ^Document, i: Node_Ref, out: ^[dynamic]Node_Ref) { 27 append(out, i) 28 child := doc.nodes.first_child[i] 29 for child != -1 { 30 visit_dfs(doc, child, out) 31 child = doc.nodes.next_sibling[child] 32 } 33 } 34 35 // assert_dfs walks the document depth-first from root (index 0) and asserts 36 // each node matches the `want` slice in order. 37 assert_dfs :: proc(t: ^testing.T, doc: ^Document, want: []Expected) { 38 order := collect_dfs(doc, 0) 39 40 for i, node_idx in order { 41 if i >= len(want) { 42 testing.expectf(t, false, "got %d nodes, expected %d", len(order), len(want)) 43 return 44 } 45 w := want[i] 46 k := doc.nodes.kind[node_idx] 47 48 testing.expect_value(t, k, w.kind) 49 if k == .Element { 50 testing.expect_value(t, doc.nodes.name[node_idx], w.name) 51 } else { 52 testing.expect_value(t, doc.nodes.text[node_idx], w.text) 53 } 54 55 for attr in w.attrs { 56 val, ok := node_attr(doc, node_idx, attr.name) 57 testing.expect(t, ok) 58 testing.expect_value(t, val, attr.value) 59 } 60 } 61 62 if len(order) != len(want) { 63 testing.expectf(t, false, "got %d nodes, expected %d", len(order), len(want)) 64 } 65 } 66 67 // Port of the original parser tests, adapted to the DOD arena model. 68 // Each test parses input and asserts the depth-first node order. 69 70 @(test) 71 test_parse_doctype :: proc(t: ^testing.T) { 72 text := "<!DOCTYPE html>" 73 want := []Expected{ 74 {kind = .Element, name = "html"}, 75 } 76 defer free_all() 77 78 doc := parse(text) 79 defer document_delete(&doc) 80 assert_dfs(t, &doc, want) 81 } 82 83 @(test) 84 test_parse_doctype_2 :: proc(t: ^testing.T) { 85 text := "<!DOCTYPE html><html><body><div>foo</div></body></html>" 86 want := []Expected{ 87 {kind = .Element, name = "html"}, 88 {kind = .Element, name = "body"}, 89 {kind = .Element, name = "div"}, 90 {kind = .Text, text = "foo"}, 91 } 92 defer free_all() 93 94 doc := parse(text) 95 defer document_delete(&doc) 96 assert_dfs(t, &doc, want) 97 } 98 99 @(test) 100 test_parse_single_tag :: proc(t: ^testing.T) { 101 text := "<button />" 102 want := []Expected{ 103 {kind = .Element, name = "html"}, 104 {kind = .Element, name = "button"}, 105 } 106 defer free_all() 107 108 doc := parse(text) 109 defer document_delete(&doc) 110 assert_dfs(t, &doc, want) 111 } 112 113 @(test) 114 test_parse_single_tag_with_attribute :: proc(t: ^testing.T) { 115 text := `<button class=".style"/>` 116 want := []Expected{ 117 {kind = .Element, name = "html"}, 118 { 119 kind = .Element, name = "button", 120 attrs = { 121 {name = "class", value = ".style"}, 122 }, 123 }, 124 } 125 defer free_all() 126 127 doc := parse(text) 128 defer document_delete(&doc) 129 assert_dfs(t, &doc, want) 130 } 131 132 @(test) 133 test_parse_single_tag_with_attributes :: proc(t: ^testing.T) { 134 text := `<button class=".style" attribute foo="bar" data-form/>` 135 want := []Expected{ 136 {kind = .Element, name = "html"}, 137 { 138 kind = .Element, name = "button", 139 attrs = { 140 {name = "class", value = ".style"}, 141 {name = "attribute", value = ""}, 142 {name = "foo", value = "bar"}, 143 {name = "data-form", value = ""}, 144 }, 145 }, 146 } 147 defer free_all() 148 149 doc := parse(text) 150 defer document_delete(&doc) 151 assert_dfs(t, &doc, want) 152 } 153 154 @(test) 155 test_parse_nested_tags :: proc(t: ^testing.T) { 156 text := "<ul><li>one</li><li>two</li><li>three</li></ul>" 157 want := []Expected{ 158 {kind = .Element, name = "html"}, 159 {kind = .Element, name = "ul"}, 160 {kind = .Element, name = "li"}, 161 {kind = .Text, text = "one"}, 162 {kind = .Element, name = "li"}, 163 {kind = .Text, text = "two"}, 164 {kind = .Element, name = "li"}, 165 {kind = .Text, text = "three"}, 166 } 167 defer free_all() 168 169 doc := parse(text) 170 defer document_delete(&doc) 171 assert_dfs(t, &doc, want) 172 } 173 174 @(test) 175 test_parse_raw_text :: proc(t: ^testing.T) { 176 text := "<ul><li> one < two < three </li></ul>" 177 want := []Expected{ 178 {kind = .Element, name = "html"}, 179 {kind = .Element, name = "ul"}, 180 {kind = .Element, name = "li"}, 181 {kind = .Text, text = " one < two < three "}, 182 } 183 defer free_all() 184 185 doc := parse(text) 186 defer document_delete(&doc) 187 assert_dfs(t, &doc, want) 188 } 189 190 @(test) 191 test_parse_invalid_tag :: proc(t: ^testing.T) { 192 text := "<ul><li> one <two three </li></ul>" 193 // <two three> is malformed: "three" becomes a boolean attribute on <two>. 194 // </li> is consumed by the attributes loop (best-effort recovery). 195 want := []Expected{ 196 {kind = .Element, name = "html"}, 197 {kind = .Element, name = "ul"}, 198 {kind = .Element, name = "li"}, 199 {kind = .Text, text = " one "}, 200 {kind = .Element, name = "two"}, 201 } 202 defer free_all() 203 204 doc := parse(text) 205 defer document_delete(&doc) 206 assert_dfs(t, &doc, want) 207 } 208 209 @(test) 210 test_parse_explicit_html_tag :: proc(t: ^testing.T) { 211 text := "<html><ul><li>one</li><li>two</li><li>three</li></ul></html>" 212 want := []Expected{ 213 {kind = .Element, name = "html"}, 214 {kind = .Element, name = "ul"}, 215 {kind = .Element, name = "li"}, 216 {kind = .Text, text = "one"}, 217 {kind = .Element, name = "li"}, 218 {kind = .Text, text = "two"}, 219 {kind = .Element, name = "li"}, 220 {kind = .Text, text = "three"}, 221 } 222 defer free_all() 223 224 doc := parse(text) 225 defer document_delete(&doc) 226 assert_dfs(t, &doc, want) 227 } 228 229 // --- new tests for DOD features ---------------------------------------- 230 231 @(test) 232 test_parse_void_element :: proc(t: ^testing.T) { 233 text := "<div><br><p>hi</p></div>" 234 want := []Expected{ 235 {kind = .Element, name = "html"}, 236 {kind = .Element, name = "div"}, 237 {kind = .Element, name = "br"}, 238 {kind = .Element, name = "p"}, 239 {kind = .Text, text = "hi"}, 240 } 241 defer free_all() 242 243 doc := parse(text) 244 defer document_delete(&doc) 245 assert_dfs(t, &doc, want) 246 247 // br should have no children. 248 br_idx := doc.nodes.first_child[doc.nodes.first_child[0]] 249 testing.expect_value(t, doc.nodes.child_count[br_idx], 0) 250 testing.expect_value(t, doc.nodes.first_child[br_idx], -1) 251 } 252 253 @(test) 254 test_parse_self_closing :: proc(t: ^testing.T) { 255 text := "<div><img src=\"x.png\"/></div>" 256 want := []Expected{ 257 {kind = .Element, name = "html"}, 258 {kind = .Element, name = "div"}, 259 {kind = .Element, name = "img"}, 260 } 261 defer free_all() 262 263 doc := parse(text) 264 defer document_delete(&doc) 265 assert_dfs(t, &doc, want) 266 267 // img should have no children and the src attribute. 268 div_idx := doc.nodes.first_child[0] 269 img_idx := doc.nodes.first_child[div_idx] 270 testing.expect_value(t, doc.nodes.child_count[img_idx], 0) 271 272 val, ok := node_attr(&doc, img_idx, "src") 273 testing.expect(t, ok) 274 testing.expect_value(t, val, "x.png") 275 } 276 277 @(test) 278 test_parse_raw_text_element :: proc(t: ^testing.T) { 279 text := "<script>if (a < b) { c = \"<div>\"; }</script>" 280 want := []Expected{ 281 {kind = .Element, name = "html"}, 282 {kind = .Element, name = "script"}, 283 {kind = .Text, text = "if (a < b) { c = \"<div>\"; }"}, 284 } 285 defer free_all() 286 287 doc := parse(text) 288 defer document_delete(&doc) 289 assert_dfs(t, &doc, want) 290 } 291 292 @(test) 293 test_parse_comment :: proc(t: ^testing.T) { 294 text := "<div><!-- hello --></div>" 295 want := []Expected{ 296 {kind = .Element, name = "html"}, 297 {kind = .Element, name = "div"}, 298 {kind = .Comment, text = " hello "}, 299 } 300 defer free_all() 301 302 doc := parse(text) 303 defer document_delete(&doc) 304 assert_dfs(t, &doc, want) 305 } 306 307 @(test) 308 test_parse_sibling_links :: proc(t: ^testing.T) { 309 text := "<ul><li>a</li><li>b</li><li>c</li></ul>" 310 defer free_all() 311 312 doc := parse(text) 313 defer document_delete(&doc) 314 315 // root (0) -> ul (1) -> li(2), li(4), li(6) 316 ul_idx := doc.nodes.first_child[0] 317 318 first := doc.nodes.first_child[ul_idx] 319 testing.expect_value(t, doc.nodes.name[first], "li") 320 321 second := doc.nodes.next_sibling[first] 322 testing.expect_value(t, doc.nodes.name[second], "li") 323 testing.expect_value(t, doc.nodes.prev_sibling[second], first) 324 325 third := doc.nodes.next_sibling[second] 326 testing.expect_value(t, doc.nodes.name[third], "li") 327 testing.expect_value(t, doc.nodes.prev_sibling[third], second) 328 testing.expect_value(t, doc.nodes.next_sibling[third], -1) 329 testing.expect_value(t, doc.nodes.last_child[ul_idx], third) 330 testing.expect_value(t, doc.nodes.child_count[ul_idx], 3) 331 } 332 333 @(test) 334 test_parse_parent_links :: proc(t: ^testing.T) { 335 text := "<div><p>hi</p></div>" 336 defer free_all() 337 338 doc := parse(text) 339 defer document_delete(&doc) 340 341 // root(0) -> div(1) -> p(2) -> text(3) 342 div_idx := doc.nodes.first_child[0] 343 testing.expect_value(t, doc.nodes.parent[div_idx], 0) 344 345 p_idx := doc.nodes.first_child[div_idx] 346 testing.expect_value(t, doc.nodes.parent[p_idx], div_idx) 347 348 text_idx := doc.nodes.first_child[p_idx] 349 testing.expect_value(t, doc.nodes.parent[text_idx], p_idx) 350 testing.expect_value(t, doc.nodes.kind[text_idx], Node_Kind.Text) 351 } 352 353 @(test) 354 test_parse_node_children :: proc(t: ^testing.T) { 355 text := "<ul><li>a</li><li>b</li></ul>" 356 defer free_all() 357 358 doc := parse(text) 359 defer document_delete(&doc) 360 361 ul_ref := doc.nodes.first_child[0] 362 363 it := node_children(&doc, ul_ref) 364 count := 0 365 for child in next(&it) { 366 count += 1 367 testing.expect_value(t, doc.nodes.name[child], "li") 368 } 369 testing.expect_value(t, count, 2) 370 }