odin-html

HTML Parsing library in Odin.
Log | Files | Refs | README | LICENSE

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 }