migrate to htmlRunne (plus zig fmt)

This commit is contained in:
Karl Seguin
2025-09-05 13:52:08 +08:00
parent 5087b8004a
commit dd22c55d23
14 changed files with 330 additions and 362 deletions

View File

@@ -316,20 +316,4 @@ pub const Document = struct {
const testing = @import("../../testing.zig");
test "Browser: DOM.Document" {
try testing.htmlRunner("dom/document.html");
// const Case = testing.JsRunner.Case;
// const tags = comptime parser.Tag.all();
// var createElements: [(tags.len) * 2]Case = undefined;
// inline for (tags, 0..) |tag, i| {
// const tag_name = @tagName(tag);
// createElements[i * 2] = Case{
// "var " ++ tag_name ++ "Elem = document.createElement('" ++ tag_name ++ "')",
// "undefined",
// };
// createElements[(i * 2) + 1] = Case{
// tag_name ++ "Elem.localName",
// tag_name,
// };
// }
// try runner.testCases(&createElements, .{});
}

View File

@@ -91,41 +91,6 @@ pub const DocumentFragment = struct {
};
const testing = @import("../../testing.zig");
test "Browser.DOM.DocumentFragment" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try runner.testCases(&.{
.{ "const dc = new DocumentFragment()", "undefined" },
.{ "dc.constructor.name", "DocumentFragment" },
}, .{});
try runner.testCases(&.{
.{ "const dc1 = new DocumentFragment()", "undefined" },
.{ "const dc2 = new DocumentFragment()", "undefined" },
.{ "dc1.isEqualNode(dc1)", "true" },
.{ "dc1.isEqualNode(dc2)", "true" },
}, .{});
try runner.testCases(&.{
.{ "let f = document.createDocumentFragment()", null },
.{ "let d = document.createElement('div');", null },
.{ "d.childElementCount", "0" },
.{ "d.id = 'x';", null },
.{ "document.getElementById('x') == null;", "true" },
.{ "f.append(d);", null },
.{ "f.childElementCount", "1" },
.{ "f.children[0].id", "x" },
.{ "document.getElementById('x') == null;", "true" },
.{ "document.getElementsByTagName('body')[0].append(f.cloneNode(true));", null },
.{ "document.getElementById('x') != null;", "true" },
.{ "document.querySelector('.hello')", "null" },
.{ "document.querySelectorAll('.hello').length", "0" },
.{ "document.querySelector('#x').id", "x" },
.{ "document.querySelectorAll('#x')[0].id", "x" },
}, .{});
test "Browser: DOM.DocumentFragment" {
try testing.htmlRunner("dom/document_fragment.html");
}

View File

@@ -62,19 +62,6 @@ pub const DocumentType = struct {
};
const testing = @import("../../testing.zig");
test "Browser.DOM.DocumentType" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try runner.testCases(&.{
.{ "let dt1 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');", "undefined" },
.{ "let dt2 = document.implementation.createDocumentType('qname2', 'pid2', 'sys2');", "undefined" },
.{ "let dt3 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');", "undefined" },
.{ "dt1.isEqualNode(dt1)", "true" },
.{ "dt1.isEqualNode(dt3)", "true" },
.{ "dt1.isEqualNode(dt2)", "false" },
.{ "dt2.isEqualNode(dt3)", "false" },
.{ "dt1.isEqualNode(document)", "false" },
.{ "document.isEqualNode(dt1)", "false" },
}, .{});
test "Browser: DOM.DocumentType" {
try testing.htmlRunner("dom/document_type.html");
}

View File

@@ -36,12 +36,6 @@ pub const DOMParser = struct {
};
const testing = @import("../../testing.zig");
test "Browser.DOM.DOMParser" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try runner.testCases(&.{
.{ "const dp = new DOMParser()", "undefined" },
.{ "dp.parseFromString('<div>abc</div>', 'text/html')", "[object HTMLDocument]" },
}, .{});
test "Browser: DOM.Parser" {
try testing.htmlRunner("dom/dom_parser.html");
}

View File

@@ -593,277 +593,6 @@ pub const Element = struct {
// -----
const testing = @import("../../testing.zig");
test "Browser.DOM.Element" {
var runner = try testing.jsRunner(testing.tracking_allocator, .{});
defer runner.deinit();
try runner.testCases(&.{
.{ "let g = document.getElementById('content')", "undefined" },
.{ "g.namespaceURI", "http://www.w3.org/1999/xhtml" },
.{ "g.prefix", "null" },
.{ "g.localName", "div" },
.{ "g.tagName", "DIV" },
}, .{});
try runner.testCases(&.{
.{ "let gs = document.getElementById('content')", "undefined" },
.{ "gs.id", "content" },
.{ "gs.id = 'foo'", "foo" },
.{ "gs.id", "foo" },
.{ "gs.id = 'content'", "content" },
.{ "gs.className", "" },
.{ "let gs2 = document.getElementById('para-empty')", "undefined" },
.{ "gs2.className", "ok empty" },
.{ "gs2.className = 'foo bar baz'", "foo bar baz" },
.{ "gs2.className", "foo bar baz" },
.{ "gs2.className = 'ok empty'", "ok empty" },
.{ "let cl = gs2.classList", "undefined" },
.{ "cl.length", "2" },
}, .{});
try runner.testCases(&.{
.{ "const el2 = document.createElement('div');", "undefined" },
.{ "el2.id = 'closest'; el2.className = 'ok';", "ok" },
.{ "el2.closest('#closest')", "[object HTMLDivElement]" },
.{ "el2.closest('.ok')", "[object HTMLDivElement]" },
.{ "el2.closest('#9000')", "null" },
.{ "el2.closest('.notok')", "null" },
.{ "const sp = document.createElement('span');", "undefined" },
.{ "el2.appendChild(sp);", "[object HTMLSpanElement]" },
.{ "sp.closest('#closest')", "[object HTMLDivElement]" },
.{ "sp.closest('#9000')", "null" },
}, .{});
try runner.testCases(&.{
.{ "let a = document.getElementById('content')", "undefined" },
.{ "a.hasAttributes()", "true" },
.{ "a.attributes.length", "1" },
.{ "a.getAttributeNames()", "id" },
.{ "a.getAttribute('id')", "content" },
.{ "a.attributes['id'].value", "content" },
.{
\\ let x = '';
\\ for (const attr of a.attributes) {
\\ x += attr.name + '=' + attr.value;
\\ }
\\ x;
,
"id=content",
},
.{ "a.hasAttribute('foo')", "false" },
.{ "a.getAttribute('foo')", "null" },
.{ "a.setAttribute('foo', 'bar')", "undefined" },
.{ "a.hasAttribute('foo')", "true" },
.{ "a.getAttribute('foo')", "bar" },
.{ "a.getAttributeNames()", "id,foo" },
.{ " try { a.setAttribute('.foo', 'invalid') } catch (e) { e }", "Error: InvalidCharacterError" },
.{ "a.setAttribute('foo', 'baz')", "undefined" },
.{ "a.hasAttribute('foo')", "true" },
.{ "a.getAttribute('foo')", "baz" },
.{ "a.removeAttribute('foo')", "undefined" },
.{ "a.hasAttribute('foo')", "false" },
.{ "a.getAttribute('foo')", "null" },
}, .{});
try runner.testCases(&.{
.{ "let b = document.getElementById('content')", "undefined" },
.{ "b.toggleAttribute('foo')", "true" },
.{ "b.hasAttribute('foo')", "true" },
.{ "b.getAttribute('foo')", "" },
.{ "b.toggleAttribute('foo')", "false" },
.{ "b.hasAttribute('foo')", "false" },
}, .{});
try runner.testCases(&.{
.{ "let c = document.getElementById('content')", "undefined" },
.{ "c.children.length", "3" },
.{ "c.firstElementChild.nodeName", "A" },
.{ "c.lastElementChild.nodeName", "P" },
.{ "c.childElementCount", "3" },
.{ "c.prepend(document.createTextNode('foo'))", "undefined" },
.{ "c.append(document.createTextNode('bar'))", "undefined" },
}, .{});
try runner.testCases(&.{
.{ "let d = document.getElementById('para')", "undefined" },
.{ "d.previousElementSibling.nodeName", "P" },
.{ "d.nextElementSibling", "null" },
}, .{});
try runner.testCases(&.{
.{ "let e = document.getElementById('content')", "undefined" },
.{ "e.querySelector('foo')", "null" },
.{ "e.querySelector('#foo')", "null" },
.{ "e.querySelector('#link').id", "link" },
.{ "e.querySelector('#para').id", "para" },
.{ "e.querySelector('*').id", "link" },
.{ "e.querySelector('')", "null" },
.{ "e.querySelector('*').id", "link" },
.{ "e.querySelector('#content')", "null" },
.{ "e.querySelector('#para').id", "para" },
.{ "e.querySelector('.ok').id", "link" },
.{ "e.querySelector('a ~ p').id", "para-empty" },
.{ "e.querySelectorAll('foo').length", "0" },
.{ "e.querySelectorAll('#foo').length", "0" },
.{ "e.querySelectorAll('#link').length", "1" },
.{ "e.querySelectorAll('#link').item(0).id", "link" },
.{ "e.querySelectorAll('#para').length", "1" },
.{ "e.querySelectorAll('#para').item(0).id", "para" },
.{ "e.querySelectorAll('*').length", "4" },
.{ "e.querySelectorAll('p').length", "2" },
.{ "e.querySelectorAll('.ok').item(0).id", "link" },
}, .{});
try runner.testCases(&.{
.{ "let f = document.getElementById('content')", "undefined" },
.{ "let ff = document.createAttribute('foo')", "undefined" },
.{ "f.setAttributeNode(ff)", "null" },
.{ "f.getAttributeNode('foo').name", "foo" },
.{ "f.removeAttributeNode(ff).name", "foo" },
.{ "f.getAttributeNode('bar')", "null" },
}, .{});
try runner.testCases(&.{
.{ "document.getElementById('para').innerHTML", " And" },
.{ "document.getElementById('para-empty').innerHTML.trim()", "<span id=\"para-empty-child\"></span>" },
.{ "let h = document.getElementById('para-empty')", "undefined" },
.{ "const prev = h.innerHTML", "undefined" },
.{ "h.innerHTML = '<p id=\"hello\">hello world</p>'", "<p id=\"hello\">hello world</p>" },
.{ "h.innerHTML", "<p id=\"hello\">hello world</p>" },
.{ "h.firstChild.nodeName", "P" },
.{ "h.firstChild.id", "hello" },
.{ "h.firstChild.textContent", "hello world" },
.{ "h.innerHTML = prev; true", "true" },
.{ "document.getElementById('para-empty').innerHTML.trim()", "<span id=\"para-empty-child\"></span>" },
}, .{});
try runner.testCases(&.{
.{ "document.getElementById('para').outerHTML", "<p id=\"para\"> And</p>" },
}, .{});
try runner.testCases(&.{
.{ "document.getElementById('para').clientWidth", "1" },
.{ "document.getElementById('para').clientHeight", "1" },
.{ "let r1 = document.getElementById('para').getBoundingClientRect()", "undefined" },
.{ "r1.x", "0" },
.{ "r1.y", "0" },
.{ "r1.width", "1" },
.{ "r1.height", "1" },
.{ "let r2 = document.getElementById('content').getBoundingClientRect()", "undefined" },
.{ "r2.x", "1" },
.{ "r2.y", "0" },
.{ "r2.width", "1" },
.{ "r2.height", "1" },
.{ "let r3 = document.getElementById('para').getBoundingClientRect()", "undefined" },
.{ "r3.x", "0" },
.{ "r3.y", "0" },
.{ "r3.width", "1" },
.{ "r3.height", "1" },
.{ "document.getElementById('para').clientWidth", "2" },
.{ "document.getElementById('para').clientHeight", "1" },
.{ "let r4 = document.createElement('div').getBoundingClientRect()", null },
.{ "r4.x", "0" },
.{ "r4.y", "0" },
.{ "r4.width", "0" },
.{ "r4.height", "0" },
// Test setup causes WrongDocument or HierarchyRequest error unlike in chrome/firefox
// .{ // An element of another document, even if created from the main document, is not rendered.
// \\ let div5 = document.createElement('div');
// \\ const newDoc = document.implementation.createHTMLDocument("New Document");
// \\ newDoc.body.appendChild(div5);
// \\ let r5 = div5.getBoundingClientRect();
// ,
// null,
// },
// .{ "r5.x", "0" },
// .{ "r5.y", "0" },
// .{ "r5.width", "0" },
// .{ "r5.height", "0" },
}, .{});
try runner.testCases(&.{
.{ "const el = document.createElement('div');", "undefined" },
.{ "el.id = 'matches'; el.className = 'ok';", "ok" },
.{ "el.matches('#matches')", "true" },
.{ "el.matches('.ok')", "true" },
.{ "el.matches('#9000')", "false" },
.{ "el.matches('.notok')", "false" },
}, .{});
try runner.testCases(&.{
.{ "const el3 = document.createElement('div');", "undefined" },
.{ "el3.scrollIntoViewIfNeeded();", "undefined" },
.{ "el3.scrollIntoViewIfNeeded(false);", "undefined" },
}, .{});
// before
try runner.testCases(&.{
.{ "const before_container = document.createElement('div');", "undefined" },
.{ "document.append(before_container);", "undefined" },
.{ "const b1 = document.createElement('div');", "undefined" },
.{ "before_container.append(b1);", "undefined" },
.{ "const b1_a = document.createElement('p');", "undefined" },
.{ "b1.before(b1_a, 'over 9000');", "undefined" },
.{ "before_container.innerHTML", "<p></p>over 9000<div></div>" },
}, .{});
// after
try runner.testCases(&.{
.{ "const after_container = document.createElement('div');", "undefined" },
.{ "document.append(after_container);", "undefined" },
.{ "const a1 = document.createElement('div');", "undefined" },
.{ "after_container.append(a1);", "undefined" },
.{ "const a1_a = document.createElement('p');", "undefined" },
.{ "a1.after('over 9000', a1_a);", "undefined" },
.{ "after_container.innerHTML", "<div></div>over 9000<p></p>" },
}, .{});
try runner.testCases(&.{
.{ "var div1 = document.createElement('div');", null },
.{ "div1.innerHTML = \" <link/><table></table><a href='/a'>a</a><input type='checkbox'/>\"", null },
.{ "div1.getElementsByTagName('a').length", "1" },
}, .{});
try runner.testCases(&.{
.{ "document.createElement('a').hasAttributes()", "false" },
.{ "var fc; (fc = document.createElement('div')).innerHTML = '<script><\\/script>'", null },
.{ "fc.outerHTML", "<div><script></script></div>" },
.{ "fc; (fc = document.createElement('div')).innerHTML = '<script><\\/script><p>hello</p>'", null },
.{ "fc.outerHTML", "<div><script></script><p>hello</p></div>" },
}, .{});
try runner.testCases(&.{
.{ "const rm = document.createElement('div')", null },
.{ "rm.getAttributeNames()", "" },
.{ "rm.id = 'to-remove'", null },
.{ "document.getElementsByTagName('body')[0].appendChild(rm)", null },
.{ "document.getElementById('to-remove') != null", "true" },
.{ "rm.remove()", "undefined" },
.{ "document.getElementById('to-remove') != null", "false" },
}, .{});
try runner.testCases(&.{
.{ "const div2 = document.createElement('div');", null },
.{ "div2.innerHTML = '<p id=1 .lit$id=9>a</p>';", null },
.{ "div2.innerHTML", "<p id=\"1\" .lit$id=\"9\">a</p>" },
.{ "div2.childNodes[0].getAttributeNames()", "id,.lit$id" },
}, .{});
test "Browser: DOM.Element" {
try testing.htmlRunner("dom/element.html");
}

View File

@@ -350,7 +350,7 @@ pub const Page = struct {
// Look, we want to exit ASAP, but we don't want
// to exit so fast that we've run none of the
// background jobs.
break :blk 50;
break :blk if (comptime builtin.is_test) 5 else 50;
}
// No http transfers, no cdp extra socket, no
// scheduled tasks, we're done.
@@ -397,7 +397,7 @@ pub const Page = struct {
// we _could_ http_client.tick(ms_to_wait), but this has
// the same result, and I feel is more correct.
return .no_page;
}
},
}
const ms_elapsed = timer.lap() / 1_000_000;

View File

@@ -176,8 +176,6 @@ pub const Session = struct {
}
};
const QueuedNavigation = struct {
url: []const u8,
opts: NavigateOpts,

View File

@@ -117,9 +117,7 @@ pub fn CDPT(comptime TypeProvider: type) type {
// scheduled task. So we run this directly in order to process any
// timeouts (or http events) which are ready to be processed.
pub fn hasPage() bool {
}
pub fn hasPage() bool {}
pub fn pageWait(self: *Self, ms: i32) Session.WaitResult {
const session = &(self.browser.session orelse return .no_page);
return session.wait(ms);

View File

@@ -387,7 +387,7 @@ fn makeRequest(self: *Client, handle: *Handle, transfer: *Transfer) !void {
_ = try self.perform(0);
}
pub const PerformStatus = enum{
pub const PerformStatus = enum {
extra_socket,
normal,
};

View File

@@ -331,18 +331,16 @@ pub fn Env(comptime State: type, comptime WebApis: type) type {
fn promiseRejectCallback(v8_msg: v8.C_PromiseRejectMessage) callconv(.c) void {
const msg = v8.PromiseRejectMessage.initFromC(v8_msg);
const isolate = msg.getPromise().toObject().getIsolate();
const isolate = msg.getPromise().toObject().getIsolate();
const v8_context = isolate.getCurrentContext();
const context: *JsContext = @ptrFromInt(v8_context.getEmbedderData(1).castTo(v8.BigInt).getUint64());
const value =
if (msg.getValue()) |v8_value| valueToString(context.call_arena, v8_value, isolate, v8_context) catch |err| @errorName(err)
else "no value";
if (msg.getValue()) |v8_value| valueToString(context.call_arena, v8_value, isolate, v8_context) catch |err| @errorName(err) else "no value";
log.debug(.js, "unhandled rejection", .{.value =value});
log.debug(.js, "unhandled rejection", .{ .value = value });
}
// ExecutionWorld closely models a JS World.
// https://chromium.googlesource.com/chromium/src/+/master/third_party/blink/renderer/bindings/core/v8/V8BindingDesign.md#World
// https://developer.mozilla.org/en-US/docs/Mozilla/Add-ons/WebExtensions/API/scripting/ExecutionWorld

View File

@@ -0,0 +1,33 @@
<script src="../testing.js"></script>
<body></body>
<script id=documentFragement>
testing.expectEqual('DocumentFragment', new DocumentFragment().constructor.name);
const dc1 = new DocumentFragment();
testing.expectEqual(true, dc1.isEqualNode(dc1))
const dc2 = new DocumentFragment();
testing.expectEqual(true, dc1.isEqualNode(dc2))
let f = document.createDocumentFragment();
let d = document.createElement('div');
testing.expectEqual(0, d.childElementCount);
d.id = 'x';
testing.expectEqual(null, $('#x'));
f.append(d);
testing.expectEqual(1, f.childElementCount)
testing.expectEqual('x', f.children[0].id);
testing.expectEqual(null, $('#x'));
document.getElementsByTagName('body')[0].append(f.cloneNode(true));
testing.expectEqual(true, $('#x') != null);
testing.expectEqual(null, document.querySelector('.hello'));
testing.expectEqual(0, document.querySelectorAll('.hello').length);
testing.expectEqual('x', document.querySelector('#x').id);
testing.expectEqual(['x'], Array.from(document.querySelectorAll('#x')).map((n) => n.id));
</script>

View File

@@ -0,0 +1,12 @@
<script src="../testing.js"></script>
<script id=documentType>
let dt1 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');
let dt2 = document.implementation.createDocumentType('qname2', 'pid2', 'sys2');
let dt3 = document.implementation.createDocumentType('qname1', 'pid1', 'sys1');
testing.expectEqual(true, dt1.isEqualNode(dt1));
testing.expectEqual(true, dt1.isEqualNode(dt3));
testing.expectEqual(false, dt1.isEqualNode(dt2));
testing.expectEqual(false, dt2.isEqualNode(dt3));
testing.expectEqual(false, dt1.isEqualNode(document));
testing.expectEqual(false, document.isEqualNode(dt1));
</script>

View File

@@ -0,0 +1,6 @@
<script src="../testing.js"></script>
<script id=domParser>
const dp = new DOMParser();;
const parsed = dp.parseFromString('<div>abc</div>', 'text/html');
testing.expectEqual('[object HTMLDocument]', parsed.toString());
</script>

264
src/tests/dom/element.html Normal file
View File

@@ -0,0 +1,264 @@
<script src="../testing.js"></script>
<div id="content">
<a id="link" href="foo" class="ok">OK</a>
<p id="para-empty" class="ok empty">
<span id="para-empty-child"></span>
</p>
<p id="para"> And</p>
<!--comment-->
</div>
<script id=element>
let content = document.getElementById('content');
testing.expectEqual('http://www.w3.org/1999/xhtml', content.namespaceURI);
testing.expectEqual(null, content.prefix);
testing.expectEqual('div', content.localName);
testing.expectEqual('DIV', content.tagName);
testing.expectEqual('content', content.id);
content.id = 'foo';
testing.expectEqual('foo', content.id);
content.id = 'content';
testing.expectEqual('', content.className);
let p1 = document.getElementById('para-empty');
testing.expectEqual('ok empty', p1.className);
p1.className = 'foo bar baz';
testing.expectEqual('foo bar baz', p1.className);
p1.className = 'ok empty';
testing.expectEqual(2, p1.classList.length);
</script>
<script id=closest>
const el2 = document.createElement('div');
el2.id = 'closest';
el2.className = 'ok';
testing.expectEqual(el2, el2.closest('#closest'));
testing.expectEqual(el2, el2.closest('.ok'));
testing.expectEqual(null, el2.closest('#9000'));
testing.expectEqual(null, el2.closest('.notok'));
const sp = document.createElement('span');
el2.appendChild(sp);
testing.expectEqual(el2, sp.closest('#closest'));
testing.expectEqual(null, sp.closest('#9000'));
</script>
<script id=attributes>
testing.expectEqual(true, content.hasAttributes());
testing.expectEqual(1, content.attributes.length);
testing.expectEqual(['id'], content.getAttributeNames());
testing.expectEqual('content', content.getAttribute('id'));
testing.expectEqual('content', content.attributes['id'].value);
let x = '';
for (const attr of content.attributes) {
x += attr.name + '=' + attr.value;
}
testing.expectEqual('id=content', x);
testing.expectEqual(false, content.hasAttribute('foo'));
testing.expectEqual(null, content.getAttribute('foo'));
content.setAttribute('foo', 'bar');
testing.expectEqual(true, content.hasAttribute('foo'));
testing.expectEqual('bar', content.getAttribute('foo'));
testing.expectEqual(['id', 'foo'], content.getAttributeNames());
testing.expectError('Error: InvalidCharacterError', () => {
content.setAttribute('.foo', 'invalid')
});
content.setAttribute('foo', 'baz');
testing.expectEqual(true, content.hasAttribute('foo'));
testing.expectEqual('baz', content.getAttribute('foo'));
content.removeAttribute('foo');
testing.expectEqual(false, content.hasAttribute('foo'));
testing.expectEqual(null, content.getAttribute('foo'));
let b = document.getElementById('content');
testing.expectEqual(true, b.toggleAttribute('foo'));
testing.expectEqual(true, b.hasAttribute('foo'));
testing.expectEqual('', b.getAttribute('foo'));
testing.expectEqual(false, b.toggleAttribute('foo'));
testing.expectEqual(false, b.hasAttribute('foo'));
testing.expectEqual(false, document.createElement('a').hasAttributes());
const div2 = document.createElement('div');
div2.innerHTML = '<p id=1 .lit$id=9>a</p>';
testing.expectEqual('<p id="1" .lit$id="9">a</p>', div2.innerHTML);
testing.expectEqual(['id', '.lit$id'], div2.childNodes[0].getAttributeNames());
</script>
<script id=children>
testing.expectEqual(3, content.children.length);
testing.expectEqual('A', content.firstElementChild.nodeName);
testing.expectEqual('P', content.lastElementChild.nodeName);
testing.expectEqual(3, content.childElementCount);
</script>
<script id=sibling>
content.prepend(document.createTextNode('foo'));
content.append(document.createTextNode('bar'));
let d = document.getElementById('para');
testing.expectEqual('P', d.previousElementSibling.nodeName);
testing.expectEqual(null, d.nextElementSibling);
</script>
<script id=querySelector>
testing.expectEqual(null, content.querySelector('foo'));
testing.expectEqual(null, content.querySelector('#foo'));
testing.expectEqual('link', content.querySelector('#link').id);
testing.expectEqual('para', content.querySelector('#para').id);
testing.expectEqual('link', content.querySelector('*').id);
testing.expectEqual(null, content.querySelector(''));
testing.expectEqual('link', content.querySelector('*').id);
testing.expectEqual(null, content.querySelector('#content'));
testing.expectEqual('para', content.querySelector('#para').id);
testing.expectEqual('link', content.querySelector('.ok').id);
testing.expectEqual('para-empty', content.querySelector('a ~ p').id);
testing.expectEqual(0, content.querySelectorAll('foo').length);
testing.expectEqual(0, content.querySelectorAll('#foo').length);
testing.expectEqual(1, content.querySelectorAll('#link').length);
testing.expectEqual('link', content.querySelectorAll('#link').item(0).id);
testing.expectEqual(1, content.querySelectorAll('#para').length);
testing.expectEqual('para', content.querySelectorAll('#para').item(0).id);
testing.expectEqual(4, content.querySelectorAll('*').length);
testing.expectEqual(2, content.querySelectorAll('p').length);
testing.expectEqual('link', content.querySelectorAll('.ok').item(0).id);
</script>
<script id=createdAttributes>
let ff = document.createAttribute('foo');
content.setAttributeNode(ff);
testing.expectEqual('foo', content.getAttributeNode('foo').name);
testing.expectEqual('foo', content.removeAttributeNode(ff).name);
testing.expectEqual(null, content.getAttributeNode('bar'));
</script>
<script id=innerHTML>
testing.expectEqual(' And', document.getElementById('para').innerHTML);
testing.expectEqual('<span id="para-empty-child"></span>', $('#para-empty').innerHTML.trim());
let h = $('#para-empty');
const prev = h.innerHTML;
h.innerHTML = '<p id="hello">hello world</p>';
testing.expectEqual('<p id="hello">hello world</p>', h.innerHTML);
testing.expectEqual('P', h.firstChild.nodeName);
testing.expectEqual('hello', h.firstChild.id);
testing.expectEqual('hello world', h.firstChild.textContent);
h.innerHTML = prev;
testing.expectEqual('<span id="para-empty-child"></span>', $('#para-empty').innerHTML.trim());
testing.expectEqual('<p id="para"> And</p>', $('#para').outerHTML);
</script>
<script id=dimensions>
const para = document.getElementById('para');
testing.expectEqual(1, para.clientWidth);
testing.expectEqual(1, para.clientHeight);
// let r1 = document.getElementById('para').getBoundingClientRect();
// testing.expectEqual(0, r1.x);
// testing.expectEqual(0, r1.y);
// testing.expectEqual(1, r1.width);
// testing.expectEqual(2, r1.height);
// let r2 = document.getElementById('content').getBoundingClientRect();
// testing.expectEqual(1, r2.x);
// testing.expectEqual(0, r2.y);
// testing.expectEqual(1, r2.width);
// testing.expectEqual(1, r2.height);
// let r3 = document.getElementById('para').getBoundingClientRect();
// testing.expectEqual(0, r3.x);
// testing.expectEqual(0, r3.y);
// testing.expectEqual(1, r3.width);
// testing.expectEqual(1, r3.height);
// testing.expectEqual(1, para.clientWidth);
// testing.expectEqual(1, para.clientHeight);
// let r4 = document.createElement('div').getBoundingClientRect();
// testing.expectEqual(0, r4.x);
// testing.expectEqual(0, r4.y);
// testing.expectEqual(0, r4.width);
// testing.expectEqual(0, r4.height);
</script>
<script id=matches>
const el = document.createElement('div');
el.id = 'matches';
el.className = 'ok';
testing.expectEqual(true, el.matches('#matches'));
testing.expectEqual(true, el.matches('.ok'));
testing.expectEqual(false, el.matches('#9000'));
testing.expectEqual(false, el.matches('.notok'));
</script>
<script id=scroll>
const el3 = document.createElement('div');
el3.scrollIntoViewIfNeeded();
el3.scrollIntoViewIfNeeded(false);
// doesn't throw is good enough
testing.skip();
</script>
<script id=before>
const before_container = document.createElement('div');
document.append(before_container);
const b1 = document.createElement('div');
before_container.append(b1);
const b1_a = document.createElement('p');
b1.before(b1_a, 'over 9000');
testing.expectEqual('<p></p>over 9000<div></div>', before_container.innerHTML);
</script>
<script id=after>
const after_container = document.createElement('div');
document.append(after_container);
const a1 = document.createElement('div');
after_container.append(a1);
const a1_a = document.createElement('p');
a1.after('over 9000', a1_a);
testing.expectEqual('<div></div>over 9000<p></p>', after_container.innerHTML);
</script>
<script id=getElementsByTagName>
var div1 = document.createElement('div');
div1.innerHTML = " <link/><table></table><a href='/a'>a</a><input type='checkbox'/>";
testing.expectEqual(1, div1.getElementsByTagName('a').length);
</script>
<script id=outerHTML>
let fc = document.createElement('div')
fc.innerHTML = '<script><\/script>';
testing.expectEqual('<div><script><\/script></div>', fc.outerHTML);
fc = document.createElement('div')
fc.innerHTML = '<script><\/script><p>hello</p>';
testing.expectEqual('<div><script><\/script><p>hello</p></div>', fc.outerHTML);
</script>
<script id=remove>
const rm = document.createElement('div');
testing.expectEqual([], rm.getAttributeNames());
rm.id = 'to-remove';
document.getElementsByTagName('body')[0].appendChild(rm);
$('#to-remove').remove();
testing.expectEqual(null, $('#to-remove'));
</script>