[ { "category": "add", "name": "can add class ref on a single div", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add class ref w/ double dash on a single div", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo--bar\").should.equal(false) && div.classList.contains(\"foo--bar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add class ref on a single form", "html": "
", "action": "form.click()", "check": "form.classList.contains(\"foo\").should.equal(false) && form.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can target another div for class ref", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && bar.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add to query in me", "html": "

", "action": "div.click()", "check": "p1.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && p1.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add to children", "html": "

", "action": "div.click()", "check": "p1.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && p1.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add non-class attributes", "html": "
", "action": "div.click()", "check": "div.hasAttribute(\"foo\").should.equal(false) && div.getAttribute(\"foo\").should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add css properties", "html": "
", "action": "div.click()", "check": "div.style.color.should.equal(\"blue\") && div.style.color.should.equal(\"red\") && div.style.fontFamily.should.equal(\"monospace\")", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add templated css properties", "html": "
", "action": "div.click()", "check": "div.style.color.should.equal(\"blue\") && div.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add multiple class refs", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add class refs w/ colons and dashes", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo:bar-doh\").should.equal(false) && div.classList.contains(\"foo:bar-doh\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can filter class addition via the when clause", "html": "
|
|
", "action": "div1.click()", "check": "div2.classList.contains(\"rey\").should.equal(false) && div3.classList.contains(\"rey\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can filter property addition via the when clause", "html": "
|
|
", "action": "div1.click()", "check": "div2.hasAttribute(\"rey\").should.equal(false) && div3.hasAttribute(\"rey\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "add", "name": "can add to an HTMLCollection", "html": "
|
", "action": "div1.click()", "check": "byId(\"c1\").classList.contains(\"foo\").should.equal(false) && byId(\"c2\").classList.contains(\"foo\").should.equal(false) && byId(\"c1\").classList.contains(\"foo\").should.equal(true) && byId(\"c2\").classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove class ref on a single div", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove class ref on a single form", "html": "
", "action": "form.click()", "check": "form.classList.contains(\"foo\").should.equal(true) && form.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can target another div for class ref", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false) && bar.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove non-class attributes", "html": "
", "action": "div.click()", "check": "div.getAttribute(\"foo\").should.equal(\"bar\") && div.hasAttribute(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove elements", "html": "
", "action": "div.click()", "check": "assert.isNotNull(div.parentElement) && assert.isNull(div.parentElement)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove other elements", "html": "
|
", "action": "div.click()", "check": "assert.isNotNull(div2.parentElement) && assert.isNull(div2.parentElement)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove parent element", "html": "
", "action": "btn.click()", "check": "assert.isNotNull(div.parentElement) && assert.isNull(div.parentElement)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove multiple class refs", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true) && div.classList.contains(\"doh\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"doh\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "remove", "name": "can remove query refs from specific things", "html": "

foo

bar

doh

", "action": "d1.click()", "check": "div.innerHTML.includes(\"foo\").should.equal(true) && div.innerHTML.includes(\"bar\").should.equal(true) && div.innerHTML.includes(\"doh\").should.equal(true) && div.innerHTML.includes(\"foo\").should.equal(false) && div.innerHTML.includes(\"bar\").should.equal(true) && div.innerHTML.includes(\"doh\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle class ref on a single div", "html": "
", "action": "div.click(); div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle class ref on a single form", "html": "
", "action": "form.click(); form.click()", "check": "form.classList.contains(\"foo\").should.equal(false) && form.classList.contains(\"foo\").should.equal(true) && form.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can target another div for class ref toggle", "html": "
|
", "action": "div.click(); div.click()", "check": "bar.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && bar.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false) && bar.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle non-class attributes", "html": "
", "action": "div.click(); div.click()", "check": "div.hasAttribute(\"foo\").should.equal(false) && div.getAttribute(\"foo\").should.equal(\"bar\") && div.hasAttribute(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle non-class attributes on selects", "html": "", "action": "select.click(); select.click()", "check": "select.hasAttribute(\"foo\").should.equal(false) && select.getAttribute(\"foo\").should.equal(\"bar\") && select.hasAttribute(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle for a fixed amount of time", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": true, "complexity": "simple" }, { "category": "toggle", "name": "can toggle until an event on another element", "html": "
|
", "action": "div.click(); d1.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"foo\").should.equal(false)", "async": true, "complexity": "simple" }, { "category": "toggle", "name": "can toggle between two classes", "html": "
", "action": "div.click(); div.click()", "check": "div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(true) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle multiple class refs", "html": "
", "action": "div.click(); div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(true) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && getComputedStyle(div).display.should.equal(\"none\") && getComputedStyle(div).display.should.equal(\"block\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle opacity", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).opacity.should.equal(\"1\") && getComputedStyle(div).opacity.should.equal(\"0\") && getComputedStyle(div).opacity.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle opacity", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).visibility.should.equal(\"visible\") && getComputedStyle(div).visibility.should.equal(\"hidden\") && getComputedStyle(div).visibility.should.equal(\"visible\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display w/ my", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && getComputedStyle(div).display.should.equal(\"none\") && getComputedStyle(div).display.should.equal(\"block\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display w/ my", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).opacity.should.equal(\"1\") && getComputedStyle(div).opacity.should.equal(\"0\") && getComputedStyle(div).opacity.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display w/ my", "html": "
", "action": "div.click(); div.click()", "check": "getComputedStyle(div).visibility.should.equal(\"visible\") && getComputedStyle(div).visibility.should.equal(\"hidden\") && getComputedStyle(div).visibility.should.equal(\"visible\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display on other elt", "html": "
|
", "action": "div.click(); div.click()", "check": "getComputedStyle(div2).display.should.equal(\"block\") && getComputedStyle(div2).display.should.equal(\"none\") && getComputedStyle(div2).display.should.equal(\"block\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display on other elt", "html": "
|
", "action": "div.click(); div.click()", "check": "getComputedStyle(div2).opacity.should.equal(\"1\") && getComputedStyle(div2).opacity.should.equal(\"0\") && getComputedStyle(div2).opacity.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle display on other elt", "html": "
|
", "action": "div.click(); div.click()", "check": "getComputedStyle(div2).visibility.should.equal(\"visible\") && getComputedStyle(div2).visibility.should.equal(\"hidden\") && getComputedStyle(div2).visibility.should.equal(\"visible\")", "async": false, "complexity": "simple" }, { "category": "toggle", "name": "can toggle crazy tailwinds class ref on a single form", "html": "
", "action": "form.click(); form.click()", "check": "form.classList.contains(\"group-[:nth-of-type(3)_&]:block\").should.equal(false) && form.classList.contains(\"group-[:nth-of-type(3)_&]:block\").should.equal(true) && form.classList.contains(\"group-[:nth-of-type(3)_&]:block\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set properties", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set indirect properties", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set complex indirect properties lhs", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set complex indirect properties rhs", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set chained indirect properties", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set styles", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set javascript globals", "html": "
lolwat
", "action": "d1.click()", "check": "window[\"temp\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set local variables", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into id ref", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into class ref", "html": "
|
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\") && d2.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into attribute ref", "html": "
", "action": "d1.click()", "check": "d1.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect attribute ref", "html": "
|
", "action": "d1.click()", "check": "d2.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect attribute ref 2", "html": "
|
", "action": "d1.click()", "check": "d2.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect attribute ref 3", "html": "
|
", "action": "d1.click()", "check": "d2.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into style ref", "html": "
", "action": "d1.click()", "check": "d1.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect style ref", "html": "
|
", "action": "d1.click()", "check": "d2.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect style ref 2", "html": "
|
", "action": "d1.click()", "check": "d2.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set into indirect style ref 3", "html": "
|
", "action": "d1.click()", "check": "d2.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "set waits on promises", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\") && d1.innerHTML.should.equal(\"foo\")", "async": true, "complexity": "promise" }, { "category": "set", "name": "can set many properties at once with object literal", "html": "
", "action": "(see body)", "check": "obj.should.deep.equal({ foo: 1, bar: 2, baz: 3 })", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set props w/ array access syntax", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set props w/ array access syntax and var", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set arrays w/ array access syntax", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "can set arrays w/ array access syntax and var", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "set", "name": "handles set url regression properly", "html": "
lolwat
", "action": "d1.click()", "check": "d1.innerText.should.equal(\"https://yyy.xxxxxx.com/path/out/foo.pdf\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set properties", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put directly into nodes", "html": "
", "action": "d1.click()", "check": "d1.textContent.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put nodes into nodes", "html": "
|
", "action": "d2.click()", "check": "d2.firstChild.should.equal(d1)", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put directly into symbols", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "me symbol doesn't get stomped on direct write", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set styles", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set javascript globals", "html": "
lolwat
", "action": "d1.click()", "check": "window[\"temp\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into class ref w/ flatmapped property", "html": "
|
", "action": "div.click()", "check": "d1.textContent.should.equal(\"foo\") && d2.textContent.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into class ref w/ flatmapped property using of", "html": "
|
", "action": "div.click()", "check": "d1.textContent.should.equal(\"foo\") && d2.textContent.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set local variables", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into id ref", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can insert before", "html": "
|
foo
", "action": "d2.click()", "check": "d2.previousSibling.textContent.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can insert after", "html": "
foo
|
", "action": "d2.click()", "check": "d2.nextSibling.textContent.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can insert after beginning", "html": "
*
", "action": "d1.click()", "check": "d1.textContent.should.equal(\"foo*\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can insert before end", "html": "
*
", "action": "d1.click()", "check": "d1.textContent.should.equal(\"*foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into attribute ref", "html": "
", "action": "d1.click()", "check": "d1.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect attribute ref", "html": "
", "action": "d1.click()", "check": "d1.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect attribute ref 2", "html": "
|
", "action": "d1.click()", "check": "d2.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect attribute ref 3", "html": "
|
", "action": "d1.click()", "check": "d2.getAttribute(\"bar\").should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into style ref", "html": "
", "action": "d1.click()", "check": "d1.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect style ref", "html": "
", "action": "d1.click()", "check": "d1.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect style ref 2", "html": "
|
", "action": "d1.click()", "check": "d2.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can set into indirect style ref 3", "html": "
|
", "action": "d1.click()", "check": "d2.style[\"color\"].should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "waits on promises", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\") && d1.innerHTML.should.equal(\"foo\")", "async": true, "complexity": "promise" }, { "category": "put", "name": "can put properties w/ array access syntax", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put properties w/ array access syntax and var", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put array vals w/ array access syntax", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "can put array vals w/ array access syntax and var", "html": "
lolwat
", "action": "d1.click()", "check": "d1.style.color.should.equal(\"red\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript in new content in a symbol write", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript in new content in a element target", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript in before", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript at start of", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript at end of", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "properly processes hyperscript after", "html": "
", "action": "div.click(); button.click()", "check": "button.innerHTML.should.equal(\"40\") && button.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "put", "name": "is null tolerant", "html": "
", "action": "d1.click()", "check": "(no explicit assertion)", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with no target", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"none\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "hide element then show element retains original display", "html": "
", "action": "div.click(); div.click()", "check": "div.style.display.should.equal(\"none\") && getComputedStyle(div).display.should.equal(\"none\") && div.style.display.should.equal(\"\") && getComputedStyle(div).display.should.equal(\"block\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with no target followed by command", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && div.classList.contains(\"foo\").should.equal(false) && getComputedStyle(div).display.should.equal(\"none\") && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with no target followed by then", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && div.classList.contains(\"foo\").should.equal(false) && getComputedStyle(div).display.should.equal(\"none\") && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with no target with a with", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && div.classList.contains(\"foo\").should.equal(false) && getComputedStyle(div).display.should.equal(\"none\") && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element, with display:none by default", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && getComputedStyle(div).display.should.equal(\"none\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with display:none explicitly", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).display.should.equal(\"block\") && getComputedStyle(div).display.should.equal(\"none\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with opacity:0", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).opacity.should.equal(\"1\") && getComputedStyle(div).opacity.should.equal(\"0\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element with opacity style literal", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).opacity.should.equal(\"1\") && getComputedStyle(div).opacity.should.equal(\"0\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide element, with visibility:hidden", "html": "
", "action": "div.click()", "check": "getComputedStyle(div).visibility.should.equal(\"visible\") && getComputedStyle(div).visibility.should.equal(\"hidden\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide other elements", "html": "
|
", "action": "div.click()", "check": "getComputedStyle(hideme).display.should.equal(\"block\") && getComputedStyle(hideme).display.should.equal(\"none\")", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can hide with custom strategy", "html": "
", "action": "classList.remove(\"foo\"); div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "hide", "name": "can set default to custom strategy", "html": "
", "action": "classList.remove(\"foo\"); div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic true branch works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic true branch works with multiple commands", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic true branch works with end", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic true branch works with naked else", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic true branch works with naked else end", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic else branch works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic else branch works with end", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic else if branch works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic else if branch works with end", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "otherwise alias works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "triple else if branch works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "triple else if branch works with end", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "basic else branch works with multiple commands", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "true branch with a wait works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\") && d1.innerHTML.should.equal(\"foo\")", "async": true, "complexity": "simple" }, { "category": "if", "name": "false branch with a wait works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\") && d1.innerHTML.should.equal(\"foo\")", "async": true, "complexity": "simple" }, { "category": "if", "name": "if properly passes execution along if child is not executed", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "if properly supports nested if statements and end block", "html": "
", "action": "d1.click(); d1.click()", "check": "d1.innerHTML.should.equal(\"\") && d1.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "if on new line does not join w/ else", "html": "
", "action": "d1.click(); d1.click()", "check": "d1.innerHTML.should.equal(\"foo\") && d1.innerHTML.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "if", "name": "passes the sieve test", "html": "(no make() call)", "action": "(see body)", "check": "evalHyperScript(str, {locals: {x: 1}}).should.equal(1) && evalHyperScript(str, {locals: {x: 2}}).should.equal(2) && evalHyperScript(str, {locals: {x: 3}}).should.equal(3) && evalHyperScript(str, {locals: {x: 4}}).should.equal(4) && evalHyperScript(str, {locals: {x: 5}}).should.equal(5) && evalHyperScript(str, {locals: {x: 6}}).should.equal(6) && evalHyperScript(str, {locals: {x: 6}}).should.equal(6) && evalHyperScript(str, {locals: {x: 7}}).should.equal(6) && evalHyperScript(str, {locals: {x: 8}}).should.equal(6) && evalHyperScript(str, {locals: {x: 9}}).should.equal(6) && evalHyperScript(str, {locals: {x: 10}}).should.equal(10) && evalHyperScript(str, {locals: {x: 11}}).should.equal(10)", "async": false, "complexity": "eval-only" }, { "category": "repeat", "name": "basic for loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"123\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "basic for loop with null works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "waiting in for loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"1\") && d1.innerHTML.should.equal(\"123\")", "async": true, "complexity": "simple" }, { "category": "repeat", "name": "basic raw for loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"123\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "basic raw for loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "waiting in raw for loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"1\") && d1.innerHTML.should.equal(\"123\")", "async": true, "complexity": "simple" }, { "category": "repeat", "name": "repeat forever works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"5\")", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "repeat forever works w/o keyword", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"5\")", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "basic in loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"123\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "index syntax works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"a0ab1abc2\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "indexed by syntax works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"a0ab1abc2\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "while keyword works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"5\")", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "until keyword works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"5\")", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "until event keyword works", "html": "
| ", "action": "div.click()", "check": "value.should.equal(42)", "async": true, "complexity": "script-tag" }, { "category": "repeat", "name": "only executes the init expression once", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"3\") && window.called.should.equal(1)", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "can nest loops", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"123246369\")", "async": false, "complexity": "script-tag" }, { "category": "repeat", "name": "basic times loop works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"aaa\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "times loop with expression works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"aaaaaa\")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "loop continue works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"success A. success B. success C. expected D. success A. success B. success C. expected D. \")", "async": false, "complexity": "simple" }, { "category": "repeat", "name": "loop break works", "html": "
", "action": "d1.click()", "check": "d1.innerHTML.should.equal(\"ABAB\")", "async": false, "complexity": "simple" }, { "category": "wait", "name": "can wait on time", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": true, "complexity": "simple" }, { "category": "wait", "name": "can wait on event", "html": "
", "action": "div.click(); div.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": true, "complexity": "simple" }, { "category": "wait", "name": "waiting on an event sets 'it' to the event", "html": "
", "action": "div.click(); div.dispatchEvent(new CustomEvent(\"foo\", { detail: \"hyperscript is hyper cool\" })", "check": "div.innerHTML.should.equal(\"\") && div.innerHTML.should.equal(\"hyperscript is hyper cool\")", "async": true, "complexity": "simple" }, { "category": "wait", "name": "can destructure properties in a wait", "html": "
", "action": "div.click(); div.dispatchEvent(new CustomEvent(\"foo\", { detail: { bar: \"bar\" } })", "check": "div.innerHTML.should.equal(\"\") && div.innerHTML.should.equal(\"bar\")", "async": true, "complexity": "simple" }, { "category": "wait", "name": "can wait on event on another element", "html": "
|
", "action": "div.click(); div2.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": true, "complexity": "simple" }, { "category": "wait", "name": "can wait on event or timeout 1", "html": "
|
", "action": "div.click(); div2.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": true, "complexity": "simple" }, { "category": "wait", "name": "can wait on event or timeout 2", "html": "
|
", "action": "div.click(); div2.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.classList.contains(\"foo\").should.equal(false) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(false) && div.classList.contains(\"foo\").should.equal(true) && div.classList.contains(\"bar\").should.equal(true)", "async": true, "complexity": "simple" }, { "category": "send", "name": "can send events", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.classList.contains(\"foo-sent\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "send", "name": "can reference sender in events", "html": "
|
", "action": "div.click()", "check": "div.classList.contains(\"foo-sent\").should.equal(false) && div.classList.contains(\"foo-sent\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events with args", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events with dots", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.classList.contains(\"foo-sent\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events with dots with args", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events with colons", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.classList.contains(\"foo-sent\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events with colons with args", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.innerHTML.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "send", "name": "can send events to any expression", "html": "
|
", "action": "div.click()", "check": "bar.classList.contains(\"foo-sent\").should.equal(false) && bar.classList.contains(\"foo-sent\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take a class from other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.classList.contains(\"foo\").should.equal(true) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"foo\").should.equal(false) && d2.classList.contains(\"foo\").should.equal(true) && d3.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take a class from other forms", "html": "
|
|
", "action": "f2.click()", "check": "f1.classList.contains(\"foo\").should.equal(true) && f2.classList.contains(\"foo\").should.equal(false) && f3.classList.contains(\"foo\").should.equal(false) && f1.classList.contains(\"foo\").should.equal(false) && f2.classList.contains(\"foo\").should.equal(true) && f3.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take a class for other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.classList.contains(\"foo\").should.equal(true) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"foo\").should.equal(false) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "take", "name": "a parent can take a class for other elements", "html": "
", "action": "d2.click()", "check": "d1.classList.contains(\"foo\").should.equal(true) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"foo\").should.equal(false) && d2.classList.contains(\"foo\").should.equal(true) && d3.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take an attribute from other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d2.getAttribute(\"data-foo\").should.equal(\"\") && assert.isNull(d2.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\") && assert.isNull(d1.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take an attribute with specific value from other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d2.getAttribute(\"data-foo\").should.equal(\"baz\") && assert.isNull(d2.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\") && assert.isNull(d1.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take an attribute value from other elements and set specific values instead", "html": "
|
|
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d1.getAttribute(\"data-foo\").should.equal(\"qux\") && d2.getAttribute(\"data-foo\").should.equal(\"baz\") && d3.getAttribute(\"data-foo\").should.equal(\"qux\") && assert.isNull(d2.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take an attribute value from other elements and set value from an expression instead", "html": "
|
|
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d2.getAttribute(\"data-foo\").should.equal(\"qux\") && d1.getAttribute(\"data-foo\").should.equal(\"qux\") && d2.getAttribute(\"data-foo\").should.equal(\"baz\") && d3.getAttribute(\"data-foo\").should.equal(\"qux\") && assert.isNull(d3.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take an attribute for other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d3.getAttribute(\"data-foo\").should.equal(\"\") && assert.isNull(d2.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\") && assert.isNull(d1.getAttribute(\"data-foo\") && assert.isNull(d2.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "a parent can take an attribute for other elements", "html": "
", "action": "d2.click()", "check": "d1.getAttribute(\"data-foo\").should.equal(\"bar\") && d2.getAttribute(\"data-foo\").should.equal(\"\") && assert.isNull(d2.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\") && assert.isNull(d1.getAttribute(\"data-foo\") && assert.isNull(d3.getAttribute(\"data-foo\")", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take multiple classes from other elements", "html": "
|
|
", "action": "d2.click()", "check": "d1.classList.contains(\"foo\").should.equal(true) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"bar\").should.equal(false) && d2.classList.contains(\"bar\").should.equal(false) && d3.classList.contains(\"bar\").should.equal(true) && d1.classList.contains(\"foo\").should.equal(false) && d2.classList.contains(\"foo\").should.equal(true) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"bar\").should.equal(false) && d2.classList.contains(\"bar\").should.equal(true) && d3.classList.contains(\"bar\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "take", "name": "can take multiple classes from specific element", "html": "
|
|
", "action": "d2.click()", "check": "d1.classList.contains(\"foo\").should.equal(true) && d2.classList.contains(\"foo\").should.equal(false) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"bar\").should.equal(true) && d2.classList.contains(\"bar\").should.equal(false) && d3.classList.contains(\"bar\").should.equal(true) && d1.classList.contains(\"foo\").should.equal(false) && d2.classList.contains(\"foo\").should.equal(true) && d3.classList.contains(\"foo\").should.equal(false) && d1.classList.contains(\"bar\").should.equal(false) && d2.classList.contains(\"bar\").should.equal(true) && d3.classList.contains(\"bar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on current element", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition with parameterized values", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on form", "html": "
", "action": "form.click()", "check": "form.style.width.should.equal(\"\") && form.style.width.should.equal(\"0px\") && form.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on current element with the my prefix", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition two properties on current element", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.height.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.height.should.equal(\"0px\") && div.style.width.should.equal(\"100px\") && div.style.height.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition on another element", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition on another element no element prefix", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition on another element no element prefix + possessive", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition on another element no element prefix with it", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition with a custom transition time", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition with a custom transition time via the over syntax", "html": "
|
", "action": "div.click()", "check": "div2.style.width.should.equal(\"\") && div2.style.width.should.equal(\"0px\") && div2.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on current element using style ref", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on form using style ref", "html": "
", "action": "form.click()", "check": "form.style.width.should.equal(\"\") && form.style.width.should.equal(\"0px\") && form.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can transition a single property on current element with the my prefix using style ref", "html": "
", "action": "div.click()", "check": "div.style.width.should.equal(\"\") && div.style.width.should.equal(\"0px\") && div.style.width.should.equal(\"100px\")", "async": true, "complexity": "simple" }, { "category": "transition", "name": "can use initial to transition to original value", "html": "
", "action": "div.click(); div.click()", "check": "div.style.width.should.equal(\"10px\") && div.style.width.should.equal(\"100px\") && div.style.width.should.equal(\"10px\")", "async": true, "complexity": "simple" }, { "category": "log", "name": "can log single item", "html": "
", "action": "d1.click()", "check": "(no explicit assertion)", "async": false, "complexity": "simple" }, { "category": "log", "name": "can log multiple items", "html": "
", "action": "d1.click()", "check": "(no explicit assertion)", "async": false, "complexity": "simple" }, { "category": "log", "name": "can log multiple items with debug", "html": "
", "action": "d1.click()", "check": "(no explicit assertion)", "async": false, "complexity": "simple" }, { "category": "log", "name": "can log multiple items with error", "html": "
", "action": "d1.click()", "check": "(no explicit assertion)", "async": false, "complexity": "simple" }, { "category": "call", "name": "can call javascript instance functions", "html": "
", "action": "d1.click()", "check": "value.should.equal(d1)", "async": false, "complexity": "simple" }, { "category": "call", "name": "can call global javascript functions", "html": "
", "action": "div.click()", "check": "\"foo\".should.equal(calledWith)", "async": false, "complexity": "simple" }, { "category": "call", "name": "can call no argument functions", "html": "
", "action": "div.click()", "check": "called.should.equal(true)", "async": false, "complexity": "simple" }, { "category": "call", "name": "can call functions w/ underscores", "html": "
", "action": "div.click()", "check": "called.should.equal(true)", "async": false, "complexity": "simple" }, { "category": "call", "name": "can call functions w/ dollar signs", "html": "
", "action": "div.click()", "check": "called.should.equal(true)", "async": false, "complexity": "simple" }, { "category": "call", "name": "call functions that return promises are waited on", "html": "
", "action": "div.click()", "check": "div.innerText.should.equal(\"\") && div.innerText.should.equal(\"42\")", "async": true, "complexity": "promise" }, { "category": "fetch", "name": "can do a simple fetch", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ a naked URL", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ html", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"[object DocumentFragment]\") && div.dataset.count.should.equal(\"1\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ json", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal('{\"foo\":1}')", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ json using Object syntax", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal('{\"foo\":1}')", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ json using Object syntax and an 'an' prefix", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal('{\"foo\":1}')", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch with a response object", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yep\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple fetch w/ a custom conversion", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"1.2\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple post", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple post alt syntax without curlies", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can do a simple post alt syntax w/ curlies", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can put response conversion after with", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can put response conversion before with", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "triggers an event just before fetching", "html": "
", "action": "div.click()", "check": "div.classList.contains(\"foo-set\").should.equal(false) && div.classList.contains(\"foo-set\").should.equal(true) && div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "submits the fetch parameters to the event handler", "html": "
", "action": "div.click()", "check": "event.detail.headers.should.have.property('X-CustomHeader', 'foo') && div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "allows the event handler to change the fetch parameters", "html": "
", "action": "div.click()", "check": "arguments[1].should.have.property('headers') && arguments[1].headers.should.have.property('X-CustomHeader', 'foo') && div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "fetch", "name": "can catch an error that occurs when using fetch", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"yay\")", "async": true, "complexity": "sinon" }, { "category": "increment", "name": "can increment an empty variable", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment a variable", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"22\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment refer to result", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment an attribute", "html": "
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"8\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment an floating point numbers", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"11.3\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment a property", "html": "
3
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"6\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment by zero", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"20\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can increment a value multiple times", "html": "
", "action": "div.click(); div.click(); div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"5\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement an empty variable", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"-1\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement a variable", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"18\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement an attribute", "html": "
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement an floating point numbers", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement a property", "html": "
3
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"0\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement a value multiple times", "html": "
", "action": "div.click(); div.click(); div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"-5\")", "async": false, "complexity": "simple" }, { "category": "increment", "name": "can decrement by zero", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"20\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a string to another string", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"Hello there. General Kenobi.\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value into an array", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"1,2,3,4\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value to 'it'", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"1,2,3,4\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value to a DOM node", "html": "
This is my inner HTML' to me\n append 'With Tags' to me\">
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"This is my inner HTMLWith Tags\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value to a DOM element", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"Content\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value to I", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"Content\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "can append a value to an object property", "html": "
", "action": "div.click()", "check": "div.id.should.equal(\"id_new\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "multiple appends work", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"foobardoh\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "append to undefined ignores the undefined", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "append", "name": "append preserves existing content rather than overwriting it", "html": "
New Content' to me\">
", "action": "btn.click(); div.click(); btn.click()", "check": "clicks.should.equal(1) && div.innerHTML.should.contain(\"New Content\") && btn.parentNode.should.equal(div)", "async": false, "complexity": "simple" }, { "category": "append", "name": "new content added by append will be live", "html": "
Test\\` to me\">
", "action": "div.click(); btn.click()", "check": "window.temp.should.equal(1)", "async": false, "complexity": "simple" }, { "category": "append", "name": "new DOM content added by append will be live", "html": "
then append it to me\">
", "action": "div.click()", "check": "span.classList.contains(\"topping\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "establishes a proper beingTold symbol", "html": "
", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(true) && div2.classList.contains(\"bar\").should.equal(true) && div2.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "does not overwrite the me symbol", "html": "
", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(true) && div1.classList.contains(\"foo\").should.equal(true) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "works with an array", "html": "

", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && p1.classList.contains(\"bar\").should.equal(false) && p1.classList.contains(\"foo\").should.equal(false) && p2.classList.contains(\"bar\").should.equal(false) && p2.classList.contains(\"foo\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(true) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && p1.classList.contains(\"bar\").should.equal(true) && p1.classList.contains(\"foo\").should.equal(false) && p2.classList.contains(\"bar\").should.equal(true) && p2.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "restores a proper implicit me symbol", "html": "
", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(true) && div2.classList.contains(\"bar\").should.equal(true) && div2.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "ignores null", "html": "
", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"foo\").should.equal(true) && div2.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"foo\").should.equal(false)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "you symbol represents the thing being told", "html": "
", "action": "div1.click()", "check": "div1.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(false) && div1.classList.contains(\"bar\").should.equal(false) && div2.classList.contains(\"bar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "tell", "name": "your symbol represents the thing being told", "html": "
foo
", "action": "div1.click()", "check": "div1.innerText.should.equal(\"\") && div2.innerText.should.equal(\"foo\") && div1.innerText.should.equal(\"foo\") && div2.innerText.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "tell", "name": "attributes refer to the thing being told", "html": "
", "action": "div1.click()", "check": "div1.innerText.should.equal(\"\") && div2.innerText.should.equal(\"\") && div1.innerText.should.equal(\"bar\") && div2.innerText.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "tell", "name": "yourself attribute also works", "html": "
", "action": "div1.click()", "check": "div1.innerHTML.should.equal(`
`) && div1.innerHTML.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "tell", "name": "tell terminates with a feature", "html": "
", "action": "div1.click()", "check": "div1.innerHTML.should.equal(`
`) && div1.innerHTML.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can respond to events with dots in names", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can respond to events with colons in names", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can respond to events with minus in names", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can respond to events on other elements", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"clicked\").should.equal(false) && div.classList.contains(\"clicked\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "listeners on other elements are removed when the registering element is removed", "html": "
|
", "action": "bar.click(); bar.click()", "check": "bar.innerHTML.should.equal(\"\") && bar.innerHTML.should.equal(\"a\") && bar.innerHTML.should.equal(\"a\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "listeners on self are not removed when the element is removed", "html": "
", "action": "div.remove(); div.dispatchEvent(new Event(\"someCustomEvent\")", "check": "div.innerHTML.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "supports \"elsewhere\" modifier", "html": "
", "action": "div.click(); body.click()", "check": "div.classList.contains(\"clicked\").should.equal(false) && div.classList.contains(\"clicked\").should.equal(false) && div.classList.contains(\"clicked\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "supports \"from elsewhere\" modifier", "html": "
", "action": "div.click(); body.click()", "check": "div.classList.contains(\"clicked\").should.equal(false) && div.classList.contains(\"clicked\").should.equal(false) && div.classList.contains(\"clicked\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can pick detail fields out by name", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"fromBar\").should.equal(false) && div.classList.contains(\"fromBar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can pick event properties out by name", "html": "
|
", "action": "bar.click()", "check": "div.classList.contains(\"fromBar\").should.equal(false) && div.classList.contains(\"fromBar\").should.equal(true)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can fire an event on load", "html": "
", "action": "(see body)", "check": "div.innerText.should.equal(\"Loaded\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can be in a top level script tag", "html": "
", "action": "(see body)", "check": "byId(\"loadedDemo\").innerText.should.equal(\"Loaded\")", "async": true, "complexity": "script-tag" }, { "category": "on", "name": "can have a simple event filter", "html": "
", "action": "div.click()", "check": "byId(\"d1\").innerText.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can refer to event properties directly in filter", "html": "
|
|
", "action": "div.click(); div.click(); div.click()", "check": "div.innerText.should.equal(\"Clicked\") && div.innerText.should.equal(\"\") && div.innerText.should.equal(\"\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can refer to event detail properties directly in filter", "html": "
", "action": "div.dispatchEvent(event); div.dispatchEvent(event); div.dispatchEvent(event)", "check": "div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can click after a positive event filter", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\", { detail: { bar: false } }); div.dispatchEvent(new CustomEvent(\"foo\", { detail: { bar: true } })", "check": "div.innerText.should.equal(\"\") && div.innerText.should.equal(\"triggered\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "multiple event handlers at a time are allowed to execute with the every keyword", "html": "
", "action": "div.click(); div.click(); div.click()", "check": "div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"2\") && div.innerText.should.equal(\"3\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can have multiple event handlers", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"2\") && div.innerText.should.equal(\"3\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can have multiple event handlers, no end", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"2\") && div.innerText.should.equal(\"3\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can queue events", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\")", "check": "i.should.equal(0) && i.should.equal(0) && i.should.equal(0) && i.should.equal(1) && i.should.equal(2) && i.should.equal(2)", "async": true, "complexity": "simple" }, { "category": "on", "name": "can queue first event", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\")", "check": "i.should.equal(0) && i.should.equal(0) && i.should.equal(0) && i.should.equal(1) && i.should.equal(2) && i.should.equal(2)", "async": true, "complexity": "simple" }, { "category": "on", "name": "can queue last event", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\")", "check": "i.should.equal(0) && i.should.equal(0) && i.should.equal(0) && i.should.equal(1) && i.should.equal(2) && i.should.equal(2)", "async": true, "complexity": "simple" }, { "category": "on", "name": "can queue all events", "html": "
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"foo\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\"); div.dispatchEvent(new CustomEvent(\"bar\")", "check": "i.should.equal(0) && i.should.equal(0) && i.should.equal(0) && i.should.equal(1) && i.should.equal(2) && i.should.equal(3)", "async": true, "complexity": "simple" }, { "category": "on", "name": "queue none does not allow future queued events", "html": "
", "action": "div.click(); div.click(); div.dispatchEvent(new CustomEvent(\"customEvent\"); div.click()", "check": "div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"1\") && div.innerText.should.equal(\"2\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can invoke on multiple events", "html": "
", "action": "div.click(); div.dispatchEvent(new CustomEvent(\"foo\")", "check": "i.should.equal(1) && i.should.equal(2)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can listen for events in another element (lazy)", "html": "
", "action": "div1.click()", "check": "div1.should.equal(window.tmp)", "async": false, "complexity": "simple" }, { "category": "on", "name": "can filter events based on count", "html": "
0
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"1\") && div.innerHTML.should.equal(\"1\") && div.innerHTML.should.equal(\"1\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can filter events based on count range", "html": "
0
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"1\") && div.innerHTML.should.equal(\"2\") && div.innerHTML.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can filter events based on unbounded count range", "html": "
0
", "action": "div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"0\") && div.innerHTML.should.equal(\"1\") && div.innerHTML.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can mix ranges", "html": "
0
", "action": "div.click(); div.click(); div.click(); div.click()", "check": "div.innerHTML.should.equal(\"one\") && div.innerHTML.should.equal(\"two\") && div.innerHTML.should.equal(\"three\") && div.innerHTML.should.equal(\"three\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can listen for general mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for attribute mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for specific attribute mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for specific attribute mutations and filter out other attribute mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for childList mutations", "html": "
", "action": "div.appendChild(document.createElement(\"P\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for childList mutation filter out other mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for characterData mutation filter out other mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for multiple mutations", "html": "
", "action": "div.setAttribute(\"foo\", \"bar\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for multiple mutations 2", "html": "
", "action": "div.setAttribute(\"bar\", \"bar\")", "check": "div.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "can listen for attribute mutations on other elements", "html": "
|
", "action": "div1.setAttribute(\"foo\", \"bar\")", "check": "div2.innerHTML.should.equal(\"Mutated\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "each behavior installation has its own event queue", "html": " |
|
|
", "action": "div.dispatchEvent(new CustomEvent(\"foo\"); div2.dispatchEvent(new CustomEvent(\"foo\"); div3.dispatchEvent(new CustomEvent(\"foo\")", "check": "div.innerHTML.should.equal(\"behavior\") && div2.innerHTML.should.equal(\"behavior\") && div3.innerHTML.should.equal(\"behavior\")", "async": true, "complexity": "script-tag" }, { "category": "on", "name": "can catch exceptions thrown in js functions", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can catch exceptions thrown in hyperscript functions", "html": "s | ", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "script-tag" }, { "category": "on", "name": "can catch top-level exceptions", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can catch async top-level exceptions", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "async exceptions don't kill the event queue", "html": "", "action": "btn.click(); btn.click()", "check": "btn.innerHTML.should.equal(\"success\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "exceptions in catch block don't kill the event queue", "html": "", "action": "btn.click(); btn.click()", "check": "btn.innerHTML.should.equal(\"success\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "uncaught exceptions trigger 'exception' event", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "caught exceptions do not trigger 'exception' event", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"foo\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "rethrown exceptions trigger 'exception' event", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "basic finally blocks work", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "finally blocks work when exception thrown in catch", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "async basic finally blocks work", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"bar\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "async finally blocks work when exception thrown in catch", "html": "", "action": "btn.click()", "check": "btn.innerHTML.should.equal(\"foobar\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "async exceptions in finally block don't kill the event queue", "html": "", "action": "btn.click(); btn.click()", "check": "btn.innerHTML.should.equal(\"success\")", "async": true, "complexity": "simple" }, { "category": "on", "name": "exceptions in finally block don't kill the event queue", "html": "", "action": "btn.click(); btn.click()", "check": "btn.innerHTML.should.equal(\"success\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can ignore when target doesn't exist", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"clicked\")", "async": false, "complexity": "simple" }, { "category": "on", "name": "can handle an or after a from clause", "html": "
|
|
", "action": "d1.click(); d2.click()", "check": "div.innerHTML.should.equal(\"1\") && div.innerHTML.should.equal(\"2\")", "async": false, "complexity": "simple" }, { "category": "init", "name": "can define an init block inline", "html": "
", "action": "div.click()", "check": "div.innerHTML.should.equal(\"42\")", "async": true, "complexity": "simple" }, { "category": "init", "name": "can define an init block in a script", "html": "", "action": "(see body)", "check": "window.foo.should.equal(42)", "async": true, "complexity": "script-tag" }, { "category": "init", "name": "can initialize immediately", "html": "", "action": "(see body)", "check": "window.foo.should.equal(10)", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can define a basic no arg function", "html": " |
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can define a basic one arg function", "html": " |
|
", "action": "bar.click()", "check": "div.innerHTML.should.equal(\"\") && div.innerHTML.should.equal(\"called\")", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "functions can be namespaced", "html": " |
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "is called synchronously", "html": " |
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can call asynchronously", "html": " |
|
", "action": "bar.click()", "check": "div.classList.contains(\"called\").should.equal(false) && div.classList.contains(\"called\").should.equal(true)", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can return a value synchronously", "html": " |
|
", "action": "bar.click()", "check": "div.innerText.should.equal(\"\") && div.innerText.should.equal(\"foo\")", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can exit", "html": "", "action": "(see body)", "check": "(no explicit assertion)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can return a value asynchronously", "html": " |
|
", "action": "bar.click()", "check": "div.innerText.should.equal(\"\") && div.innerText.should.equal(\"foo\")", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can interop with javascript", "html": "", "action": "(see body)", "check": "foo().should.equal(\"foo\")", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can interop with javascript asynchronously", "html": "", "action": "(see body)", "check": "val.should.equal(\"foo\")", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can catch exceptions", "html": "", "action": "(see body)", "check": "window.bar.should.equal(\"bar\")", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can rethrow in catch blocks", "html": "", "action": "(see body)", "check": "true.should.equal(false) && e.should.equal(\"bar\")", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can return in catch blocks", "html": "", "action": "(see body)", "check": "foo().should.equal(42)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "can catch async exceptions", "html": "", "action": "(see body)", "check": "window.bar.should.equal(\"bar\")", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can catch nested async exceptions", "html": "", "action": "(see body)", "check": "window.bar.should.equal(\"bar\")", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can rethrow in async catch blocks", "html": "", "action": "(see body)", "check": "reason.should.equal(\"bar\")", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can return in async catch blocks", "html": "", "action": "(see body)", "check": "val.should.equal(42)", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "can install a function on an element and use in children w/ no leak", "html": "
", "action": "(see body)", "check": "byId(\"d3\").innerText.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "def", "name": "can install a function on an element and use in children w/ return value", "html": "
", "action": "(see body)", "check": "byId(\"d1\").innerText.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "def", "name": "can install a function on an element and use me symbol correctly", "html": "
", "action": "(see body)", "check": "div.innerText.should.equal(\"42\")", "async": false, "complexity": "simple" }, { "category": "def", "name": "finally blocks run normally", "html": "", "action": "(see body)", "check": "window.bar.should.equal(20)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "finally blocks run when an exception occurs", "html": "", "action": "(see body)", "check": "window.bar.should.equal(20)", "async": false, "complexity": "script-tag" }, { "category": "def", "name": "finally blocks run when an exception expr occurs", "html": "", "action": "(see body)", "check": "window.bar.should.equal(20)", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "async finally blocks run normally", "html": "", "action": "(see body)", "check": "window.bar.should.equal(20)", "async": true, "complexity": "script-tag" }, { "category": "def", "name": "async finally blocks run when an exception occurs", "html": "", "action": "(see body)", "check": "window.bar.should.equal(20)", "async": true, "complexity": "script-tag" }, { "category": "askAnswer", "name": "prompts and puts result in it", "html": "
", "action": "find('button').click()", "check": "toHaveText(\"Alice\")", "async": true, "complexity": "dialog", "source": "new_file", "file": "test/commands/askAnswer.js", "body": "page.on('dialog', async dialog => {\n\t\t\texpect(dialog.type()).toBe('prompt');\n\t\t\texpect(dialog.message()).toBe('What is your name?');\n\t\t\tawait dialog.accept('Alice');\n\t\t});\n\t\tawait html(\"
\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"Alice\");" }, { "category": "askAnswer", "name": "returns null on cancel", "html": "
", "action": "find('button').click()", "check": "toHaveText(\"null\")", "async": true, "complexity": "dialog", "source": "new_file", "file": "test/commands/askAnswer.js", "body": "page.on('dialog', async dialog => {\n\t\t\tawait dialog.dismiss();\n\t\t});\n\t\tawait html(\"
\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"null\");" }, { "category": "askAnswer", "name": "shows an alert", "html": "
", "action": "find('button').click()", "check": "toHaveText(\"done\"); toBe(\"Hello!\")", "async": true, "complexity": "dialog", "source": "new_file", "file": "test/commands/askAnswer.js", "body": "var alertMessage = null;\n\t\tpage.on('dialog', async dialog => {\n\t\t\talertMessage = dialog.message();\n\t\t\tawait dialog.accept();\n\t\t});\n\t\tawait html(\"
\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"done\");\n\t\texpect(alertMessage).toBe(\"Hello!\");" }, { "category": "askAnswer", "name": "confirm returns first choice on OK", "html": "
", "action": "find('button').click()", "check": "toHaveText(\"Yes\")", "async": true, "complexity": "dialog", "source": "new_file", "file": "test/commands/askAnswer.js", "body": "page.on('dialog', async dialog => {\n\t\t\texpect(dialog.type()).toBe('confirm');\n\t\t\tawait dialog.accept();\n\t\t});\n\t\tawait html(\"
\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"Yes\");" }, { "category": "askAnswer", "name": "confirm returns second choice on cancel", "html": "
", "action": "find('button').click()", "check": "toHaveText(\"No\")", "async": true, "complexity": "dialog", "source": "new_file", "file": "test/commands/askAnswer.js", "body": "page.on('dialog', async dialog => {\n\t\t\tawait dialog.dismiss();\n\t\t});\n\t\tawait html(\"
\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"No\");" }, { "category": "dialog", "name": "show opens a dialog as modal", "html": "

Hello

", "action": "find('button').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Hello

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).toHaveAttribute('open');" }, { "category": "dialog", "name": "hide closes a dialog", "html": "

Hello

", "action": "find('#close').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Hello

\"\n\t\t);\n\t\tawait evaluate(() => document.getElementById('d').showModal());\n\t\tawait expect(find('#d')).toHaveAttribute('open');\n\t\tawait find('#close').click();\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');" }, { "category": "dialog", "name": "show on already-open dialog is a no-op", "html": "

Hello

", "action": "find('button').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Hello

\"\n\t\t);\n\t\tawait evaluate(() => document.getElementById('d').showModal());\n\t\tawait expect(find('#d')).toHaveAttribute('open');\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).toHaveAttribute('open');" }, { "category": "dialog", "name": "open opens a dialog", "html": "

Hello

", "action": "find('button').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Hello

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).toHaveAttribute('open');" }, { "category": "dialog", "name": "close closes a dialog", "html": "

Hello

", "action": "find('#close').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Hello

\"\n\t\t);\n\t\tawait evaluate(() => document.getElementById('d').showModal());\n\t\tawait expect(find('#d')).toHaveAttribute('open');\n\t\tawait find('#close').click();\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');" }, { "category": "dialog", "name": "open opens a details element", "html": "
More

Content

", "action": "find('button').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"
More

Content

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).toHaveAttribute('open');" }, { "category": "dialog", "name": "close closes a details element", "html": "
More

Content

", "action": "find('button').click()", "check": "toHaveAttribute('open'); toHaveAttribute('open')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"
More

Content

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait expect(find('#d')).toHaveAttribute('open');\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).not.toHaveAttribute('open');" }, { "category": "dialog", "name": "open shows a popover", "html": "

Popover content

", "action": "find('button').click()", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Popover content

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').click();\n\t\tvar visible = await find('#p').evaluate(el => el.matches(':popover-open'));\n\t\texpect(visible).toBe(true);" }, { "category": "dialog", "name": "close hides a popover", "html": "

Popover content

", "action": "find('#close').click()", "check": "toBe(false)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"

Popover content

\"\n\t\t);\n\t\tawait evaluate(() => document.getElementById('p').showPopover());\n\t\tawait find('#close').click();\n\t\tvar visible = await find('#p').evaluate(el => el.matches(':popover-open'));\n\t\texpect(visible).toBe(false);" }, { "category": "dialog", "name": "open on implicit me", "html": "", "action": "find('button').click()", "check": "toHaveAttribute('open')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/dialog.js", "body": "await html(\n\t\t\t\"\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').click();\n\t\tawait expect(find('#d')).toHaveAttribute('open');" }, { "category": "empty", "name": "can empty an element", "html": "

hello

world

", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"helloworld\"); toHaveText(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(\"

hello

world

\");\n\t\tawait expect(find('#d1')).toHaveText(\"helloworld\");\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"\");" }, { "category": "empty", "name": "empty with no target empties me", "html": "
content
", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"content\"); toHaveText(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(\"
content
\");\n\t\tawait expect(find('div')).toHaveText(\"content\");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"\");" }, { "category": "empty", "name": "can empty multiple elements", "html": "

a

b

", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(\n\t\t\t\"

a

\" +\n\t\t\t\"

b

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('.clearme').first()).toHaveText(\"\");\n\t\tawait expect(find('.clearme').last()).toHaveText(\"\");" }, { "category": "empty", "name": "can empty an array", "html": "
", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"0\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`
`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");" }, { "category": "empty", "name": "can empty a set", "html": "
", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"0\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`
`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");" }, { "category": "empty", "name": "can empty a map", "html": "
", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"0\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`
`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");" }, { "category": "empty", "name": "can empty a text input", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toHaveValue(\"hello\"); toHaveValue(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait expect(find('#t1')).toHaveValue(\"hello\");\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#t1')).toHaveValue(\"\");" }, { "category": "empty", "name": "can empty a textarea", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toHaveValue(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#ta1')).toHaveValue(\"\");" }, { "category": "empty", "name": "can empty a checkbox", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait expect(find('#cb1')).toBeChecked();\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#cb1')).not.toBeChecked();" }, { "category": "empty", "name": "can empty a select", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toBe(-1)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar idx = await evaluate(() => document.getElementById('sel1').selectedIndex);\n\t\texpect(idx).toBe(-1);" }, { "category": "empty", "name": "can empty a form (clears all inputs)", "html": "\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toHaveValue(\"\"); toHaveValue(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t\t\n\t\t`);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#t2')).toHaveValue(\"\");\n\t\tawait expect(find('#ta2')).toHaveValue(\"\");\n\t\tawait expect(find('#cb2')).not.toBeChecked();" }, { "category": "empty", "name": "clear is an alias for empty", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toHaveValue(\"hello\"); toHaveValue(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait expect(find('#t3')).toHaveValue(\"hello\");\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#t3')).toHaveValue(\"\");" }, { "category": "empty", "name": "clear works on elements", "html": "

content

", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"content\"); toHaveText(\"\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/empty.js", "body": "await html(\"

content

\");\n\t\tawait expect(find('#d2')).toHaveText(\"content\");\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveText(\"\");" }, { "category": "focus", "name": "can focus an element", "html": "", "action": "find('button').dispatchEvent('click')", "check": "toBe(\"i1\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/focus.js", "body": "await html(\"\");\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar focused = await evaluate(() => document.activeElement.id);\n\t\texpect(focused).toBe(\"i1\");" }, { "category": "focus", "name": "focus with no target focuses me", "html": "", "action": "find('#i1').dispatchEvent('click')", "check": "toBe(\"i1\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/focus.js", "body": "await html(\"\");\n\t\tawait find('#i1').dispatchEvent('click');\n\t\tvar focused = await evaluate(() => document.activeElement.id);\n\t\texpect(focused).toBe(\"i1\");" }, { "category": "focus", "name": "can blur an element", "html": "", "action": "find('#i1').focus()", "check": "toBe(\"BODY\")", "async": true, "complexity": "promise", "source": "new_file", "file": "test/commands/focus.js", "body": "await html(\"\");\n\t\tawait find('#i1').focus();\n\t\tawait find('#i1').evaluate(el => new Promise(r => setTimeout(r, 50)));\n\t\tvar focused = await evaluate(() => document.activeElement.tagName);\n\t\texpect(focused).toBe(\"BODY\");" }, { "category": "go", "name": "can parse go to with string URL", "html": "
", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/go.js", "body": "// verify parsing succeeds - go to with a quoted string\n\t\tawait html(\"
\");\n\t\t// no error on parse = success" }, { "category": "go", "name": "deprecated url keyword still parses", "html": "
", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/go.js", "body": "await html(\"
\");\n\t\t// no error on parse = success" }, { "category": "go", "name": "go to naked URL starting with / parses", "html": "
", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/go.js", "body": "await html(\"
\");\n\t\t// no error on parse = success" }, { "category": "go", "name": "go to element scrolls", "html": "
Target
", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/go.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
Target
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(3)').dispatchEvent('click');\n\t\tvar inView = await evaluate(() => {\n\t\t\tvar rect = document.querySelector('#target').getBoundingClientRect();\n\t\t\treturn rect.top >= 0 && rect.top < window.innerHeight;\n\t\t});\n\t\texpect(inView).toBe(true);" }, { "category": "go", "name": "deprecated scroll form still works", "html": "
Target
", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/go.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
Target
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(3)').dispatchEvent('click');\n\t\tvar inView = await evaluate(() => {\n\t\t\tvar rect = document.querySelector('#target').getBoundingClientRect();\n\t\t\treturn rect.top >= 0 && rect.top < window.innerHeight;\n\t\t});\n\t\texpect(inView).toBe(true);" }, { "category": "halt", "name": "halts event propagation and default", "html": "", "action": "find('#inner').dispatchEvent('click')", "check": "toHaveClass(/outer-clicked/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\" click me\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('#inner').dispatchEvent('click');\n\t\t// halt stops propagation \u2014 outer should NOT get the click\n\t\tawait expect(find('#outer')).not.toHaveClass(/outer-clicked/);" }, { "category": "halt", "name": "halt stops execution after the halt", "html": "
test
", "action": "find('div').dispatchEvent('click')", "check": "toHaveClass(/should-not-happen/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
test
\"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).not.toHaveClass(/should-not-happen/);" }, { "category": "halt", "name": "halt the event stops propagation but continues execution", "html": "
click me
", "action": "find('#inner').dispatchEvent('click')", "check": "toHaveClass(/outer-clicked/); toHaveClass(/continued/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
click me
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('#inner').dispatchEvent('click');\n\t\t// halt the event stops propagation but continues execution\n\t\tawait expect(find('#outer')).not.toHaveClass(/outer-clicked/);\n\t\tawait expect(find('#inner')).toHaveClass(/continued/);" }, { "category": "halt", "name": "halt the event's stops propagation but continues execution", "html": "
click me
", "action": "find('#inner').dispatchEvent('click')", "check": "toHaveClass(/outer-clicked/); toHaveClass(/continued/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
click me
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('#inner').dispatchEvent('click');\n\t\tawait expect(find('#outer')).not.toHaveClass(/outer-clicked/);\n\t\tawait expect(find('#inner')).toHaveClass(/continued/);" }, { "category": "halt", "name": "halt bubbling only stops propagation, not default", "html": "
click me
", "action": "find('#inner').dispatchEvent('click')", "check": "toHaveClass(/outer-clicked/); toHaveClass(/continued/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
click me
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('#inner').dispatchEvent('click');\n\t\t// bubbling stopped \u2014 outer should NOT get click\n\t\tawait expect(find('#outer')).not.toHaveClass(/outer-clicked/);\n\t\t// but execution halts (no keepExecuting)\n\t\tawait expect(find('#inner')).not.toHaveClass(/continued/);" }, { "category": "halt", "name": "halt works outside of event context", "html": "", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/commands/halt.js", "body": "// halt in init (no event) should not throw \"did not return a next element\"\n\t\tvar error = await evaluate(() => {\n\t\t\treturn new Promise(resolve => {\n\t\t\t\twindow.addEventListener('error', function handler(e) {\n\t\t\t\t\twindow.removeEventListener('error', handler);\n\t\t\t\t\tresolve(e.message);\n\t\t\t\t});\n\t\t\t\tvar wa = document.getElementById('work-area');\n\t\t\t\twa.innerHTML = \"
\";\n\t\t\t\t_hyperscript.processNode(wa);\n\t\t\t\tsetTimeout(() => resolve(null), 200);\n\t\t\t});\n\t\t});\n\t\texpect(error).toBeNull();" }, { "category": "halt", "name": "halt default only prevents default, not propagation", "html": "
click me
", "action": "find('#inner').dispatchEvent('click')", "check": "toHaveClass(/outer-clicked/)", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/halt.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
click me
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('#inner').dispatchEvent('click');\n\t\t// default halted but propagation NOT stopped \u2014 outer SHOULD get click\n\t\tawait expect(find('#outer')).toHaveClass(/outer-clicked/);" }, { "category": "morph", "name": "basic morph updates content", "html": "
old
", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"new\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"new\");" }, { "category": "morph", "name": "morph preserves element identity", "html": "
old
", "action": "find('#go').dispatchEvent('click')", "check": "toHaveText(\"new\"); toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\t// Save a reference before morphing\n\t\tawait evaluate(() => { window._savedRef = document.querySelector('#target'); });\n\t\tawait find('#go').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"new\");\n\t\tvar same = await evaluate(() => document.querySelector('#target') === window._savedRef);\n\t\texpect(same).toBe(true);" }, { "category": "morph", "name": "morph updates attributes", "html": "
content
", "action": "find('button').dispatchEvent('click')", "check": "toHaveClass(\"new\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
content
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveClass(\"new\");" }, { "category": "morph", "name": "morph adds new children", "html": "
first
'\\\">go", "action": "find('#go').dispatchEvent('click')", "check": "toBe(2)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
first
\" +\n\t\t\t\"
'\\\">go\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar count = await evaluate(() => document.querySelectorAll('#target span').length);\n\t\texpect(count).toBe(2);" }, { "category": "morph", "name": "morph removes old children", "html": "
firstsecond
", "action": "find('#go').dispatchEvent('click')", "check": "toBe(1)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
firstsecond
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar count = await evaluate(() => document.querySelectorAll('#target span').length);\n\t\texpect(count).toBe(1);" }, { "category": "morph", "name": "morph initializes hyperscript on new elements", "html": "

old

", "action": "find('#go').dispatchEvent('click'); find('#inner').dispatchEvent('click')", "check": "toHaveText(\"new\"); toHaveText(\"clicked\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"

old

\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tawait expect(find('#inner')).toHaveText(\"new\");\n\t\tawait find('#inner').dispatchEvent('click');\n\t\tawait expect(find('#inner')).toHaveText(\"clicked\");" }, { "category": "morph", "name": "morph cleans up removed hyperscript elements", "html": "
child
", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
child
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar gone = await evaluate(() => document.querySelector('#child'));\n\t\texpect(gone).toBeNull();" }, { "category": "morph", "name": "morph reorders children by id", "html": "
A
B
", "action": "find('button').dispatchEvent('click')", "check": "toEqual([\"b\", \"a\"])", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
A
\" +\n\t\t\t\"
B
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar order = await evaluate(() =>\n\t\t\tArray.from(document.querySelectorAll('#target > div')).map(e => e.id)\n\t\t);\n\t\texpect(order).toEqual([\"b\", \"a\"]);" }, { "category": "morph", "name": "morph preserves matched child identity", "html": "
old
", "action": "find('#go').dispatchEvent('click')", "check": "toBe(true); toBe(\"new\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait evaluate(() => { window._savedChild = document.querySelector('#child'); });\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar result = await evaluate(() => ({\n\t\t\tsame: document.querySelector('#child') === window._savedChild,\n\t\t\ttext: document.querySelector('#child').textContent\n\t\t}));\n\t\texpect(result.same).toBe(true);\n\t\texpect(result.text).toBe(\"new\");" }, { "category": "morph", "name": "morph with variable content", "html": "
original
", "action": "find('#go').dispatchEvent('click')", "check": "toHaveText(\"morphed\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/morph.js", "body": "await html(\n\t\t\t\"
original
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"morphed\");" }, { "category": "reset", "name": "can reset a form", "html": "\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t", "action": "find('#t1').fill('changed'); find('#rst').dispatchEvent('click')", "check": "toHaveValue('changed'); toHaveValue('original')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t\tawait find('#t1').fill('changed');\n\t\tawait expect(find('#t1')).toHaveValue('changed');\n\t\tawait find('#rst').dispatchEvent('click');\n\t\tawait expect(find('#t1')).toHaveValue('original');" }, { "category": "reset", "name": "reset with no target resets me (form)", "html": "\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t", "action": "find('#t2').fill('modified'); find('form').dispatchEvent('custom')", "check": "toHaveValue('modified'); toHaveValue('default')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t\n\t\t\t
\n\t\t`);\n\t\tawait find('#t2').fill('modified');\n\t\tawait expect(find('#t2')).toHaveValue('modified');\n\t\tawait find('form').dispatchEvent('custom');\n\t\tawait expect(find('#t2')).toHaveValue('default');" }, { "category": "reset", "name": "can reset a text input to defaultValue", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('#t3').fill('goodbye'); find('button').dispatchEvent('click')", "check": "toHaveValue('goodbye'); toHaveValue('hello')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('#t3').fill('goodbye');\n\t\tawait expect(find('#t3')).toHaveValue('goodbye');\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#t3')).toHaveValue('hello');" }, { "category": "reset", "name": "can reset a checkbox", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('#cb1').uncheck();\n\t\tawait expect(find('#cb1')).not.toBeChecked();\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#cb1')).toBeChecked();" }, { "category": "reset", "name": "can reset an unchecked checkbox", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('#cb2').check();\n\t\tawait expect(find('#cb2')).toBeChecked();\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#cb2')).not.toBeChecked();" }, { "category": "reset", "name": "can reset a textarea", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('#ta1').fill('new text'); find('button').dispatchEvent('click')", "check": "toHaveValue('new text'); toHaveValue('original text')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('#ta1').fill('new text');\n\t\tawait expect(find('#ta1')).toHaveValue('new text');\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#ta1')).toHaveValue('original text');" }, { "category": "reset", "name": "can reset a select", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "toHaveValue('c'); toHaveValue('b')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('#sel1').selectOption('c');\n\t\tawait expect(find('#sel1')).toHaveValue('c');\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#sel1')).toHaveValue('b');" }, { "category": "reset", "name": "can reset multiple inputs", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/reset.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t`);\n\t\tawait find('.resettable').first().fill('changed1');\n\t\tawait find('.resettable').last().fill('changed2');\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('.resettable').first()).toHaveValue('one');\n\t\tawait expect(find('.resettable').last()).toHaveValue('two');" }, { "category": "scroll", "name": "can scroll to an element", "html": "
Target
", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
Target
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(3)').dispatchEvent('click');\n\t\tvar inView = await evaluate(() => {\n\t\t\tvar rect = document.querySelector('#target').getBoundingClientRect();\n\t\t\treturn rect.top >= 0 && rect.top < window.innerHeight;\n\t\t});\n\t\texpect(inView).toBe(true);" }, { "category": "scroll", "name": "can scroll to top of element", "html": "
Target
", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
Target
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(3)').dispatchEvent('click');\n\t\tvar inView = await evaluate(() => {\n\t\t\tvar rect = document.querySelector('#target').getBoundingClientRect();\n\t\t\treturn rect.top >= 0 && rect.top < window.innerHeight;\n\t\t});\n\t\texpect(inView).toBe(true);" }, { "category": "scroll", "name": "can scroll down by amount", "html": "
", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tvar scrollY = await evaluate(() => window.scrollY || document.documentElement.scrollTop);\n\t\texpect(scrollY).toBeGreaterThanOrEqual(290);" }, { "category": "scroll", "name": "can scroll up by amount", "html": "
", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\t// scroll down first\n\t\tawait evaluate(() => window.scrollTo(0, 500));\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tvar scrollY = await evaluate(() => window.scrollY || document.documentElement.scrollTop);\n\t\texpect(scrollY).toBeGreaterThanOrEqual(390);\n\t\texpect(scrollY).toBeLessThanOrEqual(410);" }, { "category": "scroll", "name": "can scroll by without direction (defaults to down)", "html": "
", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tvar scrollY = await evaluate(() => window.scrollY || document.documentElement.scrollTop);\n\t\texpect(scrollY).toBeGreaterThanOrEqual(190);" }, { "category": "scroll", "name": "can scroll container by amount", "html": "
tall
", "action": "find('#go').dispatchEvent('click')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
tall
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar scrollTop = await evaluate(() => document.querySelector('#box').scrollTop);\n\t\texpect(scrollTop).toBeGreaterThanOrEqual(190);" }, { "category": "scroll", "name": "can scroll to element in container", "html": "
spacer
target
", "action": "find('#go').dispatchEvent('click')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
spacer
\" +\n\t\t\t\"
target
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar scrollTop = await evaluate(() => document.querySelector('#box').scrollTop);\n\t\texpect(scrollTop).toBeGreaterThanOrEqual(400);" }, { "category": "scroll", "name": "can scroll left by amount", "html": "
wide
", "action": "find('#go').dispatchEvent('click')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/scroll.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
wide
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tvar scrollLeft = await evaluate(() => document.querySelector('#box').scrollLeft);\n\t\texpect(scrollLeft).toBeGreaterThanOrEqual(290);" }, { "category": "select", "name": "selects text in an input", "html": "", "action": "find('button').click()", "check": "toBe(\"hello world\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/select.js", "body": "await html(\n\t\t\t\"\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').click();\n\t\tvar selected = await evaluate(() => {\n\t\t\tvar inp = document.getElementById('inp');\n\t\t\treturn inp.value.substring(inp.selectionStart, inp.selectionEnd);\n\t\t});\n\t\texpect(selected).toBe(\"hello world\");" }, { "category": "select", "name": "selects text in a textarea", "html": "", "action": "find('button').click()", "check": "toBe(\"some text\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/select.js", "body": "await html(\n\t\t\t\"\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').click();\n\t\tvar selected = await evaluate(() => {\n\t\t\tvar ta = document.getElementById('ta');\n\t\t\treturn ta.value.substring(ta.selectionStart, ta.selectionEnd);\n\t\t});\n\t\texpect(selected).toBe(\"some text\");" }, { "category": "select", "name": "selects implicit me", "html": "", "action": "find('#inp').click()", "check": "toBe(\"test\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/select.js", "body": "await html(\"\");\n\t\tawait find('#inp').click();\n\t\tvar selected = await evaluate(() => {\n\t\t\tvar inp = document.getElementById('inp');\n\t\t\treturn inp.value.substring(inp.selectionStart, inp.selectionEnd);\n\t\t});\n\t\texpect(selected).toBe(\"test\");" }, { "category": "select", "name": "returns selected text", "html": "

Hello World

", "action": "find('button').click()", "check": "toHaveText(\"Hello\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/commands/select.js", "body": "await html(\n\t\t\t\"

Hello World

\" +\n\t\t\t\"\" +\n\t\t\t\"
\"\n\t\t);\n\t\t// Programmatically select \"Hello\"\n\t\tawait evaluate(() => {\n\t\t\tvar range = document.createRange();\n\t\t\tvar textNode = document.getElementById('text').firstChild;\n\t\t\trange.setStart(textNode, 0);\n\t\t\trange.setEnd(textNode, 5);\n\t\t\twindow.getSelection().removeAllRanges();\n\t\t\twindow.getSelection().addRange(range);\n\t\t});\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"Hello\");" }, { "category": "swap", "name": "can swap two variables", "html": "
", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"ba\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/swap.js", "body": "await html(`
`);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"ba\");" }, { "category": "swap", "name": "can swap two properties", "html": "
\n\t\t\txy", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"world\"); toHaveText(\"hello\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/swap.js", "body": "await html(`
\n\t\t\txy`);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#a')).toHaveText(\"world\");\n\t\tawait expect(find('#b')).toHaveText(\"hello\");" }, { "category": "swap", "name": "can swap array elements", "html": "
", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"3,2,1\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/swap.js", "body": "await html(`
`);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"3,2,1\");" }, { "category": "swap", "name": "can swap a variable with a property", "html": "
\n\t\t\t", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"new\"); toHaveAttribute('data-val', 'old')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/commands/swap.js", "body": "await html(`
\n\t\t\t`);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"new\");\n\t\tawait expect(find('#target')).toHaveAttribute('data-val', 'old');" }, { "category": "bind", "name": "syncs variable and input value in both directions", "html": "", "action": "evaluate({...}); await run(\"set $name to '", "check": "toHaveText('Alice'); toHaveText('Bob')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('Alice')\n\n\t\t// User types -> variable updates\n\t\tawait evaluate(() => {\n\t\t\tvar input = document.getElementById('name-input')\n\t\t\tinput.value = 'Bob'\n\t\t\tinput.dispatchEvent(new Event('input', { bubbles: true }))\n\t\t})\n\t\tawait expect(find('span')).toHaveText('Bob')\n\n\t\t// Variable changes -> input updates\n\t\tawait run(\"set $name to 'Charlie'\")\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('name-input').value)).toBe('Charlie')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "syncs variable and attribute in both directions", "html": "
", "action": "await run(\"set $theme to '; await run(\"set $theme to '", "check": "toHaveAttribute('data-theme', 'light'); toHaveAttribute('data-theme', 'dark')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $theme to 'light'\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveAttribute('data-theme', 'light')\n\n\t\tawait run(\"set $theme to 'dark'\")\n\t\tawait expect(find('div')).toHaveAttribute('data-theme', 'dark')\n\n\t\tawait evaluate(() => document.querySelector('#work-area div').setAttribute('data-theme', 'auto'))\n\t\tawait expect.poll(() => evaluate(() => window.$theme), { timeout: 5000 }).toBe('auto')\n\t\tawait evaluate(() => { delete window.$theme })" }, { "category": "bind", "name": "dedup prevents infinite loop in two-way bind", "html": "
", "action": "await run(\"set $color to '; await run(\"set $color to '", "check": "toHaveAttribute('data-color', 'red'); toHaveAttribute('data-color', 'blue')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $color to 'red'\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveAttribute('data-color', 'red')\n\n\t\tawait run(\"set $color to 'blue'\")\n\t\tawait expect(find('div')).toHaveAttribute('data-color', 'blue')\n\t\tawait expect.poll(() => evaluate(() => window.$color)).toBe('blue')\n\t\tawait evaluate(() => { delete window.$color })" }, { "category": "bind", "name": "\"with\" is a synonym for \"and\"", "html": "", "action": "await run(\"set $city to '", "check": "toHaveText('Paris')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('Paris')\n\n\t\tawait run(\"set $city to 'London'\")\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('city-input').value)).toBe('London')\n\t\tawait evaluate(() => { delete window.$city })" }, { "category": "bind", "name": "shorthand on text input binds to value", "html": "\">", "action": "find('input').fill('goodbye'); await run(\"set $greeting to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`\">` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('hello')\n\n\t\tawait find('input').fill('goodbye')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('goodbye')\n\n\t\tawait run(\"set $greeting to 'hey'\")\n\t\tawait expect.poll(() => evaluate(() => document.querySelector('#work-area input').value)).toBe('hey')\n\t\tawait evaluate(() => { delete window.$greeting })" }, { "category": "bind", "name": "shorthand on checkbox binds to checked", "html": "", "action": "await run(\"set $isDarkMode to false\"; await run(\"set $isDarkMode to false\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $isDarkMode to false\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('false')\n\n\t\tawait find('input').check()\n\t\tawait expect.poll(() => find('span').textContent()).toBe('true')\n\n\t\tawait run(\"set $isDarkMode to false\")\n\t\tawait expect.poll(() => evaluate(() => document.querySelector('#work-area input').checked)).toBe(false)\n\t\tawait evaluate(() => { delete window.$isDarkMode })" }, { "category": "bind", "name": "shorthand on textarea binds to value", "html": "", "action": "find('textarea').fill('New bio')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('Hello world')\n\n\t\tawait find('textarea').fill('New bio')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('New bio')\n\t\tawait evaluate(() => { delete window.$bio })" }, { "category": "bind", "name": "shorthand on select binds to value", "html": "", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('us')\n\n\t\tawait find('select').selectOption('uk')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('uk')\n\t\tawait evaluate(() => { delete window.$country })" }, { "category": "bind", "name": "unsupported element: bind to plain div errors", "html": "", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "const error = await evaluate(() => {\n\t\t\treturn new Promise(resolve => {\n\t\t\t\tvar origError = console.error\n\t\t\t\tconsole.error = function(msg) {\n\t\t\t\t\tif (typeof msg === 'string' && msg.includes('bind cannot auto-detect')) {\n\t\t\t\t\t\tresolve(msg)\n\t\t\t\t\t}\n\t\t\t\t\torigError.apply(console, arguments)\n\t\t\t\t}\n\t\t\t\tvar wa = document.getElementById('work-area')\n\t\t\t\twa.innerHTML = '
'\n\t\t\t\t_hyperscript.processNode(wa)\n\t\t\t\tsetTimeout(() => { console.error = origError; resolve(null) }, 500)\n\t\t\t})\n\t\t})\n\t\texpect(await evaluate(() => window.$nope)).toBeUndefined()" }, { "category": "bind", "name": "shorthand on type=number preserves number type", "html": "", "action": "await run(\"set $price to 42\"", "check": "toHaveText('42')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $price to 42\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await evaluate(() => typeof window.$price)).toBe('number')\n\t\tawait expect(find('span')).toHaveText('42')\n\t\tawait evaluate(() => { delete window.$price })" }, { "category": "bind", "name": "boolean bind to attribute uses presence/absence", "html": "
", "action": "await run(\"set $isEnabled to true\"; await run(\"set $isEnabled to false\"", "check": "toHaveAttribute('data-active', '')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $isEnabled to true\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveAttribute('data-active', '')\n\n\t\tawait run(\"set $isEnabled to false\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() =>\n\t\t\tdocument.querySelector('#work-area div').hasAttribute('data-active')\n\t\t)).toBe(false)\n\t\tawait evaluate(() => { delete window.$isEnabled })" }, { "category": "bind", "name": "boolean bind to aria-* attribute uses \"true\"/\"false\" strings", "html": "
", "action": "await run(\"set $isHidden to true\"; await run(\"set $isHidden to false\"", "check": "toHaveAttribute('aria-hidden', 'true'); toHaveAttribute('aria-hidden', 'false')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $isHidden to true\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveAttribute('aria-hidden', 'true')\n\n\t\tawait run(\"set $isHidden to false\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveAttribute('aria-hidden', 'false')\n\t\tawait evaluate(() => { delete window.$isHidden })" }, { "category": "bind", "name": "style bind is one-way: variable drives style, not vice versa", "html": "
visible
", "action": "await run(\"set $opacity to 1\"; await run(\"set $opacity to 0.3\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $opacity to 1\")\n\t\tawait html(`
visible
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\n\t\tawait run(\"set $opacity to 0.3\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div').style.opacity)\n\t\t).toBe('0.3')\n\n\t\t// Changing style directly does NOT update the variable\n\t\tawait evaluate(() => document.querySelector('#work-area div').style.opacity = '0.9')\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await evaluate(() => window.$opacity)).toBe(0.3)\n\t\tawait evaluate(() => { delete window.$opacity })" }, { "category": "bind", "name": "same value does not re-set input (prevents cursor jump)", "html": "", "action": "", "check": "toBe(false)", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\n\t\tconst setterWasCalled = await evaluate(() => {\n\t\t\tvar input = document.querySelector('#work-area input')\n\t\t\tvar called = false\n\t\t\tvar desc = Object.getOwnPropertyDescriptor(HTMLInputElement.prototype, 'value')\n\t\t\tObject.defineProperty(input, 'value', {\n\t\t\t\tget: desc.get,\n\t\t\t\tset: function(v) { called = true; desc.set.call(this, v) },\n\t\t\t\tconfigurable: true\n\t\t\t})\n\t\t\twindow.$message = 'hello' // same value\n\t\t\treturn new Promise(resolve => {\n\t\t\t\tsetTimeout(() => { delete input.value; resolve(called) }, 100)\n\t\t\t})\n\t\t})\n\t\texpect(setterWasCalled).toBe(false)\n\t\tawait evaluate(() => { delete window.$message })" }, { "category": "bind", "name": "external JS property write does not sync (known limitation)", "html": "", "action": "evaluate({...})", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('original')\n\n\t\tawait evaluate(() => {\n\t\t\tdocument.querySelector('#work-area input').value = 'from-javascript'\n\t\t})\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await evaluate(() => window.$searchTerm)).toBe('original')\n\t\tawait evaluate(() => { delete window.$searchTerm })" }, { "category": "bind", "name": "form.reset() syncs variable back to default value", "html": "
", "action": "find('input').fill('user typed this')", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`
` +\n\t\t\t` ` +\n\t\t\t`
` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('default')\n\n\t\tawait find('input').fill('user typed this')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('user typed this')\n\n\t\tawait evaluate(() => document.getElementById('test-form').reset())\n\t\tawait expect.poll(() => find('span').textContent()).toBe('default')\n\t\texpect(await evaluate(() => window.$formField)).toBe('default')\n\t\tawait evaluate(() => { delete window.$formField })" }, { "category": "bind", "name": "clicking a radio sets the variable to its value", "html": "", "action": "find('input[value=\"blue\"]').click(); find('input[value=\"green\"]').click(); await run(\"set $color to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $color to 'red'\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('red')\n\n\t\tawait find('input[value=\"blue\"]').click()\n\t\tawait expect.poll(() => find('span').textContent()).toBe('blue')\n\n\t\tawait find('input[value=\"green\"]').click()\n\t\tawait expect.poll(() => find('span').textContent()).toBe('green')\n\t\tawait evaluate(() => { delete window.$color })" }, { "category": "bind", "name": "setting variable programmatically checks the matching radio", "html": "", "action": "await run(\"set $size to '; await run(\"set $size to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $size to 'small'\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"small\"]').checked)).toBe(true)\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"medium\"]').checked)).toBe(false)\n\n\t\tawait run(\"set $size to 'large'\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('input[value=\"large\"]').checked)\n\t\t).toBe(true)\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"small\"]').checked)).toBe(false)\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"medium\"]').checked)).toBe(false)\n\t\tawait evaluate(() => { delete window.$size })" }, { "category": "bind", "name": "initial value checks the correct radio on load", "html": "", "action": "await run(\"set $fruit to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $fruit to 'banana'\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"apple\"]').checked)).toBe(false)\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"banana\"]').checked)).toBe(true)\n\t\texpect(await evaluate(() => document.querySelector('input[value=\"cherry\"]').checked)).toBe(false)\n\t\tawait evaluate(() => { delete window.$fruit })" }, { "category": "bind", "name": "variable drives class: setting variable adds/removes class", "html": "
test
", "action": "await run(\"set $darkMode to false\"; await run(\"set $darkMode to true\"; await run(\"set $darkMode to false\"", "check": "toHaveClass('dark')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $darkMode to false\")\n\t\tawait html(`
test
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).not.toHaveClass('dark')\n\n\t\tawait run(\"set $darkMode to true\")\n\t\tawait expect.poll(() => find('div').getAttribute('class')).toContain('dark')\n\n\t\tawait run(\"set $darkMode to false\")\n\t\tawait expect.poll(() => find('div').getAttribute('class') || '').not.toContain('dark')\n\t\tawait evaluate(() => { delete window.$darkMode })" }, { "category": "bind", "name": "external class change syncs back to variable", "html": "
test
", "action": "await run(\"set $darkMode to false\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $darkMode to false\")\n\t\tawait html(`
test
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\n\t\tawait evaluate(() => document.querySelector('#work-area div').classList.add('dark'))\n\t\tawait expect.poll(() => evaluate(() => window.$darkMode)).toBe(true)\n\n\t\tawait evaluate(() => document.querySelector('#work-area div').classList.remove('dark'))\n\t\tawait expect.poll(() => evaluate(() => window.$darkMode)).toBe(false)\n\t\tawait evaluate(() => { delete window.$darkMode })" }, { "category": "bind", "name": "right side wins on class init", "html": "
test
", "action": "await run(\"set $highlighted to true\"", "check": "toHaveClass('highlight')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $highlighted to true\")\n\t\tawait html(`
test
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveClass('highlight')\n\t\tawait evaluate(() => { delete window.$highlighted })" }, { "category": "bind", "name": "init: right side wins \u2014 input value (Y) overwrites variable (X)", "html": "", "action": "await run(\"set $name to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $name to 'Alice'\")\n\t\tawait html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('#work-area input').value)).toBe('Bob')\n\t\texpect(await evaluate(() => window.$name)).toBe('Bob')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "init: right side wins \u2014 variable (Y) overwrites input value (X)", "html": "", "action": "await run(\"set $name to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $name to 'Alice'\")\n\t\tawait html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('#work-area input').value)).toBe('Alice')\n\t\texpect(await evaluate(() => window.$name)).toBe('Alice')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "init: right side wins \u2014 attribute (Y) initializes variable (X)", "html": "
", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$color)).toBe('red')\n\t\tawait evaluate(() => { delete window.$color })" }, { "category": "bind", "name": "init: right side wins \u2014 variable (Y) initializes attribute (X)", "html": "
", "action": "await run(\"set $theme to '", "check": "toHaveAttribute('data-theme', 'dark')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $theme to 'dark'\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveAttribute('data-theme', 'dark')\n\t\tawait evaluate(() => { delete window.$theme })" }, { "category": "bind", "name": "init: right side wins \u2014 variable (Y) drives class (X)", "html": "
", "action": "await run(\"set $isDark to true\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $isDark to true\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect.poll(() => find('div').getAttribute('class')).toContain('dark')\n\t\tawait evaluate(() => { delete window.$isDark })" }, { "category": "bind", "name": "init: right side wins \u2014 class (Y) drives variable (X)", "html": "
", "action": "await run(\"set $isDark to false\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $isDark to false\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$isDark)).toBe(true)\n\t\tawait evaluate(() => { delete window.$isDark })" }, { "category": "bind", "name": "possessive property: bind $var to my value", "html": "", "action": "find('input').fill('world')", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$myVal)).toBe('hello')\n\n\t\tawait find('input').fill('world')\n\t\tawait expect.poll(() => evaluate(() => window.$myVal)).toBe('world')\n\t\tawait evaluate(() => { delete window.$myVal })" }, { "category": "bind", "name": "possessive attribute: bind $var and my @data-label", "html": "
", "action": "await run(\"set $label to '; await run(\"set $label to '", "check": "toHaveAttribute('data-label', 'important')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $label to 'important'\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveAttribute('data-label', 'important')\n\n\t\tawait run(\"set $label to 'normal'\")\n\t\tawait expect.poll(() => find('div').getAttribute('data-label')).toBe('normal')\n\t\tawait evaluate(() => { delete window.$label })" }, { "category": "bind", "name": "of-expression: bind $var to value of #input", "html": "
", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$search)).toBe('initial')\n\t\tawait evaluate(() => { delete window.$search })" }, { "category": "bind", "name": "class bound to another element checkbox", "html": "
test
", "action": "", "check": "toHaveClass('dark')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t`
test
`\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).not.toHaveClass('dark')\n\n\t\tawait find('#dark-toggle').check()\n\t\tawait expect.poll(() => find('div').getAttribute('class')).toContain('dark')\n\n\t\tawait find('#dark-toggle').uncheck()\n\t\tawait expect.poll(() => find('div').getAttribute('class') || '').not.toContain('dark')" }, { "category": "bind", "name": "attribute bound to another element input value", "html": "

", "action": "find('#title-input').fill('World')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t`

`\n\t\t)\n\t\tawait expect.poll(() => find('h1').getAttribute('data-title')).toBe('Hello')\n\n\t\tawait find('#title-input').fill('World')\n\t\tawait expect.poll(() => find('h1').getAttribute('data-title')).toBe('World')" }, { "category": "bind", "name": "two inputs synced via bind", "html": "", "action": "evaluate({...})", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('#work-area input[type=number]').value)).toBe('50')\n\n\t\tawait evaluate(() => {\n\t\t\tvar slider = document.getElementById('slider')\n\t\t\tslider.value = '75'\n\t\t\tslider.dispatchEvent(new Event('input', {bubbles: true}))\n\t\t})\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area input[type=number]').value)\n\t\t).toBe('75')" }, { "category": "bind", "name": "bind variable to element by id auto-detects value", "html": "
", "action": "evaluate({...}); await run(\"set $name to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $name to 'Alice'\")\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('name-field').value)).toBe('Alice')\n\n\t\tawait evaluate(() => {\n\t\t\tvar input = document.getElementById('name-field')\n\t\t\tinput.value = 'Bob'\n\t\t\tinput.dispatchEvent(new Event('input', { bubbles: true }))\n\t\t})\n\t\tawait expect.poll(() => evaluate(() => window.$name)).toBe('Bob')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "bind variable to checkbox by id auto-detects checked", "html": "
", "action": "await run(\"set $agreed to false\"; await run(\"set $agreed to true\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $agreed to false\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.getElementById('agree-cb').checked)).toBe(false)\n\n\t\tawait run(\"set $agreed to true\")\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('agree-cb').checked)).toBe(true)\n\t\tawait evaluate(() => { delete window.$agreed })" }, { "category": "bind", "name": "bind variable to number input by id auto-detects valueAsNumber", "html": "
", "action": "await run(\"set $qty to 5\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $qty to 5\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('qty-input').valueAsNumber)).toBe(5)\n\t\texpect(await evaluate(() => typeof window.$qty)).toBe('number')\n\t\tawait evaluate(() => { delete window.$qty })" }, { "category": "bind", "name": "bind element to element: both sides auto-detect", "html": "", "action": "evaluate({...})", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => document.querySelector('#work-area input[type=number]').valueAsNumber)).toBe(50)\n\n\t\tawait evaluate(() => {\n\t\t\tvar slider = document.getElementById('range-slider')\n\t\t\tslider.value = '75'\n\t\t\tslider.dispatchEvent(new Event('input', { bubbles: true }))\n\t\t})\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area input[type=number]').valueAsNumber)\n\t\t).toBe(75)" }, { "category": "bind", "name": "right side wins on init: variable (Y) initializes input (X)", "html": "", "action": "await run(\"set $name to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $name to 'Alice'\")\n\t\tawait html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$name)).toBe('Alice')\n\t\texpect(await evaluate(() => document.querySelector('#work-area input').value)).toBe('Alice')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "right side wins on init: input (Y) initializes variable (X)", "html": "", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(``)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$name)).toBe('Bob')\n\t\texpect(await evaluate(() => document.querySelector('#work-area input').value)).toBe('Bob')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "bind", "name": "bind to contenteditable element auto-detects textContent", "html": "
initial
", "action": "await run(\"set $text to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await html(`
initial
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await evaluate(() => window.$text)).toBe('initial')\n\n\t\tawait run(\"set $text to 'updated'\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div[contenteditable]').textContent)\n\t\t).toBe('updated')\n\t\tawait evaluate(() => { delete window.$text })" }, { "category": "bind", "name": "bind to custom element with value property auto-detects value", "html": "", "action": "evaluate({...}); await run(\"set $custom to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/bind.js", "body": "await evaluate(() => {\n\t\t\tif (!customElements.get('test-input')) {\n\t\t\t\tclass TestInput extends HTMLElement {\n\t\t\t\t\tconstructor() { super(); this._value = ''; }\n\t\t\t\t\tget value() { return this._value; }\n\t\t\t\t\tset value(v) {\n\t\t\t\t\t\tthis._value = v;\n\t\t\t\t\t\tthis.dispatchEvent(new Event('input', { bubbles: true }));\n\t\t\t\t\t}\n\t\t\t\t}\n\t\t\t\tcustomElements.define('test-input', TestInput);\n\t\t\t}\n\t\t})\n\n\t\tawait html(``)\n\t\tawait run(\"set $custom to 'hello'\")\n\t\tawait expect.poll(() => evaluate(() => document.querySelector('#work-area test-input').value)).toBe('hello')\n\t\tawait evaluate(() => { delete window.$custom })" }, { "category": "bind", "name": "radio change listener is removed on cleanup", "html": "", "action": "evaluate({...}); evaluate({...}); await run(\"set $color to '; await run(\"$color\"", "check": "toBe('red')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $color to 'red'\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\n\t\t// Cleanup the blue radio\n\t\tvar blueRadio = await evaluate(() => {\n\t\t\tvar blue = document.querySelector('#work-area input[value=blue]')\n\t\t\t_hyperscript.internals.runtime.cleanup(blue)\n\t\t\treturn true\n\t\t})\n\n\t\t// Click the cleaned-up blue radio \u2014 $color should NOT change\n\t\tawait evaluate(() => {\n\t\t\tvar blue = document.querySelector('#work-area input[value=blue]')\n\t\t\tblue.checked = true\n\t\t\tblue.dispatchEvent(new Event('change'))\n\t\t})\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\t\tvar color = await run(\"$color\")\n\t\texpect(color).toBe('red')\n\t\tawait evaluate(() => { delete window.$color })" }, { "category": "bind", "name": "form reset listener is removed on cleanup", "html": "
", "action": "await run(\"set $val to '; await run(\"set $val to '; await run(\"$val\"", "check": "toBe('changed')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/bind.js", "body": "await run(\"set $val to 'initial'\")\n\t\tawait html(\n\t\t\t`
` +\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\n\t\t// Change value then cleanup the input\n\t\tawait run(\"set $val to 'changed'\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\t\tawait evaluate(() => {\n\t\t\tvar input = document.querySelector('#work-area #binput')\n\t\t\t_hyperscript.internals.runtime.cleanup(input)\n\t\t})\n\n\t\t// Reset the form \u2014 $val should NOT revert since listener was removed\n\t\tawait evaluate(() => {\n\t\t\tdocument.querySelector('#work-area form').reset()\n\t\t})\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\t\tvar val = await run(\"$val\")\n\t\texpect(val).toBe('changed')\n\t\tawait evaluate(() => { delete window.$val })" }, { "category": "live", "name": "derives a variable from a computed expression", "html": "
", "action": "await run(\"set $price to 10\"; await run(\"set $qty to 3\"; await run(\"set $price to 25\"", "check": "toHaveText('30')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $price to 10\")\n\t\tawait run(\"set $qty to 3\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('30')\n\n\t\tawait run(\"set $price to 25\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('75')\n\t\tawait evaluate(() => { delete window.$price; delete window.$qty; delete window.$total })" }, { "category": "live", "name": "updates DOM text reactively with put", "html": "
", "action": "await run(\"set $greeting to '; await run(\"set $greeting to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $greeting to 'world'\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('hello world')\n\n\t\tawait run(\"set $greeting to 'there'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('hello there')\n\t\tawait evaluate(() => { delete window.$greeting })" }, { "category": "live", "name": "sets an attribute reactively", "html": "
", "action": "await run(\"set $theme to '; await run(\"set $theme to '", "check": "toHaveAttribute('data-theme', 'light')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $theme to 'light'\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveAttribute('data-theme', 'light')\n\n\t\tawait run(\"set $theme to 'dark'\")\n\t\tawait expect.poll(() => find('div').getAttribute('data-theme')).toBe('dark')\n\t\tawait evaluate(() => { delete window.$theme })" }, { "category": "live", "name": "sets a style reactively", "html": "
visible
", "action": "await run(\"set $opacity to 1\"; await run(\"set $opacity to 0.5\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $opacity to 1\")\n\t\tawait html(`
visible
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\n\t\tawait run(\"set $opacity to 0.5\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div').style.opacity)\n\t\t).toBe('0.5')\n\t\tawait evaluate(() => { delete window.$opacity })" }, { "category": "live", "name": "puts a computed dollar amount into the DOM", "html": "
", "action": "await run(\"set $price to 10\"; await run(\"set $qty to 2\"; await run(\"set $qty to 5\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $price to 10\")\n\t\tawait run(\"set $qty to 2\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('$20')\n\n\t\tawait run(\"set $qty to 5\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('$50')\n\t\tawait evaluate(() => { delete window.$price; delete window.$qty })" }, { "category": "live", "name": "block form re-runs all commands when any dependency changes", "html": "
", "action": "await run(\"set $width to 100\"; await run(\"set $height to 200\"; await run(\"set $height to 300\"", "check": "toHaveText('200')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $width to 100\")\n\t\tawait run(\"set $height to 200\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('#w').textContent()).toBe('200')\n\t\tawait expect.poll(() => find('#h').textContent()).toBe('400')\n\n\t\t// Change $height \u2014 both commands re-run, but $doubleWidth stays the same (dedup)\n\t\tawait run(\"set $height to 300\")\n\t\tawait expect.poll(() => find('#h').textContent()).toBe('600')\n\t\tawait expect(find('#w')).toHaveText('200') // unchanged because dedup\n\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$width; delete window.$height\n\t\t\tdelete window.$doubleWidth; delete window.$doubleHeight\n\t\t})" }, { "category": "live", "name": "separate live statements create independent effects", "html": "
", "action": "await run(\"set $width to 100\"; await run(\"set $height to 200\"; await run(\"set $height to 300\"", "check": "toHaveText('200')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $width to 100\")\n\t\tawait run(\"set $height to 200\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('#w').textContent()).toBe('200')\n\t\tawait expect.poll(() => find('#h').textContent()).toBe('400')\n\n\t\t// Change only $height \u2014 only $doubleHeight updates\n\t\tawait run(\"set $height to 300\")\n\t\tawait expect.poll(() => find('#h').textContent()).toBe('600')\n\t\tawait expect(find('#w')).toHaveText('200')\n\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$width; delete window.$height\n\t\t\tdelete window.$doubleWidth; delete window.$doubleHeight\n\t\t})" }, { "category": "live", "name": "block form cascades inter-dependent commands", "html": "
", "action": "await run(\"set $price to 10\"; await run(\"set $qty to 3\"; await run(\"set $tax to 5\"; await run(\"set $price to 20\"; await run(\"set $tax to 10\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $price to 10\")\n\t\tawait run(\"set $qty to 3\")\n\t\tawait run(\"set $tax to 5\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('35')\n\n\t\tawait run(\"set $price to 20\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('65')\n\n\t\tawait run(\"set $tax to 10\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('70')\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$price; delete window.$qty; delete window.$tax\n\t\t\tdelete window.$subtotal; delete window.$total\n\t\t})" }, { "category": "live", "name": "toggles a class based on a boolean variable", "html": "
test
", "action": "await run(\"set $isActive to false\"; await run(\"set $isActive to true\"; await run(\"set $isActive to false\"", "check": "toHaveClass('active')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $isActive to false\")\n\t\tawait html(\n\t\t\t`
test
`\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).not.toHaveClass('active')\n\n\t\tawait run(\"set $isActive to true\")\n\t\tawait expect.poll(() => find('div').getAttribute('class')).toContain('active')\n\n\t\tawait run(\"set $isActive to false\")\n\t\tawait expect.poll(() => find('div').getAttribute('class') || '').not.toContain('active')\n\t\tawait evaluate(() => { delete window.$isActive })" }, { "category": "live", "name": "toggles display style based on a boolean variable", "html": "
content
", "action": "await run(\"set $isVisible to true\"; await run(\"set $isVisible to false\"; await run(\"set $isVisible to true\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $isVisible to true\")\n\t\tawait html(\n\t\t\t`
content
`\n\t\t)\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div').style.display)\n\t\t).toBe('block')\n\n\t\tawait run(\"set $isVisible to false\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div').style.display)\n\t\t).toBe('none')\n\n\t\tawait run(\"set $isVisible to true\")\n\t\tawait expect.poll(() =>\n\t\t\tevaluate(() => document.querySelector('#work-area div').style.display)\n\t\t).toBe('block')\n\t\tawait evaluate(() => { delete window.$isVisible })" }, { "category": "live", "name": "effects stop when element is removed from DOM", "html": "
", "action": "await run(\"set $message to '; await run(\"set $message to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $message to 'initial'\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('initial')\n\n\t\tawait evaluate(() => document.querySelector('#work-area div').remove())\n\t\tawait run(\"set $message to 'after removal'\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait evaluate(() => { delete window.$message })" }, { "category": "live", "name": "conditional branch only tracks the active dependency", "html": "
", "action": "await run(\"set $showFirst to true\"; await run(\"set $firstName to '; await run(\"set $lastName to '; await run(\"set $firstName to '; await run(\"set $lastName to '", "check": "toHaveText('Bob'); toHaveText('Jones')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $showFirst to true\")\n\t\tawait run(\"set $firstName to 'Alice'\")\n\t\tawait run(\"set $lastName to 'Smith'\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Alice')\n\n\t\t// Active branch: $firstName triggers\n\t\tawait run(\"set $firstName to 'Bob'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Bob')\n\n\t\t// Inactive branch: $lastName does NOT trigger\n\t\tawait run(\"set $lastName to 'Jones'\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveText('Bob')\n\n\t\t// Flip condition\n\t\tawait run(\"set $showFirst to false\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Jones')\n\n\t\t// Now $firstName is inactive\n\t\tawait run(\"set $firstName to 'Charlie'\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('div')).toHaveText('Jones')\n\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$showFirst; delete window.$firstName; delete window.$lastName\n\t\t})" }, { "category": "live", "name": "multiple live on same element work independently", "html": "
", "action": "await run(\"set $firstName to '; await run(\"set $age to 30\"; await run(\"set $firstName to '", "check": "toHaveAttribute('data-name', 'Alice'); toHaveAttribute('data-age', '30'); toHaveAttribute('data-age', '30')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $firstName to 'Alice'\")\n\t\tawait run(\"set $age to 30\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('div')).toHaveAttribute('data-name', 'Alice')\n\t\tawait expect(find('div')).toHaveAttribute('data-age', '30')\n\n\t\tawait run(\"set $firstName to 'Bob'\")\n\t\tawait expect.poll(() => find('div').getAttribute('data-name')).toBe('Bob')\n\t\tawait expect(find('div')).toHaveAttribute('data-age', '30')\n\t\tawait evaluate(() => { delete window.$firstName; delete window.$age })" }, { "category": "live", "name": "live and when on same element do not interfere", "html": "
", "action": "await run(\"set $status to '; await run(\"set $status to '", "check": "toHaveAttribute('data-status', 'online'); toHaveText('Status: online')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $status to 'online'\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('div')).toHaveAttribute('data-status', 'online')\n\t\tawait expect(find('div')).toHaveText('Status: online')\n\n\t\tawait run(\"set $status to 'offline'\")\n\t\tawait expect.poll(() => find('div').getAttribute('data-status')).toBe('offline')\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Status: offline')\n\t\tawait evaluate(() => { delete window.$status })" }, { "category": "live", "name": "bind and live on same element do not interfere", "html": "", "action": "find('input').fill('bob'); await run(\"set $username to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $username to 'alice'\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('alice')\n\t\tawait expect.poll(() => find('input').getAttribute('data-mirror')).toBe('alice')\n\n\t\tawait find('input').fill('bob')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('bob')\n\t\tawait expect.poll(() => find('input').getAttribute('data-mirror')).toBe('bob')\n\t\tawait evaluate(() => { delete window.$username })" }, { "category": "live", "name": "reactive effects are stopped on cleanup", "html": "
", "action": "await run(\"set $count to 0\"; await run(\"set $count to 99\"", "check": "toHaveText('cleaned')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $count to 0\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('#d1').textContent()).toBe('0')\n\n\t\t// Clean up the element (but keep it in DOM to detect if effect still fires)\n\t\tawait evaluate(() => {\n\t\t\tvar el = document.querySelector('#work-area #d1')\n\t\t\t_hyperscript.internals.runtime.cleanup(el)\n\t\t\tel.textContent = 'cleaned'\n\t\t})\n\n\t\t// Change the variable \u2014 the effect should NOT fire since we cleaned up\n\t\tawait run(\"set $count to 99\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 50)))\n\n\t\t// If effects were properly stopped, text stays 'cleaned'\n\t\tawait expect(find('#d1')).toHaveText('cleaned')\n\t\tawait evaluate(() => { delete window.$count })" }, { "category": "live", "name": "append triggers live block", "html": "
", "action": "await run(\"set $items to ['; await run(\"append '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $items to ['a', 'b']\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b')\n\n\t\tawait run(\"append 'c' to $items\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b, c')\n\t\tawait evaluate(() => { delete window.$items })" }, { "category": "live", "name": "push via pseudo-command triggers live block", "html": "
", "action": "await run(\"set $items to ['; await run(\"$items.push('", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $items to ['a', 'b']\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b')\n\n\t\tawait run(\"$items.push('c')\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b, c')\n\t\tawait evaluate(() => { delete window.$items })" }, { "category": "live", "name": "push via call triggers live block", "html": "
", "action": "await run(\"set $items to ['; await run(\"call $items.push('", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $items to ['a', 'b']\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b')\n\n\t\tawait run(\"call $items.push('c')\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b, c')\n\t\tawait evaluate(() => { delete window.$items })" }, { "category": "live", "name": "array + still works with live", "html": "
", "action": "await run(\"set $items to ['; await run(\"set $items to $items + ['", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $items to ['a', 'b']\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b')\n\n\t\tawait run(\"set $items to $items + ['c']\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('a, b, c')\n\t\tawait evaluate(() => { delete window.$items })" }, { "category": "live", "name": "set property still works with live", "html": "
", "action": "await run(\"set $obj to {name: '; await run(\"set $obj.name to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $obj to {name: 'Alice'}\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Alice')\n\n\t\tawait run(\"set $obj.name to 'Bob'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('Bob')\n\t\tawait evaluate(() => { delete window.$obj })" }, { "category": "live", "name": "property change on object in array triggers live re-render", "html": "
", "action": "await run(\"set $people to [{name: '; await run(\"set $people[0].name to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $people to [{name: 'Alice'}, {name: 'Bob'}]\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('div').textContent()).toContain('Alice')\n\t\tawait expect.poll(() => find('div').textContent()).toContain('Bob')\n\n\t\tawait run(\"set $people[0].name to 'Charlie'\")\n\t\tawait expect.poll(() => find('div').textContent()).toContain('Charlie')\n\t\tawait expect.poll(() => find('div').textContent()).toContain('Bob')\n\t\tawait evaluate(() => { delete window.$people })" }, { "category": "live", "name": "push object then modify its property both trigger live", "html": "
", "action": "await run(\"set $items to [{label: '; await run(\"call $items.push({label: '; await run(\"set $items[1].label to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/live.js", "body": "await run(\"set $items to [{label: 'first'}]\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect.poll(() => find('div').textContent()).toContain('first')\n\n\t\t// Push triggers re-render\n\t\tawait run(\"call $items.push({label: 'second'})\")\n\t\tawait expect.poll(() => find('div').textContent()).toContain('second')\n\n\t\t// Modify property on new object triggers re-render\n\t\tawait run(\"set $items[1].label to 'updated'\")\n\t\tawait expect.poll(() => find('div').textContent()).toContain('updated')\n\t\tawait evaluate(() => { delete window.$items })" }, { "category": "reactive-properties", "name": "setting a property on a plain object triggers reactivity", "html": "", "action": "await run(\"set $obj to {x: 1, y: 2}\"; await run(\"set $obj'", "check": "toHaveText('3')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/reactive-properties.js", "body": "await run(\"set $obj to {x: 1, y: 2}\")\n\t\tawait html(``)\n\t\tawait expect(find('output')).toHaveText('3')\n\n\t\tawait run(\"set $obj's x to 10\")\n\t\tawait expect.poll(() => find('output').textContent()).toBe('12')\n\t\tawait evaluate(() => { delete window.$obj })" }, { "category": "reactive-properties", "name": "nested property chain triggers on intermediate reassignment", "html": "", "action": "await run(\"set $data to {inner: {val: '; await run(\"set $data'", "check": "toHaveText('hello')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/reactive-properties.js", "body": "await run(\"set $data to {inner: {val: 'hello'}}\")\n\t\tawait html(``)\n\t\tawait expect(find('output')).toHaveText('hello')\n\n\t\tawait run(\"set $data's inner to {val: 'world'}\")\n\t\tawait expect.poll(() => find('output').textContent()).toBe('world')\n\t\tawait evaluate(() => { delete window.$data })" }, { "category": "reactive-properties", "name": "property change on DOM element triggers reactivity via setProperty", "html": "", "action": "await run(\"set #prop-input'", "check": "toHaveText('start')", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/reactive-properties.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('output')).toHaveText('start')\n\n\t\tawait run(\"set #prop-input's value to 'updated'\")\n\t\tawait expect.poll(() => find('output').textContent()).toBe('updated')" }, { "category": "reactive-properties", "name": "live block tracks property reads on plain objects", "html": "", "action": "await run(\"set $config to {label: '; await run(\"set $config'", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/reactive-properties.js", "body": "await run(\"set $config to {label: 'initial'}\")\n\t\tawait html(``)\n\t\tawait expect.poll(() => find('output').textContent()).toBe('initial')\n\n\t\tawait run(\"set $config's label to 'changed'\")\n\t\tawait expect.poll(() => find('output').textContent()).toBe('changed')\n\t\tawait evaluate(() => { delete window.$config })" }, { "category": "resize", "name": "fires when element is resized", "html": "
", "action": "", "check": "toHaveText(\"200\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/resize.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait page.evaluate(() => {\n\t\t\tdocument.getElementById('box').style.width = '200px';\n\t\t});\n\t\tawait expect(find('#out')).toHaveText(\"200\");" }, { "category": "resize", "name": "provides height in detail", "html": "
", "action": "", "check": "toHaveText(\"300\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/resize.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait page.evaluate(() => {\n\t\t\tdocument.getElementById('box').style.height = '300px';\n\t\t});\n\t\tawait expect(find('#out')).toHaveText(\"300\");" }, { "category": "resize", "name": "works with from clause", "html": "
", "action": "", "check": "toHaveText(\"150\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/resize.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait page.evaluate(() => {\n\t\t\tdocument.getElementById('box').style.width = '150px';\n\t\t});\n\t\tawait expect(find('#out')).toHaveText(\"150\");" }, { "category": "when", "name": "provides access to `it` and syncs initial value", "html": "
", "action": "await run(\"set $global to '; await run(\"set $global to '; await run(\"set $global to 42\"", "check": "toHaveText('initial'); toHaveText('hello world'); toHaveText('42')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $global to 'initial'\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveText('initial')\n\n\t\tawait run(\"set $global to 'hello world'\")\n\t\tawait expect(find('div')).toHaveText('hello world')\n\n\t\tawait run(\"set $global to 42\")\n\t\tawait expect(find('div')).toHaveText('42')\n\n\t\tawait evaluate(() => { delete window.$global })" }, { "category": "when", "name": "detects changes from $global variable", "html": "
", "action": "await run(\"set $global to '", "check": "toHaveText('Changed!')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait run(\"set $global to 'Changed!'\")\n\t\tawait expect(find('div')).toHaveText('Changed!')\n\t\tawait evaluate(() => { delete window.$global })" }, { "category": "when", "name": "detects changes from :element variable", "html": "
0
", "action": "find('div').click(); find('div').click()", "check": "toHaveText('0'); toHaveText('1'); toHaveText('2')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
0
`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('0')\n\t\tawait find('div').click()\n\t\tawait expect(find('div')).toHaveText('1')\n\t\tawait find('div').click()\n\t\tawait expect(find('div')).toHaveText('2')" }, { "category": "when", "name": "triggers multiple elements watching same variable", "html": "
", "action": "await run(\"set $shared to '", "check": "toHaveText('first'); toHaveText('second')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
` +\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $shared to 'changed'\")\n\t\tawait expect(find('#d1')).toHaveText('first')\n\t\tawait expect(find('#d2')).toHaveText('second')\n\t\tawait evaluate(() => { delete window.$shared })" }, { "category": "when", "name": "executes multiple commands", "html": "
", "action": "await run(\"set $multi to '", "check": "toHaveText('first'); toHaveClass(/executed/)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait run(\"set $multi to 'go'\")\n\t\tawait expect(find('div')).toHaveText('first')\n\t\tawait expect(find('div')).toHaveClass(/executed/)\n\t\tawait evaluate(() => { delete window.$multi })" }, { "category": "when", "name": "does not execute when variable is undefined initially", "html": "
original
", "action": "", "check": "toHaveText('original')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
original
`)\n\t\t// Wait a bit and verify it didn't change\n\t\tawait new Promise(r => setTimeout(r, 50))\n\t\tawait expect(find('div')).toHaveText('original')" }, { "category": "when", "name": "only triggers when variable actually changes value", "html": "
", "action": "await run(\"set $dedup to '; await run(\"set $dedup to '; await run(\"set $dedup to '", "check": "toHaveText('1'); toHaveText('1'); toHaveText('2')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $dedup to 'value1'\")\n\t\tawait expect(find('div')).toHaveText('1')\n\n\t\t// Same value \u2014 should NOT re-trigger\n\t\tawait run(\"set $dedup to 'value1'\")\n\t\tawait new Promise(r => setTimeout(r, 50))\n\t\tawait expect(find('div')).toHaveText('1')\n\n\t\tawait run(\"set $dedup to 'value2'\")\n\t\tawait expect(find('div')).toHaveText('2')\n\t\tawait evaluate(() => { delete window.$dedup })" }, { "category": "when", "name": "auto-tracks compound expressions", "html": "
", "action": "await run(\"set $a to 1\"; await run(\"set $b to 2\"; await run(\"set $a to 10\"; await run(\"set $b to 20\"", "check": "toHaveText('3'); toHaveText('12'); toHaveText('30')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $a to 1\")\n\t\tawait run(\"set $b to 2\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveText('3')\n\n\t\tawait run(\"set $a to 10\")\n\t\tawait expect(find('div')).toHaveText('12')\n\n\t\tawait run(\"set $b to 20\")\n\t\tawait expect(find('div')).toHaveText('30')\n\t\tawait evaluate(() => { delete window.$a; delete window.$b })" }, { "category": "when", "name": "detects attribute changes", "html": "
", "action": "", "check": "toHaveText('original')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait expect(find('div')).toHaveText('original')\n\n\t\tawait evaluate(() => document.querySelector('#work-area div').setAttribute('data-title', 'updated'))\n\t\t// MutationObserver + effect pipeline is async \u2014 poll for the update\n\t\tawait expect.poll(() => find('div').textContent(), { timeout: 5000 }).toBe('updated')" }, { "category": "when", "name": "detects form input value changes via user interaction", "html": "", "action": "evaluate({...})", "check": "toHaveText('start'); toHaveText('typed')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('start')\n\n\t\tawait evaluate(() => {\n\t\t\tconst input = document.getElementById('reactive-input')\n\t\t\tinput.value = 'typed'\n\t\t\tinput.dispatchEvent(new Event('input', { bubbles: true }))\n\t\t})\n\t\tawait expect(find('span')).toHaveText('typed')" }, { "category": "when", "name": "detects property change via hyperscript set", "html": "", "action": "await run(\"set #prog-input.value to '", "check": "toHaveText('initial')", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('initial')\n\n\t\tawait run(\"set #prog-input.value to 'updated'\")\n\t\tawait expect.poll(() => find('span').textContent()).toBe('updated')" }, { "category": "when", "name": "disposes effect when element is removed from DOM", "html": "
", "action": "await run(\"set $dispose to '; await run(\"set $dispose to '", "check": "toHaveText('before'); toBe('before')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $dispose to 'before'\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveText('before')\n\n\t\tconst textBefore = await evaluate(() => {\n\t\t\tconst div = document.querySelector('#work-area div')\n\t\t\tdiv.parentNode.removeChild(div)\n\t\t\treturn div.innerHTML\n\t\t})\n\t\texpect(textBefore).toBe('before')\n\n\t\tawait run(\"set $dispose to 'after'\")\n\t\tawait new Promise(r => setTimeout(r, 50))\n\n\t\t// Element was removed \u2014 should still show old value\n\t\tconst textAfter = await evaluate(() => {\n\t\t\t// The div is detached, check it still has old content\n\t\t\treturn true\n\t\t})\n\t\tawait evaluate(() => { delete window.$dispose })" }, { "category": "when", "name": "batches multiple synchronous writes into one effect run", "html": "
", "action": "await run(\"set $batchA to 0\"; await run(\"set $batchB to 0\"", "check": "toHaveText('1'); toHaveText('2')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $batchA to 0\")\n\t\tawait run(\"set $batchB to 0\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('1')\n\n\t\t// Both writes in a single evaluate so they happen in the same microtask\n\t\tawait evaluate(() => {\n\t\t\t_hyperscript(\"set $batchA to 5\")\n\t\t\t_hyperscript(\"set $batchB to 10\")\n\t\t})\n\t\tawait expect(find('div')).toHaveText('2')\n\t\tawait evaluate(() => { delete window.$batchA; delete window.$batchB })" }, { "category": "when", "name": "handles chained reactivity across elements", "html": "
", "action": "await run(\"set $source to 5\"; await run(\"set $source to 20\"", "check": "toHaveText('10'); toHaveText('40')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
` +\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $source to 5\")\n\t\tawait expect(find('#output')).toHaveText('10')\n\n\t\tawait run(\"set $source to 20\")\n\t\tawait expect(find('#output')).toHaveText('40')\n\t\tawait evaluate(() => { delete window.$source; delete window.$derived })" }, { "category": "when", "name": "supports multiple when features on the same element", "html": "
", "action": "await run(\"set $left to '; await run(\"set $right to '; await run(\"set $left to '", "check": "toHaveAttribute('data-left', 'L'); toHaveAttribute('data-right', 'R'); toHaveAttribute('data-left', 'newL'); toHaveAttribute('data-right', 'R')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $left to 'L'\")\n\t\tawait run(\"set $right to 'R'\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('div')).toHaveAttribute('data-left', 'L')\n\t\tawait expect(find('div')).toHaveAttribute('data-right', 'R')\n\n\t\tawait run(\"set $left to 'newL'\")\n\t\tawait expect(find('div')).toHaveAttribute('data-left', 'newL')\n\t\tawait expect(find('div')).toHaveAttribute('data-right', 'R')\n\t\tawait evaluate(() => { delete window.$left; delete window.$right })" }, { "category": "when", "name": "works with on handlers that modify the watched variable", "html": "
initial
", "action": "find('div').click()", "check": "toHaveText('initial'); toHaveText('clicked')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
initial
`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('initial')\n\t\tawait find('div').click()\n\t\tawait expect(find('div')).toHaveText('clicked')" }, { "category": "when", "name": "does not cross-trigger on unrelated variable writes", "html": "
", "action": "await run(\"set $trigger to '", "check": "toHaveText('1'); toHaveText('1')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $trigger to 'go'\")\n\t\tawait expect(find('div')).toHaveText('1')\n\t\tawait new Promise(r => setTimeout(r, 50))\n\t\tawait expect(find('div')).toHaveText('1')\n\t\tawait evaluate(() => { delete window.$trigger; delete window.$other })" }, { "category": "when", "name": "handles rapid successive changes correctly", "html": "
", "action": "await run(\"set $rapid to \"", "check": "toHaveText('9')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tfor (let i = 0; i < 10; i++) {\n\t\t\tawait run(\"set $rapid to \" + i)\n\t\t}\n\t\tawait expect(find('div')).toHaveText('9')\n\t\tawait evaluate(() => { delete window.$rapid })" }, { "category": "when", "name": "isolates element-scoped variables between elements", "html": "
A
B
", "action": "find('#d1').click(); find('#d2').click()", "check": "toHaveText('A'); toHaveText('B'); toHaveText('A-clicked'); toHaveText('B'); toHaveText('B-clicked'); toHaveText('A-clicked')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`
A
` +\n\t\t\t`
B
`\n\t\t)\n\t\tawait expect(find('#d1')).toHaveText('A')\n\t\tawait expect(find('#d2')).toHaveText('B')\n\n\t\tawait find('#d1').click()\n\t\tawait expect(find('#d1')).toHaveText('A-clicked')\n\t\tawait expect(find('#d2')).toHaveText('B')\n\n\t\tawait find('#d2').click()\n\t\tawait expect(find('#d2')).toHaveText('B-clicked')\n\t\tawait expect(find('#d1')).toHaveText('A-clicked')" }, { "category": "when", "name": "handles NaN without infinite re-firing", "html": "", "action": "evaluate({...})", "check": "toHaveText('NaN'); toHaveText('NaN')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('NaN')\n\n\t\tawait evaluate(() => {\n\t\t\tdocument.getElementById('nan-input').dispatchEvent(new Event('input', { bubbles: true }))\n\t\t})\n\t\tawait new Promise(r => setTimeout(r, 50))\n\t\tawait expect(find('span')).toHaveText('NaN')" }, { "category": "when", "name": "fires when either expression changes using or", "html": "
", "action": "await run(\"set $x to '; await run(\"set $y to '", "check": "toHaveText('from-x'); toHaveText('from-y')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait run(\"set $x to 'from-x'\")\n\t\tawait expect(find('div')).toHaveText('from-x')\n\n\t\tawait run(\"set $y to 'from-y'\")\n\t\tawait expect(find('div')).toHaveText('from-y')\n\t\tawait evaluate(() => { delete window.$x; delete window.$y })" }, { "category": "when", "name": "supports three or more expressions with or", "html": "
", "action": "await run(\"set $r to '; await run(\"set $g to '; await run(\"set $b to '", "check": "toHaveText('red'); toHaveText('green'); toHaveText('blue')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait run(\"set $r to 'red'\")\n\t\tawait expect(find('div')).toHaveText('red')\n\t\tawait run(\"set $g to 'green'\")\n\t\tawait expect(find('div')).toHaveText('green')\n\t\tawait run(\"set $b to 'blue'\")\n\t\tawait expect(find('div')).toHaveText('blue')\n\t\tawait evaluate(() => { delete window.$r; delete window.$g; delete window.$b })" }, { "category": "when", "name": "#element.checked is tracked", "html": "", "action": "", "check": "toHaveText('false')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('false')\n\t\tawait find('#cb-input').check()\n\t\tawait expect.poll(() => find('span').textContent()).toBe('true')" }, { "category": "when", "name": "my @attr is tracked", "html": "
", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('one')\n\t\tawait evaluate(() => document.querySelector('#work-area div').setAttribute('data-x', 'two'))\n\t\tawait expect.poll(() => find('div').textContent()).toBe('two')" }, { "category": "when", "name": "value of #element is tracked", "html": "", "action": "find('#of-input').fill('changed')", "check": "toHaveText('init')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t``\n\t\t)\n\t\tawait expect(find('span')).toHaveText('init')\n\t\tawait find('#of-input').fill('changed')\n\t\tawait expect.poll(() => find('span').textContent()).toBe('changed')" }, { "category": "when", "name": "math on tracked symbols works", "html": "
", "action": "await run(\"set $mA to 3\"; await run(\"set $mB to 4\"; await run(\"set $mA to 10\"", "check": "toHaveText('12')", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $mA to 3\")\n\t\tawait run(\"set $mB to 4\")\n\t\tawait html(`
`)\n\t\tawait expect(find('div')).toHaveText('12')\n\t\tawait run(\"set $mA to 10\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('40')" }, { "category": "when", "name": "comparison on tracked symbol works", "html": "
5) changes put it into me\">
", "action": "await run(\"set $cmpVal to 3\"; await run(\"set $cmpVal to 10\"", "check": "toHaveText('false')", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $cmpVal to 3\")\n\t\tawait html(`
5) changes put it into me\">
`)\n\t\tawait expect(find('div')).toHaveText('false')\n\t\tawait run(\"set $cmpVal to 10\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('true')" }, { "category": "when", "name": "string template with tracked symbol works", "html": "
", "action": "await run(\"set $tplName to '; await run(\"set $tplName to '", "check": "toHaveText('hello world')", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $tplName to 'world'\")\n\t\tawait html('
')\n\t\tawait expect(find('div')).toHaveText('hello world')\n\t\tawait run(\"set $tplName to 'there'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('hello there')" }, { "category": "when", "name": "function call on tracked value works (Math.round)", "html": "
", "action": "await run(\"set $rawNum to 3.7\"; await run(\"set $rawNum to 9.2\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $rawNum to 3.7\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('4')\n\t\tawait run(\"set $rawNum to 9.2\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('9')" }, { "category": "when", "name": "inline style change via JS is NOT detected", "html": "
not fired
", "action": "", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(`
not fired
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tconst initialText = await find('div').textContent()\n\t\tawait evaluate(() => document.getElementById('style-target').style.opacity = '0.5')\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await find('div').textContent()).toBe(initialText)" }, { "category": "when", "name": "reassigning whole array IS detected", "html": "
", "action": "await run(\"set $arrWhole to [1, 2, 3]\"; await run(\"set $arrWhole to [4, 5, 6]\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $arrWhole to [1, 2, 3]\")\n\t\tawait html(`
`)\n\t\tawait expect.poll(() => find('div').textContent()).toBe('1,2,3')\n\t\tawait run(\"set $arrWhole to [4, 5, 6]\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('4,5,6')" }, { "category": "when", "name": "mutating array element in place is NOT detected", "html": "
", "action": "await run(\"set $arrMut to [1, 2, 3]\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $arrMut to [1, 2, 3]\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tconst initialText = await find('div').textContent()\n\t\tawait evaluate(() => { window.$arrMut[0] = 99 })\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await find('div').textContent()).toBe(initialText)" }, { "category": "when", "name": "local variable in when expression produces a parse error", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/features/when.js", "body": "const error = await evaluate(() => {\n\t\t\tvar origError = console.error\n\t\t\tvar captured = null\n\t\t\tconsole.error = function() {\n\t\t\t\tfor (var i = 0; i < arguments.length; i++) {\n\t\t\t\t\tvar arg = String(arguments[i])\n\t\t\t\t\tif (arg.includes('local variable')) captured = arg\n\t\t\t\t}\n\t\t\t\torigError.apply(console, arguments)\n\t\t\t}\n\t\t\tvar wa = document.getElementById('work-area')\n\t\t\twa.innerHTML = '
'\n\t\t\t_hyperscript.processNode(wa)\n\t\t\tconsole.error = origError\n\t\t\treturn captured\n\t\t})\n\t\texpect(error).not.toBeNull()" }, { "category": "when", "name": "attribute observers are persistent (not recreated on re-run)", "html": "", "action": "", "check": "toBe(0)", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "const observersCreated = await evaluate(async () => {\n\t\t\tconst OrigMO = window.MutationObserver\n\t\t\tlet count = 0\n\t\t\twindow.MutationObserver = function(cb) {\n\t\t\t\tcount++\n\t\t\t\treturn new OrigMO(cb)\n\t\t\t}\n\t\t\twindow.MutationObserver.prototype = OrigMO.prototype\n\t\t\tconst wa = document.getElementById('work-area')\n\t\t\twa.innerHTML = '
'\n\t\t\t_hyperscript.processNode(wa)\n\t\t\tawait new Promise(r => setTimeout(r, 50))\n\t\t\tconst countAfterInit = count\n\t\t\tfor (let i = 2; i <= 6; i++) {\n\t\t\t\twa.querySelector('div').setAttribute('data-val', String(i))\n\t\t\t\tawait new Promise(r => setTimeout(r, 30))\n\t\t\t}\n\t\t\twindow.MutationObserver = OrigMO\n\t\t\treturn count - countAfterInit\n\t\t})\n\t\texpect(observersCreated).toBe(0)" }, { "category": "when", "name": "boolean short-circuit does not track unread branch", "html": "
", "action": "await run(\"set $x to false\"; await run(\"set $y to '; await run(\"set $y to '", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $x to false\")\n\t\tawait run(\"set $y to 'hello'\")\n\t\tawait html(`
`)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait run(\"set $y to 'world'\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\texpect(await find('div').textContent()).not.toBe('world')\n\t\tawait evaluate(() => { delete window.$x; delete window.$y })" }, { "category": "when", "name": "diamond: cascaded derived values produce correct final value", "html": "
", "action": "await run(\"set $a to 1\"; await run(\"set $a to 10\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $a to 1\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait run(\"set $a to 10\")\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(await find('div').textContent()).toContain('50')\n\t\tawait evaluate(() => { delete window.$a; delete window.$b; delete window.$c })" }, { "category": "when", "name": "error in one effect does not break other effects in the same batch", "html": "` +\n\t\t\t``\n\t\t)\n\t\tawait new Promise(r => setTimeout(r, 50))\n\t\tawait run(\"set $trigger to 42\")\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('#err-b')).toHaveText('ok:42')\n\t\tawait evaluate(() => { delete window.$trigger })" }, { "category": "when", "name": "circular guard resets after cascade settles", "html": "", "action": "await run(\"set $ping to 0\"; await run(\"set $ping to 1\"; await run(\"set $ping to 0\"; await run(\"set $ping to 999\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $ping to 0\")\n\t\tawait html(\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\t\tawait run(\"set $ping to 1\")\n\t\tawait new Promise(r => setTimeout(r, 500))\n\t\tawait run(\"set $ping to 0\")\n\t\tawait run(\"set $ping to 999\")\n\t\tawait new Promise(r => setTimeout(r, 200))\n\t\texpect(Number(await find('div').textContent())).toBeGreaterThan(0)\n\t\tawait evaluate(() => { delete window.$ping })" }, { "category": "when", "name": "cross-microtask ping-pong is caught by circular guard", "html": "", "action": "await run(\"set $ping to 1\"", "check": "", "async": true, "complexity": "promise", "source": "new_file", "file": "test/features/when.js", "body": "await html(\n\t\t\t`` +\n\t\t\t`` +\n\t\t\t`
`\n\t\t)\n\n\t\t// This creates A->B->A->B... across microtask boundaries\n\t\tawait run(\"set $ping to 1\")\n\t\tawait new Promise(r => setTimeout(r, 1000))\n\n\t\t// The browser should not freeze. The guard should have stopped it.\n\t\t// The value should be finite (not still incrementing).\n\t\tconst val = Number(await find('div').textContent())\n\t\texpect(val).toBeLessThan(200)\n\t\tawait evaluate(() => { delete window.$ping; delete window.$pong })" }, { "category": "when", "name": "element moved in DOM retains reactivity", "html": "
", "action": "await run(\"set $movable to '; await run(\"set $movable to '", "check": "toHaveText('start')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $movable to 'start'\")\n\t\tawait html(\n\t\t\t`
` +\n\t\t\t`
`\n\t\t)\n\t\tawait expect(find('span')).toHaveText('start')\n\n\t\t// Move the span from container-a to container-b\n\t\tawait evaluate(() => {\n\t\t\tvar span = document.querySelector('#work-area span')\n\t\t\tdocument.getElementById('container-b').appendChild(span)\n\t\t})\n\n\t\t// Element is still connected, just in a different parent\n\t\tawait run(\"set $movable to 'moved'\")\n\t\tawait expect.poll(() => find('span').textContent()).toBe('moved')\n\t\tawait evaluate(() => { delete window.$movable })" }, { "category": "when", "name": "rapid detach/reattach in same sync block does not kill effect", "html": "
", "action": "await run(\"set $thrash to '; await run(\"set $thrash to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/features/when.js", "body": "await run(\"set $thrash to 'before'\")\n\t\tawait html(\n\t\t\t`
`\n\t\t)\n\t\tawait evaluate(() => {\n\t\t\tvar parent = document.getElementById('thrash-parent')\n\t\t\tparent.innerHTML = ''\n\t\t\t_hyperscript.processNode(parent)\n\t\t})\n\t\tawait expect.poll(() => find('span').textContent()).toBe('before')\n\n\t\t// Detach and immediately reattach in the same synchronous block\n\t\tawait evaluate(() => {\n\t\t\tvar span = document.querySelector('#thrash-parent span')\n\t\t\tvar parent = span.parentNode\n\t\t\tparent.removeChild(span)\n\t\t\tparent.appendChild(span)\n\t\t})\n\n\t\t// Effect should still work\n\t\tawait run(\"set $thrash to 'after'\")\n\t\tawait expect.poll(() => find('span').textContent()).toBe('after')\n\t\tawait evaluate(() => { delete window.$thrash })" }, { "category": "asyncError", "name": "rejected promise stops execution", "html": "\n\t\t\t
original
", "action": "find('button').click()", "check": "toHaveText(\"original\")", "async": true, "complexity": "promise", "source": "new_file", "file": "test/core/asyncError.js", "body": "await evaluate(() => {\n\t\t\twindow.failAsync = function() {\n\t\t\t\treturn Promise.reject(new Error(\"boom\"));\n\t\t\t}\n\t\t});\n\t\tawait html(\n\t\t\t`\n\t\t\t
original
`\n\t\t);\n\t\tawait find('button').click();\n\t\tawait new Promise(r => setTimeout(r, 200));\n\t\tawait expect(find('#out')).toHaveText(\"original\");" }, { "category": "asyncError", "name": "rejected promise triggers catch block", "html": "\n\t\t\t
", "action": "find('button').click()", "check": "toHaveText(\"boom\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/core/asyncError.js", "body": "await evaluate(() => {\n\t\t\twindow.failAsync = function() {\n\t\t\t\treturn Promise.reject(new Error(\"boom\"));\n\t\t\t}\n\t\t});\n\t\tawait html(\n\t\t\t`\n\t\t\t
`\n\t\t);\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"boom\");" }, { "category": "dom-scope", "name": "isolated stops ^var resolution", "html": "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\twaiting\n\t\t\t\t
\n\t\t\t
\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/dom-scope.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\twaiting\n\t\t\t\t
\n\t\t\t
\n\t\t`)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('blocked')" }, { "category": "dom-scope", "name": "closest jumps to matching ancestor", "html": "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/dom-scope.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t`)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('from-outer')" }, { "category": "dom-scope", "name": "closest with no match stops resolution", "html": "\n\t\t\t
\n\t\t\t\twaiting\n\t\t\t
\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/dom-scope.js", "body": "await html(`\n\t\t\t
\n\t\t\t\twaiting\n\t\t\t
\n\t\t`)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('blocked')" }, { "category": "dom-scope", "name": "parent of jumps past matching ancestor to its parent", "html": "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/dom-scope.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t`)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('from-outer')" }, { "category": "dom-scope", "name": "isolated allows setting ^var on the isolated element itself", "html": "\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/dom-scope.js", "body": "await html(`\n\t\t\t
\n\t\t\t\t
\n\t\t\t\t\tnone\n\t\t\t\t
\n\t\t\t
\n\t\t`)\n\t\tawait expect.poll(() => find('span').textContent()).toBe('contained')" }, { "category": "evalStatically", "name": "works on number literals", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "expect(await evaluate(() => _hyperscript.parse(\"42\").evalStatically())).toBe(42);\n\t\texpect(await evaluate(() => _hyperscript.parse(\"3.14\").evalStatically())).toBe(3.14);" }, { "category": "evalStatically", "name": "works on boolean literals", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "expect(await evaluate(() => _hyperscript.parse(\"true\").evalStatically())).toBe(true);\n\t\texpect(await evaluate(() => _hyperscript.parse(\"false\").evalStatically())).toBe(false);" }, { "category": "evalStatically", "name": "works on null literal", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "expect(await evaluate(() => _hyperscript.parse(\"null\").evalStatically())).toBe(null);" }, { "category": "evalStatically", "name": "works on plain string literals", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "expect(await evaluate(() => _hyperscript.parse('\"hello\"').evalStatically())).toBe(\"hello\");\n\t\texpect(await evaluate(() => _hyperscript.parse(\"'world'\").evalStatically())).toBe(\"world\");" }, { "category": "evalStatically", "name": "works on time expressions", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "expect(await evaluate(() => _hyperscript.parse(\"200ms\").evalStatically())).toBe(200);\n\t\texpect(await evaluate(() => _hyperscript.parse(\"2s\").evalStatically())).toBe(2000);" }, { "category": "evalStatically", "name": "throws on template strings", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "var msg = await evaluate(() => {\n\t\t\ttry { _hyperscript.parse('`hello ${name}`').evalStatically(); return null; }\n\t\t\tcatch (e) { return e.message; }\n\t\t});\n\t\texpect(msg).toMatch(/cannot be evaluated statically/);" }, { "category": "evalStatically", "name": "throws on symbol references", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "var msg = await evaluate(() => {\n\t\t\ttry { _hyperscript.parse(\"x\").evalStatically(); return null; }\n\t\t\tcatch (e) { return e.message; }\n\t\t});\n\t\texpect(msg).toMatch(/cannot be evaluated statically/);" }, { "category": "evalStatically", "name": "throws on math expressions", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_file", "file": "test/core/evalStatically.js", "body": "var msg = await evaluate(() => {\n\t\t\ttry { _hyperscript.parse(\"1 + 2\").evalStatically(); return null; }\n\t\t\tcatch (e) { return e.message; }\n\t\t});\n\t\texpect(msg).toMatch(/cannot be evaluated statically/);" }, { "category": "liveTemplate", "name": "renders static content after the template", "html": "\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('Hello World')" }, { "category": "liveTemplate", "name": "renders template expressions", "html": "\n\t\t\t\n\t\t", "action": "await run(\"set $ltName to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await run(\"set $ltName to 'hyperscript'\")\n\t\tawait html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('Hello hyperscript!')\n\t\tawait evaluate(() => { delete window.$ltName })" }, { "category": "liveTemplate", "name": "applies init script from _ attribute", "html": "\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('initialized')" }, { "category": "liveTemplate", "name": "reactively updates when dependencies change", "html": "\n\t\t\t\n\t\t", "action": "find('[data-live-template] button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('Count: 0')\n\t\tawait find('[data-live-template] button').click()\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('Count: 1')" }, { "category": "liveTemplate", "name": "processes hyperscript on inner elements", "html": "\n\t\t\t\n\t\t", "action": "find('[data-live-template] button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] output').textContent()).toBe('0')\n\t\tawait find('[data-live-template] button').click()\n\t\tawait expect.poll(() => find('[data-live-template] output').textContent()).toBe('1')" }, { "category": "liveTemplate", "name": "supports #for loops", "html": "\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] li').count()).toBe(3)\n\t\tawait expect.poll(() => find('[data-live-template] li').first().textContent()).toBe('a')" }, { "category": "liveTemplate", "name": "supports #if conditionals", "html": "\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('visible')" }, { "category": "liveTemplate", "name": "reacts to global state without init script", "html": "\n\t\t\t\n\t\t", "action": "await run(\"set $ltGlobal to '; await run(\"set $ltGlobal to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await run(\"set $ltGlobal to 'World'\")\n\t\tawait html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] p').textContent()).toBe('Hello, World!')\n\t\tawait run(\"set $ltGlobal to 'Carson'\")\n\t\tawait expect.poll(() => find('[data-live-template] p').textContent()).toBe('Hello, Carson!')\n\t\tawait evaluate(() => { delete window.$ltGlobal })" }, { "category": "liveTemplate", "name": "wrapper has display:contents", "html": "\n\t\t\t\n\t\t", "action": "", "check": "toBe('contents')", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] span').textContent()).toBe('test')\n\t\tvar display = await evaluate(() =>\n\t\t\tdocument.querySelector('[data-live-template]').style.display\n\t\t)\n\t\texpect(display).toBe('contents')" }, { "category": "liveTemplate", "name": "multiple live templates are independent", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/core/liveTemplate.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] .a').textContent()).toBe('first')\n\t\tawait expect.poll(() => find('[data-live-template] .b').textContent()).toBe('second')" }, { "category": "assignableElements", "name": "set #id replaces element with HTML string", "html": "
old
", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"new\"); toBe(\"SPAN\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"new\");\n\t\tvar tag = await evaluate(() => document.querySelector('#target').tagName);\n\t\texpect(tag).toBe(\"SPAN\");" }, { "category": "assignableElements", "name": "set #id replaces element with another element", "html": "
old
", "action": "find('button').dispatchEvent('click')", "check": "toBe(\"moved\")", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar text = await evaluate(() => document.querySelector('.replaced').textContent);\n\t\texpect(text).toBe(\"moved\");\n\t\tvar gone = await evaluate(() => document.querySelector('#target'));\n\t\texpect(gone).toBeNull();" }, { "category": "assignableElements", "name": "set .class replaces all matching elements", "html": "", "action": "find('button').dispatchEvent('click')", "check": "toBe(3); toEqual([\"replaced\", \"replaced\", \"replaced\"])", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar count = await evaluate(() => document.querySelectorAll('.item').length);\n\t\texpect(count).toBe(3);\n\t\tvar texts = await evaluate(() => Array.from(document.querySelectorAll('.item')).map(e => e.textContent));\n\t\texpect(texts).toEqual([\"replaced\", \"replaced\", \"replaced\"]);" }, { "category": "assignableElements", "name": "set replaces all matching elements", "html": "

one

two

", "action": "find('button').dispatchEvent('click')", "check": "toEqual([\"done\", \"done\"])", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"

one

\" +\n\t\t\t\"

two

\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar texts = await evaluate(() => Array.from(document.querySelectorAll('#box p')).map(e => e.textContent));\n\t\texpect(texts).toEqual([\"done\", \"done\"]);" }, { "category": "assignableElements", "name": "set closest replaces ancestor", "html": "
", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"replaced\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('.wrapper')).toHaveText(\"replaced\");" }, { "category": "assignableElements", "name": "hyperscript in replacement content is initialized", "html": "
old
", "action": "find('#go').dispatchEvent('click'); find('#target').dispatchEvent('click')", "check": "toHaveText(\"new\"); toHaveText(\"clicked\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#go').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"new\");\n\t\tawait find('#target').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"clicked\");" }, { "category": "assignableElements", "name": "swap #a with #b swaps DOM positions", "html": "
A
B
", "action": "find('button').dispatchEvent('click')", "check": "toEqual([\"B\", \"A\"])", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"
A
\" +\n\t\t\t\"
B
\" +\n\t\t\t\"
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tvar order = await evaluate(() =>\n\t\t\tArray.from(document.querySelector('#container').children).map(e => e.textContent.trim())\n\t\t);\n\t\texpect(order).toEqual([\"B\", \"A\"]);" }, { "category": "assignableElements", "name": "put into still works as innerHTML", "html": "
old
", "action": "find('button').dispatchEvent('click')", "check": "toHaveText(\"new\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/assignableElements.js", "body": "await html(\n\t\t\t\"
old
\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveText(\"new\");" }, { "category": "collectionExpressions", "name": "filters an array by condition", "html": "", "action": "", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"a\", active: true}, {name: \"b\", active: false}, {name: \"c\", active: true}]\n\t\t\tthen return arr where its active`);\n\t\texpect(result.map(x => x.name)).toEqual([\"a\", \"c\"]);" }, { "category": "collectionExpressions", "name": "filters with comparison", "html": "", "action": "", "check": "toEqual([4, 5])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [1, 2, 3, 4, 5]\n\t\t\tthen return arr where it > 3`);\n\t\texpect(result).toEqual([4, 5]);" }, { "category": "collectionExpressions", "name": "works with DOM elements", "html": "
  • A
  • B
  • C
", "action": "find('button').click()", "check": "toHaveText(\"AC\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
  • A
  • B
  • C
\" +\n\t\t\t\"\" +\n\t\t\t\"
\"\n\t\t);\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"AC\");" }, { "category": "collectionExpressions", "name": "sorts by a property", "html": "", "action": "", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"Charlie\"}, {name: \"Alice\"}, {name: \"Bob\"}]\n\t\t\tthen return arr sorted by its name`);\n\t\texpect(result.map(x => x.name)).toEqual([\"Alice\", \"Bob\", \"Charlie\"]);" }, { "category": "collectionExpressions", "name": "sorts descending", "html": "", "action": "", "check": "toEqual([3, 2, 1])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [3, 1, 2]\n\t\t\tthen return arr sorted by it descending`);\n\t\texpect(result).toEqual([3, 2, 1]);" }, { "category": "collectionExpressions", "name": "sorts numbers by a computed key", "html": "", "action": "", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"b\", age: 30}, {name: \"a\", age: 20}, {name: \"c\", age: 25}]\n\t\t\tthen return arr sorted by its age`);\n\t\texpect(result.map(x => x.name)).toEqual([\"a\", \"c\", \"b\"]);" }, { "category": "collectionExpressions", "name": "maps to a property", "html": "", "action": "", "check": "toEqual([\"Alice\", \"Bob\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"Alice\"}, {name: \"Bob\"}]\n\t\t\tthen return arr mapped to its name`);\n\t\texpect(result).toEqual([\"Alice\", \"Bob\"]);" }, { "category": "collectionExpressions", "name": "maps with an expression", "html": "", "action": "", "check": "toEqual([2, 4, 6])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [1, 2, 3]\n\t\t\tthen return arr mapped to (it * 2)`);\n\t\texpect(result).toEqual([2, 4, 6]);" }, { "category": "collectionExpressions", "name": "where then mapped to", "html": "", "action": "", "check": "toEqual([\"Alice\", \"Charlie\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"Alice\", active: true}, {name: \"Bob\", active: false}, {name: \"Charlie\", active: true}]\n\t\t\tthen return arr where its active mapped to its name`);\n\t\texpect(result).toEqual([\"Alice\", \"Charlie\"]);" }, { "category": "collectionExpressions", "name": "sorted by then mapped to", "html": "", "action": "", "check": "toEqual([\"Alice\", \"Charlie\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"Charlie\", age: 30}, {name: \"Alice\", age: 20}]\n\t\t\tthen return arr sorted by its age mapped to its name`);\n\t\texpect(result).toEqual([\"Alice\", \"Charlie\"]);" }, { "category": "collectionExpressions", "name": "where then sorted by then mapped to", "html": "", "action": "", "check": "toEqual([\"Bob\", \"Charlie\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`set arr to [{name: \"Charlie\", active: true, age: 30}, {name: \"Alice\", active: false, age: 20}, {name: \"Bob\", active: true, age: 25}]\n\t\t\tthen return arr where its active sorted by its age mapped to its name`);\n\t\texpect(result).toEqual([\"Bob\", \"Charlie\"]);" }, { "category": "collectionExpressions", "name": "the result inside where refers to previous command result, not current element", "html": "", "action": "", "check": "toEqual([4, 5])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(`get 3\n\t\t\tthen set arr to [1, 2, 3, 4, 5]\n\t\t\tthen return arr where it > the result`);\n\t\texpect(result).toEqual([4, 5]);" }, { "category": "collectionExpressions", "name": "where binds after in without parens", "html": "
ABC
", "action": "await run(\" in #container where it matches .a\"", "check": "toBe(2)", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
\" +\n\t\t\t\"ABC\" +\n\t\t\t\"
\"\n\t\t);\n\t\tvar result = await run(\" in #container where it matches .a\");\n\t\texpect(result.length).toBe(2);" }, { "category": "collectionExpressions", "name": "sorted by binds after in without parens", "html": "
  • C
  • A
  • B
", "action": "await run(\"
  • in #list where its textContent is not '", "check": "toBe(2)", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
    • C
    • A
    • B
    \"\n\t\t);\n\t\tvar result = await run(\"
  • in #list where its textContent is not 'A'\");\n\t\texpect(result.length).toBe(2);" }, { "category": "collectionExpressions", "name": "where binds after property access", "html": "", "action": "await run(\"obj.items where it > 2\"", "check": "toEqual([3, 4])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "var result = await run(\"obj.items where it > 2\", {\n\t\t\tlocals: { obj: { items: [1, 2, 3, 4] } }\n\t\t});\n\t\texpect(result).toEqual([3, 4]);" }, { "category": "collectionExpressions", "name": "where after in with mapped to", "html": "
    • A
    • B
    • C
    ", "action": "await run(\n\t\t\t\"
  • in #items where it matches .yes mapped to its textContent\"", "check": "toEqual([\"A\", \"C\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
    • A
    • B
    • C
    \"\n\t\t);\n\t\tvar result = await run(\n\t\t\t\"
  • in #items where it matches .yes mapped to its textContent\"\n\t\t);\n\t\texpect(result).toEqual([\"A\", \"C\"]);" }, { "category": "collectionExpressions", "name": "where binds after in on closest", "html": "
    ABC
    ", "action": "find('#b2').click()", "check": "toHaveText(\"2\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
    ABC
    \" +\n\t\t\t\"\" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').first().click();\n\t\tawait expect(find('button').first()).toHaveText(\"2\");\n\t\tawait find('#b2').click();\n\t\tawait expect(find('#b2')).toHaveText(\"2\");" }, { "category": "collectionExpressions", "name": "where in init followed by on feature", "html": "
    AB
    ", "action": "find('button').click()", "check": "toHaveText(\"1\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(\n\t\t\t\"
    AB
    \" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('button').click();\n\t\tawait expect(find('button')).toHaveText(\"1\");" }, { "category": "collectionExpressions", "name": "where in component init followed by on feature", "html": "\n\t\t\t
    AB
    \n\t\t\t\n\t\t\tgo\n\t\t", "action": "find('test-where-comp').click()", "check": "toHaveText(\"1\")", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(`\n\t\t\t
    AB
    \n\t\t\t\n\t\t\tgo\n\t\t`);\n\t\tawait find('test-where-comp').click();\n\t\tawait expect(find('test-where-comp')).toHaveText(\"1\");" }, { "category": "collectionExpressions", "name": "where with is not me in component template", "html": "\n\t\t\t
    \n\t\t\t\n\t\t\t\n\t\t", "action": "find('test-where-me input').click()", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(`\n\t\t\t
    \n\t\t\t\n\t\t\t\n\t\t`);\n\t\tvar attr = await evaluate(() => document.querySelector('test-where-me input')?.getAttribute('_'));\n\t\tconsole.log(\"ATTR:\", attr);\n\t\tawait find('test-where-me input').click();\n\t\tawait expect.poll(() => find('.cb').first().isChecked()).toBe(true);" }, { "category": "collectionExpressions", "name": "where with is not me followed by on feature", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t
    in the closest where it is not me\n\t\t\t\t on change set checked of the :checkboxes to my checked\">\n\t\t\t
    \n\t\t", "action": "find('#master').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t
    in the closest where it is not me\n\t\t\t\t on change set checked of the :checkboxes to my checked\">\n\t\t\t
    \n\t\t`);\n\t\tawait find('#master').click();\n\t\tawait expect.poll(() => find('.cb').first().isChecked()).toBe(true);" }, { "category": "collectionExpressions", "name": "full select-all pattern with multiple on features", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t
    in the closest where it is not me\n\t\t\t\t on change\n\t\t\t\t set checked of the :checkboxes to my checked\n\t\t\t\t on change from the closest
    \n\t\t\t\t if no :checkboxes where it is checked\n\t\t\t\t set my indeterminate to false\n\t\t\t\t set my checked to false\n\t\t\t\t else if no :checkboxes where it is not checked\n\t\t\t\t set my indeterminate to false\n\t\t\t\t set my checked to true\n\t\t\t\t else\n\t\t\t\t set my indeterminate to true\n\t\t\t\t end\">\n\t\t\t
    \n\t\t", "action": "find('#master').click()", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/expressions/collectionExpressions.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t\t
    in the closest where it is not me\n\t\t\t\t on change\n\t\t\t\t set checked of the :checkboxes to my checked\n\t\t\t\t on change from the closest
    \n\t\t\t\t if no :checkboxes where it is checked\n\t\t\t\t set my indeterminate to false\n\t\t\t\t set my checked to false\n\t\t\t\t else if no :checkboxes where it is not checked\n\t\t\t\t set my indeterminate to false\n\t\t\t\t set my checked to true\n\t\t\t\t else\n\t\t\t\t set my indeterminate to true\n\t\t\t\t end\">\n\t\t\t
    \n\t\t`);\n\t\t// master check should check all\n\t\tawait find('#master').click();\n\t\tawait expect.poll(() => find('.cb').first().isChecked()).toBe(true);\n\t\t// uncheck one child - master should become indeterminate\n\t\tawait find('.cb').first().click();\n\t\tvar indet = await evaluate(() => document.querySelector('#master').indeterminate);\n\t\texpect(indet).toBe(true);" }, { "category": "dom-scope", "name": "child reads ^var set by parent", "html": "
    0
    ", "action": "find('span').click()", "check": "toHaveText('42')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('42')" }, { "category": "dom-scope", "name": "child writes ^var and parent sees it", "html": "
    0
    ", "action": "find('button').click(); find('span').click()", "check": "toHaveText('99')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('button').click()\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('99')" }, { "category": "dom-scope", "name": "deeply nested child reads ^var from grandparent", "html": "
    empty
    ", "action": "find('span').click()", "check": "toHaveText('alice')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t` empty` +\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('alice')" }, { "category": "dom-scope", "name": "closest ancestor wins (shadowing)", "html": "
    empty
    ", "action": "find('span').click()", "check": "toHaveText('blue')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t` empty` +\n\t\t\t`
    ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('blue')" }, { "category": "dom-scope", "name": "sibling subtrees have independent ^vars", "html": "
    empty
    empty
    ", "action": "find('#a span').click(); find('#b span').click()", "check": "toHaveText('A'); toHaveText('B')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` empty` +\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t` empty` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('#a span').click()\n\t\tawait expect(find('#a span')).toHaveText('A')\n\t\tawait find('#b span').click()\n\t\tawait expect(find('#b span')).toHaveText('B')" }, { "category": "dom-scope", "name": "write to ^var not found anywhere creates on current element", "html": "
    empty
    ", "action": "find('button').click()", "check": "toHaveText('created')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` empty` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('button').click()\n\t\tawait expect(find('span')).toHaveText('created')" }, { "category": "dom-scope", "name": "child write updates the ancestor, not a local copy", "html": "
    0
    ", "action": "find('button').click(); find('span').click()", "check": "toHaveText('10')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('button').click()\n\t\t// read from a sibling \u2014 should see the ancestor's updated value\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('10')" }, { "category": "dom-scope", "name": "increment works on inherited var", "html": "
    ", "action": "find('button').click(); find('button').click(); find('button').click(); find('span').click()", "check": "toHaveText('3')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('button').click()\n\t\tawait find('button').click()\n\t\tawait find('button').click()\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('3')" }, { "category": "dom-scope", "name": "dom keyword works as scope modifier", "html": "
    0
    ", "action": "find('span').click()", "check": "toHaveText('42')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('42')" }, { "category": "dom-scope", "name": "set ^var on explicit element", "html": "
    read
    ", "action": "find('button').click(); find('span').click()", "check": "toHaveText('hello')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` read` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('button').click()\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('hello')" }, { "category": "dom-scope", "name": "on clause targets a specific ancestor", "html": "
    read
    ", "action": "find('span').click()", "check": "toHaveText('outer')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t` read` +\n\t\t\t`
    ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('outer')" }, { "category": "dom-scope", "name": "on clause with id reference", "html": "
    read", "action": "find('button').click(); find('span').click()", "check": "toHaveText('99')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t`` +\n\t\t\t`read`\n\t\t)\n\t\tawait find('button').click()\n\t\tawait find('span').click()\n\t\tawait expect(find('span')).toHaveText('99')" }, { "category": "dom-scope", "name": "when reacts to ^var changes", "html": "
    ", "action": "find('button').click(); find('button').click()", "check": "toHaveText('0'); toHaveText('1'); toHaveText('2')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect(find('output')).toHaveText('0')\n\t\tawait find('button').click()\n\t\tawait expect(find('output')).toHaveText('1')\n\t\tawait find('button').click()\n\t\tawait expect(find('output')).toHaveText('2')" }, { "category": "dom-scope", "name": "always reacts to ^var changes", "html": "
    ", "action": "find('button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` loading` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect.poll(() => find('output').textContent()).toBe('Hello alice')\n\t\tawait find('button').click()\n\t\tawait expect.poll(() => find('output').textContent()).toBe('Hello bob')" }, { "category": "dom-scope", "name": "multiple children react to same ^var", "html": "
    ", "action": "find('button').click()", "check": "toHaveText('red'); toHaveText('red'); toHaveText('blue'); toHaveText('blue')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect(find('#a')).toHaveText('red')\n\t\tawait expect(find('#b')).toHaveText('red')\n\t\tawait find('button').click()\n\t\tawait expect(find('#a')).toHaveText('blue')\n\t\tawait expect(find('#b')).toHaveText('blue')" }, { "category": "dom-scope", "name": "sibling subtrees react independently with ^var", "html": "
    ", "action": "find('#a button').click(); find('#a button').click(); find('#b button').click()", "check": "toHaveText('0'); toHaveText('0'); toHaveText('2'); toHaveText('0'); toHaveText('1'); toHaveText('2')", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    ` +\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` 0` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect(find('#a output')).toHaveText('0')\n\t\tawait expect(find('#b output')).toHaveText('0')\n\n\t\tawait find('#a button').click()\n\t\tawait find('#a button').click()\n\t\tawait expect(find('#a output')).toHaveText('2')\n\t\tawait expect(find('#b output')).toHaveText('0')\n\n\t\tawait find('#b button').click()\n\t\tawait expect(find('#b output')).toHaveText('1')\n\t\tawait expect(find('#a output')).toHaveText('2')" }, { "category": "dom-scope", "name": "bind works with ^var", "html": "
    ", "action": "find('input').fill('hello')", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait find('input').fill('hello')\n\t\tawait expect.poll(() => find('output').textContent()).toBe('hello')" }, { "category": "dom-scope", "name": "derived ^var chains reactively", "html": "
    ", "action": "find('#price-btn').click(); find('#qty-btn').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect.poll(() => find('output').textContent()).toBe('20')\n\t\tawait find('#price-btn').click()\n\t\tawait expect.poll(() => find('output').textContent()).toBe('50')\n\t\tawait find('#qty-btn').click()\n\t\tawait expect.poll(() => find('output').textContent()).toBe('125')" }, { "category": "dom-scope", "name": "effect stops when element is removed", "html": "
    ", "action": "find('button').click()", "check": "toHaveText('hello')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect(find('output')).toHaveText('hello')\n\t\tawait evaluate(() => document.querySelector('#work-area output').remove())\n\t\tawait find('button').click()\n\t\t// Output was removed \u2014 no error, effect is disposed\n\t\tawait new Promise(r => setTimeout(r, 100))" }, { "category": "dom-scope", "name": "dedup prevents re-fire on same ^var value", "html": "
    ", "action": "find('#diff').click()", "check": "toHaveText('1'); toHaveText('1'); toHaveText('2')", "async": true, "complexity": "promise", "source": "new_file", "file": "test/expressions/dom-scope.js", "body": "await html(\n\t\t\t`
    ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t` ` +\n\t\t\t`
    `\n\t\t)\n\t\tawait expect(find('output')).toHaveText('1')\n\t\tawait find('button:text(\"same\")').click()\n\t\tawait new Promise(r => setTimeout(r, 100))\n\t\tawait expect(find('output')).toHaveText('1')\n\t\tawait find('#diff').click()\n\t\tawait expect(find('output')).toHaveText('2')" }, { "category": "splitJoin", "name": "splits a string by delimiter", "html": "", "action": "", "check": "toEqual([\"a\", \"b\", \"c\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return \"a,b,c\" split by \",\"`);\n\t\texpect(result).toEqual([\"a\", \"b\", \"c\"]);" }, { "category": "splitJoin", "name": "splits by whitespace", "html": "", "action": "", "check": "toEqual([\"hello\", \"world\"])", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return \"hello world\" split by \" \"`);\n\t\texpect(result).toEqual([\"hello\", \"world\"]);" }, { "category": "splitJoin", "name": "joins an array with delimiter", "html": "", "action": "", "check": "toBe(\"a, b, c\")", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return [\"a\", \"b\", \"c\"] joined by \", \"`);\n\t\texpect(result).toBe(\"a, b, c\");" }, { "category": "splitJoin", "name": "joins with empty string", "html": "", "action": "", "check": "toBe(\"xyz\")", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return [\"x\", \"y\", \"z\"] joined by \"\"`);\n\t\texpect(result).toBe(\"xyz\");" }, { "category": "splitJoin", "name": "split then where then joined", "html": "", "action": "", "check": "toBe(\"a-b-c\")", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return \"a,,b,,c\" split by \",\" where it is not \"\" joined by \"-\"`);\n\t\texpect(result).toBe(\"a-b-c\");" }, { "category": "splitJoin", "name": "split then sorted then joined", "html": "", "action": "", "check": "toBe(\"apple, banana, cherry\")", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return \"banana,apple,cherry\" split by \",\" sorted by it joined by \", \"`);\n\t\texpect(result).toBe(\"apple, banana, cherry\");" }, { "category": "splitJoin", "name": "split then mapped then joined", "html": "", "action": "", "check": "toBe(\"5,5\")", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/expressions/splitJoin.js", "body": "var result = await run(`return \"hello world\" split by \" \" mapped to its length joined by \",\"`);\n\t\texpect(result).toBe(\"5,5\");" }, { "category": "component", "name": "registers a custom element from a template", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-hello span').textContent()).toBe('Hello World')" }, { "category": "component", "name": "renders template expressions", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "await run(\"set $name to '", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/ext/component.js", "body": "await run(\"set $name to 'hyperscript'\")\n\t\tawait html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-greet span').textContent()).toBe('Hello hyperscript!')\n\t\tawait evaluate(() => { delete window.$name })" }, { "category": "component", "name": "applies _ hyperscript to component instance", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-init span').textContent()).toBe('initialized')" }, { "category": "component", "name": "processes _ on inner elements", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('test-inner button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-inner span').textContent()).toBe('0')\n\t\tawait find('test-inner button').click()\n\t\tawait expect.poll(() => find('test-inner span').textContent()).toBe('1')" }, { "category": "component", "name": "reactively updates template expressions", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('test-reactive button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-reactive span').textContent()).toBe('Count: 0')\n\t\tawait find('test-reactive button').click()\n\t\tawait expect.poll(() => find('test-reactive span').textContent()).toBe('Count: 1')" }, { "category": "component", "name": "supports multiple independent instances", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t", "action": "find('#a button').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait find('#a button').click()\n\t\tawait expect.poll(() => find('#a span').textContent()).toBe('1')\n\t\tawait expect.poll(() => find('#b span').textContent()).toBe('0')" }, { "category": "component", "name": "reads attributes via @", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-attrs span').textContent()).toBe('42')" }, { "category": "component", "name": "supports #for loops in template", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-loop li').count()).toBe(3)\n\t\tawait expect.poll(() => find('test-loop li').first().textContent()).toBe('a')" }, { "category": "component", "name": "supports #if conditionals in template", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-cond span').textContent()).toBe('visible')" }, { "category": "component", "name": "substitutes slot content into template", "html": "\n\t\t\t\n\t\t\t

    Hello from slot

    \n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t

    Hello from slot

    \n\t\t`)\n\t\tawait expect.poll(() => find('test-card .card p').textContent()).toBe('Hello from slot')" }, { "category": "component", "name": "blocks processing of inner hyperscript until render", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "find('test-block span').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-block span').textContent()).toBe('click me')\n\t\tawait find('test-block span').click()\n\t\tawait expect.poll(() => find('test-block span').textContent()).toBe('ready')" }, { "category": "component", "name": "supports named slots", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\t

    My Title

    \n\t\t\t\t

    Default content

    \n\t\t\t\tFooter text\n\t\t\t
    \n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t\t\t

    My Title

    \n\t\t\t\t

    Default content

    \n\t\t\t\tFooter text\n\t\t\t
    \n\t\t`)\n\t\tawait expect.poll(() => find('test-named-slot header h1').textContent()).toBe('My Title')\n\t\tawait expect.poll(() => find('test-named-slot main p').textContent()).toBe('Default content')\n\t\tawait expect.poll(() => find('test-named-slot footer span').textContent()).toBe('Footer text')" }, { "category": "component", "name": "does not process slotted _ attributes prematurely", "html": "\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tbefore\n\t\t\t\t\n\t\t\t
    \n\t\t", "action": "find('test-slot-hs span').click()", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\tbefore\n\t\t\t\t\n\t\t\t
    \n\t\t`)\n\t\tawait expect.poll(() => find('test-slot-hs span').textContent()).toBe('before')\n\t\tawait find('test-slot-hs span').click()\n\t\tawait expect.poll(() => find('test-slot-hs span').textContent()).toBe('42')" }, { "category": "component", "name": "slotted content resolves ^var from outer scope, not component scope", "html": "\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\twaiting\n\t\t\t\t\n\t\t\t
    \n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t\t\twaiting\n\t\t\t\t\n\t\t\t
    \n\t\t`)\n\t\tawait expect.poll(() => find('test-scope-slot span').textContent()).toBe('from-outside')" }, { "category": "component", "name": "component isolation prevents ^var leaking inward", "html": "\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t
    \n\t\t", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t
    \n\t\t\t\t\n\t\t\t\t\n\t\t\t
    \n\t\t`)\n\t\tawait expect.poll(() => find('test-isolated span').textContent()).toBe('component-only')" }, { "category": "component", "name": "bind keeps ^var in sync with attribute changes", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/ext/component.js", "body": "await html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-bind-attr span').textContent()).toBe('5')\n\t\tawait evaluate(() => document.querySelector('test-bind-attr').setAttribute('data-count', '99'))\n\t\tawait expect.poll(() => find('test-bind-attr span').textContent()).toBe('99')" }, { "category": "component", "name": "attrs evaluates attribute as hyperscript in parent scope", "html": "\n\t\t\t\n\t\t\t\n\t\t", "action": "await run(\"set $stuff to ['", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/ext/component.js", "body": "await run(\"set $stuff to ['a', 'b', 'c']\")\n\t\tawait html(`\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-args li').count()).toBe(3)\n\t\tawait expect.poll(() => find('test-args li').first().textContent()).toBe('a')\n\t\tawait evaluate(() => { delete window.$stuff })" }, { "category": "component", "name": "attrs works with bind for reactive pass-through", "html": "\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t", "action": "find('button').click(); await run(\"set $count to 10\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_file", "file": "test/ext/component.js", "body": "await run(\"set $count to 10\")\n\t\tawait html(`\n\t\t\t\n\t\t\t\n\t\t\t\n\t\t`)\n\t\tawait expect.poll(() => find('test-args-bind span').textContent()).toBe('10')\n\t\tawait find('button').click()\n\t\tawait expect.poll(() => find('test-args-bind span').textContent()).toBe('11')" }, { "category": "component", "name": "attrs bind is bidirectional \u2014 inner changes flow outward", "html": "\n\t\t\t\n\t\t\t\n\t\t\t

    \n\t\t", "action": "find('test-args-bidir button').click(); await run(\"set $count to 10\"", "check": "", "async": true, "complexity": "evaluate", "source": "new_file", "file": "test/ext/component.js", "body": "await run(\"set $count to 10\")\n\t\tawait html(`\n\t\t\t\n\t\t\t\n\t\t\t

    \n\t\t`)\n\t\tawait expect.poll(() => find('test-args-bidir span').textContent()).toBe('10')\n\t\tawait expect.poll(() => find('p').textContent()).toBe('10')\n\t\tawait find('test-args-bidir button').click()\n\t\t// Inner ^count should update\n\t\tawait expect.poll(() => find('test-args-bidir span').textContent()).toBe('11')\n\t\t// Outer $count should also update via attrs write-back\n\t\tawait expect.poll(() => find('p').textContent(), { timeout: 2000 }).toBe('11')\n\t\tawait evaluate(() => { delete window.$count })" }, { "category": "add", "name": "supports async expressions in when clause", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "toHaveClass(/foo/)", "async": true, "complexity": "promise", "source": "new_in_common", "file": "test/commands/add.js", "body": "await evaluate(() => {\n\t\t\twindow.asyncCheck = function() {\n\t\t\t\treturn new Promise(r => setTimeout(() => r(true), 10));\n\t\t\t}\n\t\t});\n\t\tawait html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveClass(/foo/);" }, { "category": "add", "name": "when clause sets result to matched elements", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "toBeHidden()", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/add.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\t// d1 matches .yes, so result is not empty -> #none stays hidden\n\t\tawait expect(find('#none')).toBeHidden();" }, { "category": "add", "name": "when clause result is empty when nothing matches", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "toHaveAttribute('hidden')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/add.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"\"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\t// nothing matches .nope, so result is empty -> #none shown\n\t\tawait expect(find('#none')).not.toHaveAttribute('hidden');" }, { "category": "add", "name": "can add a value to an array", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1,2,3,4\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/add.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1,2,3,4\");" }, { "category": "add", "name": "can add a value to a set", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"2\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/add.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"2\");" }, { "category": "append", "name": "can append a value to a set", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/append.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"3\");" }, { "category": "default", "name": "can default possessive properties", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"bar\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"bar\");" }, { "category": "default", "name": "can default of-expression properties", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"bar\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"bar\");" }, { "category": "default", "name": "can default array elements", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"yes\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t`
    `\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"yes\");" }, { "category": "default", "name": "default array element respects existing value", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"existing\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t`
    `\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"existing\");" }, { "category": "default", "name": "default preserves zero", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"0\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");" }, { "category": "default", "name": "default overwrites empty string", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"fallback\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"fallback\");" }, { "category": "default", "name": "default preserves false", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"false\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"false\");" }, { "category": "default", "name": "can default style ref when unset", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveCSS('background-color', 'rgb(255, 0, 0)", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t`
    `\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('background-color', 'rgb(255, 0, 0)');" }, { "category": "default", "name": "default style ref preserves existing value", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveCSS('color', 'rgb(0, 0, 255)", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/default.js", "body": "await html(\n\t\t\t`
    `\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('color', 'rgb(0, 0, 255)');" }, { "category": "fetch", "name": "can do a simple fetch w/ json using JSON syntax", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText('{\"foo\":1}')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await mock('GET', '/test', '{\"foo\":1}', {status: 200, contentType: 'application/json'});\n\t\tawait html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText('{\"foo\":1}');" }, { "category": "fetch", "name": "throws on non-2xx response by default", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"caught\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await page.route('**/test', async (route) => {\n\t\t\tawait route.fulfill({ status: 404, body: 'not found' });\n\t\t});\n\t\tawait html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"caught\");" }, { "category": "fetch", "name": "do not throw passes through 404 response", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"the body\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await page.route('**/test', async (route) => {\n\t\t\tawait route.fulfill({ status: 404, body: 'the body' });\n\t\t});\n\t\tawait html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"the body\");" }, { "category": "fetch", "name": "don't throw passes through 404 response", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"the body\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await page.route('**/test', async (route) => {\n\t\t\tawait route.fulfill({ status: 404, body: 'the body' });\n\t\t});\n\t\tawait html('
    ');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"the body\");" }, { "category": "fetch", "name": "as response does not throw on 404", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"404\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await page.route('**/test', async (route) => {\n\t\t\tawait route.fulfill({ status: 404, body: 'not found' });\n\t\t});\n\t\tawait html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"404\");" }, { "category": "fetch", "name": "Response can be converted to JSON via as JSON", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"Joe\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/fetch.js", "body": "await page.route('**/test', async (route) => {\n\t\t\tawait route.fulfill({\n\t\t\t\tstatus: 200,\n\t\t\t\tcontentType: 'application/json',\n\t\t\t\tbody: JSON.stringify({ name: \"Joe\" })\n\t\t\t});\n\t\t});\n\t\tawait html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"Joe\");" }, { "category": "hide", "name": "can filter hide via the when clause", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "toHaveCSS('display', 'none'); toHaveCSS('display', 'block')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/hide.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveCSS('display', 'none');\n\t\tawait expect(find('#d2')).toHaveCSS('display', 'block');" }, { "category": "increment", "name": "can increment an array element", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"21\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/increment.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"21\");" }, { "category": "increment", "name": "can decrement an array element", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"19\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/increment.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"19\");" }, { "category": "increment", "name": "can increment a possessive property", "html": "
    5
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"6\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/increment.js", "body": "await html(\n\t\t\t`
    5
    `\n\t\t);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"6\");" }, { "category": "increment", "name": "can increment a property of expression", "html": "
    5
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"6\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/increment.js", "body": "await html(\n\t\t\t`
    5
    `\n\t\t);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"6\");" }, { "category": "increment", "name": "can increment a style ref", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"0.75\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/increment.js", "body": "await html(\n\t\t\t`
    `\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0.75\");" }, { "category": "js", "name": "handles rejected promises without hanging", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"boom\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/js.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"boom\");" }, { "category": "measure", "name": "can measure with possessive syntax", "html": "
    ", "action": "", "check": "toBe(89)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/measure.js", "body": "await html(\"
    \" +\n\t\t\t'
    ');\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tconst top = await evaluate(() => Math.round(window.measurement.top));\n\t\texpect(top).toBe(89);" }, { "category": "measure", "name": "can measure with of syntax", "html": "
    ", "action": "", "check": "toBe(89)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/measure.js", "body": "await html(\"
    \" +\n\t\t\t\"
    \");\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tconst top = await evaluate(() => Math.round(window.measurement.top));\n\t\texpect(top).toBe(89);" }, { "category": "pick", "name": "does not hang on zero-length regex matches", "html": "", "action": "", "check": "toContain(\"1\")", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(String.raw`pick matches of \"\\\\d*\" from haystack\n\t\t\tset window.test to it`, {locals: {haystack: \"a1b\"}});\n\t\tconst result = await evaluate(() => Array.from(window.test).map(m => m[0]));\n\t\texpect(result).toContain(\"1\");" }, { "category": "pick", "name": "can pick first n items", "html": "", "action": "", "check": "toEqual([10, 20, 30])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(`pick first 3 of arr\n\t\t\tset $test to it`, {locals: {arr: [10, 20, 30, 40, 50]}});\n\t\tconst result = await evaluate(() => window.$test);\n\t\texpect(result).toEqual([10, 20, 30]);" }, { "category": "pick", "name": "can pick last n items", "html": "", "action": "", "check": "toEqual([40, 50])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(`pick last 2 of arr\n\t\t\tset $test to it`, {locals: {arr: [10, 20, 30, 40, 50]}});\n\t\tconst result = await evaluate(() => window.$test);\n\t\texpect(result).toEqual([40, 50]);" }, { "category": "pick", "name": "can pick random item", "html": "", "action": "", "check": "toContain(result)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(`pick random of arr\n\t\t\tset $test to it`, {locals: {arr: [10, 20, 30]}});\n\t\tconst result = await evaluate(() => window.$test);\n\t\texpect([10, 20, 30]).toContain(result);" }, { "category": "pick", "name": "can pick random n items", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(`pick random 2 of arr\n\t\t\tset $test to it`, {locals: {arr: [10, 20, 30, 40, 50]}});\n\t\tconst result = await evaluate(() => window.$test);\n\t\texpect(result).toHaveLength(2);" }, { "category": "pick", "name": "can pick items using 'of' syntax", "html": "", "action": "", "check": "toEqual([11, 12])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "await run(`pick items 1 to 3 of arr\n\t\t\tset $test to it`, {locals: {arr: [10, 11, 12, 13, 14, 15, 16]}});\n\t\tconst result = await evaluate(() => window.$test);\n\t\texpect(result).toEqual([11, 12]);" }, { "category": "pick", "name": "can pick match using 'of' syntax", "html": "", "action": "", "check": "toEqual([\"32\"])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/pick.js", "body": "const haystack = \"The 32 quick brown foxes\";\n\t\tawait run(String.raw`pick match of \"\\\\d+\" of haystack\n\t\t\tset window.test to it`, {locals: {haystack}});\n\t\tconst result = await evaluate(() => [...window.test]);\n\t\texpect(result).toEqual([\"32\"]);" }, { "category": "put", "name": "put null into attribute removes it", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveAttribute('foo', 'bar'); toHaveAttribute('foo')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/put.js", "body": "await html(\"
    \");\n\t\tawait expect(find('#d1')).toHaveAttribute('foo', 'bar');\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).not.toHaveAttribute('foo');" }, { "category": "put", "name": "can put at start of an array", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1,2,3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/put.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1,2,3\");" }, { "category": "put", "name": "can put at end of an array", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1,2,3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/put.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1,2,3\");" }, { "category": "remove", "name": "can filter class removal via the when clause", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "toHaveClass(/highlight/); toHaveClass(/highlight/)", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/remove.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\t// d1 matches .old -> remove .highlight\n\t\tawait expect(find('#d1')).not.toHaveClass(/highlight/);\n\t\t// d2 does not match .old -> reverse (add .highlight, but it already has it)\n\t\tawait expect(find('#d2')).toHaveClass(/highlight/);" }, { "category": "remove", "name": "can remove CSS properties", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toBe(''); toBe('bold')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/remove.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tvar style = await evaluate(() => document.querySelector('#work-area div').style.color);\n\t\texpect(style).toBe('');\n\t\t// font-weight should remain\n\t\tvar fw = await evaluate(() => document.querySelector('#work-area div').style.fontWeight);\n\t\texpect(fw).toBe('bold');" }, { "category": "remove", "name": "can remove multiple CSS properties", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toBe(''); toBe(''); toBe('0.5')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/remove.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tvar color = await evaluate(() => document.querySelector('#work-area div').style.color);\n\t\tvar fw = await evaluate(() => document.querySelector('#work-area div').style.fontWeight);\n\t\tvar op = await evaluate(() => document.querySelector('#work-area div').style.opacity);\n\t\texpect(color).toBe('');\n\t\texpect(fw).toBe('');\n\t\texpect(op).toBe('0.5');" }, { "category": "remove", "name": "can remove a value from an array", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1,2,4\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/remove.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1,2,4\");" }, { "category": "remove", "name": "can remove a value from a set", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"2\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/remove.js", "body": "await html(`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"2\");" }, { "category": "repeat", "name": "basic raw for loop with null works", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"\");" }, { "category": "repeat", "name": "basic property for loop works", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"123\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"123\");" }, { "category": "repeat", "name": "bottom-tested repeat until", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"3\");" }, { "category": "repeat", "name": "bottom-tested repeat while", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"3\");" }, { "category": "repeat", "name": "bottom-tested loop always runs at least once", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1\");" }, { "category": "repeat", "name": "break exits a simple repeat loop", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"3\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"3\");" }, { "category": "repeat", "name": "continue skips rest of iteration in simple repeat loop", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"1245\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"1245\");" }, { "category": "repeat", "name": "break exits a for-in loop", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"123\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"123\");" }, { "category": "repeat", "name": "break exits a while loop", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"5\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t`
    `);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"5\");" }, { "category": "repeat", "name": "for loop over undefined skips without error", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"done\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/repeat.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"done\");" }, { "category": "settle", "name": "can settle a collection of elements", "html": "
    ", "action": "find('#trigger').dispatchEvent('click')", "check": "", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/settle.js", "body": "test.setTimeout(5000);\n\t\tawait html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#trigger').dispatchEvent('click');\n\t\tawait expect(find('.item').first()).toHaveClass(/done/);\n\t\tawait expect(find('.item').nth(1)).toHaveClass(/done/);" }, { "category": "show", "name": "the result in a when clause refers to previous command result, not element being tested", "html": "
    in me when the result is 'found'\\\">
    ", "action": "find('div').dispatchEvent('click')", "check": "toBeVisible(); toBeVisible()", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/show.js", "body": "await html(\n\t\t\t\"
    in me when the result is 'found'\\\">\" +\n\t\t\t\"\" +\n\t\t\t\"\" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('#s1')).toBeVisible();\n\t\tawait expect(find('#s2')).toBeVisible();" }, { "category": "show", "name": "the result after show...when is the matched elements", "html": "
    in me when its textContent is 'yes' if the result is empty put 'none' into #out else put 'some' into #out\\\">

    yes

    no

    --
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"some\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/show.js", "body": "await html(\n\t\t\t\"
    in me when its textContent is 'yes' \" +\n\t\t\t\" if the result is empty put 'none' into #out \" +\n\t\t\t\" else put 'some' into #out\\\">\" +\n\t\t\t\"

    yes

    \" +\n\t\t\t\"

    no

    \" +\n\t\t\t\"--\" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('#out')).toHaveText(\"some\");" }, { "category": "toggle", "name": "can toggle between two attribute values", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveAttribute('data-state', 'active'); toHaveAttribute('data-state', 'inactive'); toHaveAttribute('data-state', 'active')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveAttribute('data-state', 'active');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveAttribute('data-state', 'inactive');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveAttribute('data-state', 'active');" }, { "category": "toggle", "name": "can toggle between different attributes", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveAttribute('enabled', 'true'); toHaveAttribute('disabled', 'true'); toHaveAttribute('enabled', 'true')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveAttribute('enabled', 'true');\n\t\tawait find('div').dispatchEvent('click');\n\t\texpect(await find('div').getAttribute('enabled')).toBeNull();\n\t\tawait expect(find('div')).toHaveAttribute('disabled', 'true');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveAttribute('enabled', 'true');\n\t\texpect(await find('div').getAttribute('disabled')).toBeNull();" }, { "category": "toggle", "name": "can toggle visibility", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveCSS('visibility', 'visible'); toHaveCSS('visibility', 'hidden'); toHaveCSS('visibility', 'visible')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'visible');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'hidden');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'visible');" }, { "category": "toggle", "name": "can toggle opacity w/ my", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveCSS('opacity', '1'); toHaveCSS('opacity', '0'); toHaveCSS('opacity', '1')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveCSS('opacity', '1');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('opacity', '0');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('opacity', '1');" }, { "category": "toggle", "name": "can toggle visibility w/ my", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveCSS('visibility', 'visible'); toHaveCSS('visibility', 'hidden'); toHaveCSS('visibility', 'visible')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'visible');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'hidden');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('visibility', 'visible');" }, { "category": "toggle", "name": "can toggle opacity on other elt", "html": "
    ", "action": "", "check": "toHaveCSS('opacity', '1'); toHaveCSS('opacity', '0'); toHaveCSS('opacity', '1')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('#d2')).toHaveCSS('opacity', '1');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveCSS('opacity', '0');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveCSS('opacity', '1');" }, { "category": "toggle", "name": "can toggle visibility on other elt", "html": "
    ", "action": "", "check": "toHaveCSS('visibility', 'visible'); toHaveCSS('visibility', 'hidden'); toHaveCSS('visibility', 'visible')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('#d2')).toHaveCSS('visibility', 'visible');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveCSS('visibility', 'hidden');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveCSS('visibility', 'visible');" }, { "category": "toggle", "name": "can toggle *display between two values", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveCSS('display', 'none'); toHaveCSS('display', 'flex'); toHaveCSS('display', 'none')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveCSS('display', 'none');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('display', 'flex');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('display', 'none');" }, { "category": "toggle", "name": "can toggle *opacity between three values", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveCSS('opacity', '0'); toHaveCSS('opacity', '0.5'); toHaveCSS('opacity', '1'); toHaveCSS('opacity', '0')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveCSS('opacity', '0');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('opacity', '0.5');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('opacity', '1');\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveCSS('opacity', '0');" }, { "category": "toggle", "name": "can toggle a global variable between two values", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toBe('edit'); toBe('preview'); toBe('edit')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tvar val = await evaluate(() => window.$mode);\n\t\texpect(val).toBe('edit');\n\t\tawait find('div').dispatchEvent('click');\n\t\tval = await evaluate(() => window.$mode);\n\t\texpect(val).toBe('preview');\n\t\tawait find('div').dispatchEvent('click');\n\t\tval = await evaluate(() => window.$mode);\n\t\texpect(val).toBe('edit');" }, { "category": "toggle", "name": "can toggle a global variable between three values", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click'); find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toBe('a'); toBe('b'); toBe('c'); toBe('a')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/commands/toggle.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tvar val = await evaluate(() => window.$state);\n\t\texpect(val).toBe('a');\n\t\tawait find('div').dispatchEvent('click');\n\t\tval = await evaluate(() => window.$state);\n\t\texpect(val).toBe('b');\n\t\tawait find('div').dispatchEvent('click');\n\t\tval = await evaluate(() => window.$state);\n\t\texpect(val).toBe('c');\n\t\tawait find('div').dispatchEvent('click');\n\t\tval = await evaluate(() => window.$state);\n\t\texpect(val).toBe('a');" }, { "category": "transition", "name": "can transition on another element with of syntax", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html('
    ');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#foo')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition on another element with possessive", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html('
    ');\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#foo')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition on another element with it", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html(\"
    \");\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#foo')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition with a custom transition string", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html(\n\t\t\t'
    '\n\t\t);\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('#foo')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition a single property on form using style ref", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px'); toBe('0px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html(\"
    \");\n\t\tconst fromValue = await clickAndReadStyle(evaluate, '#work-area form', 'width')\n\t\texpect(fromValue).toBe('0px');\n\t\tawait expect(find('form')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition a single property on current element with the my prefix using style ref", "html": "
    ", "action": "", "check": "toHaveCSS('width', '100px'); toBe('0px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html(\"
    \");\n\t\tconst fromValue = await clickAndReadStyle(evaluate, '#work-area div', 'width')\n\t\texpect(fromValue).toBe('0px');\n\t\tawait expect(find('div')).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition on query ref with possessive", "html": "", "action": "evaluate({...})", "check": "", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await evaluate(() => {\n\t\t\tvar wa = document.getElementById('work-area');\n\t\t\twa.innerHTML = '
    ';\n\t\t\twa.querySelector('div').setAttribute('_', \"on click transition the next
    's *width from 0px to 100px\");\n\t\t\t_hyperscript.process(wa);\n\t\t});\n\t\tawait find('div').first().dispatchEvent('click');\n\t\tawait expect(find('div').nth(1)).toHaveCSS('width', '100px');" }, { "category": "transition", "name": "can transition on query ref with of syntax", "html": "
    from 0px to 100px\\\">
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveCSS('width', '100px')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/commands/transition.js", "body": "await html(\"
    from 0px to 100px\\\">
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('span')).toHaveCSS('width', '100px');" }, { "category": "def", "name": "exit stops execution mid-function", "html": "", "action": "", "check": "", "async": true, "complexity": "script-tag", "source": "new_in_common", "file": "test/features/def.js", "body": "await html(\"\")\n\t\tconst result = await evaluate(() => foo())\n\t\texpect(result).toBeNull()" }, { "category": "def", "name": "can return without a value", "html": "", "action": "", "check": "", "async": true, "complexity": "script-tag", "source": "new_in_common", "file": "test/features/def.js", "body": "await html(\"\")\n\t\tconst result = await evaluate(() => foo())\n\t\texpect(result).toBeNull()" }, { "category": "on", "name": "handles custom events with null detail", "html": "
    ", "action": "evaluate({...})", "check": "toHaveText('no-detail')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/features/on.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t)\n\t\tawait evaluate(() => {\n\t\t\tdocument.querySelector('#work-area #d1').dispatchEvent(new CustomEvent(\"myEvent\"))\n\t\t})\n\t\tawait expect(find('#d1')).toHaveText('no-detail')" }, { "category": "on", "name": "on first click fires only once", "html": "
    0
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click')", "check": "toHaveText('1'); toHaveText('1')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/features/on.js", "body": "await html(\"
    0
    \")\n\t\tawait find('div').dispatchEvent('click')\n\t\tawait expect(find('div')).toHaveText('1')\n\t\tawait find('div').dispatchEvent('click')\n\t\tawait expect(find('div')).toHaveText('1')" }, { "category": "on", "name": "caught exceptions do not trigger 'exception' event", "html": "", "action": "find('button').dispatchEvent('click')", "check": "toHaveText('foo')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/features/on.js", "body": "await html(\n\t\t\t\"\"\n\t\t)\n\t\tawait find('button').dispatchEvent('click')\n\t\tawait expect(find('button')).toHaveText('foo')" }, { "category": "on", "name": "rethrown exceptions trigger 'exception' event", "html": "", "action": "find('button').dispatchEvent('click')", "check": "toHaveText('bar')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/features/on.js", "body": "await html(\n\t\t\t\"\"\n\t\t)\n\t\tawait find('button').dispatchEvent('click')\n\t\tawait expect(find('button')).toHaveText('bar')" }, { "category": "on", "name": "can ignore when target doesn\\'t exist", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText('clicked')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/features/on.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t)\n\t\tawait find('div').dispatchEvent('click')\n\t\tawait expect(find('div')).toHaveText('clicked')" }, { "category": "socket", "name": "parses socket with absolute ws:// URL", "html": "", "action": "", "check": "toBe('ws://localhost:1234/ws')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/features/socket.js", "body": "var result = await evaluate(() => {\n\t\t\tvar urls = [];\n\t\t\tvar OrigWS = window.WebSocket;\n\t\t\twindow.WebSocket = function(url) {\n\t\t\t\turls.push(url);\n\t\t\t\treturn { onmessage: null, send: function(){}, addEventListener: function(){}, close: function(){} };\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar script = document.createElement('script');\n\t\t\t\tscript.type = 'text/hyperscript';\n\t\t\t\tscript.textContent = 'socket MySocket ws://localhost:1234/ws end';\n\t\t\t\tdocument.body.appendChild(script);\n\t\t\t\t_hyperscript.processNode(script);\n\t\t\t\treturn { url: urls[0], error: null };\n\t\t\t} catch (e) {\n\t\t\t\treturn { url: null, error: e.message };\n\t\t\t} finally {\n\t\t\t\twindow.WebSocket = OrigWS;\n\t\t\t}\n\t\t});\n\t\texpect(result.error).toBeNull();\n\t\texpect(result.url).toBe('ws://localhost:1234/ws');" }, { "category": "socket", "name": "converts relative URL to wss:// on https pages", "html": "", "action": "", "check": "toBe('wss://localhost/my-ws')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/features/socket.js", "body": "await page.route('https://localhost/test', async route => {\n\t\t\tawait route.fulfill({\n\t\t\t\tstatus: 200,\n\t\t\t\tcontentType: 'text/html',\n\t\t\t\tbody: '',\n\t\t\t})\n\t\t})\n\t\tawait page.goto('https://localhost/test')\n\t\tawait page.addScriptTag({\n\t\t\tpath: path.join(__dirname, '../.bundle/_hyperscript.js')\n\t\t})\n\t\tawait page.waitForFunction(() => typeof _hyperscript !== 'undefined')\n\n\t\tvar result = await page.evaluate(() => {\n\t\t\tvar urls = [];\n\t\t\tvar OrigWS = window.WebSocket;\n\t\t\twindow.WebSocket = function(url) {\n\t\t\t\turls.push(url);\n\t\t\t\treturn { onmessage: null, send: function(){}, addEventListener: function(){}, close: function(){} };\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar script = document.createElement('script');\n\t\t\t\tscript.type = 'text/hyperscript';\n\t\t\t\tscript.textContent = 'socket RelSocket /my-ws end';\n\t\t\t\tdocument.body.appendChild(script);\n\t\t\t\t_hyperscript.processNode(script);\n\t\t\t\treturn { url: urls[0], error: null };\n\t\t\t} catch (e) {\n\t\t\t\treturn { url: null, error: e.message };\n\t\t\t} finally {\n\t\t\t\twindow.WebSocket = OrigWS;\n\t\t\t}\n\t\t});\n\t\texpect(result.error).toBeNull();\n\t\texpect(result.url).toBe('wss://localhost/my-ws');" }, { "category": "socket", "name": "converts relative URL to ws:// on http pages", "html": "", "action": "", "check": "toBe('ws://localhost/my-ws')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/features/socket.js", "body": "await page.route('http://localhost/test', async route => {\n\t\t\tawait route.fulfill({\n\t\t\t\tstatus: 200,\n\t\t\t\tcontentType: 'text/html',\n\t\t\t\tbody: '',\n\t\t\t})\n\t\t})\n\t\tawait page.goto('http://localhost/test')\n\t\tawait page.addScriptTag({\n\t\t\tpath: path.join(__dirname, '../.bundle/_hyperscript.js')\n\t\t})\n\t\tawait page.waitForFunction(() => typeof _hyperscript !== 'undefined')\n\n\t\tvar result = await page.evaluate(() => {\n\t\t\tvar urls = [];\n\t\t\tvar OrigWS = window.WebSocket;\n\t\t\twindow.WebSocket = function(url) {\n\t\t\t\turls.push(url);\n\t\t\t\treturn { onmessage: null, send: function(){}, addEventListener: function(){}, close: function(){} };\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar script = document.createElement('script');\n\t\t\t\tscript.type = 'text/hyperscript';\n\t\t\t\tscript.textContent = 'socket RelSocket /my-ws end';\n\t\t\t\tdocument.body.appendChild(script);\n\t\t\t\t_hyperscript.processNode(script);\n\t\t\t\treturn { url: urls[0], error: null };\n\t\t\t} catch (e) {\n\t\t\t\treturn { url: null, error: e.message };\n\t\t\t} finally {\n\t\t\t\twindow.WebSocket = OrigWS;\n\t\t\t}\n\t\t});\n\t\texpect(result.error).toBeNull();\n\t\texpect(result.url).toBe('ws://localhost/my-ws');" }, { "category": "socket", "name": "namespaced sockets work", "html": "", "action": "", "check": "toBe(true)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/features/socket.js", "body": "var result = await evaluate(() => {\n\t\t\tvar OrigWS = window.WebSocket;\n\t\t\twindow.WebSocket = function(url) {\n\t\t\t\treturn { onmessage: null, send: function(){}, addEventListener: function(){}, close: function(){} };\n\t\t\t};\n\t\t\ttry {\n\t\t\t\tvar script = document.createElement('script');\n\t\t\t\tscript.type = 'text/hyperscript';\n\t\t\t\tscript.textContent = 'socket MyApp.chat ws://localhost/ws end';\n\t\t\t\tdocument.body.appendChild(script);\n\t\t\t\t_hyperscript.processNode(script);\n\t\t\t\treturn { hasRaw: MyApp && MyApp.chat && typeof MyApp.chat.raw !== 'undefined', error: null };\n\t\t\t} catch (e) {\n\t\t\t\treturn { hasRaw: false, error: e.message };\n\t\t\t} finally {\n\t\t\t\twindow.WebSocket = OrigWS;\n\t\t\t}\n\t\t});\n\t\texpect(result.error).toBeNull();\n\t\texpect(result.hasRaw).toBe(true);" }, { "category": "bootstrap", "name": "hyperscript can have more than one action", "html": "
    ", "action": "", "check": "toHaveClass(/foo/); toHaveClass(/blah/)", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div:nth-of-type(2)').dispatchEvent('click');\n\t\tawait expect(find('#bar')).toHaveClass(/foo/);\n\t\tawait expect(find('#bar')).not.toHaveClass(/blah/);\n\t\tawait expect(find('div:nth-of-type(2)')).not.toHaveClass(/foo/);\n\t\tawait expect(find('div:nth-of-type(2)')).toHaveClass(/blah/);" }, { "category": "bootstrap", "name": "stores state on elt._hyperscript", "html": "
    ", "action": "", "check": "toBe(true); toBe(true); toBe(true)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tvar state = await evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\treturn {\n\t\t\t\thasProperty: '_hyperscript' in div,\n\t\t\t\tinitialized: div._hyperscript?.initialized,\n\t\t\t\thasScriptHash: typeof div._hyperscript?.scriptHash === 'number',\n\t\t\t};\n\t\t});\n\t\texpect(state.hasProperty).toBe(true);\n\t\texpect(state.initialized).toBe(true);\n\t\texpect(state.hasScriptHash).toBe(true);" }, { "category": "bootstrap", "name": "skips reinitialization if script unchanged", "html": "
    ", "action": "evaluate({...}); div.click()", "check": "toBe(1)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\t// Process again \u2014 should be a no-op\n\t\tvar clickCount = await evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\tvar count = 0;\n\t\t\tdiv.addEventListener('click', () => count++);\n\t\t\t_hyperscript.processNode(div);\n\t\t\tdiv.click();\n\t\t\treturn count;\n\t\t});\n\t\t// Only 1 click handler should fire (the original), not 2\n\t\texpect(clickCount).toBe(1);" }, { "category": "bootstrap", "name": "reinitializes if script attribute changes", "html": "
    ", "action": "find('div').dispatchEvent('click'); find('div').dispatchEvent('click'); evaluate({...})", "check": "toHaveClass(/foo/); toHaveClass(/bar/)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveClass(/foo/);\n\n\t\t// Change the script and reprocess (simulates morph swap)\n\t\tawait evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\tdiv.setAttribute('_', 'on click add .bar');\n\t\t\t_hyperscript.processNode(div);\n\t\t});\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveClass(/bar/);" }, { "category": "bootstrap", "name": "cleanup removes event listeners on the element", "html": "
    ", "action": "find('div').dispatchEvent('click'); evaluate({...}); div.click()", "check": "toHaveClass(/foo/); toHaveClass(/foo/)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveClass(/foo/);\n\n\t\t// Cleanup and verify listener is gone\n\t\tawait evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\t_hyperscript.cleanup(div);\n\t\t\tdiv.classList.remove('foo');\n\t\t\tdiv.click();\n\t\t});\n\t\tawait expect(find('div')).not.toHaveClass(/foo/);" }, { "category": "bootstrap", "name": "cleanup removes cross-element event listeners", "html": "
    ", "action": "find('#source').dispatchEvent('click'); evaluate({...}); source.click()", "check": "toHaveClass(/foo/); toBe(true)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\n\t\t// Verify the cross-element listener works\n\t\tawait find('#source').dispatchEvent('click');\n\t\tawait expect(find('#target')).toHaveClass(/foo/);\n\n\t\t// Cleanup target and verify listener on source is removed\n\t\tvar listenerRemoved = await evaluate(() => {\n\t\t\tvar source = document.getElementById('source');\n\t\t\tvar target = document.getElementById('target');\n\t\t\t_hyperscript.cleanup(target);\n\t\t\ttarget.classList.remove('foo');\n\t\t\tsource.click();\n\t\t\t// If cleanup worked, target should NOT get .foo again\n\t\t\treturn !target.classList.contains('foo');\n\t\t});\n\t\texpect(listenerRemoved).toBe(true);" }, { "category": "bootstrap", "name": "cleanup tracks listeners in elt._hyperscript", "html": "
    ", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tvar info = await evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\treturn {\n\t\t\t\thasListeners: Array.isArray(div._hyperscript.listeners),\n\t\t\t\tlistenerCount: div._hyperscript.listeners.length,\n\t\t\t};\n\t\t});\n\t\texpect(info.hasListeners).toBe(true);\n\t\texpect(info.listenerCount).toBeGreaterThan(0);" }, { "category": "bootstrap", "name": "cleanup clears elt._hyperscript", "html": "
    ", "action": "", "check": "toBe(false)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tvar hasState = await evaluate(() => {\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\t_hyperscript.cleanup(div);\n\t\t\treturn '_hyperscript' in div;\n\t\t});\n\t\texpect(hasState).toBe(false);" }, { "category": "bootstrap", "name": "sets data-hyperscript-powered on initialized elements", "html": "
    ", "action": "", "check": "toHaveAttribute('data-hyperscript-powered', 'true')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveAttribute('data-hyperscript-powered', 'true');" }, { "category": "bootstrap", "name": "cleanup removes data-hyperscript-powered", "html": "
    ", "action": "", "check": "toHaveAttribute('data-hyperscript-powered', 'true')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tawait expect(find('div')).toHaveAttribute('data-hyperscript-powered', 'true');\n\t\tawait evaluate(() => {\n\t\t\t_hyperscript.cleanup(document.querySelector('#work-area div'));\n\t\t});\n\t\texpect(await find('div').getAttribute('data-hyperscript-powered')).toBeNull();" }, { "category": "bootstrap", "name": "fires hyperscript:before:init and hyperscript:after:init", "html": "", "action": "evaluate({...})", "check": "toEqual(['before:init', 'after:init'])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "var events = await evaluate(() => {\n\t\t\tvar events = [];\n\t\t\tvar wa = document.getElementById('work-area');\n\t\t\twa.addEventListener('hyperscript:before:init', () => events.push('before:init'));\n\t\t\twa.addEventListener('hyperscript:after:init', () => events.push('after:init'));\n\t\t\twa.innerHTML = \"
    \";\n\t\t\t_hyperscript.processNode(wa);\n\t\t\treturn events;\n\t\t});\n\t\texpect(events).toEqual(['before:init', 'after:init']);" }, { "category": "bootstrap", "name": "hyperscript:before:init can cancel initialization", "html": "", "action": "", "check": "toBe(false); toBe(false)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "var result = await evaluate(() => {\n\t\t\tvar wa = document.getElementById('work-area');\n\t\t\twa.addEventListener('hyperscript:before:init', (e) => e.preventDefault(), { once: true });\n\t\t\twa.innerHTML = \"
    \";\n\t\t\t_hyperscript.processNode(wa);\n\t\t\tvar div = wa.querySelector('div');\n\t\t\treturn {\n\t\t\t\tinitialized: !!div._hyperscript?.initialized,\n\t\t\t\thasPowered: div.hasAttribute('data-hyperscript-powered'),\n\t\t\t};\n\t\t});\n\t\texpect(result.initialized).toBe(false);\n\t\texpect(result.hasPowered).toBe(false);" }, { "category": "bootstrap", "name": "fires hyperscript:before:cleanup and hyperscript:after:cleanup", "html": "
    ", "action": "", "check": "toEqual(['before:cleanup', 'after:cleanup'])", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "await html(\"
    \");\n\t\tvar events = await evaluate(() => {\n\t\t\tvar events = [];\n\t\t\tvar div = document.querySelector('#work-area div');\n\t\t\tdiv.addEventListener('hyperscript:before:cleanup', () => events.push('before:cleanup'));\n\t\t\tdiv.addEventListener('hyperscript:after:cleanup', () => events.push('after:cleanup'));\n\t\t\t_hyperscript.cleanup(div);\n\t\t\treturn events;\n\t\t});\n\t\texpect(events).toEqual(['before:cleanup', 'after:cleanup']);" }, { "category": "bootstrap", "name": "logAll config logs events to console", "html": "", "action": "evaluate({...})", "check": "toBe(true)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/core/bootstrap.js", "body": "var logged = await evaluate(() => {\n\t\t\tvar logs = [];\n\t\t\tvar origLog = console.log;\n\t\t\tconsole.log = (...args) => logs.push(args[0]);\n\t\t\t_hyperscript.config.logAll = true;\n\t\t\ttry {\n\t\t\t\tvar wa = document.getElementById('work-area');\n\t\t\t\twa.innerHTML = \"
    \";\n\t\t\t\t_hyperscript.processNode(wa);\n\t\t\t} finally {\n\t\t\t\t_hyperscript.config.logAll = false;\n\t\t\t\tconsole.log = origLog;\n\t\t\t}\n\t\t\treturn logs.some(l => typeof l === 'string' && l.includes('hyperscript:'));\n\t\t});\n\t\texpect(logged).toBe(true);" }, { "category": "parser", "name": "can have comments in attributes (triple dash)", "html": "
    ", "action": "find('div').dispatchEvent('click')", "check": "toHaveText(\"clicked\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/parser.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"clicked\");" }, { "category": "parser", "name": "recovers across feature boundaries and reports all errors", "html": "
    ", "action": "", "check": "toBe(false)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/parser.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\t// Element should not execute at all (no features installed)\n\t\tvar powered = await evaluate(() => document.querySelector('#d1').hasAttribute('data-hyperscript-powered'));\n\t\texpect(powered).toBe(false);" }, { "category": "parser", "name": "recovers across multiple feature errors", "html": "
    ", "action": "", "check": "toBe(false)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/core/parser.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\t// Element should not execute \u2014 errors prevent all features\n\t\tvar powered = await evaluate(() => document.querySelector('#d1').hasAttribute('data-hyperscript-powered'));\n\t\texpect(powered).toBe(false);" }, { "category": "parser", "name": "fires hyperscript:parse-error event with all errors", "html": "", "action": "evaluate({...})", "check": "toBe(2)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/core/parser.js", "body": "var errorCount = await evaluate(() => {\n\t\t\tvar div = document.createElement('div');\n\t\t\tdiv.setAttribute('_', 'on click blargh end on mouseenter also_bad');\n\t\t\tvar count = 0;\n\t\t\tdiv.addEventListener('hyperscript:parse-error', (e) => {\n\t\t\t\tcount = e.detail.errors.length;\n\t\t\t});\n\t\t\tdocument.body.appendChild(div);\n\t\t\t_hyperscript.processNode(div);\n\t\t\treturn count;\n\t\t});\n\t\texpect(errorCount).toBe(2);" }, { "category": "parser", "name": "element-level isolation still works with error recovery", "html": "
    ", "action": "find('#d2').dispatchEvent('click')", "check": "toHaveText(\"clicked\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/parser.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\t\"
    \" +\n\t\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#d2').dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveText(\"clicked\");" }, { "category": "parser", "name": "_hyperscript() evaluate API still throws on first error", "html": "", "action": "", "check": "", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/parser.js", "body": "var msg = await error(\"add - to\");\n\t\texpect(msg).toMatch(/^Expected either a class reference or attribute expression/);" }, { "category": "parser", "name": "parse error at EOF on trailing newline does not crash", "html": "", "action": "", "check": "", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/core/parser.js", "body": "// source ending with \\n means last line is empty; EOF token has no .line\n\t\tvar result = await evaluate((src) => {\n\t\t\ttry {\n\t\t\t\t_hyperscript(src);\n\t\t\t\treturn \"no-error\";\n\t\t\t} catch (e) {\n\t\t\t\tif (e instanceof RangeError) return \"RangeError: \" + e.message;\n\t\t\t\treturn \"ok: \" + typeof e.message;\n\t\t\t}\n\t\t}, \"set x to\\n\");\n\t\texpect(result).toMatch(/^ok:/);" }, { "category": "scoping", "name": "element scoped variables span features w/short syntax", "html": "
    ", "action": "find('#d1').dispatchEvent('click'); find('#d1').dispatchEvent('click')", "check": "toHaveAttribute('out', '10')", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/core/scoping.js", "body": "await html(\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveAttribute('out', '10');" }, { "category": "asExpression", "name": "converts value as Boolean", "html": "", "action": "await run(\"1 as Boolean\"; await run(\"0 as Boolean\"; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "expect(await run(\"1 as Boolean\")).toBe(true)\n\t\texpect(await run(\"0 as Boolean\")).toBe(false)\n\t\texpect(await run(\"'' as Boolean\")).toBe(false)\n\t\texpect(await run(\"'hello' as Boolean\")).toBe(true)" }, { "category": "asExpression", "name": "can use the a modifier if you like", "html": "", "action": "", "check": "toBe(new Date(1)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst r = _hyperscript(\"1 as a Date\")\n\t\t\treturn r.getTime()\n\t\t})\n\t\texpect(result).toBe(new Date(1).getTime())" }, { "category": "asExpression", "name": "parses string as JSON to object", "html": "", "action": "await run('\\'", "check": "toBe(\"bar\")", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run('\\'{\"foo\":\"bar\"}\\' as JSON')\n\t\texpect(result[\"foo\"]).toBe(\"bar\")" }, { "category": "asExpression", "name": "converts value as JSONString", "html": "", "action": "await run(\"{foo:'", "check": "toBe('{\"foo\":\"bar\"}')", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"{foo:'bar'} as JSONString\")\n\t\texpect(result).toBe('{\"foo\":\"bar\"}')" }, { "category": "asExpression", "name": "pipe operator chains conversions", "html": "", "action": "await run(\"{foo:'", "check": "toBe(\"bar\")", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"{foo:'bar'} as JSONString | JSON\")\n\t\texpect(result[\"foo\"]).toBe(\"bar\")" }, { "category": "asExpression", "name": "can use the an modifier if you'd like", "html": "", "action": "await run('\\'", "check": "toBe(\"bar\")", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run('\\'{\"foo\":\"bar\"}\\' as an Object')\n\t\texpect(result[\"foo\"]).toBe(\"bar\")" }, { "category": "asExpression", "name": "collects duplicate text inputs into an array", "html": "", "action": "evaluate({...})", "check": "toEqual([\"alpha\", \"beta\", \"gamma\"])", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst node = document.createElement(\"form\")\n\t\t\tnode.innerHTML = `\n\t\t\t\t\n\t\t\t\t\n\t\t\t\t`\n\t\t\treturn _hyperscript(\"x as Values\", { locals: { x: node } })\n\t\t})\n\t\texpect(result.tag).toEqual([\"alpha\", \"beta\", \"gamma\"])" }, { "category": "asExpression", "name": "converts multiple selects with programmatically changed selections", "html": "", "action": "evaluate({...})", "check": "toBe(\"cat\"); toBe(\"raccoon\")", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst node = document.createElement(\"form\")\n\t\t\tnode.innerHTML = `\n\t\t\t\t`\n\t\t\tvar select = node.querySelector(\"select\")\n\t\t\tselect.options[0].selected = false // deselect dog\n\t\t\tselect.options[1].selected = true // select cat\n\t\t\treturn _hyperscript(\"x as Values\", { locals: { x: node } })\n\t\t})\n\t\texpect(result.animal[0]).toBe(\"cat\")\n\t\texpect(result.animal[1]).toBe(\"raccoon\")" }, { "category": "asExpression", "name": "converts a form element into Values | JSONString", "html": "", "action": "evaluate({...})", "check": "toBe('{\"firstName\":\"John\",\"lastName\":\"Connor\",\"areaCode\":\"213\",\"phone\":\"555-1212\"}')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst node = document.createElement(\"form\")\n\t\t\tnode.innerHTML = `\n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
    `\n\t\t\treturn _hyperscript(\"x as Values | JSONString\", { locals: { x: node } })\n\t\t})\n\t\texpect(result).toBe('{\"firstName\":\"John\",\"lastName\":\"Connor\",\"areaCode\":\"213\",\"phone\":\"555-1212\"}')" }, { "category": "asExpression", "name": "converts a form element into Values | FormEncoded", "html": "", "action": "evaluate({...})", "check": "toBe('firstName=John&lastName=Connor&areaCode=213&phone=555-1212')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst node = document.createElement(\"form\")\n\t\t\tnode.innerHTML = `\n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t
    \n\t\t\t\t\t\n\t\t\t\t\t\n\t\t\t\t
    `\n\t\t\treturn _hyperscript(\"x as Values | FormEncoded\", { locals: { x: node } })\n\t\t})\n\t\texpect(result).toBe('firstName=John&lastName=Connor&areaCode=213&phone=555-1212')" }, { "category": "asExpression", "name": "converts array as Set", "html": "", "action": "", "check": "toBe(true); toBe(3)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst r = _hyperscript(\"[1,2,2,3] as Set\")\n\t\t\treturn { isSet: r instanceof Set, size: r.size }\n\t\t})\n\t\texpect(result.isSet).toBe(true)\n\t\texpect(result.size).toBe(3)" }, { "category": "asExpression", "name": "converts object as Map", "html": "", "action": "", "check": "toBe(true); toBe(1); toBe(2)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await evaluate(() => {\n\t\t\tconst r = _hyperscript(\"{a:1, b:2} as Map\")\n\t\t\treturn { isMap: r instanceof Map, a: r.get(\"a\"), size: r.size }\n\t\t})\n\t\texpect(result.isMap).toBe(true)\n\t\texpect(result.a).toBe(1)\n\t\texpect(result.size).toBe(2)" }, { "category": "asExpression", "name": "converts object as Keys", "html": "", "action": "await run(\"{a:1, b:2} as Keys\"", "check": "toEqual([\"a\", \"b\"])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"{a:1, b:2} as Keys\", {})\n\t\texpect(result).toEqual([\"a\", \"b\"])" }, { "category": "asExpression", "name": "converts object as Entries", "html": "", "action": "await run(\"{a:1} as Entries\"", "check": "toEqual([[\"a\", 1]])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"{a:1} as Entries\", {})\n\t\texpect(result).toEqual([[\"a\", 1]])" }, { "category": "asExpression", "name": "converts array as Reversed", "html": "", "action": "await run(\"[1,2,3] as Reversed\"", "check": "toEqual([3, 2, 1])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"[1,2,3] as Reversed\", {})\n\t\texpect(result).toEqual([3, 2, 1])" }, { "category": "asExpression", "name": "converts array as Unique", "html": "", "action": "await run(\"[1,2,2,3,3] as Unique\"", "check": "toEqual([1, 2, 3])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"[1,2,2,3,3] as Unique\", {})\n\t\texpect(result).toEqual([1, 2, 3])" }, { "category": "asExpression", "name": "converts nested array as Flat", "html": "", "action": "await run(\"[[1,2],[3,4]] as Flat\"", "check": "toEqual([1, 2, 3, 4])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/asExpression.js", "body": "const result = await run(\"[[1,2],[3,4]] as Flat\", {})\n\t\texpect(result).toEqual([1, 2, 3, 4])" }, { "category": "attributeRef", "name": "attributeRef can be set through possessive w/ short syntax", "html": "
    ", "action": "find('#arDiv').dispatchEvent('click')", "check": "toBe(\"blue\")", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/attributeRef.js", "body": "await html(\"
    \")\n\t\tawait find('#arDiv').dispatchEvent('click')\n\t\tconst value = await evaluate(() => document.getElementById('arDiv').getAttribute(\"data-foo\"))\n\t\texpect(value).toBe(\"blue\")" }, { "category": "closest", "name": "attributes can be set via the closest expression 2", "html": "
    ", "action": "find('#d1b').dispatchEvent('click')", "check": "toBe(\"bar\")", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/closest.js", "body": "await html(\"
    \")\n\t\tlet value = await evaluate(() => document.getElementById('outerDiv2').getAttribute(\"foo\"))\n\t\texpect(value).toBe(\"bar\")\n\t\tawait find('#d1b').dispatchEvent('click')\n\t\tawait expect.poll(() => evaluate(() => document.getElementById('outerDiv2').getAttribute(\"foo\"))).toBe(\"doh\")" }, { "category": "closest", "name": "closest does not consume a following where clause", "html": "
    in the closest where it is not me on click put :others.length into #out\\\">
    ", "action": "find('#master').click()", "check": "toHaveText(\"2\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/closest.js", "body": "await html(\n\t\t\t\"
    \" +\n\t\t\t\"\" +\n\t\t\t\"\" +\n\t\t\t\" in the closest where it is not me \" +\n\t\t\t\"on click put :others.length into #out\\\">\" +\n\t\t\t\"
    \" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('#master').click();\n\t\tawait expect(find('#out')).toHaveText(\"2\");" }, { "category": "closest", "name": "closest with to modifier still works after parse change", "html": "
    ", "action": "", "check": "toBe(true)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/closest.js", "body": "await html(\"
    \")\n\t\tlet result = await evaluate(() => {\n\t\t\tconst inner = document.getElementById('inner')\n\t\t\tconst outer = document.getElementById('outer')\n\t\t\treturn _hyperscript(\"closest
    to my.parentElement\", { me: inner }) === outer\n\t\t})\n\t\texpect(result).toBe(true)" }, { "category": "comparisonOperator", "name": "is a works with instanceof fallback", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"yes\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \");\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"yes\");" }, { "category": "comparisonOperator", "name": "is a Node works via instanceof", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"yes\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \");\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"yes\");" }, { "category": "comparisonOperator", "name": "is not a works with instanceof fallback", "html": "
    ", "action": "find('#d1').dispatchEvent('click')", "check": "toHaveText(\"yes\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \");\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"yes\");" }, { "category": "comparisonOperator", "name": "is ignoring case works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello' is 'hello' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello' is 'HELLO' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello' is 'world' ignoring case\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not ignoring case works", "html": "", "action": "await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello' is not 'world' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello' is not 'hello' ignoring case\")).toBe(false)" }, { "category": "comparisonOperator", "name": "contains ignoring case works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello World' contains 'hello' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' contains 'WORLD' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' contains 'missing' ignoring case\")).toBe(false)" }, { "category": "comparisonOperator", "name": "matches ignoring case works", "html": "", "action": "await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello' matches 'hello' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello' matches 'HELLO' ignoring case\")).toBe(true)" }, { "category": "comparisonOperator", "name": "starts with works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'hello world' starts with 'hello'\")).toBe(true)\n\t\texpect(await run(\"'hello world' starts with 'world'\")).toBe(false)\n\t\texpect(await run(\"'hello' starts with 'hello'\")).toBe(true)\n\t\texpect(await run(\"'' starts with 'x'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "ends with works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'hello world' ends with 'world'\")).toBe(true)\n\t\texpect(await run(\"'hello world' ends with 'hello'\")).toBe(false)\n\t\texpect(await run(\"'hello' ends with 'hello'\")).toBe(true)\n\t\texpect(await run(\"'' ends with 'x'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "does not start with works", "html": "", "action": "await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'hello world' does not start with 'hello'\")).toBe(false)\n\t\texpect(await run(\"'hello world' does not start with 'world'\")).toBe(true)" }, { "category": "comparisonOperator", "name": "does not end with works", "html": "", "action": "await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'hello world' does not end with 'world'\")).toBe(false)\n\t\texpect(await run(\"'hello world' does not end with 'hello'\")).toBe(true)" }, { "category": "comparisonOperator", "name": "starts with null is false", "html": "", "action": "await run(\"null starts with '; await run(\"null does not start with '", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"null starts with 'x'\")).toBe(false)\n\t\texpect(await run(\"null does not start with 'x'\")).toBe(true)" }, { "category": "comparisonOperator", "name": "ends with null is false", "html": "", "action": "await run(\"null ends with '; await run(\"null does not end with '", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"null ends with 'x'\")).toBe(false)\n\t\texpect(await run(\"null does not end with 'x'\")).toBe(true)" }, { "category": "comparisonOperator", "name": "starts with ignoring case works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello World' starts with 'hello' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' starts with 'HELLO' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' starts with 'world' ignoring case\")).toBe(false)" }, { "category": "comparisonOperator", "name": "ends with ignoring case works", "html": "", "action": "await run(\"'; await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'Hello World' ends with 'world' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' ends with 'WORLD' ignoring case\")).toBe(true)\n\t\texpect(await run(\"'Hello World' ends with 'hello' ignoring case\")).toBe(false)" }, { "category": "comparisonOperator", "name": "starts with coerces to string", "html": "", "action": "await run(\"123 starts with '; await run(\"123 starts with '", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"123 starts with '12'\")).toBe(true)\n\t\texpect(await run(\"123 starts with '23'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "ends with coerces to string", "html": "", "action": "await run(\"123 ends with '; await run(\"123 ends with '", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"123 ends with '23'\")).toBe(true)\n\t\texpect(await run(\"123 ends with '12'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is between works", "html": "", "action": "await run(\"5 is between 1 and 10\"; await run(\"1 is between 1 and 10\"; await run(\"10 is between 1 and 10\"; await run(\"0 is between 1 and 10\"; await run(\"11 is between 1 and 10\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"5 is between 1 and 10\")).toBe(true)\n\t\texpect(await run(\"1 is between 1 and 10\")).toBe(true)\n\t\texpect(await run(\"10 is between 1 and 10\")).toBe(true)\n\t\texpect(await run(\"0 is between 1 and 10\")).toBe(false)\n\t\texpect(await run(\"11 is between 1 and 10\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not between works", "html": "", "action": "await run(\"5 is not between 1 and 10\"; await run(\"0 is not between 1 and 10\"; await run(\"11 is not between 1 and 10\"; await run(\"1 is not between 1 and 10\"; await run(\"10 is not between 1 and 10\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"5 is not between 1 and 10\")).toBe(false)\n\t\texpect(await run(\"0 is not between 1 and 10\")).toBe(true)\n\t\texpect(await run(\"11 is not between 1 and 10\")).toBe(true)\n\t\texpect(await run(\"1 is not between 1 and 10\")).toBe(false)\n\t\texpect(await run(\"10 is not between 1 and 10\")).toBe(false)" }, { "category": "comparisonOperator", "name": "between works with strings", "html": "", "action": "await run(\"'; await run(\"'", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"'b' is between 'a' and 'c'\")).toBe(true)\n\t\texpect(await run(\"'d' is between 'a' and 'c'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "I am between works", "html": "", "action": "await run(\"I am between 1 and 10\"; await run(\"I am between 1 and 10\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"I am between 1 and 10\", { me: 5 })).toBe(true)\n\t\texpect(await run(\"I am between 1 and 10\", { me: 0 })).toBe(false)" }, { "category": "comparisonOperator", "name": "I am not between works", "html": "", "action": "await run(\"I am not between 1 and 10\"; await run(\"I am not between 1 and 10\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"I am not between 1 and 10\", { me: 5 })).toBe(false)\n\t\texpect(await run(\"I am not between 1 and 10\", { me: 0 })).toBe(true)" }, { "category": "comparisonOperator", "name": "precedes works", "html": "
    ", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \")\n\t\texpect(await evaluate(() => _hyperscript(\"x precedes y\", {\n\t\t\tlocals: { x: document.getElementById('a'), y: document.getElementById('b') }\n\t\t}))).toBe(true)\n\t\texpect(await evaluate(() => _hyperscript(\"x precedes y\", {\n\t\t\tlocals: { x: document.getElementById('b'), y: document.getElementById('a') }\n\t\t}))).toBe(false)\n\t\texpect(await evaluate(() => _hyperscript(\"x precedes y\", {\n\t\t\tlocals: { x: document.getElementById('a'), y: document.getElementById('c') }\n\t\t}))).toBe(true)" }, { "category": "comparisonOperator", "name": "follows works", "html": "
    ", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \")\n\t\texpect(await evaluate(() => _hyperscript(\"x follows y\", {\n\t\t\tlocals: { x: document.getElementById('b'), y: document.getElementById('a') }\n\t\t}))).toBe(true)\n\t\texpect(await evaluate(() => _hyperscript(\"x follows y\", {\n\t\t\tlocals: { x: document.getElementById('a'), y: document.getElementById('b') }\n\t\t}))).toBe(false)\n\t\texpect(await evaluate(() => _hyperscript(\"x follows y\", {\n\t\t\tlocals: { x: document.getElementById('c'), y: document.getElementById('a') }\n\t\t}))).toBe(true)" }, { "category": "comparisonOperator", "name": "does not precede works", "html": "
    ", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \")\n\t\texpect(await evaluate(() => _hyperscript(\"x does not precede y\", {\n\t\t\tlocals: { x: document.getElementById('b'), y: document.getElementById('a') }\n\t\t}))).toBe(true)\n\t\texpect(await evaluate(() => _hyperscript(\"x does not precede y\", {\n\t\t\tlocals: { x: document.getElementById('a'), y: document.getElementById('b') }\n\t\t}))).toBe(false)" }, { "category": "comparisonOperator", "name": "does not follow works", "html": "
    ", "action": "", "check": "", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \")\n\t\texpect(await evaluate(() => _hyperscript(\"x does not follow y\", {\n\t\t\tlocals: { x: document.getElementById('a'), y: document.getElementById('b') }\n\t\t}))).toBe(true)\n\t\texpect(await evaluate(() => _hyperscript(\"x does not follow y\", {\n\t\t\tlocals: { x: document.getElementById('b'), y: document.getElementById('a') }\n\t\t}))).toBe(false)" }, { "category": "comparisonOperator", "name": "precedes with null is false", "html": "", "action": "await run(\"null precedes null\"; await run(\"null does not precede null\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"null precedes null\")).toBe(false)\n\t\texpect(await run(\"null does not precede null\")).toBe(true)" }, { "category": "comparisonOperator", "name": "I precede works", "html": "
    ", "action": "find('#a').dispatchEvent('click')", "check": "toHaveText(\"yes\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"
    \")\n\t\tawait find('#a').dispatchEvent('click')\n\t\tawait expect(find('#a')).toHaveText(\"yes\")" }, { "category": "comparisonOperator", "name": "is really works without equal to", "html": "", "action": "await run(\"2 is really 2\"; await run(\"2 is really '", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"2 is really 2\")).toBe(true)\n\t\texpect(await run(\"2 is really '2'\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not really works without equal to", "html": "", "action": "await run(\"2 is not really '; await run(\"2 is not really 2\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"2 is not really '2'\")).toBe(true)\n\t\texpect(await run(\"2 is not really 2\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is equal works without to", "html": "", "action": "await run(\"2 is equal 2\"; await run(\"2 is equal 1\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"2 is equal 2\")).toBe(true)\n\t\texpect(await run(\"2 is equal 1\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not equal works without to", "html": "", "action": "await run(\"2 is not equal 2\"; await run(\"2 is not equal 1\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"2 is not equal 2\")).toBe(false)\n\t\texpect(await run(\"2 is not equal 1\")).toBe(true)" }, { "category": "comparisonOperator", "name": "am works as alias for is", "html": "", "action": "await run(\"2 am 2\"; await run(\"2 am 1\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"2 am 2\")).toBe(true)\n\t\texpect(await run(\"2 am 1\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not undefined still works as equality", "html": "", "action": "await run(\"5 is not undefined\"; await run(\"null is not undefined\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"5 is not undefined\")).toBe(true)\n\t\texpect(await run(\"null is not undefined\")).toBe(false) // null == undefined in JS" }, { "category": "comparisonOperator", "name": "is not null still works as equality", "html": "", "action": "await run(\"5 is not null\"; await run(\"null is not null\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"5 is not null\")).toBe(true)\n\t\texpect(await run(\"null is not null\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is falls back to boolean property when rhs is undefined", "html": "", "action": "await run(\"#c1 is checked\"; await run(\"#c2 is checked\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"\")\n\t\texpect(await run(\"#c1 is checked\")).toBe(true)\n\t\texpect(await run(\"#c2 is checked\")).toBe(false)" }, { "category": "comparisonOperator", "name": "is not falls back to boolean property when rhs is undefined", "html": "", "action": "await run(\"#c1 is not checked\"; await run(\"#c2 is not checked\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"\")\n\t\texpect(await run(\"#c1 is not checked\")).toBe(false)\n\t\texpect(await run(\"#c2 is not checked\")).toBe(true)" }, { "category": "comparisonOperator", "name": "is boolean property works with disabled", "html": "", "action": "await run(\"#b1 is disabled\"; await run(\"#b2 is disabled\"; await run(\"#b2 is not disabled\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"\")\n\t\texpect(await run(\"#b1 is disabled\")).toBe(true)\n\t\texpect(await run(\"#b2 is disabled\")).toBe(false)\n\t\texpect(await run(\"#b2 is not disabled\")).toBe(true)" }, { "category": "comparisonOperator", "name": "is still does equality when rhs variable exists", "html": "", "action": "await run(\"x is y\"; await run(\"x is y\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "expect(await run(\"x is y\", { locals: { x: 5, y: 5 } })).toBe(true)\n\t\texpect(await run(\"x is y\", { locals: { x: 5, y: 6 } })).toBe(false)" }, { "category": "comparisonOperator", "name": "is boolean property works in where clause", "html": "", "action": "await run(\".cb where it is checked\"", "check": "toBe(2)", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/comparisonOperator.js", "body": "await html(\"\" +\n\t\t\t\"\" +\n\t\t\t\"\")\n\t\tvar result = await run(\".cb where it is checked\")\n\t\texpect(result.length).toBe(2)" }, { "category": "cookies", "name": "length is 0 when no cookies are set", "html": "", "action": "", "check": "toBe(0)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/cookies.js", "body": "const result = await page.evaluate(() => {\n\t\t\t// Clear all cookies first\n\t\t\tfor (var c of document.cookie.split(\"; \")) {\n\t\t\t\tvar name = c.split(\"=\")[0]\n\t\t\t\tif (name) document.cookie = name + \"=;expires=Thu, 01 Jan 1970 00:00:00 GMT\"\n\t\t\t}\n\t\t\treturn _hyperscript(\"cookies.length\")\n\t\t})\n\t\texpect(result).toBe(0)" }, { "category": "in", "name": "null value in array returns empty", "html": "", "action": "await run(\"null in [1, 2, 3]\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/in.js", "body": "expect(await run(\"null in [1, 2, 3]\")).toEqual([])" }, { "category": "logicalOperator", "name": "and short-circuits when lhs promise resolves to false", "html": "", "action": "", "check": "toBe(false); toBe(false)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/logicalOperator.js", "body": "const result = await evaluate(async () => {\n\t\t\tlet rhsCalled = false\n\t\t\tconst returnsFalse = () => Promise.resolve(false)\n\t\t\tconst rhs = () => { rhsCalled = true; return true }\n\t\t\tconst r = await _hyperscript(\"returnsFalse() and rhs()\", {locals: {returnsFalse, rhs}})\n\t\t\treturn { result: r, rhsCalled }\n\t\t})\n\t\texpect(result.result).toBe(false)\n\t\texpect(result.rhsCalled).toBe(false)" }, { "category": "logicalOperator", "name": "or short-circuits when lhs promise resolves to true", "html": "", "action": "", "check": "toBe(true); toBe(false)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/logicalOperator.js", "body": "const result = await evaluate(async () => {\n\t\t\tlet rhsCalled = false\n\t\t\tconst returnsTrue = () => Promise.resolve(true)\n\t\t\tconst rhs = () => { rhsCalled = true; return false }\n\t\t\tconst r = await _hyperscript(\"returnsTrue() or rhs()\", {locals: {returnsTrue, rhs}})\n\t\t\treturn { result: r, rhsCalled }\n\t\t})\n\t\texpect(result.result).toBe(true)\n\t\texpect(result.rhsCalled).toBe(false)" }, { "category": "logicalOperator", "name": "or evaluates rhs when lhs promise resolves to false", "html": "", "action": "", "check": "toBe(\"fallback\"); toBe(true)", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/logicalOperator.js", "body": "const result = await evaluate(async () => {\n\t\t\tlet rhsCalled = false\n\t\t\tconst returnsFalse = () => Promise.resolve(false)\n\t\t\tconst rhs = () => { rhsCalled = true; return \"fallback\" }\n\t\t\tconst r = await _hyperscript(\"returnsFalse() or rhs()\", {locals: {returnsFalse, rhs}})\n\t\t\treturn { result: r, rhsCalled }\n\t\t})\n\t\texpect(result.result).toBe(\"fallback\")\n\t\texpect(result.rhsCalled).toBe(true)" }, { "category": "mathOperator", "name": "array + array concats", "html": "", "action": "await run(\"[1, 2] + [3, 4]\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/mathOperator.js", "body": "expect(await run(\"[1, 2] + [3, 4]\")).toEqual([1, 2, 3, 4])" }, { "category": "mathOperator", "name": "array + single value appends", "html": "", "action": "await run(\"[1, 2] + 3\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/mathOperator.js", "body": "expect(await run(\"[1, 2] + 3\")).toEqual([1, 2, 3])" }, { "category": "mathOperator", "name": "array + array does not mutate original", "html": "", "action": "await run(\"set a to [1, 2] then set b to a + [3] then return a\"", "check": "toEqual([1, 2])", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/mathOperator.js", "body": "var result = await run(\"set a to [1, 2] then set b to a + [3] then return a\")\n\t\texpect(result).toEqual([1, 2])" }, { "category": "mathOperator", "name": "array concat chains", "html": "", "action": "await run(\"[1] + [2] + [3]\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/mathOperator.js", "body": "expect(await run(\"[1] + [2] + [3]\")).toEqual([1, 2, 3])" }, { "category": "mathOperator", "name": "empty array + array works", "html": "", "action": "await run(\"[] + [1, 2]\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/mathOperator.js", "body": "expect(await run(\"[] + [1, 2]\")).toEqual([1, 2])" }, { "category": "no", "name": "no returns false for non-empty array", "html": "", "action": "await run(\"no ['", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/no.js", "body": "expect(await run(\"no ['thing']\")).toBe(false)" }, { "category": "no", "name": "no with where filters then checks emptiness", "html": "", "action": "await run(\"no [1, 2, 3] where it > 5\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/no.js", "body": "expect(await run(\"no [1, 2, 3] where it > 5\")).toBe(true)" }, { "category": "no", "name": "no with where returns false when matches exist", "html": "", "action": "await run(\"no [1, 2, 3] where it > 1\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/no.js", "body": "expect(await run(\"no [1, 2, 3] where it > 1\")).toBe(false)" }, { "category": "no", "name": "no with where and is not", "html": "", "action": "await run(\"no [1, 2, 3] where it is not 2\"", "check": "toBe(false)", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/no.js", "body": "var result = await run(\"no [1, 2, 3] where it is not 2\");\n\t\texpect(result).toBe(false)" }, { "category": "no", "name": "no with where on DOM elements", "html": "
    AB
    ", "action": "find('button').click()", "check": "toHaveText(\"none\")", "async": true, "complexity": "simple", "source": "new_in_common", "file": "test/expressions/no.js", "body": "await html(\n\t\t\t\"
    AB
    \" +\n\t\t\t\"\" +\n\t\t\t\"
    \"\n\t\t);\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"none\");" }, { "category": "objectLiteral", "name": "allows trailing commas", "html": "", "action": "await run(\"{foo:true, bar-baz:false,}\"", "check": "", "async": true, "complexity": "run-eval", "source": "new_in_common", "file": "test/expressions/objectLiteral.js", "body": "expect(await run(\"{foo:true, bar-baz:false,}\")).toEqual({ \"foo\": true, \"bar-baz\": false })" }, { "category": "queryRef", "name": "queryRef w/ $ works", "html": "
    ", "action": "", "check": "toBe(1)", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/queryRef.js", "body": "await html(\"
    \")\n\t\tconst len = await evaluate(() => {\n\t\t\treturn Array.from(_hyperscript(\"<[foo='${x}']/>\", { locals: { x: \"bar\" } })).length\n\t\t})\n\t\texpect(len).toBe(1)" }, { "category": "relativePositionalExpression", "name": "can access property of next element with possessive", "html": "
    hello
    ", "action": "", "check": "toBe('hello')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/relativePositionalExpression.js", "body": "await html('
    hello
    ')\n\t\tlet result = await evaluate(() => _hyperscript(\"the next
    's textContent\", {me: document.getElementById('d1')}))\n\t\texpect(result).toBe('hello')" }, { "category": "relativePositionalExpression", "name": "can access property of previous element with possessive", "html": "
    world
    ", "action": "", "check": "toBe('world')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/relativePositionalExpression.js", "body": "await html('
    world
    ')\n\t\tlet result = await evaluate(() => _hyperscript(\"the previous
    's textContent\", {me: document.getElementById('d2')}))\n\t\texpect(result).toBe('world')" }, { "category": "relativePositionalExpression", "name": "can access style of next element with possessive", "html": "
    ", "action": "", "check": "toBe('red')", "async": true, "complexity": "evaluate", "source": "new_in_common", "file": "test/expressions/relativePositionalExpression.js", "body": "await html('
    ')\n\t\tlet result = await evaluate(() => _hyperscript(\"the next
    's *color\", {me: document.getElementById('d1')}))\n\t\texpect(result).toBe('red')" }, { "category": "relativePositionalExpression", "name": "can write to next element with put command", "html": "", "action": "find('#d1').dispatchEvent('click'); evaluate({...})", "check": "toHaveText('updated')", "async": true, "complexity": "eval-only", "source": "new_in_common", "file": "test/expressions/relativePositionalExpression.js", "body": "await evaluate(() => {\n\t\t\tvar wa = document.getElementById('work-area');\n\t\t\twa.innerHTML = '
    original
    ';\n\t\t\twa.querySelector('#d1').setAttribute('_', \"on click put 'updated' into the next
    's textContent\");\n\t\t\t_hyperscript.process(wa);\n\t\t});\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d2')).toHaveText('updated');" } ]