Files
rose-ash/spec/tests/hyperscript-upstream-tests.pre-0.9.90.json
giles fd1dfea9b3 HS tests: scrape v0.9.90 upstream in full, flip silent stubs to loud SKIPs
- scrape-hs-upstream.py: new scraper walks /tmp/hs-upstream/test/**/*.js
  and emits body-style records for all 1,496 v0.9.90 tests (up from 831).
  Widens coverage into 66 previously-missing categories — templates,
  reactivity, behavior, worker, classRef, make, throw, htmx, tailwind,
  viewTransition, and more.

- build-hs-manifest.py + hyperscript-upstream-manifest.{json,md}:
  coverage manifest tagging each upstream test with a status
  (runnable / skip-listed / untranslated / missing) and block reason.

- generate-sx-tests.py: emit (error "SKIP (...)") instead of silent
  (hs-cleanup!) no-op for both skip-listed tests and generator-
  untranslatable bodies. Stub counter now reports both buckets.

- hyperscript-feature-audit-0.9.90.md: gap audit against the 0.9.90
  spec; pre-0.9.90.json backs up prior 831-test snapshot.

New honest baseline (ocaml runner, test-hyperscript-behavioral):
  831 -> 1,496 tests; 645 -> 1,013 passing (67.7% conformance).
  483 failures split: 45 skip-list, 151 untranslated, 287 real.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-04-22 20:27:22 +00:00

8936 lines
511 KiB
JSON

[
{
"category": "add",
"name": "can add class ref on a single div",
"html": "<div _='on click add .foo'></div>",
"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": "<div _='on click add .foo--bar'></div>",
"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": "<form _='on click add .foo'></form>",
"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": "<div id='bar'></div> | <div _='on click add .foo to #bar'></div>",
"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": "<div _='on click add .foo to <p/> in me'><p id='p1'></p></div>",
"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": "<div _='on click add .foo to my children'><p id='p1'></p></div>",
"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": "<div _='on click add [@foo=\"bar\"]'></div>",
"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": "<div style='color: blue' _='on click add {color: red; font-family: monospace}'></div>",
"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": "<div style='color: blue' _='on click add {color: ${\"red\"};}'></div>",
"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": "<div _='on click add .foo .bar'></div>",
"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": "<div _='on click add .foo:bar-doh'></div>",
"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": "<div _='on click add .rey to .bar when it matches .doh'></div> | <div class='bar'></div> | <div class='bar doh'></div>",
"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": "<div _='on click add @rey to .bar when it matches .doh'></div> | <div class='bar'></div> | <div class='bar doh'></div>",
"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": "<div _='on click add .foo to the children of #bar'></div> | <div id='bar'><div id='c1'></div><div id='c2'></div></div>",
"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": "<div class='foo' _='on click remove .foo'></div>",
"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": "<form class='foo' _='on click remove .foo'></form>",
"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": "<div class='foo' id='bar'></div> | <div _='on click remove .foo from #bar'></div>",
"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": "<div foo='bar' _='on click remove [@foo]'></div>",
"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": "<div _='on click remove me'></div>",
"action": "div.click()",
"check": "assert.isNotNull(div.parentElement) && assert.isNull(div.parentElement)",
"async": false,
"complexity": "simple"
},
{
"category": "remove",
"name": "can remove other elements",
"html": "<div _='on click remove #that'></div> | <div id='that'></div>",
"action": "div.click()",
"check": "assert.isNotNull(div2.parentElement) && assert.isNull(div2.parentElement)",
"async": false,
"complexity": "simple"
},
{
"category": "remove",
"name": "can remove parent element",
"html": "<div id='p1'><button id='b1' _=\"on click remove my.parentElement\"></button></div> ",
"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": "<div class='foo bar doh' _='on click remove .foo .bar'></div>",
"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": "<div><div id='d1' _='on click remove <p/> from me'><p>foo</p>bar</div><p>doh</p></div>",
"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": "<div _='on click toggle .foo'></div>",
"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": "<form _='on click toggle .foo'></form>",
"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": "<div id='bar'></div> | <div _='on click toggle .foo on #bar'></div>",
"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": "<div _='on click toggle [@foo=\"bar\"]'></div>",
"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": "<select _='on click toggle [@foo=\"bar\"]'></select>",
"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": "<div _='on click toggle .foo for 10ms'></div>",
"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": "<div id='d1'></div> | <div _='on click toggle .foo until foo from #d1'></div>",
"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": "<div class='foo' _='on click toggle between .foo and .bar'></div>",
"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": "<div class='bar' _='on click toggle .foo .bar'></div>",
"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": "<div _='on click toggle *display'></div>",
"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": "<div _='on click toggle *opacity'></div>",
"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": "<div _='on click toggle *visibility'></div>",
"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": "<div _='on click toggle my *display'></div>",
"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": "<div _='on click toggle my *opacity'></div>",
"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": "<div _='on click toggle my *visibility'></div>",
"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": "<div _='on click toggle the *display of #d2'></div> | <div id='d2'></div>",
"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": "<div _='on click toggle the *opacity of #d2'></div> | <div id='d2'></div>",
"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": "<div _='on click toggle the *visibility of #d2'></div> | <div id='d2'></div>",
"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": "<form _='on click toggle .group-\\\\[:nth-of-type\\\\(3\\\\)_\\\\&\\\\]:block'></form>",
"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": "<div id='d1' _='on click set #d1.innerHTML to \"foo\"'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set indirect properties",
"html": "<div id='d1' _='on click set innerHTML of #d1 to \"foo\"'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set complex indirect properties lhs",
"html": "<div _='on click set parentNode.innerHTML of #d1 to \"foo\"'><div id='d1'></div></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set complex indirect properties rhs",
"html": "<div _='on click set innerHTML of #d1.parentNode to \"foo\"'><div id='d1'></div></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set chained indirect properties",
"html": "<div _='on click set the innerHTML of the parentNode of #d1 to \"foo\"'><div id='d1'></div></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set styles",
"html": "<div _='on click set my.style.color to \"red\"'>lolwat</div>",
"action": "d1.click()",
"check": "d1.style.color.should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set javascript globals",
"html": "<div _='on click set window.temp to \"red\"'>lolwat</div>",
"action": "d1.click()",
"check": "window[\"temp\"].should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set local variables",
"html": "<div id='d1' _='on click set newVar to \"foo\" then put newVar into #d1.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set into id ref",
"html": "<div id='d1' _='on click set #d1.innerHTML to \"foo\"'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set into class ref",
"html": "<div class='divs' _='on click set .divs.innerHTML to \"foo\"'></div> | <div class='divs''></div>",
"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": "<div class='divs' _='on click set @bar to \"foo\"'></div>",
"action": "d1.click()",
"check": "d1.getAttribute(\"bar\").should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set into indirect attribute ref",
"html": "<div class='divs' _=\"on click set #div2's @bar to 'foo'\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click set #div2's @bar to 'foo'\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click set @bar of #div2 to 'foo'\"></div> | <div id='div2'></div>",
"action": "d1.click()",
"check": "d2.getAttribute(\"bar\").should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set into style ref",
"html": "<div class='divs' _='on click set *color to \"red\"'></div>",
"action": "d1.click()",
"check": "d1.style[\"color\"].should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "can set into indirect style ref",
"html": "<div class='divs' _=\"on click set #div2's *color to 'red'\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click set #div2's *color to 'red'\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click set *color of #div2 to 'red'\"></div> | <div id='div2'></div>",
"action": "d1.click()",
"check": "d2.style[\"color\"].should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "set waits on promises",
"html": "<div id='d1' _='on click set #d1.innerHTML to promiseAString()'></div>",
"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": "<div _='on click set {bar: 2, baz: 3} on obj'></div>",
"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": "<div _='on click set my style[\"color\"] to \"red\"'>lolwat</div>",
"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": "<div _='on click set foo to \"color\" then set my style[foo] to \"red\"'>lolwat</div>",
"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": "<div _='on click set arr to [1, 2, 3] set arr[0] to \"red\" set my *color to arr[0]'>lolwat</div>",
"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": "<div _='on click set arr to [1, 2, 3] set idx to 0 set arr[idx] to \"red\" set my *color to arr[0]'>lolwat</div>",
"action": "d1.click()",
"check": "d1.style.color.should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "set",
"name": "handles set url regression properly",
"html": "<div _='on click set trackingcode to `foo` set pdfurl to `https://yyy.xxxxxx.com/path/out/${trackingcode}.pdf` put pdfurl into me'>lolwat</div>",
"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": "<div id='d1' _='on click put \"foo\" into #d1.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can put directly into nodes",
"html": "<div id='d1' _='on click put \"foo\" into #d1'></div>",
"action": "d1.click()",
"check": "d1.textContent.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can put nodes into nodes",
"html": "<div id='d1'></div> | <div id='d2' _='on click put #d1 into #d2'></div>",
"action": "d2.click()",
"check": "d2.firstChild.should.equal(d1)",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can put directly into symbols",
"html": "<div _='on click put \"foo\" into me'></div>",
"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": "<div _='on click put \"foo\" into me then put \"bar\" into me'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set styles",
"html": "<div _='on click put \"red\" into my.style.color'>lolwat</div>",
"action": "d1.click()",
"check": "d1.style.color.should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set javascript globals",
"html": "<div _='on click put \"red\" into window.temp'>lolwat</div>",
"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": "<div _='on click put \"foo\" into .divs.parentElement.innerHTML'></div> | <div id='d1'><div class='divs'></div></div><div id='d2'><div class='divs'></div></div>",
"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": "<div _='on click put \"foo\" into innerHTML of parentElement of .divs'></div> | <div id='d1'><div class='divs'></div></div><div id='d2'><div class='divs'></div></div>",
"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": "<div id='d1' _='on click put \"foo\" into newVar then put newVar into #d1.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set into id ref",
"html": "<div id='d1' _='on click put \"foo\" into #d1.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can insert before",
"html": "<div id='d2' _='on click put #d1 before #d2'></div> | <div id='d1'>foo</div>",
"action": "d2.click()",
"check": "d2.previousSibling.textContent.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can insert after",
"html": "<div id='d1'>foo</div> | <div id='d2' _='on click put #d1 after #d2'></div>",
"action": "d2.click()",
"check": "d2.nextSibling.textContent.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can insert after beginning",
"html": "<div id='d1' _='on click put \"foo\" at start of #d1'>*</div>",
"action": "d1.click()",
"check": "d1.textContent.should.equal(\"foo*\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can insert before end",
"html": "<div id='d1' _='on click put \"foo\" at end of #d1'>*</div>",
"action": "d1.click()",
"check": "d1.textContent.should.equal(\"*foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set into attribute ref",
"html": "<div class='divs' _='on click put \"foo\" into @bar'></div>",
"action": "d1.click()",
"check": "d1.getAttribute(\"bar\").should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set into indirect attribute ref",
"html": "<div class='divs' _='on click put \"foo\" into my @bar'></div>",
"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": "<div class='divs' _=\"on click put 'foo' into #div2's @bar\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click put 'foo' into @bar of #div2\"></div> | <div id='div2'></div>",
"action": "d1.click()",
"check": "d2.getAttribute(\"bar\").should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set into style ref",
"html": "<div class='divs' _='on click put \"red\" into *color'></div>",
"action": "d1.click()",
"check": "d1.style[\"color\"].should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "can set into indirect style ref",
"html": "<div class='divs' _='on click put \"red\" into my *color'></div>",
"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": "<div class='divs' _=\"on click put 'red' into #div2's *color\"></div> | <div id='div2'></div>",
"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": "<div class='divs' _=\"on click put 'red' into the *color of #div2\"></div> | <div id='div2'></div>",
"action": "d1.click()",
"check": "d2.style[\"color\"].should.equal(\"red\")",
"async": false,
"complexity": "simple"
},
{
"category": "put",
"name": "waits on promises",
"html": "<div id='d1' _='on click put promiseAString() into #d1.innerHTML'></div>",
"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": "<div _='on click put \"red\" into my style[\"color\"]'>lolwat</div>",
"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": "<div _='on click set foo to \"color\" then put \"red\" into my style[foo]'>lolwat</div>",
"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": "<div _='on click set arr to [1, 2, 3] put \"red\" into arr[0] put arr[0] into my *color'>lolwat</div>",
"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": "<div _='on click set arr to [1, 2, 3] set idx to 0 put \"red\" into arr[idx] put arr[0] into my *color'>lolwat</div>",
"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": "<div _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" into me'></div>",
"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": "<div id='d1' _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" into <div#d1/>'></div>",
"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": "<div id='d1' _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" before me'></div>",
"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": "<div id='d1' _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" at the start of me'></div>",
"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": "<div id='d1' _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" at the end of me'></div>",
"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": "<div id='d1' _='on click put \"<button id=\\\\\"b1\\\\\" _=\\\\\"on click put 42 into me\\\\\">40</button>\" after me'></div>",
"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": "<div class='divs' _='on click put \"red\" into #a-bad-id-that-does-not-exist'></div>",
"action": "d1.click()",
"check": "(no explicit assertion)",
"async": false,
"complexity": "simple"
},
{
"category": "hide",
"name": "can hide element with no target",
"html": "<div _='on click hide'></div>",
"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": "<div _='on click 1 hide on click 2 show'></div>",
"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": "<div _='on click hide add .foo'></div>",
"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": "<div _='on click hide then add .foo'></div>",
"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": "<div _='on click hide with display then add .foo'></div>",
"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": "<div _='on click hide me'></div>",
"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": "<div _='on click hide me with display'></div>",
"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": "<div _='on click hide me with opacity'></div>",
"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": "<div _='on click hide me with *opacity'></div>",
"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": "<div _='on click hide me with visibility'></div>",
"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": "<div class=hideme></div> | <div _='on click hide .hideme'></div>",
"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": "<div _='on click hide with myHide'></div>",
"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": "<div _='on click hide'></div>",
"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": "<div _='on click if true put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic true branch works with multiple commands",
"html": "<div _='on click if true log me then put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic true branch works with end",
"html": "<div _='on click if true put \"foo\" into me.innerHTML end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic true branch works with naked else",
"html": "<div _='on click if true put \"foo\" into me.innerHTML else'></div>",
"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": "<div _='on click if true put \"foo\" into me.innerHTML else end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic else branch works",
"html": "<div _='on click if false else put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic else branch works with end",
"html": "<div _='on click if false else put \"foo\" into me.innerHTML end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic else if branch works",
"html": "<div _='on click if false else if true put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic else if branch works with end",
"html": "<div _='on click if false else if true put \"foo\" into me.innerHTML end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "otherwise alias works",
"html": "<div _='on click if false otherwise put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "triple else if branch works",
"html": "<div _='on click if false else if false else put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "triple else if branch works with end",
"html": "<div _='on click if false else if false else put \"foo\" into me.innerHTML end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "basic else branch works with multiple commands",
"html": "<div _='on click if false put \"bar\" into me.innerHTML else log me then put \"foo\" into me.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "if",
"name": "true branch with a wait works",
"html": "<div _='on click if true wait 10 ms then put \"foo\" into me.innerHTML'></div>",
"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": "<div _='on click if false else wait 10 ms then put \"foo\" into me.innerHTML'></div>",
"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": "<div _='on click if false end put \"foo\" into me.innerHTML'></div>",
"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": "<div _='on click \\n if window.tmp then\\n put \"foo\" into me\\n else if not window.tmp then\\n // do nothing\\n end\\n catch e\\n // just here for the parsing...\\n'</div>",
"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": "<div _='on click \\n if window.tmp then\\n else\\n if window.tmp then end\\n put \"foo\" into me\\n end\\n '</div>",
"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": "<div _='on click repeat for x in [1, 2, 3] put x at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"123\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "basic for loop with null works",
"html": "<div _='on click repeat for x in null put x at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "waiting in for loop works",
"html": "<div _='on click repeat for x in [1, 2, 3]\\n log me put x at end of me\\n wait 1ms\\n end'></div>",
"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": "<div _='on click for x in [1, 2, 3] put x at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"123\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "basic raw for loop works",
"html": "<div _='on click for x in null put x at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "waiting in raw for loop works",
"html": "<div _='on click for x in [1, 2, 3]\\n put x at end of me\\n wait 1ms\\n end'></div>",
"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": "<script type='text/hyperscript'> def repeatForeverWithReturn() set retVal to 0 repeat forever set retVal to retVal + 1 if retVal == 5 then return retVal end end end</script><div id='d1' _='on click put repeatForeverWithReturn() into my.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"5\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "repeat forever works w/o keyword",
"html": "<script type='text/hyperscript'> def repeatForeverWithReturn() set retVal to 0 repeat set retVal to retVal + 1 if retVal == 5 then return retVal end end end</script><div id='d1' _='on click put repeatForeverWithReturn() into my.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"5\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "basic in loop works",
"html": "<div _='on click repeat in [1, 2, 3] put it at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"123\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "index syntax works",
"html": "<div _='on click repeat for x in [\"a\", \"ab\", \"abc\"] index i put x + i at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"a0ab1abc2\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "indexed by syntax works",
"html": "<div _='on click repeat for x in [\"a\", \"ab\", \"abc\"] indexed by i put x + i at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"a0ab1abc2\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "while keyword works",
"html": "<script type='text/hyperscript'> def repeatWhileTest() set retVal to 0 repeat while retVal < 5 set retVal to retVal + 1 end return retVal end</script><div id='d1' _='on click put repeatWhileTest() into my.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"5\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "until keyword works",
"html": "<script type='text/hyperscript'> def repeatUntilTest() set retVal to 0 repeat until retVal == 5 set retVal to retVal + 1 end return retVal end</script><div id='d1' _='on click put repeatUntilTest() into my.innerHTML'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"5\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "until event keyword works",
"html": "<div id='untilTest'></div> | <script type='text/hyperscript'> def repeatUntilTest() repeat until event click from #untilTest wait 2ms end return 42 end</script>",
"action": "div.click()",
"check": "value.should.equal(42)",
"async": true,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "only executes the init expression once",
"html": "<script type='text/hyperscript'> def getArray() set window.called to (window.called or 0) + 1 return [1, 2, 3] end</script><div id='d1' _='on click for x in getArray() put x into my.innerHTML end'></div>",
"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": "<script type='text/hyperscript'> def sprayInto(elt) for x in [1, 2, 3] for y in [1, 2, 3] put x * y at end of elt end end end</script><div id='d1' _='on click call sprayInto(me)'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"123246369\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "repeat",
"name": "basic times loop works",
"html": "<div _='on click repeat 3 times put \"a\" at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"aaa\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "times loop with expression works",
"html": "<div _='on click repeat 3 + 3 times put \"a\" at end of me end'></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"aaaaaa\")",
"async": false,
"complexity": "simple"
},
{
"category": "repeat",
"name": "loop continue works",
"html": "<div _=\"on click\n\t\t\t\trepeat 2 times\n\t\t\t\t\tfor x in ['A', 'B', 'C', 'D']\n\t\t\t\t\t\tif (x != 'D') then\n\t\t\t\t\t\t\tput 'success ' + x + '. ' at end of me\n\t\t\t\t\t\t\tcontinue\n\t\t\t\t\t\t\tput 'FAIL!!. ' at end of me\n\t\t\t\t\t\tend\n\t\t\t\t\t\tput 'expected D. ' at end of me\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\"></div>",
"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": "<div _=\"on click\n\t\t\t\trepeat 2 times\n\t\t\t\t\tfor x in ['A', 'B', 'C', 'D']\n\t\t\t\t\t\tif x is 'C'\n\t\t\t\t\t\t break\n\t\t\t\t\t\tend\n\t\t\t\t\t\tput x at end of me\n\t\t\t\t\tend\n\t\t\t\tend\n\t\t\t\"></div>",
"action": "d1.click()",
"check": "d1.innerHTML.should.equal(\"ABAB\")",
"async": false,
"complexity": "simple"
},
{
"category": "wait",
"name": "can wait on time",
"html": "<div _='on click add .foo then wait 20ms then add .bar'></div>",
"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": "<div _='on click add .foo then wait for foo then add .bar'></div>",
"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": "<div _='on click wait for foo then put its.detail into me'></div>",
"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": "<div _='on click wait for foo(bar) then put bar into me'></div>",
"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": "<div id='d2'></div> | <div _='on click add .foo then wait for foo from #d2 then add .bar'></div>",
"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": "<div id='d2'></div> | <div _='on click add .foo then wait for foo or 0ms then add .bar'></div>",
"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": "<div id='d2'></div> | <div _='on click add .foo then wait for foo or 0ms then add .bar'></div>",
"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": "<div _='on click send foo to #bar'></div> | <div id='bar' _='on foo add .foo-sent'></div>",
"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": "<div _='on click log 0 send foo to #bar log 3'></div> | <div id='bar' _='on foo add .foo-sent to sender log 1, me, sender'></div>",
"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": "<div _='on click send foo(x:42) to #bar'></div> | <div id='bar' _='on foo put event.detail.x into my.innerHTML'></div>",
"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": "<div _='on click send foo.bar to #bar'></div> | <div id='bar' _='on foo.bar add .foo-sent'></div>",
"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": "<div _='on click send foo.bar(x:42) to #bar'></div> | <div id='bar' _='on foo.bar put event.detail.x into my.innerHTML'></div>",
"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": "<div _='on click send foo:bar to #bar'></div> | <div id='bar' _='on foo:bar add .foo-sent'></div>",
"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": "<div _='on click send foo:bar(x:42) to #bar'></div> | <div id='bar' _='on foo:bar put event.detail.x into my.innerHTML'></div>",
"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": "<div _='def bar return #bar on click send foo to bar()'></div> | <div id='bar' _='on foo add .foo-sent'></div>",
"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": "<div class='div foo'></div> | <div class='div' _='on click take .foo from .div'></div> | <div class='div'></div>",
"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": "<form class='div foo'></form> | <form class='div' _='on click take .foo from .div'></form> | <form class='div'></form>",
"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": "<div class='div foo'></div> | <div class='div' _='on click take .foo from .div for #d3'></div> | <div id='d3' class='div'></div>",
"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": "<div _='on click take .foo from .div for event.target'><div id='d1' class='div foo'></div><div id='d2' class='div'></div><div id='d3' class='div'></div></div>",
"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": "<div class='div' data-foo='bar'></div> | <div class='div' _='on click take @data-foo from .div'></div> | <div class='div'></div>",
"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": "<div class='div' data-foo='bar'></div> | <div class='div' _='on click take @data-foo=baz from .div'></div> | <div class='div'></div>",
"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": "<div class='div' data-foo='bar'></div> | <div class='div' _='on click take @data-foo=baz with \"qux\" from .div'></div> | <div class='div'></div>",
"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": "<div class='div' data-foo='bar'></div> | <div class='div' data-foo='qux' _='on click take @data-foo=baz with my @data-foo from .div'></div> | <div class='div'></div>",
"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": "<div class='div' data-foo='bar'></div> | <div class='div' _='on click take @data-foo from .div for #d3'></div> | <div id='d3' class='div'></div>",
"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": "<div _='on click take @data-foo from .div for event.target'><div id='d1' class='div' data-foo='bar'></div><div id='d2' class='div'></div><div id='d3' class='div'></div></div>",
"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": "<div class='div foo'></div> | <div class='div' _='on click take .foo .bar'></div> | <div class='div bar'></div>",
"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": "<div class='div1 foo bar'></div> | <div class='div' _='on click take .foo .bar from .div1'></div> | <div class='div bar'></div>",
"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": "<div _='on click transition width from 0px to 100px'></div>",
"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": "<div _='on click set startWidth to 0 set endWidth to 100 transition width from (startWidth)px to (endWidth)px'></div>",
"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": "<form _='on click transition width from 0px to 100px'></form>",
"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": "<div _='on click transition my width from 0px to 100px'></div>",
"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": "<div _='on click transition width from 0px to 100px height from 0px to 100px'></div>",
"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": "<div _='on click transition element #foo width from 0px to 100px'></div> | <div id='foo'></div>",
"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": "<div _='on click transition #foo width from 0px to 100px'></div> | <div id='foo'></div>",
"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": "<div _=\"on click transition #foo's width from 0px to 100px\"></div> | <div id='foo'></div>",
"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": "<div _='on click get #foo then transition its width from 0px to 100px'></div> | <div id='foo'></div>",
"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": "<div _='on click transition element #foo width from 0px to 100px using \"width 2s ease-in\"'></div> | <div id='foo'></div>",
"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": "<div _='on click transition element #foo width from 0px to 100px over 2s'></div> | <div id='foo'></div>",
"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": "<div _='on click transition *width from 0px to 100px'></div>",
"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": "<form _='on click transition *width from 0px to 100px'></form>",
"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": "<div _='on click transition my *width from 0px to 100px'></div>",
"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": "<div style='width: 10px' _='on click 1 transition my *width to 100px on click 2 transition my *width to initial'></div>",
"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": "<div _='on click log me'></div>",
"action": "d1.click()",
"check": "(no explicit assertion)",
"async": false,
"complexity": "simple"
},
{
"category": "log",
"name": "can log multiple items",
"html": "<div _='on click log me, my'></div>",
"action": "d1.click()",
"check": "(no explicit assertion)",
"async": false,
"complexity": "simple"
},
{
"category": "log",
"name": "can log multiple items with debug",
"html": "<div _='on click log me, my with console.debug'></div>",
"action": "d1.click()",
"check": "(no explicit assertion)",
"async": false,
"complexity": "simple"
},
{
"category": "log",
"name": "can log multiple items with error",
"html": "<div _='on click log me, my with console.error'></div>",
"action": "d1.click()",
"check": "(no explicit assertion)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "can call javascript instance functions",
"html": "<div id='d1' _='on click call document.getElementById(\"d1\") then put it into window.results'></div>",
"action": "d1.click()",
"check": "value.should.equal(d1)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "can call global javascript functions",
"html": "<div _='on click call globalFunction(\"foo\")'></div>",
"action": "div.click()",
"check": "\"foo\".should.equal(calledWith)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "can call no argument functions",
"html": "<div _='on click call globalFunction()'></div>",
"action": "div.click()",
"check": "called.should.equal(true)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "can call functions w/ underscores",
"html": "<div _='on click call global_function()'></div>",
"action": "div.click()",
"check": "called.should.equal(true)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "can call functions w/ dollar signs",
"html": "<div _='on click call $()'></div>",
"action": "div.click()",
"check": "called.should.equal(true)",
"async": false,
"complexity": "simple"
},
{
"category": "call",
"name": "call functions that return promises are waited on",
"html": "<div _='on click call promiseAnInt() then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch \"/test\" then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test then put it into my.innerHTML'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"yay\")",
"async": true,
"complexity": "sinon"
},
{
"category": "fetch",
"name": "can do a simple fetch w/ html",
"html": "<div _='on click fetch /test as html then log it then put it into my.innerHTML put its childElementCount into my @data-count'></div>",
"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": "<div _='on click fetch /test as json then get result as JSON then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test as Object then get result as JSON then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test as an Object then get result as JSON then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test as response then if its.ok put \"yep\" into my.innerHTML'></div>",
"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": "<div _='on click fetch /test as Number then put it into my.innerHTML'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"1.2\")",
"async": true,
"complexity": "sinon"
},
{
"category": "fetch",
"name": "can do a simple post",
"html": "<div _='on click fetch /test {method:\"POST\"} then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test with method:\"POST\" then put it into my.innerHTML'></div>",
"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": "<div _='on click fetch /test with {method:\"POST\"} then put it into my.innerHTML'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"yay\")",
"async": true,
"complexity": "sinon"
},
{
"category": "fetch",
"name": "can put response conversion after with",
"html": "<div _='on click fetch /test with {method:\"POST\"} as text then put it into my.innerHTML'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"yay\")",
"async": true,
"complexity": "sinon"
},
{
"category": "fetch",
"name": "can put response conversion before with",
"html": "<div _='on click fetch /test as text with {method:\"POST\"} then put it into my.innerHTML'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"yay\")",
"async": true,
"complexity": "sinon"
},
{
"category": "fetch",
"name": "triggers an event just before fetching",
"html": "<div _='on click fetch \"/test\" then put it into my.innerHTML end'></div>",
"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": "<div _='on click fetch \"/test\" {headers: {\"X-CustomHeader\": \"foo\"}} then put it into my.innerHTML end'></div>",
"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": "<div _='on click fetch \"/test\" then put it into my.innerHTML end'></div>",
"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": "<div _='on click fetch /test catch e log e put \"yay\" into me'></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"yay\")",
"async": true,
"complexity": "sinon"
},
{
"category": "increment",
"name": "can increment an empty variable",
"html": "<div _=\"on click increment value then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"1\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can increment a variable",
"html": "<div _=\"on click set value to 20 then increment value by 2 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"22\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can increment refer to result",
"html": "<div _=\"on click increment value by 2 then put it into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"2\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can increment an attribute",
"html": "<div value=\"5\" _=\"on click increment @value then put @value into me\"></div>",
"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": "<div value=\"5\" _=\"on click set value to 5.2 then increment value by 6.1 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"11.3\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can increment a property",
"html": "<div _=\"on click increment my.innerHTML\">3</div>",
"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": "<div _=\"on click set value to 20 then increment value by 0 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"20\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can increment a value multiple times",
"html": "<div _=\"on click increment my.innerHTML\"></div>",
"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": "<div _=\"on click decrement value then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"-1\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can decrement a variable",
"html": "<div _=\"on click set value to 20 then decrement value by 2 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"18\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can decrement an attribute",
"html": "<div value=\"5\" _=\"on click decrement @value then put @value into me\"></div>",
"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": "<div value=\"5\" _=\"on click set value to 6.1 then decrement value by 5.1 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"1\")",
"async": false,
"complexity": "simple"
},
{
"category": "increment",
"name": "can decrement a property",
"html": "<div _=\"on click decrement my.innerHTML\">3</div>",
"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": "<div _=\"on click decrement my.innerHTML\"></div>",
"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": "<div _=\"on click set value to 20 then decrement value by 0 then put value into me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"20\")",
"async": false,
"complexity": "simple"
},
{
"category": "append",
"name": "can append a string to another string",
"html": "<div _=\"on click\n set value to 'Hello there.' then\n append ' General Kenobi.' to value then\n set my.innerHTML to value\"></div>",
"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": "<div _=\"on click\n set value to [1,2,3]\n append 4 to value\n set my.innerHTML to value as String\"></div>",
"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": "<div _=\"on click\n set result to [1,2,3]\n append 4\n put it as String into me\"></div>",
"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": "<div _=\"on click\n append '<span>This is my inner HTML</span>' to me\n append '<b>With Tags</b>' to me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"<span>This is my inner HTML</span><b>With Tags</b>\")",
"async": false,
"complexity": "simple"
},
{
"category": "append",
"name": "can append a value to a DOM element",
"html": "<div id=\"content\" _=\"on click\n append 'Content' to #content\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"Content\")",
"async": false,
"complexity": "simple"
},
{
"category": "append",
"name": "can append a value to I",
"html": "<div _=\"on click \n append 'Content' to I\"></div>",
"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": "<div id=\"id\" _=\"on click append '_new' to my id\"></div>",
"action": "div.click()",
"check": "div.id.should.equal(\"id_new\")",
"async": false,
"complexity": "simple"
},
{
"category": "append",
"name": "multiple appends work",
"html": "<div id=\"id\" _=\"on click get 'foo' then append 'bar' then append 'doh' then append it to me\"></div>",
"action": "div.click()",
"check": "div.innerHTML.should.equal(\"foobardoh\")",
"async": false,
"complexity": "simple"
},
{
"category": "append",
"name": "append to undefined ignores the undefined",
"html": "<div id=\"id\" _=\"on click append 'bar' then append it to me\"></div>",
"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": "<div _=\"on click append '<a>New Content</a>' to me\"><button id=\"btn1\">Click Me</button></div>",
"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": "<div _=\"on click append \\`<button id='b1' _='on click increment window.temp'>Test</button>\\` to me\"></div>",
"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": "<div _=\"on click make a <span.topping/> then append it to me\"></div>",
"action": "div.click()",
"check": "span.classList.contains(\"topping\").should.equal(true)",
"async": false,
"complexity": "simple"
},
{
"category": "tell",
"name": "establishes a proper beingTold symbol",
"html": "<div id='d1' _='on click add .foo tell #d2 add .bar'></div><div id='d2'></div>",
"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": "<div id='d1' _='on click add .foo tell #d2 add .bar to me'></div><div id='d2'></div>",
"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": "<div id='d1' _='on click add .foo tell <p/> in me add .bar'><p id='p1'></p><p id='p2'></p><div id='d2'></div></div>",
"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": "<div id='d1' _='on click tell #d2 add .bar end add .foo'></div><div id='d2'></div>",
"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": "<div id='d1' _='on click tell null add .bar end add .foo'></div><div id='d2'></div>",
"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": "<div id='d1' _='on click tell #d2 add .bar to you'></div><div id='d2'></div>",
"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": "<div id='d1' _='on click tell #d2 put your innerText into me'></div><div id='d2'>foo</div>",
"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": "<div id='d1' _='on click tell #d2 put @foo into me'></div><div foo='bar' id='d2'></div>",
"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": "<div id=\"d1\" _=\"on click tell #d2 remove yourself\"><div id=\"d2\"></div></div>",
"action": "div1.click()",
"check": "div1.innerHTML.should.equal(`<div id=\"d2\"></div>`) && div1.innerHTML.should.equal(\"\")",
"async": false,
"complexity": "simple"
},
{
"category": "tell",
"name": "tell terminates with a feature",
"html": "<div id=\"d1\" _=\"on click tell #d2 remove yourself on click tell #d3 remove yourself\"><div id=\"d2\"></div><div id=\"d3\"></div></div>",
"action": "div1.click()",
"check": "div1.innerHTML.should.equal(`<div id=\"d2\"></div><div id=\"d3\"></div>`) && div1.innerHTML.should.equal(\"\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "can respond to events with dots in names",
"html": "<div _='on click send example.event to #d1'></div> | <div id='d1' _='on example.event add .called'></div>",
"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": "<div _='on click send example:event to #d1'></div> | <div id='d1' _='on example:event add .called'></div>",
"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": "<div _='on click send \"a-b\" to #d1'></div> | <div id='d1' _='on \"a-b\" add .called'></div>",
"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": "<div id='bar'></div> | <div _='on click from #bar add .clicked'></div>",
"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": "<div id='bar'></div> | <div _='on click from #bar set #bar.innerHTML to #bar.innerHTML + \"a\"'></div>",
"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": "<div _='on someCustomEvent put 1 into me'></div>",
"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": "<div _='on click elsewhere add .clicked'></div>",
"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": "<div _='on click from elsewhere add .clicked'></div>",
"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": "<div id='d1' _='on click send custom(foo:\"fromBar\") to #d2'></div> | <div id='d2' _='on custom(foo) call me.classList.add(foo)'></div>",
"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": "<div id='d1' _='on click send fromBar to #d2'></div> | <div id='d2' _='on fromBar(type) call me.classList.add(type)'></div>",
"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": "<div id='d1' _='on load put \"Loaded\" into my.innerHTML'></div>",
"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": "<script type='text/hyperscript'>on load put \"Loaded\" into #loadedDemo.innerHTML</script><div id='loadedDemo'></div>",
"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": "<div id='d1' _='on click[false] log event then put \"Clicked\" into my.innerHTML'></div>",
"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": "<div _='on click[buttons==0] log event then put \"Clicked\" into my.innerHTML'></div> | <div _='on click[buttons==1] log event then put \"Clicked\" into my.innerHTML'></div> | <div _='on click[buttons==1 and buttons==0] log event then put \"Clicked\" into my.innerHTML'></div>",
"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": "<div _='on example[foo] increment @count then put it into me'></div>",
"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": "<div _='on foo(bar)[bar] put \"triggered\" into my.innerHTML'></div>",
"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": "<div _='on every click put increment() into my.innerHTML then wait for a customEvent'></div>",
"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": "<div _='on foo put increment() into my.innerHTML end on bar put increment() into my.innerHTML'></div>",
"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": "<div _='on foo put increment() into my.innerHTML on bar put increment() into my.innerHTML'></div>",
"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": "<div _='on foo wait for bar then call increment()'></div>",
"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": "<div _='on foo queue first wait for bar then call increment()'></div>",
"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": "<div _='on foo queue last wait for bar then call increment()'></div>",
"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": "<div _='on foo queue all wait for bar then call increment()'></div>",
"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": "<div _='on click queue none put increment() into my.innerHTML then wait for a customEvent'></div>",
"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": "<div _='on click or foo call increment()'></div>",
"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": "<div _='on click in #d1 put it into window.tmp'> <div id='d1'></div> <div id='d2'></div> </div>",
"action": "div1.click()",
"check": "div1.should.equal(window.tmp)",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "can filter events based on count",
"html": "<div _='on click 1 put 1 + my.innerHTML as Int into my.innerHTML'>0</div>",
"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": "<div _='on click 1 to 2 put 1 + my.innerHTML as Int into my.innerHTML'>0</div>",
"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": "<div _='on click 2 and on put 1 + my.innerHTML as Int into my.innerHTML'>0</div>",
"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": "<div _='on click 1 put \"one\" into my.innerHTML on click 3 put \"three\" into my.innerHTML on click 2 put \"two\" into my.innerHTML '>0</div>",
"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": "<div _='on mutation put \"Mutated\" into me then wait for hyperscript:mutation'></div>",
"action": "div.setAttribute(\"foo\", \"bar\")",
"check": "div.innerHTML.should.equal(\"Mutated\")",
"async": true,
"complexity": "simple"
},
{
"category": "on",
"name": "can listen for attribute mutations",
"html": "<div _='on mutation of attributes put \"Mutated\" into me'></div>",
"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": "<div _='on mutation of @foo put \"Mutated\" into me'></div>",
"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": "<div _='on mutation of @bar put \"Mutated\" into me'></div>",
"action": "div.setAttribute(\"foo\", \"bar\")",
"check": "div.innerHTML.should.equal(\"\")",
"async": true,
"complexity": "simple"
},
{
"category": "on",
"name": "can listen for childList mutations",
"html": "<div _='on mutation of childList put \"Mutated\" into me then wait for hyperscript:mutation'></div>",
"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": "<div _='on mutation of childList put \"Mutated\" into me'></div>",
"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": "<div _='on mutation of characterData put \"Mutated\" into me'></div>",
"action": "div.setAttribute(\"foo\", \"bar\")",
"check": "div.innerHTML.should.equal(\"\")",
"async": true,
"complexity": "simple"
},
{
"category": "on",
"name": "can listen for multiple mutations",
"html": "<div _='on mutation of @foo or @bar put \"Mutated\" into me'></div>",
"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": "<div _='on mutation of @foo or @bar put \"Mutated\" into me'></div>",
"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": "<div id='d1'></div> | <div _='on mutation of attributes from #d1 put \"Mutated\" into me'></div>",
"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": "<script type=text/hyperscript>behavior DemoBehavior on foo wait 10ms then set my innerHTML to 'behavior'</script> | <div _='install DemoBehavior'></div> | <div _='install DemoBehavior'></div> | <div _='install DemoBehavior'></div>",
"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": "<button _='on click throwBar() catch e put e into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "can catch exceptions thrown in hyperscript functions",
"html": "<script type='text/hyperscript'> def throwBar() throw 'bar' end</script>s | <button _='on click throwBar() catch e put e into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "on",
"name": "can catch top-level exceptions",
"html": "<button _='on click throw \"bar\" catch e put e into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "can catch async top-level exceptions",
"html": "<button _='on click wait 1ms then throw \"bar\" catch e put e into me'></button>",
"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": "<button _='on click increment :x if :x is 1 wait 1ms then throw \"bar\" otherwise put \"success\" into me end catch e put e into me'></button>",
"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": "<button _='on click increment :x if :x is 1 throw \"bar\" otherwise put \"success\" into me end catch e put e into me then throw e'></button>",
"action": "btn.click(); btn.click()",
"check": "btn.innerHTML.should.equal(\"success\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "uncaught exceptions trigger 'exception' event",
"html": "<button _='on click put \"foo\" into me then throw \"bar\" on exception(error) put error into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "caught exceptions do not trigger 'exception' event",
"html": "<button _='on click put \"foo\" into me then throw \"bar\" catch e log e on exception(error) put error into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"foo\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "rethrown exceptions trigger 'exception' event",
"html": "<button _='on click put \"foo\" into me then throw \"bar\" catch e throw e on exception(error) put error into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "basic finally blocks work",
"html": "<button _='on click throw \"bar\" finally put \"bar\" into me'></button>",
"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": "<button _='on click throw \"bar\" catch e throw e finally put \"bar\" into me'></button>",
"action": "btn.click()",
"check": "btn.innerHTML.should.equal(\"bar\")",
"async": false,
"complexity": "simple"
},
{
"category": "on",
"name": "async basic finally blocks work",
"html": "<button _='on click wait a tick then throw \"bar\" finally put \"bar\" into me'></button>",
"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": "<button _='on click wait a tick then throw \"bar\" catch e set :foo to \"foo\" then throw e finally put :foo + \"bar\" into me'></button>",
"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": "<button _='on click increment :x finally if :x is 1 wait 1ms then throw \"bar\" otherwise put \"success\" into me end '></button>",
"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": "<button _='on click increment :x finally if :x is 1 throw \"bar\" otherwise put \"success\" into me end '></button>",
"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": "<div id='#d1' _=' \ton click from #doesntExist \t\tthrow \"bar\" \ton click \t\tput \"clicked\" into me\t'></div>",
"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": "<div id='d1'></div> | <div id='d2'></div> | <div _=' \ton click from #d1 or click from #d2 \t\t increment @count then put @count into me\t'></div>",
"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": "<div _='init set my.foo to 42 end on click put my.foo into my.innerHTML'></div>",
"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": "<script type='text/hyperscript'> init set window.foo to 42 end</script>",
"action": "(see body)",
"check": "window.foo.should.equal(42)",
"async": true,
"complexity": "script-tag"
},
{
"category": "init",
"name": "can initialize immediately",
"html": "<script type='text/hyperscript'>init set window.foo to 10 init immediately set window.bar to window.foo </script>",
"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": "<script type='text/hyperscript'>def foo() add .called to #d1 end</script> | <div _='on click call foo()'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def foo(str) put str into #d1.innerHTML end</script> | <div _='on click call foo(\"called\")'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def utils.foo() add .called to #d1 end</script> | <div _='on click call utils.foo()'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def foo() log meend</script> | <div _='on click call foo() then add .called to #d1'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def foo() wait 1ms log meend</script> | <div _='on click call foo() then add .called to #d1'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def foo() return \"foo\"end</script> | <div _='on click call foo() then put it into #d1.innerText'></div> | <div id='d1'></div>",
"action": "bar.click()",
"check": "div.innerText.should.equal(\"\") && div.innerText.should.equal(\"foo\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can exit",
"html": "<script type='text/hyperscript'>def foo() exit end</script>",
"action": "(see body)",
"check": "(no explicit assertion)",
"async": false,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can return a value asynchronously",
"html": "<script type='text/hyperscript'>def foo() wait 1ms return \"foo\"end</script> | <div _='on click call foo() then put it into #d1.innerText'></div> | <div id='d1'></div>",
"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": "<script type='text/hyperscript'>def foo() return \"foo\"end</script>",
"action": "(see body)",
"check": "foo().should.equal(\"foo\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can interop with javascript asynchronously",
"html": "<script type='text/hyperscript'>def foo() wait 1ms return \"foo\"end</script>",
"action": "(see body)",
"check": "val.should.equal(\"foo\")",
"async": true,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can catch exceptions",
"html": "<script type='text/hyperscript'>def foo() throw \"bar\"catch e set window.bar to e end</script>",
"action": "(see body)",
"check": "window.bar.should.equal(\"bar\")",
"async": false,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can rethrow in catch blocks",
"html": "<script type='text/hyperscript'>def foo() throw \"bar\"catch e throw e end</script>",
"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": "<script type='text/hyperscript'>def foo() throw \"bar\"catch e return 42 end</script>",
"action": "(see body)",
"check": "foo().should.equal(42)",
"async": false,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can catch async exceptions",
"html": "<script type='text/hyperscript'>def doh() wait 10ms throw \"bar\"end def foo() call doh()catch e set window.bar to e end</script>",
"action": "(see body)",
"check": "window.bar.should.equal(\"bar\")",
"async": true,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can catch nested async exceptions",
"html": "<script type='text/hyperscript'>def doh() wait 10ms throw \"bar\"end def foo() call doh()catch e set window.bar to e end</script>",
"action": "(see body)",
"check": "window.bar.should.equal(\"bar\")",
"async": true,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can rethrow in async catch blocks",
"html": "<script type='text/hyperscript'>def foo() throw \"bar\"catch e wait 10ms throw e end</script>",
"action": "(see body)",
"check": "reason.should.equal(\"bar\")",
"async": true,
"complexity": "script-tag"
},
{
"category": "def",
"name": "can return in async catch blocks",
"html": "<script type='text/hyperscript'>def foo() throw \"bar\"catch e wait 10ms return 42 end</script>",
"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": "<div _='def func() put 42 into #d3'><div id='d1' _='on click call func()'></div><div id='d2'></div><div id='d3'></div> </div>",
"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": "<div _='def func() return 42'><div id='d1' _='on click put func() into me'></div><div id='d2'></div><div id='d3'></div> </div>",
"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": "<div _='def func() put 42 into me'><div id='d1' _='on click call func()'></div><div id='d2'></div><div id='d3'></div> </div>",
"action": "(see body)",
"check": "div.innerText.should.equal(\"42\")",
"async": false,
"complexity": "simple"
},
{
"category": "def",
"name": "finally blocks run normally",
"html": "<script type='text/hyperscript'>def foo() set window.bar to 10finally set window.bar to 20 end</script>",
"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": "<script type='text/hyperscript'>def foo() set window.bar to 10 throw \"foo\"finally set window.bar to 20 end</script>",
"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": "<script type='text/hyperscript'>def foo() set window.bar to 10 call throwsAsyncException()finally set window.bar to 20 end</script>",
"action": "(see body)",
"check": "window.bar.should.equal(20)",
"async": true,
"complexity": "script-tag"
},
{
"category": "def",
"name": "async finally blocks run normally",
"html": "<script type='text/hyperscript'>def foo() wait a tick then set window.bar to 10finally set window.bar to 20 end</script>",
"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": "<script type='text/hyperscript'>def foo() wait a tick then set window.bar to 10 throw \"foo\"finally set window.bar to 20 end</script>",
"action": "(see body)",
"check": "window.bar.should.equal(20)",
"async": true,
"complexity": "script-tag"
},
{
"category": "askAnswer",
"name": "prompts and puts result in it",
"html": "<button _='on click ask \\\"What is your name?\\\" then put it into #out'>Ask</button><div id='out'></div>",
"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(\"<button _='on click ask \\\"What is your name?\\\" then put it into #out'>Ask</button><div id='out'></div>\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"Alice\");"
},
{
"category": "askAnswer",
"name": "returns null on cancel",
"html": "<button _='on click ask \\\"Name?\\\" then put it into #out'>Ask</button><div id='out'></div>",
"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(\"<button _='on click ask \\\"Name?\\\" then put it into #out'>Ask</button><div id='out'></div>\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"null\");"
},
{
"category": "askAnswer",
"name": "shows an alert",
"html": "<button _='on click answer \\\"Hello!\\\" then put \\\"done\\\" into #out'>Go</button><div id='out'></div>",
"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(\"<button _='on click answer \\\"Hello!\\\" then put \\\"done\\\" into #out'>Go</button><div id='out'></div>\");\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": "<button _='on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out'>Go</button><div id='out'></div>",
"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(\"<button _='on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out'>Go</button><div id='out'></div>\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"Yes\");"
},
{
"category": "askAnswer",
"name": "confirm returns second choice on cancel",
"html": "<button _='on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out'>Go</button><div id='out'></div>",
"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(\"<button _='on click answer \\\"Save?\\\" with \\\"Yes\\\" or \\\"No\\\" then put it into #out'>Go</button><div id='out'></div>\");\n\t\tawait find('button').click();\n\t\tawait expect(find('#out')).toHaveText(\"No\");"
},
{
"category": "dialog",
"name": "show opens a dialog as modal",
"html": "<dialog id='d'><p>Hello</p></dialog><button _='on click show #d'>Open</button>",
"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\"<dialog id='d'><p>Hello</p></dialog>\" +\n\t\t\t\"<button _='on click show #d'>Open</button>\"\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": "<dialog id='d'><p>Hello</p><button id='close' _='on click hide #d'>Close</button></dialog>",
"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\"<dialog id='d'><p>Hello</p><button id='close' _='on click hide #d'>Close</button></dialog>\"\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": "<dialog id='d'><p>Hello</p><button _='on click show #d'>Show Again</button></dialog>",
"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\"<dialog id='d'><p>Hello</p><button _='on click show #d'>Show Again</button></dialog>\"\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": "<dialog id='d'><p>Hello</p></dialog><button _='on click open #d'>Open</button>",
"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\"<dialog id='d'><p>Hello</p></dialog>\" +\n\t\t\t\"<button _='on click open #d'>Open</button>\"\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": "<dialog id='d'><p>Hello</p><button id='close' _='on click close #d'>Close</button></dialog>",
"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\"<dialog id='d'><p>Hello</p><button id='close' _='on click close #d'>Close</button></dialog>\"\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": "<details id='d'><summary>More</summary><p>Content</p></details><button _='on click open #d'>Open</button>",
"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\"<details id='d'><summary>More</summary><p>Content</p></details>\" +\n\t\t\t\"<button _='on click open #d'>Open</button>\"\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": "<details id='d' open><summary>More</summary><p>Content</p></details><button _='on click close #d'>Close</button>",
"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\"<details id='d' open><summary>More</summary><p>Content</p></details>\" +\n\t\t\t\"<button _='on click close #d'>Close</button>\"\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": "<div id='p' popover><p>Popover content</p></div><button _='on click open #p'>Open</button>",
"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\"<div id='p' popover><p>Popover content</p></div>\" +\n\t\t\t\"<button _='on click open #p'>Open</button>\"\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": "<div id='p' popover><p>Popover content</p><button id='close' _='on click close #p'>Close</button></div>",
"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\"<div id='p' popover><p>Popover content</p><button id='close' _='on click close #p'>Close</button></div>\"\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": "<dialog id='d' _='on myOpen open'></dialog><button _='on click send myOpen to #d'>Open</button>",
"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\"<dialog id='d' _='on myOpen open'></dialog>\" +\n\t\t\t\"<button _='on click send myOpen to #d'>Open</button>\"\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": "<div id='d1'><p>hello</p><p>world</p></div><button _='on click empty #d1'></button>",
"action": "find('button').dispatchEvent('click')",
"check": "toHaveText(\"helloworld\"); toHaveText(\"\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(\"<div id='d1'><p>hello</p><p>world</p></div><button _='on click empty #d1'></button>\");\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": "<div _='on click empty'>content</div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"content\"); toHaveText(\"\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(\"<div _='on click empty'>content</div>\");\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": "<div class='clearme'><p>a</p></div><div class='clearme'><p>b</p></div><button _='on click empty .clearme'></button>",
"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\"<div class='clearme'><p>a</p></div>\" +\n\t\t\t\"<div class='clearme'><p>b</p></div>\" +\n\t\t\t\"<button _='on click empty .clearme'></button>\"\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": "<div _=\"on click\n\t\t set :arr to [1,2,3]\n\t\t empty :arr\n\t\t put :arr.length into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"0\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(`<div _=\"on click\n\t\t set :arr to [1,2,3]\n\t\t empty :arr\n\t\t put :arr.length into me\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");"
},
{
"category": "empty",
"name": "can empty a set",
"html": "<div _=\"on click\n\t\t set :s to [1,2,3] as Set\n\t\t empty :s\n\t\t put :s.size into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"0\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(`<div _=\"on click\n\t\t set :s to [1,2,3] as Set\n\t\t empty :s\n\t\t put :s.size into me\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");"
},
{
"category": "empty",
"name": "can empty a map",
"html": "<div _=\"on click\n\t\t set :m to {a:1, b:2} as Map\n\t\t empty :m\n\t\t put :m.size into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"0\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(`<div _=\"on click\n\t\t set :m to {a:1, b:2} as Map\n\t\t empty :m\n\t\t put :m.size into me\"></div>`);\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<input type=\"text\" id=\"t1\" value=\"hello\" />\n\t\t\t<button _=\"on click empty #t1\">Empty</button>\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<input type=\"text\" id=\"t1\" value=\"hello\" />\n\t\t\t<button _=\"on click empty #t1\">Empty</button>\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<textarea id=\"ta1\">some text</textarea>\n\t\t\t<button _=\"on click empty #ta1\">Empty</button>\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<textarea id=\"ta1\">some text</textarea>\n\t\t\t<button _=\"on click empty #ta1\">Empty</button>\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<input type=\"checkbox\" id=\"cb1\" checked />\n\t\t\t<button _=\"on click empty #cb1\">Empty</button>\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<input type=\"checkbox\" id=\"cb1\" checked />\n\t\t\t<button _=\"on click empty #cb1\">Empty</button>\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<select id=\"sel1\">\n\t\t\t\t<option value=\"a\">A</option>\n\t\t\t\t<option value=\"b\" selected>B</option>\n\t\t\t</select>\n\t\t\t<button _=\"on click empty #sel1\">Empty</button>\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<select id=\"sel1\">\n\t\t\t\t<option value=\"a\">A</option>\n\t\t\t\t<option value=\"b\" selected>B</option>\n\t\t\t</select>\n\t\t\t<button _=\"on click empty #sel1\">Empty</button>\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<form id=\"f1\">\n\t\t\t\t<input type=\"text\" id=\"t2\" value=\"val\" />\n\t\t\t\t<textarea id=\"ta2\">text</textarea>\n\t\t\t\t<input type=\"checkbox\" id=\"cb2\" checked />\n\t\t\t</form>\n\t\t\t<button _=\"on click empty #f1\">Empty</button>\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<form id=\"f1\">\n\t\t\t\t<input type=\"text\" id=\"t2\" value=\"val\" />\n\t\t\t\t<textarea id=\"ta2\">text</textarea>\n\t\t\t\t<input type=\"checkbox\" id=\"cb2\" checked />\n\t\t\t</form>\n\t\t\t<button _=\"on click empty #f1\">Empty</button>\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<input type=\"text\" id=\"t3\" value=\"hello\" />\n\t\t\t<button _=\"on click clear #t3\">Clear</button>\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<input type=\"text\" id=\"t3\" value=\"hello\" />\n\t\t\t<button _=\"on click clear #t3\">Clear</button>\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": "<div id='d2'><p>content</p></div><button _='on click clear #d2'></button>",
"action": "find('button').dispatchEvent('click')",
"check": "toHaveText(\"content\"); toHaveText(\"\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/empty.js",
"body": "await html(\"<div id='d2'><p>content</p></div><button _='on click clear #d2'></button>\");\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": "<input id='i1' /><button _='on click focus #i1'></button>",
"action": "find('button').dispatchEvent('click')",
"check": "toBe(\"i1\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/focus.js",
"body": "await html(\"<input id='i1' /><button _='on click focus #i1'></button>\");\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": "<input id='i1' _='on click focus' />",
"action": "find('#i1').dispatchEvent('click')",
"check": "toBe(\"i1\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/focus.js",
"body": "await html(\"<input id='i1' _='on click focus' />\");\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": "<input id='i1' _='on focus wait 10ms then blur me' />",
"action": "find('#i1').focus()",
"check": "toBe(\"BODY\")",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/commands/focus.js",
"body": "await html(\"<input id='i1' _='on focus wait 10ms then blur me' />\");\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": "<div _='on click go to \\\"#test-hash\\\"'></div>",
"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(\"<div _='on click go to \\\"#test-hash\\\"'></div>\");\n\t\t// no error on parse = success"
},
{
"category": "go",
"name": "deprecated url keyword still parses",
"html": "<div _='on click go to url /test'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/go.js",
"body": "await html(\"<div _='on click go to url /test'></div>\");\n\t\t// no error on parse = success"
},
{
"category": "go",
"name": "go to naked URL starting with / parses",
"html": "<div _='on click go to /test/path'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/go.js",
"body": "await html(\"<div _='on click go to /test/path'></div>\");\n\t\t// no error on parse = success"
},
{
"category": "go",
"name": "go to element scrolls",
"html": "<div style='height: 2000px'></div><div id='target'>Target</div><div _='on click go to #target'></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/go.js",
"body": "await html(\n\t\t\t\"<div style='height: 2000px'></div>\" +\n\t\t\t\"<div id='target'>Target</div>\" +\n\t\t\t\"<div _='on click go to #target'></div>\"\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": "<div style='height: 2000px'></div><div id='target'>Target</div><div _='on click go to the top of #target'></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/go.js",
"body": "await html(\n\t\t\t\"<div style='height: 2000px'></div>\" +\n\t\t\t\"<div id='target'>Target</div>\" +\n\t\t\t\"<div _='on click go to the top of #target'></div>\"\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": "<div id='outer' _='on click add .outer-clicked'> <a id='inner' href='#shouldnot' _='on click halt'>click me</a></div>",
"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\"<div id='outer' _='on click add .outer-clicked'>\" +\n\t\t\t\" <a id='inner' href='#shouldnot' _='on click halt'>click me</a>\" +\n\t\t\t\"</div>\"\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": "<div _='on click halt then add .should-not-happen'>test</div>",
"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\"<div _='on click halt then add .should-not-happen'>test</div>\"\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": "<div id='outer' _='on click add .outer-clicked'> <div id='inner' _='on click halt the event then add .continued'>click me</div></div>",
"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\"<div id='outer' _='on click add .outer-clicked'>\" +\n\t\t\t\" <div id='inner' _='on click halt the event then add .continued'>click me</div>\" +\n\t\t\t\"</div>\"\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": "<div id='outer' _='on click add .outer-clicked'> <div id='inner' _=\\\"on click halt the event's then add .continued\\\">click me</div></div>",
"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\"<div id='outer' _='on click add .outer-clicked'>\" +\n\t\t\t\" <div id='inner' _=\\\"on click halt the event's then add .continued\\\">click me</div>\" +\n\t\t\t\"</div>\"\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": "<div id='outer' _='on click add .outer-clicked'> <div id='inner' _='on click halt bubbling then add .continued'>click me</div></div>",
"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\"<div id='outer' _='on click add .outer-clicked'>\" +\n\t\t\t\" <div id='inner' _='on click halt bubbling then add .continued'>click me</div>\" +\n\t\t\t\"</div>\"\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 = \"<div _='init halt'></div>\";\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": "<div id='outer' _='on click add .outer-clicked'> <div id='inner' _='on click halt default'>click me</div></div>",
"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\"<div id='outer' _='on click add .outer-clicked'>\" +\n\t\t\t\" <div id='inner' _='on click halt default'>click me</div>\" +\n\t\t\t\"</div>\"\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": "<div id='target'>old</div><button _='on click morph #target to \\\"<div id=target>new</div>\\\"'>go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button _='on click morph #target to \\\"<div id=target>new</div>\\\"'>go</button>\"\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": "<div id='target'>old</div><button id='go' _='on click morph #target to \\\"<div id=target>new</div>\\\"'>go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button id='go' _='on click morph #target to \\\"<div id=target>new</div>\\\"'>go</button>\"\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": "<div id='target' class='old'>content</div><button _='on click morph #target to \\\"<div id=target class=new>content</div>\\\"'>go</button>",
"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\"<div id='target' class='old'>content</div>\" +\n\t\t\t\"<button _='on click morph #target to \\\"<div id=target class=new>content</div>\\\"'>go</button>\"\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": "<div id='target'><span>first</span></div><button id='go' _=\\\"on click morph #target to '<div id=target><span>first</span><span>second</span></div>'\\\">go</button>",
"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\"<div id='target'><span>first</span></div>\" +\n\t\t\t\"<button id='go' _=\\\"on click morph #target to '<div id=target><span>first</span><span>second</span></div>'\\\">go</button>\"\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": "<div id='target'><span>first</span><span>second</span></div><button id='go' _=\\\"on click morph #target to '<div id=target><span>first</span></div>'\\\">go</button>",
"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\"<div id='target'><span>first</span><span>second</span></div>\" +\n\t\t\t\"<button id='go' _=\\\"on click morph #target to '<div id=target><span>first</span></div>'\\\">go</button>\"\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": "<div id='target'><p>old</p></div><button id='go' _=\\\"on click morph #target to '<div id=target><p id=inner _=&#34;on click put `clicked` into me&#34;>new</p></div>'\\\">go</button>",
"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\"<div id='target'><p>old</p></div>\" +\n\t\t\t\"<button id='go' _=\\\"on click morph #target to '<div id=target><p id=inner _=&#34;on click put `clicked` into me&#34;>new</p></div>'\\\">go</button>\"\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": "<div id='target'> <div id='child' _='on click put \\\"alive\\\" into me'>child</div></div><button _='on click morph #target to \\\"<div id=target><p>replaced</p></div>\\\"'>go</button>",
"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\"<div id='target'>\" +\n\t\t\t\" <div id='child' _='on click put \\\"alive\\\" into me'>child</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button _='on click morph #target to \\\"<div id=target><p>replaced</p></div>\\\"'>go</button>\"\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": "<div id='target'> <div id='a'>A</div> <div id='b'>B</div></div><button _='on click morph #target to \\\"<div id=target><div id=b>B2</div><div id=a>A2</div></div>\\\"'>go</button>",
"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\"<div id='target'>\" +\n\t\t\t\" <div id='a'>A</div>\" +\n\t\t\t\" <div id='b'>B</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button _='on click morph #target to \\\"<div id=target><div id=b>B2</div><div id=a>A2</div></div>\\\"'>go</button>\"\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": "<div id='target'><div id='child'>old</div></div><button id='go' _='on click morph #target to \\\"<div id=target><div id=child>new</div></div>\\\"'>go</button>",
"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\"<div id='target'><div id='child'>old</div></div>\" +\n\t\t\t\"<button id='go' _='on click morph #target to \\\"<div id=target><div id=child>new</div></div>\\\"'>go</button>\"\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": "<div id='target'>original</div><button id='go' _='on click set content to \\\"<div id=target>morphed</div>\\\" then morph #target to content'>go</button>",
"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\"<div id='target'>original</div>\" +\n\t\t\t\"<button id='go' _='on click set content to \\\"<div id=target>morphed</div>\\\" then morph #target to content'>go</button>\"\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<form id=\"f1\">\n\t\t\t\t<input type=\"text\" id=\"t1\" value=\"original\" />\n\t\t\t\t<button type=\"button\" _=\"on click set #t1's value to 'changed'\">Change</button>\n\t\t\t\t<button type=\"button\" id=\"rst\" _=\"on click reset #f1\">Reset</button>\n\t\t\t</form>\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<form id=\"f1\">\n\t\t\t\t<input type=\"text\" id=\"t1\" value=\"original\" />\n\t\t\t\t<button type=\"button\" _=\"on click set #t1's value to 'changed'\">Change</button>\n\t\t\t\t<button type=\"button\" id=\"rst\" _=\"on click reset #f1\">Reset</button>\n\t\t\t</form>\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<form _=\"on custom reset\">\n\t\t\t\t<input type=\"text\" id=\"t2\" value=\"default\" />\n\t\t\t</form>\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<form _=\"on custom reset\">\n\t\t\t\t<input type=\"text\" id=\"t2\" value=\"default\" />\n\t\t\t</form>\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<input type=\"text\" id=\"t3\" value=\"hello\" />\n\t\t\t<button _=\"on click reset #t3\">Reset</button>\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<input type=\"text\" id=\"t3\" value=\"hello\" />\n\t\t\t<button _=\"on click reset #t3\">Reset</button>\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<input type=\"checkbox\" id=\"cb1\" checked />\n\t\t\t<button _=\"on click reset #cb1\">Reset</button>\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<input type=\"checkbox\" id=\"cb1\" checked />\n\t\t\t<button _=\"on click reset #cb1\">Reset</button>\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<input type=\"checkbox\" id=\"cb2\" />\n\t\t\t<button _=\"on click reset #cb2\">Reset</button>\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<input type=\"checkbox\" id=\"cb2\" />\n\t\t\t<button _=\"on click reset #cb2\">Reset</button>\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<textarea id=\"ta1\">original text</textarea>\n\t\t\t<button _=\"on click reset #ta1\">Reset</button>\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<textarea id=\"ta1\">original text</textarea>\n\t\t\t<button _=\"on click reset #ta1\">Reset</button>\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<select id=\"sel1\">\n\t\t\t\t<option value=\"a\">A</option>\n\t\t\t\t<option value=\"b\" selected>B</option>\n\t\t\t\t<option value=\"c\">C</option>\n\t\t\t</select>\n\t\t\t<button _=\"on click reset #sel1\">Reset</button>\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<select id=\"sel1\">\n\t\t\t\t<option value=\"a\">A</option>\n\t\t\t\t<option value=\"b\" selected>B</option>\n\t\t\t\t<option value=\"c\">C</option>\n\t\t\t</select>\n\t\t\t<button _=\"on click reset #sel1\">Reset</button>\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<input type=\"text\" class=\"resettable\" value=\"one\" />\n\t\t\t<input type=\"text\" class=\"resettable\" value=\"two\" />\n\t\t\t<button _=\"on click reset .resettable\">Reset</button>\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<input type=\"text\" class=\"resettable\" value=\"one\" />\n\t\t\t<input type=\"text\" class=\"resettable\" value=\"two\" />\n\t\t\t<button _=\"on click reset .resettable\">Reset</button>\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": "<div style='height: 2000px'></div><div id='target'>Target</div><div _='on click scroll to #target'></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/scroll.js",
"body": "await html(\n\t\t\t\"<div style='height: 2000px'></div>\" +\n\t\t\t\"<div id='target'>Target</div>\" +\n\t\t\t\"<div _='on click scroll to #target'></div>\"\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": "<div style='height: 2000px'></div><div id='target' style='height: 200px'>Target</div><div _='on click scroll to the top of #target'></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/scroll.js",
"body": "await html(\n\t\t\t\"<div style='height: 2000px'></div>\" +\n\t\t\t\"<div id='target' style='height: 200px'>Target</div>\" +\n\t\t\t\"<div _='on click scroll to the top of #target'></div>\"\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": "<div style='height: 5000px'></div><div _='on click scroll down by 300px'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/scroll.js",
"body": "await html(\n\t\t\t\"<div style='height: 5000px'></div>\" +\n\t\t\t\"<div _='on click scroll down by 300px'></div>\"\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": "<div style='height: 5000px'></div><div _='on click scroll up by 100px'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/scroll.js",
"body": "await html(\n\t\t\t\"<div style='height: 5000px'></div>\" +\n\t\t\t\"<div _='on click scroll up by 100px'></div>\"\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": "<div style='height: 5000px'></div><div _='on click scroll by 200px'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/scroll.js",
"body": "await html(\n\t\t\t\"<div style='height: 5000px'></div>\" +\n\t\t\t\"<div _='on click scroll by 200px'></div>\"\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": "<div id='box' style='height: 100px; overflow: auto'> <div style='height: 1000px'>tall</div></div><button id='go' _='on click scroll #box down by 200px'>go</button>",
"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\"<div id='box' style='height: 100px; overflow: auto'>\" +\n\t\t\t\" <div style='height: 1000px'>tall</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button id='go' _='on click scroll #box down by 200px'>go</button>\"\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": "<div id='box' style='height: 100px; overflow: auto'> <div style='height: 500px'>spacer</div> <div id='item'>target</div></div><button id='go' _='on click scroll to #item in #box'>go</button>",
"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\"<div id='box' style='height: 100px; overflow: auto'>\" +\n\t\t\t\" <div style='height: 500px'>spacer</div>\" +\n\t\t\t\" <div id='item'>target</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button id='go' _='on click scroll to #item in #box'>go</button>\"\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": "<div id='box' style='width: 100px; overflow: auto; white-space: nowrap'> <div style='width: 5000px; height: 50px'>wide</div></div><button id='go' _='on click scroll #box right by 300px'>go</button>",
"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\"<div id='box' style='width: 100px; overflow: auto; white-space: nowrap'>\" +\n\t\t\t\" <div style='width: 5000px; height: 50px'>wide</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button id='go' _='on click scroll #box right by 300px'>go</button>\"\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": "<input id='inp' value='hello world' /><button _='on click select #inp'>Select</button>",
"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\"<input id='inp' value='hello world' />\" +\n\t\t\t\"<button _='on click select #inp'>Select</button>\"\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": "<textarea id='ta'>some text</textarea><button _='on click select #ta'>Select</button>",
"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\"<textarea id='ta'>some text</textarea>\" +\n\t\t\t\"<button _='on click select #ta'>Select</button>\"\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": "<input id='inp' value='test' _='on click select' />",
"action": "find('#inp').click()",
"check": "toBe(\"test\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/commands/select.js",
"body": "await html(\"<input id='inp' value='test' _='on click select' />\");\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": "<p id='text'>Hello World</p><button _='on click put the selection into #out'>Get</button><div id='out'></div>",
"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\"<p id='text'>Hello World</p>\" +\n\t\t\t\"<button _='on click put the selection into #out'>Get</button>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div id='d1' _='on click set x to \"a\" then set y to \"b\" then swap x with y then put x + y into me'></div>",
"action": "find('#d1').dispatchEvent('click')",
"check": "toHaveText(\"ba\")",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/commands/swap.js",
"body": "await html(`<div id='d1' _='on click set x to \"a\" then set y to \"b\" then swap x with y then put x + y into me'></div>`);\n\t\tawait find('#d1').dispatchEvent('click');\n\t\tawait expect(find('#d1')).toHaveText(\"ba\");"
},
{
"category": "swap",
"name": "can swap two properties",
"html": "<div id='d1' _='on click set #a.textContent to \"hello\" then set #b.textContent to \"world\" then swap #a.textContent with #b.textContent'></div>\n\t\t\t<span id='a'>x</span><span id='b'>y</span>",
"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(`<div id='d1' _='on click set #a.textContent to \"hello\" then set #b.textContent to \"world\" then swap #a.textContent with #b.textContent'></div>\n\t\t\t<span id='a'>x</span><span id='b'>y</span>`);\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": "<div id='d1' _='on click set arr to [1,2,3] then swap arr[0] with arr[2] then put arr as String into me'></div>",
"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(`<div id='d1' _='on click set arr to [1,2,3] then swap arr[0] with arr[2] then put arr as String into me'></div>`);\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": "<div id='d1' _='on click set x to \"old\" then set #target.dataset.val to \"new\" then swap x with #target.dataset.val then put x into me'></div>\n\t\t\t<span id='target' data-val='x'></span>",
"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(`<div id='d1' _='on click set x to \"old\" then set #target.dataset.val to \"new\" then swap x with #target.dataset.val then put x into me'></div>\n\t\t\t<span id='target' data-val='x'></span>`);\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": "<input type=\"text\" id=\"name-input\" value=\"Alice\" /><span _=\"bind $name and #name-input.value end\n\t\t\t when $name changes put it into me\"></span>",
"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`<input type=\"text\" id=\"name-input\" value=\"Alice\" />` +\n\t\t\t`<span _=\"bind $name and #name-input.value end\n\t\t\t when $name changes put it into me\"></span>`\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": "<div _=\"bind $theme and @data-theme\"></div>",
"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(`<div _=\"bind $theme and @data-theme\"></div>`)\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": "<div _=\"bind $color and @data-color\"></div>",
"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(`<div _=\"bind $color and @data-color\"></div>`)\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": "<input type=\"text\" id=\"city-input\" value=\"Paris\" /><span _=\"bind $city to #city-input.value end\n\t\t\t when $city changes put it into me\"></span>",
"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`<input type=\"text\" id=\"city-input\" value=\"Paris\" />` +\n\t\t\t`<span _=\"bind $city to #city-input.value end\n\t\t\t when $city changes put it into me\"></span>`\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": "<input type=\"text\" value=\"hello\"\n\t\t\t _=\"bind $greeting to me end\n\t\t\t when $greeting changes put it into next <span/>\"></input><span></span>",
"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`<input type=\"text\" value=\"hello\"\n\t\t\t _=\"bind $greeting to me end\n\t\t\t when $greeting changes put it into next <span/>\"></input>` +\n\t\t\t`<span></span>`\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": "<input type=\"checkbox\" _=\"bind $isDarkMode to me\" /><span _=\"when $isDarkMode changes put it into me\"></span>",
"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`<input type=\"checkbox\" _=\"bind $isDarkMode to me\" />` +\n\t\t\t`<span _=\"when $isDarkMode changes put it into me\"></span>`\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": "<textarea _=\"bind $bio to me\">Hello world</textarea><span _=\"when $bio changes put it into me\"></span>",
"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`<textarea _=\"bind $bio to me\">Hello world</textarea>` +\n\t\t\t`<span _=\"when $bio changes put it into me\"></span>`\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": "<select _=\"bind $country to me\">\n\t\t\t <option value=\"us\">United States</option>\n\t\t\t <option value=\"uk\">United Kingdom</option>\n\t\t\t <option value=\"fr\">France</option>\n\t\t\t </select><span _=\"when $country changes put it into me\"></span>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<select _=\"bind $country to me\">\n\t\t\t <option value=\"us\">United States</option>\n\t\t\t <option value=\"uk\">United Kingdom</option>\n\t\t\t <option value=\"fr\">France</option>\n\t\t\t </select>` +\n\t\t\t`<span _=\"when $country changes put it into me\"></span>`\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 = '<div _=\"bind $nope to me\"></div>'\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": "<input type=\"number\" _=\"bind $price to me\" /><span _=\"when $price changes put it into me\"></span>",
"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`<input type=\"number\" _=\"bind $price to me\" />` +\n\t\t\t`<span _=\"when $price changes put it into me\"></span>`\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": "<div _=\"bind $isEnabled and @data-active\"></div>",
"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(`<div _=\"bind $isEnabled and @data-active\"></div>`)\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": "<div _=\"bind $isHidden and @aria-hidden\"></div>",
"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(`<div _=\"bind $isHidden and @aria-hidden\"></div>`)\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": "<div _=\"bind $opacity and *opacity\">visible</div>",
"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(`<div _=\"bind $opacity and *opacity\">visible</div>`)\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": "<input type=\"text\" value=\"hello\" _=\"bind $message to me\" />",
"action": "",
"check": "toBe(false)",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(`<input type=\"text\" value=\"hello\" _=\"bind $message to me\" />`)\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": "<input type=\"text\" value=\"original\" _=\"bind $searchTerm to me\" /><span _=\"when $searchTerm changes put it into me\"></span>",
"action": "evaluate({...})",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<input type=\"text\" value=\"original\" _=\"bind $searchTerm to me\" />` +\n\t\t\t`<span _=\"when $searchTerm changes put it into me\"></span>`\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": "<form id=\"test-form\"> <input type=\"text\" value=\"default\" _=\"bind $formField to me\" /></form><span _=\"when $formField changes put it into me\"></span>",
"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`<form id=\"test-form\">` +\n\t\t\t` <input type=\"text\" value=\"default\" _=\"bind $formField to me\" />` +\n\t\t\t`</form>` +\n\t\t\t`<span _=\"when $formField changes put it into me\"></span>`\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": "<input type=\"radio\" name=\"color\" value=\"red\" _=\"bind $color to me\" /><input type=\"radio\" name=\"color\" value=\"blue\" _=\"bind $color to me\" /><input type=\"radio\" name=\"color\" value=\"green\" _=\"bind $color to me\" /><span _=\"when $color changes put it into me\"></span>",
"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`<input type=\"radio\" name=\"color\" value=\"red\" _=\"bind $color to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"color\" value=\"blue\" _=\"bind $color to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"color\" value=\"green\" _=\"bind $color to me\" />` +\n\t\t\t`<span _=\"when $color changes put it into me\"></span>`\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": "<input type=\"radio\" name=\"size\" value=\"small\" _=\"bind $size to me\" /><input type=\"radio\" name=\"size\" value=\"medium\" _=\"bind $size to me\" /><input type=\"radio\" name=\"size\" value=\"large\" _=\"bind $size to me\" />",
"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`<input type=\"radio\" name=\"size\" value=\"small\" _=\"bind $size to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"size\" value=\"medium\" _=\"bind $size to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"size\" value=\"large\" _=\"bind $size to me\" />`\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": "<input type=\"radio\" name=\"fruit\" value=\"apple\" _=\"bind $fruit to me\" /><input type=\"radio\" name=\"fruit\" value=\"banana\" _=\"bind $fruit to me\" /><input type=\"radio\" name=\"fruit\" value=\"cherry\" _=\"bind $fruit to me\" />",
"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`<input type=\"radio\" name=\"fruit\" value=\"apple\" _=\"bind $fruit to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"fruit\" value=\"banana\" _=\"bind $fruit to me\" />` +\n\t\t\t`<input type=\"radio\" name=\"fruit\" value=\"cherry\" _=\"bind $fruit to me\" />`\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": "<div _=\"bind .dark and $darkMode\">test</div>",
"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(`<div _=\"bind .dark and $darkMode\">test</div>`)\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": "<div _=\"bind .dark and $darkMode\">test</div>",
"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(`<div _=\"bind .dark and $darkMode\">test</div>`)\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": "<div _=\"bind .highlight to $highlighted\">test</div>",
"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(`<div _=\"bind .highlight to $highlighted\">test</div>`)\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": "<input type=\"text\" value=\"Bob\" _=\"bind $name to my value\" />",
"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(`<input type=\"text\" value=\"Bob\" _=\"bind $name to my value\" />`)\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": "<input type=\"text\" value=\"Bob\" _=\"bind my value to $name\" />",
"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(`<input type=\"text\" value=\"Bob\" _=\"bind my value to $name\" />`)\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": "<div data-color=\"red\" _=\"bind $color to @data-color\"></div>",
"action": "",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(`<div data-color=\"red\" _=\"bind $color to @data-color\"></div>`)\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": "<div _=\"bind @data-theme to $theme\"></div>",
"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(`<div _=\"bind @data-theme to $theme\"></div>`)\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": "<div _=\"bind .dark to $isDark\"></div>",
"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(`<div _=\"bind .dark to $isDark\"></div>`)\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": "<div class=\"dark\" _=\"bind $isDark to .dark\"></div>",
"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(`<div class=\"dark\" _=\"bind $isDark to .dark\"></div>`)\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": "<input type=\"text\" value=\"hello\" _=\"bind $myVal to my value\" />",
"action": "find('input').fill('world')",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(`<input type=\"text\" value=\"hello\" _=\"bind $myVal to my value\" />`)\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": "<div _=\"bind $label and my @data-label\"></div>",
"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(`<div _=\"bind $label and my @data-label\"></div>`)\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": "<input type=\"text\" id=\"of-input\" value=\"initial\" /><div _=\"bind $search to value of #of-input\"></div>",
"action": "",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<input type=\"text\" id=\"of-input\" value=\"initial\" />` +\n\t\t\t`<div _=\"bind $search to value of #of-input\"></div>`\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": "<input type=\"checkbox\" id=\"dark-toggle\" /><div _=\"bind .dark and #dark-toggle's checked\">test</div>",
"action": "",
"check": "toHaveClass('dark')",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<input type=\"checkbox\" id=\"dark-toggle\" />` +\n\t\t\t`<div _=\"bind .dark and #dark-toggle's checked\">test</div>`\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": "<input type=\"text\" id=\"title-input\" value=\"Hello\" /><h1 _=\"bind @data-title and #title-input's value\"></h1>",
"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`<input type=\"text\" id=\"title-input\" value=\"Hello\" />` +\n\t\t\t`<h1 _=\"bind @data-title and #title-input's value\"></h1>`\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": "<input type=\"range\" id=\"slider\" value=\"50\" /><input type=\"number\" _=\"bind my value and #slider's value\" />",
"action": "evaluate({...})",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<input type=\"range\" id=\"slider\" value=\"50\" />` +\n\t\t\t`<input type=\"number\" _=\"bind my value and #slider's value\" />`\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": "<input type=\"text\" id=\"name-field\" value=\"\" /><div _=\"bind $name to #name-field\"></div>",
"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`<input type=\"text\" id=\"name-field\" value=\"\" />` +\n\t\t\t`<div _=\"bind $name to #name-field\"></div>`\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": "<input type=\"checkbox\" id=\"agree-cb\" /><div _=\"bind $agreed to #agree-cb\"></div>",
"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`<input type=\"checkbox\" id=\"agree-cb\" />` +\n\t\t\t`<div _=\"bind $agreed to #agree-cb\"></div>`\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": "<input type=\"number\" id=\"qty-input\" /><div _=\"bind $qty to #qty-input\"></div>",
"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`<input type=\"number\" id=\"qty-input\" />` +\n\t\t\t`<div _=\"bind $qty to #qty-input\"></div>`\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": "<input type=\"range\" id=\"range-slider\" value=\"50\" /><input type=\"number\" _=\"bind me to #range-slider\" />",
"action": "evaluate({...})",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(\n\t\t\t`<input type=\"range\" id=\"range-slider\" value=\"50\" />` +\n\t\t\t`<input type=\"number\" _=\"bind me to #range-slider\" />`\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": "<input type=\"text\" value=\"Bob\" _=\"bind me to $name\" />",
"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(`<input type=\"text\" value=\"Bob\" _=\"bind me to $name\" />`)\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": "<input type=\"text\" value=\"Bob\" _=\"bind $name to me\" />",
"action": "",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(`<input type=\"text\" value=\"Bob\" _=\"bind $name to me\" />`)\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": "<div contenteditable=\"true\" _=\"bind $text to me\">initial</div>",
"action": "await run(\"set $text to '",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/bind.js",
"body": "await html(`<div contenteditable=\"true\" _=\"bind $text to me\">initial</div>`)\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": "<test-input _=\"bind $custom to me\"></test-input>",
"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(`<test-input _=\"bind $custom to me\"></test-input>`)\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": "<input type=\"radio\" name=\"color\" value=\"red\" _=\"bind $color to me\" checked /><input type=\"radio\" name=\"color\" value=\"blue\" _=\"bind $color to me\" />",
"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`<input type=\"radio\" name=\"color\" value=\"red\" _=\"bind $color to me\" checked />` +\n\t\t\t`<input type=\"radio\" name=\"color\" value=\"blue\" _=\"bind $color to me\" />`\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": "<form><input type=\"text\" id=\"binput\" value=\"initial\" _=\"bind $val to me\" /><button type=\"reset\">Reset</button></form>",
"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`<form>` +\n\t\t\t`<input type=\"text\" id=\"binput\" value=\"initial\" _=\"bind $val to me\" />` +\n\t\t\t`<button type=\"reset\">Reset</button>` +\n\t\t\t`</form>`\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": "<div _=\"live set $total to ($price * $qty) end\n\t\t\t when $total changes put it into me\"></div>",
"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`<div _=\"live set $total to ($price * $qty) end\n\t\t\t when $total changes put it into me\"></div>`\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": "<div _=\"live put 'hello ' + $greeting into me\"></div>",
"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(`<div _=\"live put 'hello ' + $greeting into me\"></div>`)\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": "<div _=\"live set my @data-theme to $theme\"></div>",
"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(`<div _=\"live set my @data-theme to $theme\"></div>`)\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": "<div _=\"live set *opacity to $opacity\">visible</div>",
"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(`<div _=\"live set *opacity to $opacity\">visible</div>`)\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": "<div _=\"live put '$' + ($price * $qty) into me\"></div>",
"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(`<div _=\"live put '$' + ($price * $qty) into me\"></div>`)\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": "<span id=\"w\" _=\"when $doubleWidth changes put it into me\"></span><span id=\"h\" _=\"when $doubleHeight changes put it into me\"></span><div _=\"live\n\t\t\t set $doubleWidth to ($width * 2)\n\t\t\t set $doubleHeight to ($height * 2)\n\t\t\t end\"></div>",
"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`<span id=\"w\" _=\"when $doubleWidth changes put it into me\"></span>` +\n\t\t\t`<span id=\"h\" _=\"when $doubleHeight changes put it into me\"></span>` +\n\t\t\t`<div _=\"live\n\t\t\t set $doubleWidth to ($width * 2)\n\t\t\t set $doubleHeight to ($height * 2)\n\t\t\t end\"></div>`\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": "<span id=\"w\" _=\"when $doubleWidth changes put it into me\"></span><span id=\"h\" _=\"when $doubleHeight changes put it into me\"></span><div _=\"live set $doubleWidth to ($width * 2) end\n\t\t\t live set $doubleHeight to ($height * 2)\"></div>",
"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`<span id=\"w\" _=\"when $doubleWidth changes put it into me\"></span>` +\n\t\t\t`<span id=\"h\" _=\"when $doubleHeight changes put it into me\"></span>` +\n\t\t\t`<div _=\"live set $doubleWidth to ($width * 2) end\n\t\t\t live set $doubleHeight to ($height * 2)\"></div>`\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": "<div _=\"live\n\t\t\t set $subtotal to ($price * $qty)\n\t\t\t set $total to ($subtotal + $tax)\n\t\t\t end\n\t\t\t when $total changes put it into me\"></div>",
"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`<div _=\"live\n\t\t\t set $subtotal to ($price * $qty)\n\t\t\t set $total to ($subtotal + $tax)\n\t\t\t end\n\t\t\t when $total changes put it into me\"></div>`\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": "<div _=\"live\n\t\t\t if $isActive add .active to me else remove .active from me end\n\t\t\t end\">test</div>",
"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`<div _=\"live\n\t\t\t if $isActive add .active to me else remove .active from me end\n\t\t\t end\">test</div>`\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": "<div _=\"live\n\t\t\t if $isVisible set *display to 'block' else set *display to 'none' end\n\t\t\t end\">content</div>",
"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`<div _=\"live\n\t\t\t if $isVisible set *display to 'block' else set *display to 'none' end\n\t\t\t end\">content</div>`\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": "<div _=\"live put $message into me\"></div>",
"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(`<div _=\"live put $message into me\"></div>`)\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": "<div _=\"live\n\t\t\t if $showFirst put $firstName into me else put $lastName into me end\n\t\t\t end\"></div>",
"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`<div _=\"live\n\t\t\t if $showFirst put $firstName into me else put $lastName into me end\n\t\t\t end\"></div>`\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": "<div _=\"live set my @data-name to $firstName end\n\t\t\t live set my @data-age to $age\"></div>",
"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`<div _=\"live set my @data-name to $firstName end\n\t\t\t live set my @data-age to $age\"></div>`\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": "<div _=\"live set my @data-status to $status end\n\t\t\t when $status changes put 'Status: ' + it into me\"></div>",
"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`<div _=\"live set my @data-status to $status end\n\t\t\t when $status changes put 'Status: ' + it into me\"></div>`\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": "<input type=\"text\" value=\"alice\"\n\t\t\t _=\"bind $username to me end\n\t\t\t live set my @data-mirror to $username\" /><span _=\"when $username changes put it into me\"></span>",
"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`<input type=\"text\" value=\"alice\"\n\t\t\t _=\"bind $username to me end\n\t\t\t live set my @data-mirror to $username\" />` +\n\t\t\t`<span _=\"when $username changes put it into me\"></span>`\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": "<div id=\"d1\" _=\"live put $count into me\"></div>",
"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(`<div id=\"d1\" _=\"live put $count into me\"></div>`)\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": "<div _=\"live put $items.join(', ') into me\"></div>",
"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(`<div _=\"live put $items.join(', ') into me\"></div>`)\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": "<div _=\"live put $items.join(', ') into me\"></div>",
"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(`<div _=\"live put $items.join(', ') into me\"></div>`)\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": "<div _=\"live put $items.join(', ') into me\"></div>",
"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(`<div _=\"live put $items.join(', ') into me\"></div>`)\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": "<div _=\"live put $items.join(', ') into me\"></div>",
"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(`<div _=\"live put $items.join(', ') into me\"></div>`)\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": "<div _=\"live put $obj.name into me\"></div>",
"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(`<div _=\"live put $obj.name into me\"></div>`)\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": "<template id=\"people-tmpl\">#for p in people\\n<span>\\${p.name}</span>\\n#end</template><div _=\"live render #people-tmpl with people: $people then put it into my.innerHTML end\"></div>",
"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`<template id=\"people-tmpl\">#for p in people\\n<span>\\${p.name}</span>\\n#end</template>` +\n\t\t\t`<div _=\"live render #people-tmpl with people: $people then put it into my.innerHTML end\"></div>`\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": "<template id=\"items-tmpl\">#for item in items\\n<span>\\${item.label}</span>\\n#end</template><div _=\"live render #items-tmpl with items: $items then put it into my.innerHTML end\"></div>",
"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`<template id=\"items-tmpl\">#for item in items\\n<span>\\${item.label}</span>\\n#end</template>` +\n\t\t\t`<div _=\"live render #items-tmpl with items: $items then put it into my.innerHTML end\"></div>`\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": "<output _=\"when ($obj's x + $obj's y) changes put it into me\"></output>",
"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(`<output _=\"when ($obj's x + $obj's y) changes put it into me\"></output>`)\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": "<output _=\"when $data's inner's val changes put it into me\"></output>",
"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(`<output _=\"when $data's inner's val changes put it into me\"></output>`)\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": "<input type=\"text\" id=\"prop-input\" value=\"start\" /><output _=\"when #prop-input's value changes put it into me\"></output>",
"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`<input type=\"text\" id=\"prop-input\" value=\"start\" />` +\n\t\t\t`<output _=\"when #prop-input's value changes put it into me\"></output>`\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": "<output _=\"live put $config's label into me\"></output>",
"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(`<output _=\"live put $config's label into me\"></output>`)\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": "<div id='box' style='width:100px; height:100px' _='on resize put detail.width into #out'></div><div id='out'></div>",
"action": "",
"check": "toHaveText(\"200\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/resize.js",
"body": "await html(\n\t\t\t\"<div id='box' style='width:100px; height:100px' \" +\n\t\t\t\"_='on resize put detail.width into #out'></div>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div id='box' style='width:100px; height:100px' _='on resize put detail.height into #out'></div><div id='out'></div>",
"action": "",
"check": "toHaveText(\"300\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/resize.js",
"body": "await html(\n\t\t\t\"<div id='box' style='width:100px; height:100px' \" +\n\t\t\t\"_='on resize put detail.height into #out'></div>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div id='box' style='width:100px; height:100px'></div><div id='out' _='on resize from #box put detail.width into me'></div>",
"action": "",
"check": "toHaveText(\"150\")",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/resize.js",
"body": "await html(\n\t\t\t\"<div id='box' style='width:100px; height:100px'></div>\" +\n\t\t\t\"<div id='out' _='on resize from #box put detail.width into me'></div>\"\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": "<div _=\"when $global changes put it into me\"></div>",
"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(`<div _=\"when $global changes put it into me\"></div>`)\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": "<div _=\"when $global changes put it into me\"></div>",
"action": "await run(\"set $global to '",
"check": "toHaveText('Changed!')",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div _=\"when $global changes put it into me\"></div>`)\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": "<div _=\"init set :count to 0 end\n\t\t\t when :count changes put it into me end\n\t\t\t on click increment :count\">0</div>",
"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`<div _=\"init set :count to 0 end\n\t\t\t when :count changes put it into me end\n\t\t\t on click increment :count\">0</div>`\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": "<div id=\"d1\" _=\"when $shared changes put 'first' into me\"></div><div id=\"d2\" _=\"when $shared changes put 'second' into me\"></div>",
"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`<div id=\"d1\" _=\"when $shared changes put 'first' into me\"></div>` +\n\t\t\t`<div id=\"d2\" _=\"when $shared changes put 'second' into me\"></div>`\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": "<div _=\"when $multi changes put 'first' into me then add .executed to me\"></div>",
"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(`<div _=\"when $multi changes put 'first' into me then add .executed to me\"></div>`)\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": "<div _=\"when $neverSet changes put 'synced' into me\">original</div>",
"action": "",
"check": "toHaveText('original')",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div _=\"when $neverSet changes put 'synced' into me\">original</div>`)\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": "<div _=\"when $dedup changes increment :callCount then put :callCount into me\"></div>",
"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`<div _=\"when $dedup changes increment :callCount then put :callCount into me\"></div>`\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": "<div _=\"when ($a + $b) changes put it into me\"></div>",
"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(`<div _=\"when ($a + $b) changes put it into me\"></div>`)\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": "<div data-title=\"original\" _=\"when @data-title changes put it into me\"></div>",
"action": "",
"check": "toHaveText('original')",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div data-title=\"original\" _=\"when @data-title changes put it into me\"></div>`)\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": "<input type=\"text\" id=\"reactive-input\" value=\"start\" /><span _=\"when #reactive-input.value changes put it into me\"></span>",
"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`<input type=\"text\" id=\"reactive-input\" value=\"start\" />` +\n\t\t\t`<span _=\"when #reactive-input.value changes put it into me\"></span>`\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": "<input type=\"text\" id=\"prog-input\" value=\"initial\" /><span _=\"when #prog-input.value changes put it into me\"></span>",
"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`<input type=\"text\" id=\"prog-input\" value=\"initial\" />` +\n\t\t\t`<span _=\"when #prog-input.value changes put it into me\"></span>`\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": "<div _=\"when $dispose changes put it into me\"></div>",
"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(`<div _=\"when $dispose changes put it into me\"></div>`)\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": "<div _=\"when ($batchA + $batchB) changes increment :runCount then put :runCount into me\"></div>",
"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`<div _=\"when ($batchA + $batchB) changes increment :runCount then put :runCount into me\"></div>`\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": "<div _=\"when $source changes set $derived to (it * 2)\"></div><div id=\"output\" _=\"when $derived changes put it into me\"></div>",
"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`<div _=\"when $source changes set $derived to (it * 2)\"></div>` +\n\t\t\t`<div id=\"output\" _=\"when $derived changes put it into me\"></div>`\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": "<div _=\"when $left changes put it into my @data-left end\n\t\t\t when $right changes put it into my @data-right\"></div>",
"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`<div _=\"when $left changes put it into my @data-left end\n\t\t\t when $right changes put it into my @data-right\"></div>`\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": "<div _=\"init set :label to 'initial' end\n\t\t\t when :label changes put it into me end\n\t\t\t on click set :label to 'clicked'\">initial</div>",
"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`<div _=\"init set :label to 'initial' end\n\t\t\t when :label changes put it into me end\n\t\t\t on click set :label to 'clicked'\">initial</div>`\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": "<div _=\"when $trigger changes\n\t\t\t increment :count\n\t\t\t put :count into me\n\t\t\t set $other to 'side-effect'\"></div>",
"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`<div _=\"when $trigger changes\n\t\t\t increment :count\n\t\t\t put :count into me\n\t\t\t set $other to 'side-effect'\"></div>`\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": "<div _=\"when $rapid changes put it into me\"></div>",
"action": "await run(\"set $rapid to \"",
"check": "toHaveText('9')",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div _=\"when $rapid changes put it into me\"></div>`)\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": "<div id=\"d1\" _=\"init set :value to 'A' end\n\t\t\t when :value changes put it into me end\n\t\t\t on click set :value to 'A-clicked'\">A</div><div id=\"d2\" _=\"init set :value to 'B' end\n\t\t\t when :value changes put it into me end\n\t\t\t on click set :value to 'B-clicked'\">B</div>",
"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`<div id=\"d1\" _=\"init set :value to 'A' end\n\t\t\t when :value changes put it into me end\n\t\t\t on click set :value to 'A-clicked'\">A</div>` +\n\t\t\t`<div id=\"d2\" _=\"init set :value to 'B' end\n\t\t\t when :value changes put it into me end\n\t\t\t on click set :value to 'B-clicked'\">B</div>`\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": "<input type=\"text\" id=\"nan-input\" value=\"not a number\" /><span _=\"when (#nan-input.value * 1) changes put it into me\"></span>",
"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`<input type=\"text\" id=\"nan-input\" value=\"not a number\" />` +\n\t\t\t`<span _=\"when (#nan-input.value * 1) changes put it into me\"></span>`\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": "<div _=\"when $x or $y changes put it into me\"></div>",
"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(`<div _=\"when $x or $y changes put it into me\"></div>`)\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": "<div _=\"when $r or $g or $b changes put it into me\"></div>",
"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(`<div _=\"when $r or $g or $b changes put it into me\"></div>`)\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": "<input type=\"checkbox\" id=\"cb-input\" /><span _=\"when #cb-input.checked changes put it into me\"></span>",
"action": "",
"check": "toHaveText('false')",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(\n\t\t\t`<input type=\"checkbox\" id=\"cb-input\" />` +\n\t\t\t`<span _=\"when #cb-input.checked changes put it into me\"></span>`\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": "<div data-x=\"one\" _=\"when my @data-x changes put it into me\"></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div data-x=\"one\" _=\"when my @data-x changes put it into me\"></div>`)\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": "<input type=\"text\" id=\"of-input\" value=\"init\" /><span _=\"when (value of #of-input) changes put it into me\"></span>",
"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`<input type=\"text\" id=\"of-input\" value=\"init\" />` +\n\t\t\t`<span _=\"when (value of #of-input) changes put it into me\"></span>`\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": "<div _=\"when ($mA * $mB) changes put it into me\"></div>",
"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(`<div _=\"when ($mA * $mB) changes put it into me\"></div>`)\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": "<div _=\"when ($cmpVal > 5) changes put it into me\"></div>",
"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(`<div _=\"when ($cmpVal > 5) changes put it into me\"></div>`)\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": "<div _=\"when `hello ${$tplName}` changes put it into me\"></div>",
"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('<div _=\"when `hello ${$tplName}` changes put it into me\"></div>')\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": "<div _=\"when (Math.round($rawNum)) changes put it into me\"></div>",
"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(`<div _=\"when (Math.round($rawNum)) changes put it into me\"></div>`)\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": "<div id=\"style-target\" style=\"opacity: 1\" _=\"when (*opacity) changes put it into me\">not fired</div>",
"action": "",
"check": "",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/when.js",
"body": "await html(`<div id=\"style-target\" style=\"opacity: 1\" _=\"when (*opacity) changes put it into me\">not fired</div>`)\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": "<div _=\"when $arrWhole changes put it.join(',') into me\"></div>",
"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(`<div _=\"when $arrWhole changes put it.join(',') into me\"></div>`)\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": "<div _=\"when $arrMut[0] changes put it into me\"></div>",
"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(`<div _=\"when $arrMut[0] changes put it into me\"></div>`)\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 = '<div _=\"when myVar changes put it into me\"></div>'\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 = '<div data-val=\"1\" _=\"when @data-val changes put it into me\"></div>'\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": "<div _=\"when ($x and $y) changes put it into me\"></div>",
"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(`<div _=\"when ($x and $y) changes put it into me\"></div>`)\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": "<span id=\"d-b\" _=\"when $a changes set $b to (it * 2)\"></span><span id=\"d-c\" _=\"when $a changes set $c to (it * 3)\"></span><div _=\"live increment :runs then put ($ (runs:)' into me\"></div>",
"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`<span id=\"d-b\" _=\"when $a changes set $b to (it * 2)\"></span>` +\n\t\t\t`<span id=\"d-c\" _=\"when $a changes set $c to (it * 3)\"></span>` +\n\t\t\t`<div _=\"live increment :runs then put ($b + $c) + ' (runs:' + :runs + ')' into me\"></div>`\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": "<span id=\"err-a\" _=\"when $trigger changes put null.boom into me\"></span><span id=\"err-b\" _=\"when $trigger changes put 'ok:",
"action": "await run(\"set $trigger to 0\"; await run(\"set $trigger to 42\"",
"check": "toHaveText('ok:42')",
"async": true,
"complexity": "promise",
"source": "new_file",
"file": "test/features/when.js",
"body": "await run(\"set $trigger to 0\")\n\t\tawait html(\n\t\t\t`<span id=\"err-a\" _=\"when $trigger changes put null.boom into me\"></span>` +\n\t\t\t`<span id=\"err-b\" _=\"when $trigger changes put 'ok:' + it into me\"></span>`\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": "<span _=\"when $ping changes set $ping to (i<div _=\"when $ping changes put it into me\"></div>",
"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`<span _=\"when $ping changes set $ping to (it + 1)\"></span>` +\n\t\t\t`<div _=\"when $ping changes put it into me\"></div>`\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": "<span _=\"when $ping changes set $pong to (i<span _=\"when $pong changes set $ping to (i<div _=\"when $ping changes put it into me\"></div>",
"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`<span _=\"when $ping changes set $pong to (it + 1)\"></span>` +\n\t\t\t`<span _=\"when $pong changes set $ping to (it + 1)\"></span>` +\n\t\t\t`<div _=\"when $ping changes put it into me\"></div>`\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": "<div id=\"container-a\"><span _=\"when $movable changes put it into me\"></span></div><div id=\"container-b\"></div>",
"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`<div id=\"container-a\"><span _=\"when $movable changes put it into me\"></span></div>` +\n\t\t\t`<div id=\"container-b\"></div>`\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": "<div id=\"thrash-parent\"></div>",
"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`<div id=\"thrash-parent\"></div>`\n\t\t)\n\t\tawait evaluate(() => {\n\t\t\tvar parent = document.getElementById('thrash-parent')\n\t\t\tparent.innerHTML = '<span _=\"when $thrash changes put it into me\"></span>'\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": "<button _=\"on click\n\t\t\t\tcall failAsync()\n\t\t\t\tput 'should not reach' into #out\n\t\t\t\">Go</button>\n\t\t\t<div id='out'>original</div>",
"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`<button _=\"on click\n\t\t\t\tcall failAsync()\n\t\t\t\tput 'should not reach' into #out\n\t\t\t\">Go</button>\n\t\t\t<div id='out'>original</div>`\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": "<button _=\"on click\n\t\t\t\tcall failAsync()\n\t\t\t\tput 'unreachable' into #out\n\t\t\tcatch e\n\t\t\t\tput e.message into #out\n\t\t\t\">Go</button>\n\t\t\t<div id='out'></div>",
"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`<button _=\"on click\n\t\t\t\tcall failAsync()\n\t\t\t\tput 'unreachable' into #out\n\t\t\tcatch e\n\t\t\t\tput e.message into #out\n\t\t\t\">Go</button>\n\t\t\t<div id='out'></div>`\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<div _=\"init set ^color to 'red'\">\n\t\t\t\t<div dom-scope=\"isolated\">\n\t\t\t\t\t<span _=\"init if ^color is not undefined put 'leaked' into me else put 'blocked' into me\">waiting</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div _=\"init set ^color to 'red'\">\n\t\t\t\t<div dom-scope=\"isolated\">\n\t\t\t\t\t<span _=\"init if ^color is not undefined put 'leaked' into me else put 'blocked' into me\">waiting</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div class=\"outer\" _=\"init set ^val to 'from-outer'\">\n\t\t\t\t<div dom-scope=\"isolated\" _=\"init set ^val to 'from-inner'\">\n\t\t\t\t\t<span dom-scope=\"closest .outer\" _=\"init put ^val into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div class=\"outer\" _=\"init set ^val to 'from-outer'\">\n\t\t\t\t<div dom-scope=\"isolated\" _=\"init set ^val to 'from-inner'\">\n\t\t\t\t\t<span dom-scope=\"closest .outer\" _=\"init put ^val into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div _=\"init set ^val to 'found'\">\n\t\t\t\t<span dom-scope=\"closest .nonexistent\" _=\"init if ^val is not undefined put 'leaked' into me else put 'blocked' into me\">waiting</span>\n\t\t\t</div>\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<div _=\"init set ^val to 'found'\">\n\t\t\t\t<span dom-scope=\"closest .nonexistent\" _=\"init if ^val is not undefined put 'leaked' into me else put 'blocked' into me\">waiting</span>\n\t\t\t</div>\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<div class=\"outer\" _=\"init set ^val to 'from-outer'\">\n\t\t\t\t<div class=\"middle\" dom-scope=\"isolated\" _=\"init set ^val to 'from-middle'\">\n\t\t\t\t\t<span dom-scope=\"parent of .middle\" _=\"init put ^val into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div class=\"outer\" _=\"init set ^val to 'from-outer'\">\n\t\t\t\t<div class=\"middle\" dom-scope=\"isolated\" _=\"init set ^val to 'from-middle'\">\n\t\t\t\t\t<span dom-scope=\"parent of .middle\" _=\"init put ^val into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div _=\"init set ^outer to 'leaked'\">\n\t\t\t\t<div dom-scope=\"isolated\" _=\"init set ^inner to 'contained'\">\n\t\t\t\t\t<span _=\"init put ^inner into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<div _=\"init set ^outer to 'leaked'\">\n\t\t\t\t<div dom-scope=\"isolated\" _=\"init set ^inner to 'contained'\">\n\t\t\t\t\t<span _=\"init put ^inner into me\">none</span>\n\t\t\t\t</div>\n\t\t\t</div>\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<template live>\n\t\t\t\t<span>Hello World</span>\n\t\t\t</template>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/core/liveTemplate.js",
"body": "await html(`\n\t\t\t<template live>\n\t\t\t\t<span>Hello World</span>\n\t\t\t</template>\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<template live>\n\t\t\t\t<span>Hello ${\"\\x24\"}{$ltName}!</span>\n\t\t\t</template>\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<template live>\n\t\t\t\t<span>Hello ${\"\\x24\"}{$ltName}!</span>\n\t\t\t</template>\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<template live _=\"init set ^msg to 'initialized'\">\n\t\t\t\t<span>${\"\\x24\"}{^msg}</span>\n\t\t\t</template>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/core/liveTemplate.js",
"body": "await html(`\n\t\t\t<template live _=\"init set ^msg to 'initialized'\">\n\t\t\t\t<span>${\"\\x24\"}{^msg}</span>\n\t\t\t</template>\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<template live _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t\t<span>Count: ${\"\\x24\"}{^count}</span>\n\t\t\t</template>\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<template live _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t\t<span>Count: ${\"\\x24\"}{^count}</span>\n\t\t\t</template>\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<template live _=\"init set ^val to 0\">\n\t\t\t\t<button _=\"on click increment ^val then put ^val into the next <output/>\">+</button>\n\t\t\t\t<output>0</output>\n\t\t\t</template>\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<template live _=\"init set ^val to 0\">\n\t\t\t\t<button _=\"on click increment ^val then put ^val into the next <output/>\">+</button>\n\t\t\t\t<output>0</output>\n\t\t\t</template>\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<template live _=\"init set ^items to ['a', 'b', 'c']\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^items\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/core/liveTemplate.js",
"body": "await html(`\n\t\t\t<template live _=\"init set ^items to ['a', 'b', 'c']\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^items\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\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<template live _=\"init set ^show to true\">\n\t\t\t\t#if ^show\n\t\t\t\t\t<span>visible</span>\n\t\t\t\t#end\n\t\t\t</template>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/core/liveTemplate.js",
"body": "await html(`\n\t\t\t<template live _=\"init set ^show to true\">\n\t\t\t\t#if ^show\n\t\t\t\t\t<span>visible</span>\n\t\t\t\t#end\n\t\t\t</template>\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<template live>\n\t\t\t\t<p>Hello, ${\"\\x24\"}{$ltGlobal}!</p>\n\t\t\t</template>\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<template live>\n\t\t\t\t<p>Hello, ${\"\\x24\"}{$ltGlobal}!</p>\n\t\t\t</template>\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<template live>\n\t\t\t\t<span>test</span>\n\t\t\t</template>\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<template live>\n\t\t\t\t<span>test</span>\n\t\t\t</template>\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<template live _=\"init set ^x to 'first'\">\n\t\t\t\t<span class=\"a\">${\"\\x24\"}{^x}</span>\n\t\t\t</template>\n\t\t\t<template live _=\"init set ^x to 'second'\">\n\t\t\t\t<span class=\"b\">${\"\\x24\"}{^x}</span>\n\t\t\t</template>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/core/liveTemplate.js",
"body": "await html(`\n\t\t\t<template live _=\"init set ^x to 'first'\">\n\t\t\t\t<span class=\"a\">${\"\\x24\"}{^x}</span>\n\t\t\t</template>\n\t\t\t<template live _=\"init set ^x to 'second'\">\n\t\t\t\t<span class=\"b\">${\"\\x24\"}{^x}</span>\n\t\t\t</template>\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": "<div id='target'>old</div><button _='on click set #target to \\\"<span id=target>new</span>\\\"'>go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button _='on click set #target to \\\"<span id=target>new</span>\\\"'>go</button>\"\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": "<div id='target'>old</div><button _='on click make a <span.replaced/> then put \\\"moved\\\" into it then set #target to it'>go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button _='on click make a <span.replaced/> then put \\\"moved\\\" into it then set #target to it'>go</button>\"\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": "<ul id='list'> <li class='item'>a</li> <li class='item'>b</li> <li class='item'>c</li></ul><button _='on click set .item to \\\"<li class=item>replaced</li>\\\"'>go</button>",
"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\"<ul id='list'>\" +\n\t\t\t\" <li class='item'>a</li>\" +\n\t\t\t\" <li class='item'>b</li>\" +\n\t\t\t\" <li class='item'>c</li>\" +\n\t\t\t\"</ul>\" +\n\t\t\t\"<button _='on click set .item to \\\"<li class=item>replaced</li>\\\"'>go</button>\"\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 <query/> replaces all matching elements",
"html": "<div id='box'> <p>one</p> <p>two</p></div><button _='on click set <p/> in #box to \\\"<p>done</p>\\\"'>go</button>",
"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\"<div id='box'>\" +\n\t\t\t\" <p>one</p>\" +\n\t\t\t\" <p>two</p>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button _='on click set <p/> in #box to \\\"<p>done</p>\\\"'>go</button>\"\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": "<div class='wrapper'><button _='on click set (closest <div/>) to \\\"<div class=wrapper>replaced</div>\\\"'>go</button></div>",
"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\"<div class='wrapper'><button _='on click set (closest <div/>) to \\\"<div class=wrapper>replaced</div>\\\"'>go</button></div>\"\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": "<div id='target'>old</div><button id='go' _=\\\"on click set #target to '<div id=target _=&#34;on click put `clicked` into me&#34;>new</div>'\\\">go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button id='go' _=\\\"on click set #target to '<div id=target _=&#34;on click put `clicked` into me&#34;>new</div>'\\\">go</button>\"\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": "<div id='container'> <div id='a'>A</div> <div id='b'>B</div></div><button _='on click swap #a with #b'>go</button>",
"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\"<div id='container'>\" +\n\t\t\t\" <div id='a'>A</div>\" +\n\t\t\t\" <div id='b'>B</div>\" +\n\t\t\t\"</div>\" +\n\t\t\t\"<button _='on click swap #a with #b'>go</button>\"\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": "<div id='target'>old</div><button _='on click put \\\"new\\\" into #target'>go</button>",
"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\"<div id='target'>old</div>\" +\n\t\t\t\"<button _='on click put \\\"new\\\" into #target'>go</button>\"\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": "<ul id='list'><li class='yes'>A</li><li>B</li><li class='yes'>C</li></ul><button _='on click set items to <li/> in #list then set matches to items where it matches .yes then put matches mapped to its textContent into #out'>Go</button><div id='out'></div>",
"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\"<ul id='list'><li class='yes'>A</li><li>B</li><li class='yes'>C</li></ul>\" +\n\t\t\t\"<button _='on click set items to <li/> in #list then set matches to items where it matches .yes then put matches mapped to its textContent into #out'>Go</button>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div id='container'><span class='a'>A</span><span class='b'>B</span><span class='a'>C</span></div>",
"action": "await run(\"<span/> 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\"<div id='container'>\" +\n\t\t\t\"<span class='a'>A</span><span class='b'>B</span><span class='a'>C</span>\" +\n\t\t\t\"</div>\"\n\t\t);\n\t\tvar result = await run(\"<span/> in #container where it matches .a\");\n\t\texpect(result.length).toBe(2);"
},
{
"category": "collectionExpressions",
"name": "sorted by binds after in without parens",
"html": "<ul id='list'><li>C</li><li>A</li><li>B</li></ul>",
"action": "await run(\"<li/> 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\"<ul id='list'><li>C</li><li>A</li><li>B</li></ul>\"\n\t\t);\n\t\tvar result = await run(\"<li/> 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": "<ul id='items'><li class='yes'>A</li><li>B</li><li class='yes'>C</li></ul>",
"action": "await run(\n\t\t\t\"<li/> 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\"<ul id='items'><li class='yes'>A</li><li>B</li><li class='yes'>C</li></ul>\"\n\t\t);\n\t\tvar result = await run(\n\t\t\t\"<li/> 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": "<div id='box'><span class='a'>A</span><span class='b'>B</span><span class='a'>C</span></div><button _=\\\"on click set result to (<span/> in #box) where it matches .a then put result.length into me\\\">go (parens)</button><button id='b2' _=\\\"on click set result to <span/> in #box where it matches .a then put result.length into me\\\">go</button>",
"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\"<div id='box'><span class='a'>A</span><span class='b'>B</span><span class='a'>C</span></div>\" +\n\t\t\t\"<button _=\\\"on click set result to (<span/> in #box) where it matches .a then put result.length into me\\\">go (parens)</button>\" +\n\t\t\t\"<button id='b2' _=\\\"on click set result to <span/> in #box where it matches .a then put result.length into me\\\">go</button>\"\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": "<div id='box'><span class='a'>A</span><span class='b'>B</span></div><button _=\\\"set :items to <span/> in #box where it matches .a on click put :items.length into me\\\">go</button>",
"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\"<div id='box'><span class='a'>A</span><span class='b'>B</span></div>\" +\n\t\t\t\"<button _=\\\"set :items to <span/> in #box where it matches .a \" +\n\t\t\t\"on click put :items.length into me\\\">go</button>\"\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<div id='box'><span class='a'>A</span><span class='b'>B</span></div>\n\t\t\t<template component=\"test-where-comp\"\n\t\t\t\t_=\"set :items to <span/> in #box where it matches .a\n\t\t\t\t on click put :items.length into me\">\n\t\t\t\t<slot/>\n\t\t\t</template>\n\t\t\t<test-where-comp>go</test-where-comp>\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<div id='box'><span class='a'>A</span><span class='b'>B</span></div>\n\t\t\t<template component=\"test-where-comp\"\n\t\t\t\t_=\"set :items to <span/> in #box where it matches .a\n\t\t\t\t on click put :items.length into me\">\n\t\t\t\t<slot/>\n\t\t\t</template>\n\t\t\t<test-where-comp>go</test-where-comp>\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<div id='box'><input type=\"checkbox\" class=\"cb\"><input type=\"checkbox\" class=\"cb\"></div>\n\t\t\t<template component=\"test-where-me\">\n\t\t\t\t<input type=\"checkbox\" _=\"set :checkboxes to <input[type=checkbox]/> in #box where it is not me on change set checked of the :checkboxes to my checked\">\n\t\t\t</template>\n\t\t\t<test-where-me></test-where-me>\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<div id='box'><input type=\"checkbox\" class=\"cb\"><input type=\"checkbox\" class=\"cb\"></div>\n\t\t\t<template component=\"test-where-me\">\n\t\t\t\t<input type=\"checkbox\" _=\"set :checkboxes to <input[type=checkbox]/> in #box where it is not me on change set checked of the :checkboxes to my checked\">\n\t\t\t</template>\n\t\t\t<test-where-me></test-where-me>\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<table>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\"></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input id=\"master\" type=\"checkbox\"\n\t\t\t\t_=\"set :checkboxes to <input[type=checkbox]/> in the closest <table/> where it is not me\n\t\t\t\t on change set checked of the :checkboxes to my checked\"></td></tr>\n\t\t\t</table>\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<table>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\"></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input id=\"master\" type=\"checkbox\"\n\t\t\t\t_=\"set :checkboxes to <input[type=checkbox]/> in the closest <table/> where it is not me\n\t\t\t\t on change set checked of the :checkboxes to my checked\"></td></tr>\n\t\t\t</table>\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<table>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\"></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input id=\"master\" type=\"checkbox\"\n\t\t\t\t_=\"set :checkboxes to <input[type=checkbox]/> in the closest <table/> 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 <table/>\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\"></td></tr>\n\t\t\t</table>\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<table>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\"></td></tr>\n\t\t\t<tr><td><input type=\"checkbox\" class=\"cb\" checked></td></tr>\n\t\t\t<tr><td><input id=\"master\" type=\"checkbox\"\n\t\t\t\t_=\"set :checkboxes to <input[type=checkbox]/> in the closest <table/> 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 <table/>\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\"></td></tr>\n\t\t\t</table>\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": "<div _=\"init set ^count to 42\"> <span _=\"on click put ^count into me\">0</span></div>",
"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`<div _=\"init set ^count to 42\">` +\n\t\t\t` <span _=\"on click put ^count into me\">0</span>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^count to 0\"> <button _=\"on click set ^count to 99\">set</button> <span _=\"on click put ^count into me\">0</span></div>",
"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`<div _=\"init set ^count to 0\">` +\n\t\t\t` <button _=\"on click set ^count to 99\">set</button>` +\n\t\t\t` <span _=\"on click put ^count into me\">0</span>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^name to 'alice'\"> <div> <div> <span _=\"on click put ^name into me\">empty</span> </div> </div></div>",
"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`<div _=\"init set ^name to 'alice'\">` +\n\t\t\t` <div>` +\n\t\t\t` <div>` +\n\t\t\t` <span _=\"on click put ^name into me\">empty</span>` +\n\t\t\t` </div>` +\n\t\t\t` </div>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^color to 'red'\"> <div _=\"init set ^color to 'blue'\"> <span _=\"on click put ^color into me\">empty</span> </div></div>",
"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`<div _=\"init set ^color to 'red'\">` +\n\t\t\t` <div _=\"init set ^color to 'blue'\">` +\n\t\t\t` <span _=\"on click put ^color into me\">empty</span>` +\n\t\t\t` </div>` +\n\t\t\t`</div>`\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": "<div id=\"a\" _=\"init set ^val to 'A'\"> <span _=\"on click put ^val into me\">empty</span></div><div id=\"b\" _=\"init set ^val to 'B'\"> <span _=\"on click put ^val into me\">empty</span></div>",
"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`<div id=\"a\" _=\"init set ^val to 'A'\">` +\n\t\t\t` <span _=\"on click put ^val into me\">empty</span>` +\n\t\t\t`</div>` +\n\t\t\t`<div id=\"b\" _=\"init set ^val to 'B'\">` +\n\t\t\t` <span _=\"on click put ^val into me\">empty</span>` +\n\t\t\t`</div>`\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": "<div> <button _=\"on click set ^newvar to 'created' then put ^newvar into next <span/>\">go</button> <span>empty</span></div>",
"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`<div>` +\n\t\t\t` <button _=\"on click set ^newvar to 'created' then put ^newvar into next <span/>\">go</button>` +\n\t\t\t` <span>empty</span>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^shared to 0\"> <button _=\"on click set ^shared to 10\">set</button> <span _=\"on click put ^shared into me\">0</span></div>",
"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`<div _=\"init set ^shared to 0\">` +\n\t\t\t` <button _=\"on click set ^shared to 10\">set</button>` +\n\t\t\t` <span _=\"on click put ^shared into me\">0</span>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^count to 0\"> <button _=\"on click increment ^count\" <span _=\"on click put ^count into me\">0</span></div>",
"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`<div _=\"init set ^count to 0\">` +\n\t\t\t` <button _=\"on click increment ^count\">+1</button>` +\n\t\t\t` <span _=\"on click put ^count into me\">0</span>` +\n\t\t\t`</div>`\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": "<div _=\"init set dom count to 42\"> <span _=\"on click put dom count into me\">0</span></div>",
"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`<div _=\"init set dom count to 42\">` +\n\t\t\t` <span _=\"on click put dom count into me\">0</span>` +\n\t\t\t`</div>`\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": "<div class=\"store\"> <button _=\"on click set ^data on closest .store to 'hello'\">set</button> <span _=\"on click put ^data on closest .store into me\">read</span></div>",
"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`<div class=\"store\">` +\n\t\t\t` <button _=\"on click set ^data on closest .store to 'hello'\">set</button>` +\n\t\t\t` <span _=\"on click put ^data on closest .store into me\">read</span>` +\n\t\t\t`</div>`\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": "<div class=\"outer\" _=\"init set ^outerVal to 'outer'\"> <div class=\"inner\" _=\"init set ^innerVal to 'inner'\"> <span _=\"on click put ^outerVal on closest .outer into me\">read</span> </div></div>",
"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`<div class=\"outer\" _=\"init set ^outerVal to 'outer'\">` +\n\t\t\t` <div class=\"inner\" _=\"init set ^innerVal to 'inner'\">` +\n\t\t\t` <span _=\"on click put ^outerVal on closest .outer into me\">read</span>` +\n\t\t\t` </div>` +\n\t\t\t`</div>`\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": "<div id=\"state-holder\"></div><button _=\"on click set ^count on #state-holder to 99\">set</button><span _=\"on click put ^count on #state-holder into me\">read</span>",
"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`<div id=\"state-holder\"></div>` +\n\t\t\t`<button _=\"on click set ^count on #state-holder to 99\">set</button>` +\n\t\t\t`<span _=\"on click put ^count on #state-holder into me\">read</span>`\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": "<div _=\"init set ^count to 0\"> <button _=\"on click increment ^count\" <output _=\"when ^count changes put it into me\">0</output></div>",
"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`<div _=\"init set ^count to 0\">` +\n\t\t\t` <button _=\"on click increment ^count\">+1</button>` +\n\t\t\t` <output _=\"when ^count changes put it into me\">0</output>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^name to 'alice'\"> <button _=\"on click set ^name to 'bob'\">rename</button> <output _=\"live put 'Hello </div>",
"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`<div _=\"init set ^name to 'alice'\">` +\n\t\t\t` <button _=\"on click set ^name to 'bob'\">rename</button>` +\n\t\t\t` <output _=\"live put 'Hello ' + ^name into me\">loading</output>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^color to 'red'\"> <button _=\"on click set ^color to 'blue'\">change</button> <span id=\"a\" _=\"when ^color changes put it into me\"></span> <span id=\"b\" _=\"when ^color changes put it into me\"></span></div>",
"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`<div _=\"init set ^color to 'red'\">` +\n\t\t\t` <button _=\"on click set ^color to 'blue'\">change</button>` +\n\t\t\t` <span id=\"a\" _=\"when ^color changes put it into me\"></span>` +\n\t\t\t` <span id=\"b\" _=\"when ^color changes put it into me\"></span>` +\n\t\t\t`</div>`\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": "<div id=\"a\" _=\"init set ^val to 0\"> <button _=\"on click increment ^val\" <output _=\"when ^val changes put it into me\">0</output></div><div id=\"b\" _=\"init set ^val to 0\"> <button _=\"on click increment ^val\" <output _=\"when ^val changes put it into me\">0</output></div>",
"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`<div id=\"a\" _=\"init set ^val to 0\">` +\n\t\t\t` <button _=\"on click increment ^val\">+1</button>` +\n\t\t\t` <output _=\"when ^val changes put it into me\">0</output>` +\n\t\t\t`</div>` +\n\t\t\t`<div id=\"b\" _=\"init set ^val to 0\">` +\n\t\t\t` <button _=\"on click increment ^val\">+1</button>` +\n\t\t\t` <output _=\"when ^val changes put it into me\">0</output>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^search to ''\"> <input type=\"text\" _=\"bind ^search and my value\" /> <output _=\"when ^search changes put it into me\"></output></div>",
"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`<div _=\"init set ^search to ''\">` +\n\t\t\t` <input type=\"text\" _=\"bind ^search and my value\" />` +\n\t\t\t` <output _=\"when ^search changes put it into me\"></output>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^price to 10 then set ^qty to 2 then set ^total to 20\"> <span _=\"when ^price changes set ^total to (^price * ^qty)\"></span> <span _=\"when ^qty changes set ^total to (^price * ^qty)\"></span> <output _=\"when ^total changes put it into me\"></output> <button id=\"price-btn\" _=\"on click set ^price to 25\">set price</button> <button id=\"qty-btn\" _=\"on click set ^qty to 5\">set qty</button></div>",
"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`<div _=\"init set ^price to 10 then set ^qty to 2 then set ^total to 20\">` +\n\t\t\t` <span _=\"when ^price changes set ^total to (^price * ^qty)\"></span>` +\n\t\t\t` <span _=\"when ^qty changes set ^total to (^price * ^qty)\"></span>` +\n\t\t\t` <output _=\"when ^total changes put it into me\"></output>` +\n\t\t\t` <button id=\"price-btn\" _=\"on click set ^price to 25\">set price</button>` +\n\t\t\t` <button id=\"qty-btn\" _=\"on click set ^qty to 5\">set qty</button>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^msg to 'hello'\"> <output _=\"when ^msg changes put it into me\"></output> <button _=\"on click set ^msg to 'updated'\">update</button></div>",
"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`<div _=\"init set ^msg to 'hello'\">` +\n\t\t\t` <output _=\"when ^msg changes put it into me\"></output>` +\n\t\t\t` <button _=\"on click set ^msg to 'updated'\">update</button>` +\n\t\t\t`</div>`\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": "<div _=\"init set ^val to 'same'\"> <output _=\"when ^val changes increment :runs then put :runs into me\"></output> <button _=\"on click set ^val to 'same'\">same</button> <button id=\"diff\" _=\"on click set ^val to 'different'\">diff</button></div>",
"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`<div _=\"init set ^val to 'same'\">` +\n\t\t\t` <output _=\"when ^val changes increment :runs then put :runs into me\"></output>` +\n\t\t\t` <button _=\"on click set ^val to 'same'\">same</button>` +\n\t\t\t` <button id=\"diff\" _=\"on click set ^val to 'different'\">diff</button>` +\n\t\t\t`</div>`\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<template component=\"test-hello\">\n\t\t\t\t<span>Hello World</span>\n\t\t\t</template>\n\t\t\t<test-hello></test-hello>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-hello\">\n\t\t\t\t<span>Hello World</span>\n\t\t\t</template>\n\t\t\t<test-hello></test-hello>\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<template component=\"test-greet\">\n\t\t\t\t<span>Hello ${\"\\x24\"}{$name}!</span>\n\t\t\t</template>\n\t\t\t<test-greet></test-greet>\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<template component=\"test-greet\">\n\t\t\t\t<span>Hello ${\"\\x24\"}{$name}!</span>\n\t\t\t</template>\n\t\t\t<test-greet></test-greet>\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<template component=\"test-init\" _=\"init set ^msg to 'initialized'\">\n\t\t\t\t<span>${\"\\x24\"}{^msg}</span>\n\t\t\t</template>\n\t\t\t<test-init></test-init>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-init\" _=\"init set ^msg to 'initialized'\">\n\t\t\t\t<span>${\"\\x24\"}{^msg}</span>\n\t\t\t</template>\n\t\t\t<test-init></test-init>\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<template component=\"test-inner\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count then put ^count into the next <span/>\">+</button>\n\t\t\t\t<span>0</span>\n\t\t\t</template>\n\t\t\t<test-inner></test-inner>\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<template component=\"test-inner\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count then put ^count into the next <span/>\">+</button>\n\t\t\t\t<span>0</span>\n\t\t\t</template>\n\t\t\t<test-inner></test-inner>\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<template component=\"test-reactive\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t\t<span>Count: ${\"\\x24\"}{^count}</span>\n\t\t\t</template>\n\t\t\t<test-reactive></test-reactive>\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<template component=\"test-reactive\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t\t<span>Count: ${\"\\x24\"}{^count}</span>\n\t\t\t</template>\n\t\t\t<test-reactive></test-reactive>\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<template component=\"test-multi\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count then put ^count into the next <span/>\">+</button>\n\t\t\t\t<span>0</span>\n\t\t\t</template>\n\t\t\t<test-multi id=\"a\"></test-multi>\n\t\t\t<test-multi id=\"b\"></test-multi>\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<template component=\"test-multi\" _=\"init set ^count to 0\">\n\t\t\t\t<button _=\"on click increment ^count then put ^count into the next <span/>\">+</button>\n\t\t\t\t<span>0</span>\n\t\t\t</template>\n\t\t\t<test-multi id=\"a\"></test-multi>\n\t\t\t<test-multi id=\"b\"></test-multi>\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<template component=\"test-attrs\" _=\"init set ^val to @data-start as Int\">\n\t\t\t\t<span>${\"\\x24\"}{^val}</span>\n\t\t\t</template>\n\t\t\t<test-attrs data-start=\"42\"></test-attrs>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-attrs\" _=\"init set ^val to @data-start as Int\">\n\t\t\t\t<span>${\"\\x24\"}{^val}</span>\n\t\t\t</template>\n\t\t\t<test-attrs data-start=\"42\"></test-attrs>\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<template component=\"test-loop\" _=\"init set ^items to ['a', 'b', 'c']\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^items\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\n\t\t\t<test-loop></test-loop>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-loop\" _=\"init set ^items to ['a', 'b', 'c']\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^items\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\n\t\t\t<test-loop></test-loop>\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<template component=\"test-cond\" _=\"init set ^show to true\">\n\t\t\t\t#if ^show\n\t\t\t\t\t<span>visible</span>\n\t\t\t\t#end\n\t\t\t</template>\n\t\t\t<test-cond></test-cond>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-cond\" _=\"init set ^show to true\">\n\t\t\t\t#if ^show\n\t\t\t\t\t<span>visible</span>\n\t\t\t\t#end\n\t\t\t</template>\n\t\t\t<test-cond></test-cond>\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<template component=\"test-card\">\n\t\t\t\t<div class=\"card\"><slot/></div>\n\t\t\t</template>\n\t\t\t<test-card><p>Hello from slot</p></test-card>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-card\">\n\t\t\t\t<div class=\"card\"><slot/></div>\n\t\t\t</template>\n\t\t\t<test-card><p>Hello from slot</p></test-card>\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<template component=\"test-block\" _=\"init set ^msg to 'ready'\">\n\t\t\t\t<span _=\"on click put ^msg into me\">click me</span>\n\t\t\t</template>\n\t\t\t<test-block></test-block>\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<template component=\"test-block\" _=\"init set ^msg to 'ready'\">\n\t\t\t\t<span _=\"on click put ^msg into me\">click me</span>\n\t\t\t</template>\n\t\t\t<test-block></test-block>\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<template component=\"test-named-slot\">\n\t\t\t\t<header><slot name=\"title\"></slot></header>\n\t\t\t\t<main><slot/></main>\n\t\t\t\t<footer><slot name=\"footer\"></slot></footer>\n\t\t\t</template>\n\t\t\t<test-named-slot>\n\t\t\t\t<h1 slot=\"title\">My Title</h1>\n\t\t\t\t<p>Default content</p>\n\t\t\t\t<span slot=\"footer\">Footer text</span>\n\t\t\t</test-named-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<template component=\"test-named-slot\">\n\t\t\t\t<header><slot name=\"title\"></slot></header>\n\t\t\t\t<main><slot/></main>\n\t\t\t\t<footer><slot name=\"footer\"></slot></footer>\n\t\t\t</template>\n\t\t\t<test-named-slot>\n\t\t\t\t<h1 slot=\"title\">My Title</h1>\n\t\t\t\t<p>Default content</p>\n\t\t\t\t<span slot=\"footer\">Footer text</span>\n\t\t\t</test-named-slot>\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<div _=\"init set ^x to 42\">\n\t\t\t\t<template component=\"test-slot-hs\">\n\t\t\t\t\t<div class=\"wrap\"><slot/></div>\n\t\t\t\t</template>\n\t\t\t\t<test-slot-hs>\n\t\t\t\t\t<span _=\"on click put ^x into me\">before</span>\n\t\t\t\t</test-slot-hs>\n\t\t\t</div>\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<div _=\"init set ^x to 42\">\n\t\t\t\t<template component=\"test-slot-hs\">\n\t\t\t\t\t<div class=\"wrap\"><slot/></div>\n\t\t\t\t</template>\n\t\t\t\t<test-slot-hs>\n\t\t\t\t\t<span _=\"on click put ^x into me\">before</span>\n\t\t\t\t</test-slot-hs>\n\t\t\t</div>\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<div _=\"init set ^outer to 'from-outside'\">\n\t\t\t\t<template component=\"test-scope-slot\" _=\"init set ^outer to 'from-component'\">\n\t\t\t\t\t<div class=\"inner\"><slot/></div>\n\t\t\t\t</template>\n\t\t\t\t<test-scope-slot>\n\t\t\t\t\t<span _=\"init put ^outer into me\">waiting</span>\n\t\t\t\t</test-scope-slot>\n\t\t\t</div>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<div _=\"init set ^outer to 'from-outside'\">\n\t\t\t\t<template component=\"test-scope-slot\" _=\"init set ^outer to 'from-component'\">\n\t\t\t\t\t<div class=\"inner\"><slot/></div>\n\t\t\t\t</template>\n\t\t\t\t<test-scope-slot>\n\t\t\t\t\t<span _=\"init put ^outer into me\">waiting</span>\n\t\t\t\t</test-scope-slot>\n\t\t\t</div>\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<div _=\"init set ^leaked to 'should-not-see'\">\n\t\t\t\t<template component=\"test-isolated\" _=\"init set ^internal to 'component-only'\">\n\t\t\t\t\t<span _=\"init if ^leaked is not undefined put 'leaked!' into me else put ^internal into me\">waiting</span>\n\t\t\t\t</template>\n\t\t\t\t<test-isolated></test-isolated>\n\t\t\t</div>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "simple",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<div _=\"init set ^leaked to 'should-not-see'\">\n\t\t\t\t<template component=\"test-isolated\" _=\"init set ^internal to 'component-only'\">\n\t\t\t\t\t<span _=\"init if ^leaked is not undefined put 'leaked!' into me else put ^internal into me\">waiting</span>\n\t\t\t\t</template>\n\t\t\t\t<test-isolated></test-isolated>\n\t\t\t</div>\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<template component=\"test-bind-attr\" _=\"bind ^count to @data-count\">\n\t\t\t\t<span>${\"\\x24\"}{^count}</span>\n\t\t\t</template>\n\t\t\t<test-bind-attr data-count=\"5\"></test-bind-attr>\n\t\t",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_file",
"file": "test/ext/component.js",
"body": "await html(`\n\t\t\t<template component=\"test-bind-attr\" _=\"bind ^count to @data-count\">\n\t\t\t\t<span>${\"\\x24\"}{^count}</span>\n\t\t\t</template>\n\t\t\t<test-bind-attr data-count=\"5\"></test-bind-attr>\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<template component=\"test-args\" _=\"init set ^list to attrs.items\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^list\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\n\t\t\t<test-args items=\"$stuff\"></test-args>\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<template component=\"test-args\" _=\"init set ^list to attrs.items\">\n\t\t\t\t<ul>\n\t\t\t\t#for item in ^list\n\t\t\t\t\t<li>${\"\\x24\"}{item}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</template>\n\t\t\t<test-args items=\"$stuff\"></test-args>\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<template component=\"test-args-bind\" _=\"bind ^val to attrs.count\">\n\t\t\t\t<span>${\"\\x24\"}{^val}</span>\n\t\t\t</template>\n\t\t\t<test-args-bind count=\"$count\"></test-args-bind>\n\t\t\t<button _=\"on click increment $count\">+</button>\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<template component=\"test-args-bind\" _=\"bind ^val to attrs.count\">\n\t\t\t\t<span>${\"\\x24\"}{^val}</span>\n\t\t\t</template>\n\t\t\t<test-args-bind count=\"$count\"></test-args-bind>\n\t\t\t<button _=\"on click increment $count\">+</button>\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<template component=\"test-args-bidir\" _=\"bind ^count to attrs.count\">\n\t\t\t\t<span>${\"\\x24\"}{^count}</span>\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t</template>\n\t\t\t<test-args-bidir count=\"$count\"></test-args-bidir>\n\t\t\t<p _=\"live put $count into me\"></p>\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<template component=\"test-args-bidir\" _=\"bind ^count to attrs.count\">\n\t\t\t\t<span>${\"\\x24\"}{^count}</span>\n\t\t\t\t<button _=\"on click increment ^count\">+</button>\n\t\t\t</template>\n\t\t\t<test-args-bidir count=\"$count\"></test-args-bidir>\n\t\t\t<p _=\"live put $count into me\"></p>\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": "<div id='trigger' _='on click add .foo to #d2 when asyncCheck()'></div><div id='d2'></div>",
"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\"<div id='trigger' _='on click add .foo to #d2 when asyncCheck()'></div>\" +\n\t\t\t\"<div id='d2'></div>\"\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": "<div id='trigger' _='on click add .foo to .item when it matches .yes then if the result is empty show #none else hide #none'></div><div id='d1' class='item yes'></div><div id='d2' class='item'></div><div id='none' style='display:none'></div>",
"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\"<div id='trigger' _='on click add .foo to .item when it matches .yes then if the result is empty show #none else hide #none'></div>\" +\n\t\t\t\"<div id='d1' class='item yes'></div>\" +\n\t\t\t\"<div id='d2' class='item'></div>\" +\n\t\t\t\"<div id='none' style='display:none'></div>\"\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": "<div id='trigger' _='on click add .foo to .item when it matches .nope then if the result is empty remove @hidden from #none'></div><div id='d1' class='item'></div><div id='none' hidden></div>",
"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\"<div id='trigger' _='on click add .foo to .item when it matches .nope then if the result is empty remove @hidden from #none'></div>\" +\n\t\t\t\"<div id='d1' class='item'></div>\" +\n\t\t\t\"<div id='none' hidden></div>\"\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": "<div _=\"on click\n\t\t set :arr to [1,2,3]\n\t\t add 4 to :arr\n\t\t put :arr as String into me\"></div>",
"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(`<div _=\"on click\n\t\t set :arr to [1,2,3]\n\t\t add 4 to :arr\n\t\t put :arr as String into me\"></div>`);\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": "<div _=\"on click\n\t\t set :s to [] as Set\n\t\t add 'a' to :s\n\t\t add 'b' to :s\n\t\t add 'a' to :s\n\t\t put :s.size into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"2\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/add.js",
"body": "await html(`<div _=\"on click\n\t\t set :s to [] as Set\n\t\t add 'a' to :s\n\t\t add 'b' to :s\n\t\t add 'a' to :s\n\t\t put :s.size into me\"></div>`);\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": "<div _=\"on click\n\t\t set :s to [1,2] as Set\n\t\t append 3 to :s\n\t\t append 1 to :s\n\t\t put :s.size into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"3\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/append.js",
"body": "await html(`<div _=\"on click\n\t\t set :s to [1,2] as Set\n\t\t append 3 to :s\n\t\t append 1 to :s\n\t\t put :s.size into me\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"3\");"
},
{
"category": "default",
"name": "can default possessive properties",
"html": "<div id='d1' _=\\\"on click default #d1's foo to 'bar' then put #d1's foo into me\\\"></div>",
"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\"<div id='d1' _=\\\"on click default #d1's foo to 'bar' then put #d1's foo into me\\\"></div>\"\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": "<div id='d1' _=\\\"on click default foo of me to 'bar' then put my foo into me\\\"></div>",
"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\"<div id='d1' _=\\\"on click default foo of me to 'bar' then put my foo into me\\\"></div>\"\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": "<div _=\"on click set arr to [null, null] then default arr[0] to 'yes' then put arr[0] into me\"></div>",
"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`<div _=\"on click set arr to [null, null] then default arr[0] to 'yes' then put arr[0] into me\"></div>`\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": "<div _=\"on click set arr to ['existing', null] then default arr[0] to 'new' then put arr[0] into me\"></div>",
"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`<div _=\"on click set arr to ['existing', null] then default arr[0] to 'new' then put arr[0] into me\"></div>`\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": "<div _='on click set x to 0 then default x to 10 then put x into me'></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"0\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/default.js",
"body": "await html(\"<div _='on click set x to 0 then default x to 10 then put x into me'></div>\");\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"0\");"
},
{
"category": "default",
"name": "default overwrites empty string",
"html": "<div _='on click set x to \"\" then default x to \"fallback\" then put x into me'></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"fallback\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/default.js",
"body": "await html(`<div _='on click set x to \"\" then default x to \"fallback\" then put x into me'></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"fallback\");"
},
{
"category": "default",
"name": "default preserves false",
"html": "<div _='on click set x to false then default x to true then put x into me'></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"false\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/default.js",
"body": "await html(\"<div _='on click set x to false then default x to true then put x into me'></div>\");\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": "<div _=\"on click default *background-color to 'red'\"></div>",
"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`<div _=\"on click default *background-color to 'red'\"></div>`\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": "<div style=\"color: blue\" _=\"on click default *color to 'red'\"></div>",
"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`<div style=\"color: blue\" _=\"on click default *color to 'red'\"></div>`\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": "<div _='on click fetch /test as JSON then get result as JSONString then put it into my.innerHTML'></div>",
"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\"<div _='on click fetch /test as JSON then get result as JSONString then put it into my.innerHTML'></div>\"\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": "<div _='on click fetch /test catch e put \\\"caught\\\" into me'></div>",
"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(\"<div _='on click fetch /test catch e put \\\"caught\\\" into me'></div>\");\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": "<div _='on click fetch /test do not throw then put it into me'></div>",
"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(\"<div _='on click fetch /test do not throw then put it into me'></div>\");\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": "<div _=\"on click fetch /test don\\'t throw then put it into me\"></div>",
"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('<div _=\"on click fetch /test don\\'t throw then put it into me\"></div>');\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": "<div _='on click fetch /test as response then put it.status into me'></div>",
"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(\"<div _='on click fetch /test as response then put it.status into me'></div>\");\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": "<div _=\\\"on click fetch /test as Response then put (it as JSON).name into me\\\"></div>",
"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(\"<div _=\\\"on click fetch /test as Response then put (it as JSON).name into me\\\"></div>\");\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": "<div id='trigger' _='on click hide <div/> in me when it matches .hideable'> <div id='d1' class='hideable'></div> <div id='d2'></div></div>",
"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\"<div id='trigger' _='on click hide <div/> in me when it matches .hideable'>\" +\n\t\t\t\" <div id='d1' class='hideable'></div>\" +\n\t\t\t\" <div id='d2'></div>\" +\n\t\t\t\"</div>\"\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": "<div _=\"on click set arr to [10, 20, 30] then increment arr[1] then put arr[1] into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"21\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/increment.js",
"body": "await html(`<div _=\"on click set arr to [10, 20, 30] then increment arr[1] then put arr[1] into me\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"21\");"
},
{
"category": "increment",
"name": "can decrement an array element",
"html": "<div _=\"on click set arr to [10, 20, 30] then decrement arr[1] then put arr[1] into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"19\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/increment.js",
"body": "await html(`<div _=\"on click set arr to [10, 20, 30] then decrement arr[1] then put arr[1] into me\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"19\");"
},
{
"category": "increment",
"name": "can increment a possessive property",
"html": "<div id=\"d1\" _=\"on click increment #d1's innerHTML\">5</div>",
"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`<div id=\"d1\" _=\"on click increment #d1's innerHTML\">5</div>`\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": "<div id=\"d1\" _=\"on click increment innerHTML of #d1\">5</div>",
"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`<div id=\"d1\" _=\"on click increment innerHTML of #d1\">5</div>`\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": "<div _=\"on click set my *opacity to 0.5 then increment *opacity by 0.25 then put *opacity into me\"></div>",
"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`<div _=\"on click set my *opacity to 0.5 then increment *opacity by 0.25 then put *opacity into me\"></div>`\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": "<div _='on click js return Promise.reject(\\\"boom\\\") end catch e put e into my.innerHTML'></div>",
"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\"<div _='on click js return Promise.reject(\\\"boom\\\") end \" +\n\t\t\t\"catch e put e into my.innerHTML'></div>\"\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": "<div id='other' style='all: initial; position: fixed; top: 89px'></div><div _=\"on click measure #other\\'s top then set window.measurement to {top: top}\"></div>",
"action": "",
"check": "toBe(89)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/commands/measure.js",
"body": "await html(\"<div id='other' style='all: initial; position: fixed; top: 89px'></div>\" +\n\t\t\t'<div _=\"on click measure #other\\'s top then set window.measurement to {top: top}\"></div>');\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": "<div id='other' style='all: initial; position: fixed; top: 89px'></div><div _='on click measure top of #other then set window.measurement to {top: top}'></div>",
"action": "",
"check": "toBe(89)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/commands/measure.js",
"body": "await html(\"<div id='other' style='all: initial; position: fixed; top: 89px'></div>\" +\n\t\t\t\"<div _='on click measure top of #other then set window.measurement to {top: top}'></div>\");\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": "<div id='d1' foo='bar' _='on click put null into @foo'></div>",
"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(\"<div id='d1' foo='bar' _='on click put null into @foo'></div>\");\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": "<div _=\"on click\n\t\t set :arr to [2,3]\n\t\t put 1 at start of :arr\n\t\t put :arr as String into me\"></div>",
"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(`<div _=\"on click\n\t\t set :arr to [2,3]\n\t\t put 1 at start of :arr\n\t\t put :arr as String into me\"></div>`);\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": "<div _=\"on click\n\t\t set :arr to [1,2]\n\t\t put 3 at end of :arr\n\t\t put :arr as String into me\"></div>",
"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(`<div _=\"on click\n\t\t set :arr to [1,2]\n\t\t put 3 at end of :arr\n\t\t put :arr as String into me\"></div>`);\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": "<div id='trigger' _='on click remove .highlight from .item when it matches .old'></div><div id='d1' class='item old highlight'></div><div id='d2' class='item highlight'></div>",
"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\"<div id='trigger' _='on click remove .highlight from .item when it matches .old'></div>\" +\n\t\t\t\"<div id='d1' class='item old highlight'></div>\" +\n\t\t\t\"<div id='d2' class='item highlight'></div>\"\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": "<div style='color: red; font-weight: bold;' _='on click remove {color} from me'></div>",
"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(\"<div style='color: red; font-weight: bold;' _='on click remove {color} from me'></div>\");\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": "<div style='color: red; font-weight: bold; opacity: 0.5;' _='on click remove {color; font-weight} from me'></div>",
"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(\"<div style='color: red; font-weight: bold; opacity: 0.5;' _='on click remove {color; font-weight} from me'></div>\");\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": "<div _=\"on click\n\t\t set :arr to [1,2,3,4]\n\t\t remove 3 from :arr\n\t\t put :arr as String into me\"></div>",
"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(`<div _=\"on click\n\t\t set :arr to [1,2,3,4]\n\t\t remove 3 from :arr\n\t\t put :arr as String into me\"></div>`);\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": "<div _=\"on click\n\t\t set :s to ['a','b','c'] as Set\n\t\t remove 'b' from :s\n\t\t put :s.size into me\"></div>",
"action": "find('div').dispatchEvent('click')",
"check": "toHaveText(\"2\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/remove.js",
"body": "await html(`<div _=\"on click\n\t\t set :s to ['a','b','c'] as Set\n\t\t remove 'b' from :s\n\t\t put :s.size into me\"></div>`);\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": "<div _='on click for x in null put x at end of me end'></div>",
"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\"<div _='on click for x in null\" +\n\t\t\t\t\" put x at end of me\" +\n\t\t\t\t\" end'></div>\"\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": "<div _='on click set x to {foo:1, bar:2, baz:3} for prop in x put x[prop] at end of me end'></div>",
"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\"<div _='on click set x to {foo:1, bar:2, baz:3}\" +\n\t\t\t\" for prop in x \" +\n\t\t\t\" put x[prop] at end of me\" +\n\t\t\t\" end'></div>\"\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": "<div _='on click set x to 0 repeat set x to until x is 3 end put x into me'></div>",
"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\"<div _='on click set x to 0 \" +\n\t\t\t\" repeat \" +\n\t\t\t\" set x to x + 1 \" +\n\t\t\t\" until x is 3 end \" +\n\t\t\t\" put x into me'></div>\"\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": "<div _='on click set x to 0 repeat set x to while x < 3 end put x into me'></div>",
"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\"<div _='on click set x to 0 \" +\n\t\t\t\" repeat \" +\n\t\t\t\" set x to x + 1 \" +\n\t\t\t\" while x < 3 end \" +\n\t\t\t\" put x into me'></div>\"\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": "<div _='on click set x to 0 repeat set x to until true end put x into me'></div>",
"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\"<div _='on click set x to 0 \" +\n\t\t\t\" repeat \" +\n\t\t\t\" set x to x + 1 \" +\n\t\t\t\" until true end \" +\n\t\t\t\" put x into me'></div>\"\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": "<div _=\"on click\n\t\t\t\tset x to 0\n\t\t\t\trepeat 10 times\n\t\t\t\t\tset x to x + 1\n\t\t\t\t\tif x is 3 break end\n\t\t\t\tend\n\t\t\t\tput x into me\n\t\t\t\"></div>",
"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`<div _=\"on click\n\t\t\t\tset x to 0\n\t\t\t\trepeat 10 times\n\t\t\t\t\tset x to x + 1\n\t\t\t\t\tif x is 3 break end\n\t\t\t\tend\n\t\t\t\tput x into me\n\t\t\t\"></div>`);\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": "<div _=\"on click\n\t\t\t\trepeat for x in [1, 2, 3, 4, 5]\n\t\t\t\t\tif x is 3 continue end\n\t\t\t\t\tput x at end of me\n\t\t\t\tend\n\t\t\t\"></div>",
"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`<div _=\"on click\n\t\t\t\trepeat for x in [1, 2, 3, 4, 5]\n\t\t\t\t\tif x is 3 continue end\n\t\t\t\t\tput x at end of me\n\t\t\t\tend\n\t\t\t\"></div>`);\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": "<div _=\"on click\n\t\t\t\trepeat for x in [1, 2, 3, 4, 5]\n\t\t\t\t\tif x is 4 break end\n\t\t\t\t\tput x at end of me\n\t\t\t\tend\n\t\t\t\"></div>",
"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`<div _=\"on click\n\t\t\t\trepeat for x in [1, 2, 3, 4, 5]\n\t\t\t\t\tif x is 4 break end\n\t\t\t\t\tput x at end of me\n\t\t\t\tend\n\t\t\t\"></div>`);\n\t\tawait find('div').dispatchEvent('click');\n\t\tawait expect(find('div')).toHaveText(\"123\");"
},
{
"category": "repeat",
"name": "break exits a while loop",
"html": "<div _=\"on click\n\t\t\t\tset x to 0\n\t\t\t\trepeat while x < 100\n\t\t\t\t\tset x to x + 1\n\t\t\t\t\tif x is 5 break end\n\t\t\t\tend\n\t\t\t\tput x into me\n\t\t\t\"></div>",
"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`<div _=\"on click\n\t\t\t\tset x to 0\n\t\t\t\trepeat while x < 100\n\t\t\t\t\tset x to x + 1\n\t\t\t\t\tif x is 5 break end\n\t\t\t\tend\n\t\t\t\tput x into me\n\t\t\t\"></div>`);\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": "<div _='on click repeat for x in doesNotExist put x at end of me end then put \\\"done\\\" into me'></div>",
"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\"<div _='on click repeat for x in doesNotExist put x at end of me end then put \\\"done\\\" into me'></div>\"\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": "<div class='item'></div><div class='item'></div><div id='trigger' _='on click settle <.item/> then add .done to <.item/>'></div>",
"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\"<div class='item'></div><div class='item'></div>\" +\n\t\t\t\"<div id='trigger' _='on click settle <.item/> then add .done to <.item/>'></div>\"\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": "<div _=\\\"on click get 'found' show <span/> in me when the result is 'found'\\\"><span id='s1' style='display:none'>A</span><span id='s2' style='display:none'>B</span></div>",
"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\"<div _=\\\"on click \" +\n\t\t\t\" get 'found' \" +\n\t\t\t\" show <span/> in me when the result is 'found'\\\">\" +\n\t\t\t\"<span id='s1' style='display:none'>A</span>\" +\n\t\t\t\"<span id='s2' style='display:none'>B</span>\" +\n\t\t\t\"</div>\"\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": "<div _=\\\"on click show <p/> in me when its textContent is 'yes' if the result is empty put 'none' into #out else put 'some' into #out\\\"><p style='display:none'>yes</p><p style='display:none'>no</p><span id='out'>--</span></div>",
"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\"<div _=\\\"on click \" +\n\t\t\t\" show <p/> 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\"<p style='display:none'>yes</p>\" +\n\t\t\t\"<p style='display:none'>no</p>\" +\n\t\t\t\"<span id='out'>--</span>\" +\n\t\t\t\"</div>\"\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": "<div data-state='active' _=\\\"on click toggle between [@data-state='active'] and [@data-state='inactive']\\\"></div>",
"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(\"<div data-state='active' _=\\\"on click toggle between [@data-state='active'] and [@data-state='inactive']\\\"></div>\");\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": "<div enabled='true' _=\\\"on click toggle between [@enabled='true'] and [@disabled='true']\\\"></div>",
"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(\"<div enabled='true' _=\\\"on click toggle between [@enabled='true'] and [@disabled='true']\\\"></div>\");\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": "<div _='on click toggle *visibility'></div>",
"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(\"<div _='on click toggle *visibility'></div>\");\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": "<div _='on click toggle my *opacity'></div>",
"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(\"<div _='on click toggle my *opacity'></div>\");\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": "<div _='on click toggle my *visibility'></div>",
"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(\"<div _='on click toggle my *visibility'></div>\");\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": "<div _='on click toggle the *opacity of #d2'></div><div id='d2'></div>",
"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(\"<div _='on click toggle the *opacity of #d2'></div><div id='d2'></div>\");\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": "<div _='on click toggle the *visibility of #d2'></div><div id='d2'></div>",
"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(\"<div _='on click toggle the *visibility of #d2'></div><div id='d2'></div>\");\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": "<div style='display:none' _=\\\"on click toggle *display of me between 'none' and 'flex'\\\"></div>",
"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(\"<div style='display:none' _=\\\"on click toggle *display of me between 'none' and 'flex'\\\"></div>\");\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": "<div style='opacity:0' _=\\\"on click toggle *opacity of me between '0', '0.5' and '1'\\\"></div>",
"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(\"<div style='opacity:0' _=\\\"on click toggle *opacity of me between '0', '0.5' and '1'\\\"></div>\");\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": "<div _=\\\"on click toggle $mode between 'edit' and 'preview'\\\"></div>",
"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(\"<div _=\\\"on click toggle $mode between 'edit' and 'preview'\\\"></div>\");\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": "<div _=\\\"on click toggle $state between 'a', 'b' and 'c'\\\"></div>",
"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(\"<div _=\\\"on click toggle $state between 'a', 'b' and 'c'\\\"></div>\");\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": "<div _=\"on click transition *width of #foo from 0px to 100px\"></div><div id=\"foo\"></div>",
"action": "",
"check": "toHaveCSS('width', '100px')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/transition.js",
"body": "await html('<div _=\"on click transition *width of #foo from 0px to 100px\"></div><div id=\"foo\"></div>');\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": "<div _=\"on click transition #foo\\'s *width from 0px to 100px\"></div><div id=\"foo\"></div>",
"action": "",
"check": "toHaveCSS('width', '100px')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/transition.js",
"body": "await html('<div _=\"on click transition #foo\\'s *width from 0px to 100px\"></div><div id=\"foo\"></div>');\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": "<div _='on click get #foo then transition its *width from 0px to 100px'></div><div id='foo'></div>",
"action": "",
"check": "toHaveCSS('width', '100px')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/transition.js",
"body": "await html(\"<div _='on click get #foo then transition its *width from 0px to 100px'></div><div id='foo'></div>\");\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": "<div _=\"on click transition #foo\\'s *width from 0px to 100px using &quot;width 2s ease-in&quot;\"></div><div id=\"foo\"></div>",
"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'<div _=\"on click transition #foo\\'s *width from 0px to 100px using &quot;width 2s ease-in&quot;\"></div><div id=\"foo\"></div>'\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": "<form _='on click transition *width from 0px to 100px'></form>",
"action": "",
"check": "toHaveCSS('width', '100px'); toBe('0px')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/transition.js",
"body": "await html(\"<form _='on click transition *width from 0px to 100px'></form>\");\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": "<div _='on click transition my *width from 0px to 100px'></div>",
"action": "",
"check": "toHaveCSS('width', '100px'); toBe('0px')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/commands/transition.js",
"body": "await html(\"<div _='on click transition my *width from 0px to 100px'></div>\");\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 = '<div></div><div></div>';\n\t\t\twa.querySelector('div').setAttribute('_', \"on click transition the next <div/>'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": "<div _=\\\"on click transition *width of the next &lt;span/> from 0px to 100px\\\"></div><span></span>",
"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(\"<div _=\\\"on click transition *width of the next &lt;span/> from 0px to 100px\\\"></div><span></span>\");\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": "<script type='text/hyperscript'>def foo() set x to 1 then exit then set x to 2 then return x end</script>",
"action": "",
"check": "",
"async": true,
"complexity": "script-tag",
"source": "new_in_common",
"file": "test/features/def.js",
"body": "await html(\"<script type='text/hyperscript'>def foo() set x to 1 then exit then set x to 2 then return x end</script>\")\n\t\tconst result = await evaluate(() => foo())\n\t\texpect(result).toBeNull()"
},
{
"category": "def",
"name": "can return without a value",
"html": "<script type='text/hyperscript'>def foo() return end</script>",
"action": "",
"check": "",
"async": true,
"complexity": "script-tag",
"source": "new_in_common",
"file": "test/features/def.js",
"body": "await html(\"<script type='text/hyperscript'>def foo() return end</script>\")\n\t\tconst result = await evaluate(() => foo())\n\t\texpect(result).toBeNull()"
},
{
"category": "on",
"name": "handles custom events with null detail",
"html": "<div id='d1' _='on myEvent(foo) if foo put foo into me else put \\\"no-detail\\\" into me'></div>",
"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\"<div id='d1' _='on myEvent(foo) if foo put foo into me else put \\\"no-detail\\\" into me'></div>\"\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": "<div _='on first click put 1 + my.innerHTML as Int into my.innerHTML'>0</div>",
"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(\"<div _='on first click put 1 + my.innerHTML as Int into my.innerHTML'>0</div>\")\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": "<button _='on click put \\\"foo\\\" into me then throw \\\"bar\\\" catch e log e on exception(error) put error into me'></button>",
"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\"<button _='on click put \\\"foo\\\" into me then throw \\\"bar\\\"\" +\n\t\t\t\" catch e log e\" +\n\t\t\t\" on exception(error) put error into me'></button>\"\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": "<button _='on click put \\\"foo\\\" into me then throw \\\"bar\\\" catch e throw e on exception(error) put error into me'></button>",
"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\"<button _='on click put \\\"foo\\\" into me then throw \\\"bar\\\"\" +\n\t\t\t\" catch e throw e\" +\n\t\t\t\" on exception(error) put error into me'></button>\"\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": "<div _=' \ton click from #doesntExist \t\tthrow \\\"bar\\\" \ton click \t\tput \\\"clicked\\\" into me\t'></div>",
"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\"<div _=' \" +\n\t\t\t\"\ton click from #doesntExist \" +\n\t\t\t\"\t\tthrow \\\"bar\\\" \" +\n\t\t\t\"\ton click \" +\n\t\t\t\"\t\tput \\\"clicked\\\" into me\" +\n\t\t\t\"\t'></div>\"\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: '<!DOCTYPE html><html><body></body></html>',\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: '<!DOCTYPE html><html><body></body></html>',\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": "<div id='bar'></div><div _='on click add .foo to #bar then add .blah'></div>",
"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\"<div id='bar'></div>\" +\n\t\t\t\"<div _='on click \" +\n\t\t\t\t\" add .foo to #bar then \" +\n\t\t\t\t\" add .blah'></div>\"\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": "<div _='on click add .foo'></div>",
"action": "",
"check": "toBe(true); toBe(true); toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"action": "evaluate({...}); div.click()",
"check": "toBe(1)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"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(\"<div _='on click add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"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(\"<div _='on click add .foo'></div>\");\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": "<div id='source'></div><div id='target' _='on click from #source add .foo'></div>",
"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(\"<div id='source'></div><div id='target' _='on click from #source add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"action": "",
"check": "toBe(false)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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": "<div _='on click add .foo'></div>",
"action": "",
"check": "toHaveAttribute('data-hyperscript-powered', 'true')",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\n\t\tawait expect(find('div')).toHaveAttribute('data-hyperscript-powered', 'true');"
},
{
"category": "bootstrap",
"name": "cleanup removes data-hyperscript-powered",
"html": "<div _='on click add .foo'></div>",
"action": "",
"check": "toHaveAttribute('data-hyperscript-powered', 'true')",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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 = \"<div _='on click add .foo'></div>\";\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 = \"<div _='on click add .foo'></div>\";\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": "<div _='on click add .foo'></div>",
"action": "",
"check": "toEqual(['before:cleanup', 'after:cleanup'])",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/bootstrap.js",
"body": "await html(\"<div _='on click add .foo'></div>\");\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 = \"<div _='on click add .foo'></div>\";\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": "<div _='on click put \\\"clicked\\\" into my.innerHTML ---put some content into the div...'></div>",
"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\"<div _='on click put \\\"clicked\\\" into my.innerHTML ---put some content into the div...'></div>\"\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": "<div id='d1' _='on click blargh end on mouseenter put \\\"hovered\\\" into my.innerHTML'></div>",
"action": "",
"check": "toBe(false)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/parser.js",
"body": "await html(\n\t\t\t\"<div id='d1' _='on click blargh end on mouseenter put \\\"hovered\\\" into my.innerHTML'></div>\"\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": "<div id='d1' _='on click blargh end on mouseenter also_bad end on focus put \\\"focused\\\" into my.innerHTML'></div>",
"action": "",
"check": "toBe(false)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/core/parser.js",
"body": "await html(\n\t\t\t\"<div id='d1' _='on click blargh end on mouseenter also_bad end on focus put \\\"focused\\\" into my.innerHTML'></div>\"\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": "<div><div id='d1' _='on click blargh end on mouseenter also_bad'></div><div id='d2' _='on click put \\\"clicked\\\" into my.innerHTML'></div></div>",
"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\"<div>\" +\n\t\t\t\t\"<div id='d1' _='on click blargh end on mouseenter also_bad'></div>\" +\n\t\t\t\t\"<div id='d2' _='on click put \\\"clicked\\\" into my.innerHTML'></div>\" +\n\t\t\t\"</div>\"\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": "<div id='d1' _='on click 1 set :x to 10 on click 2 set @out to :x'></div>",
"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\"<div id='d1' _='on click 1 set :x to 10 \" +\n\t\t\t\" on click 2 set @out to :x'></div>\"\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<input name=\"tag\" value=\"alpha\">\n\t\t\t\t<input name=\"tag\" value=\"beta\">\n\t\t\t\t<input name=\"tag\" value=\"gamma\">`\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<select name=\"animal\" multiple>\n\t\t\t\t\t<option value=\"dog\" selected>Doggo</option>\n\t\t\t\t\t<option value=\"cat\">Kitteh</option>\n\t\t\t\t\t<option value=\"raccoon\" selected>Trash Panda</option>\n\t\t\t\t\t<option value=\"possum\">Sleepy Boi</option>\n\t\t\t\t</select>`\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<input name=\"firstName\" value=\"John\"><br>\n\t\t\t\t<input name=\"lastName\" value=\"Connor\"><br>\n\t\t\t\t<div>\n\t\t\t\t\t<input name=\"areaCode\" value=\"213\">\n\t\t\t\t\t<input name=\"phone\" value=\"555-1212\">\n\t\t\t\t</div>`\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<input name=\"firstName\" value=\"John\"><br>\n\t\t\t\t<input name=\"lastName\" value=\"Connor\"><br>\n\t\t\t\t<div>\n\t\t\t\t\t<input name=\"areaCode\" value=\"213\">\n\t\t\t\t\t<input name=\"phone\" value=\"555-1212\">\n\t\t\t\t</div>`\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": "<div id='arDiv' _='on click set my @data-foo to \\\"blue\\\"' data-foo='red'></div>",
"action": "find('#arDiv').dispatchEvent('click')",
"check": "toBe(\"blue\")",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/attributeRef.js",
"body": "await html(\"<div id='arDiv' _='on click set my @data-foo to \\\"blue\\\"' data-foo='red'></div>\")\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": "<div id='outerDiv2' foo='bar'><div id='d1b' _='on click set closest @foo to \\\"doh\\\"'></div></div>",
"action": "find('#d1b').dispatchEvent('click')",
"check": "toBe(\"bar\")",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/closest.js",
"body": "await html(\"<div id='outerDiv2' foo='bar'><div id='d1b' _='on click set closest @foo to \\\"doh\\\"'></div></div>\")\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": "<table><tr><td><input type='checkbox' class='cb'><input type='checkbox' class='cb'><input id='master' type='checkbox' _=\\\"set :others to <input[type=checkbox]/> in the closest <table/> where it is not me on click put :others.length into #out\\\"></td></tr></table><div id='out'></div>",
"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\"<table><tr><td>\" +\n\t\t\t\"<input type='checkbox' class='cb'>\" +\n\t\t\t\"<input type='checkbox' class='cb'>\" +\n\t\t\t\"<input id='master' type='checkbox' \" +\n\t\t\t\"_=\\\"set :others to <input[type=checkbox]/> in the closest <table/> where it is not me \" +\n\t\t\t\"on click put :others.length into #out\\\">\" +\n\t\t\t\"</td></tr></table>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div id='outer'><div id='inner'></div></div>",
"action": "",
"check": "toBe(true)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/closest.js",
"body": "await html(\"<div id='outer'><div id='inner'></div></div>\")\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 <div/> 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": "<div id='d1' _='on click if I am a Element put \\\"yes\\\" into me'></div>",
"action": "find('#d1').dispatchEvent('click')",
"check": "toHaveText(\"yes\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='d1' _='on click if I am a Element put \\\"yes\\\" into me'></div>\");\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": "<div id='d1' _='on click if I am a Node put \\\"yes\\\" into me'></div>",
"action": "find('#d1').dispatchEvent('click')",
"check": "toHaveText(\"yes\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='d1' _='on click if I am a Node put \\\"yes\\\" into me'></div>\");\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": "<div id='d1' _='on click if \\\"hello\\\" is not a Element put \\\"yes\\\" into me'></div>",
"action": "find('#d1').dispatchEvent('click')",
"check": "toHaveText(\"yes\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='d1' _='on click if \\\"hello\\\" is not a Element put \\\"yes\\\" into me'></div>\");\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": "<div id='a'></div><div id='b'></div><div id='c'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='a'></div><div id='b'></div><div id='c'></div>\")\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": "<div id='a'></div><div id='b'></div><div id='c'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='a'></div><div id='b'></div><div id='c'></div>\")\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": "<div id='a'></div><div id='b'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='a'></div><div id='b'></div>\")\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": "<div id='a'></div><div id='b'></div>",
"action": "",
"check": "",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='a'></div><div id='b'></div>\")\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": "<div id='a' _=\\\"on click if I precede #b put 'yes' into me\\\"></div><div id='b'></div>",
"action": "find('#a').dispatchEvent('click')",
"check": "toHaveText(\"yes\")",
"async": true,
"complexity": "simple",
"source": "new_in_common",
"file": "test/expressions/comparisonOperator.js",
"body": "await html(\"<div id='a' _=\\\"on click if I precede #b put 'yes' into me\\\"></div><div id='b'></div>\")\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": "<input id='c1' type='checkbox' checked='checked'/><input id='c2' type='checkbox'/>",
"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(\"<input id='c1' type='checkbox' checked='checked'/><input id='c2' type='checkbox'/>\")\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": "<input id='c1' type='checkbox' checked='checked'/><input id='c2' type='checkbox'/>",
"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(\"<input id='c1' type='checkbox' checked='checked'/><input id='c2' type='checkbox'/>\")\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": "<button id='b1' disabled>Disabled</button><button id='b2'>Enabled</button>",
"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(\"<button id='b1' disabled>Disabled</button><button id='b2'>Enabled</button>\")\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": "<input class='cb' type='checkbox' checked='checked'/><input class='cb' type='checkbox'/><input class='cb' type='checkbox' checked='checked'/>",
"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(\"<input class='cb' type='checkbox' checked='checked'/>\" +\n\t\t\t\"<input class='cb' type='checkbox'/>\" +\n\t\t\t\"<input class='cb' type='checkbox' checked='checked'/>\")\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": "<div id='box'><span class='a'>A</span><span class='b'>B</span></div><button _=\\\"on click if no <span/> in #box where it matches .c then put 'none' into #out else put 'found' into #out\\\">go</button><div id='out'></div>",
"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\"<div id='box'><span class='a'>A</span><span class='b'>B</span></div>\" +\n\t\t\t\"<button _=\\\"on click if no <span/> in #box where it matches .c then put 'none' into #out else put 'found' into #out\\\">go</button>\" +\n\t\t\t\"<div id='out'></div>\"\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": "<div class='c1'></div><div foo='bar' class='c2'></div><div class='c3'></div>",
"action": "",
"check": "toBe(1)",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/queryRef.js",
"body": "await html(\"<div class='c1'></div><div foo='bar' class='c2'></div><div class='c3'></div>\")\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": "<div id=\"d1\"></div><div id=\"d2\">hello</div>",
"action": "",
"check": "toBe('hello')",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/relativePositionalExpression.js",
"body": "await html('<div id=\"d1\"></div><div id=\"d2\">hello</div>')\n\t\tlet result = await evaluate(() => _hyperscript(\"the next <div/>'s textContent\", {me: document.getElementById('d1')}))\n\t\texpect(result).toBe('hello')"
},
{
"category": "relativePositionalExpression",
"name": "can access property of previous element with possessive",
"html": "<div id=\"d1\">world</div><div id=\"d2\"></div>",
"action": "",
"check": "toBe('world')",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/relativePositionalExpression.js",
"body": "await html('<div id=\"d1\">world</div><div id=\"d2\"></div>')\n\t\tlet result = await evaluate(() => _hyperscript(\"the previous <div/>'s textContent\", {me: document.getElementById('d2')}))\n\t\texpect(result).toBe('world')"
},
{
"category": "relativePositionalExpression",
"name": "can access style of next element with possessive",
"html": "<div id=\"d1\"></div><div id=\"d2\" style=\"color: red\"></div>",
"action": "",
"check": "toBe('red')",
"async": true,
"complexity": "evaluate",
"source": "new_in_common",
"file": "test/expressions/relativePositionalExpression.js",
"body": "await html('<div id=\"d1\"></div><div id=\"d2\" style=\"color: red\"></div>')\n\t\tlet result = await evaluate(() => _hyperscript(\"the next <div/>'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 = '<div id=\"d1\"></div><div id=\"d2\">original</div>';\n\t\t\twa.querySelector('#d1').setAttribute('_', \"on click put 'updated' into the next <div/>'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');"
}
]