{
  "version": "0.9.90-dev",
  "description": "New test cases in _hyperscript dev branch vs master (0.9.90 additions)",
  "extracted": "2026-04-08",
  "stats": {
    "total_new_tests": 485,
    "from_new_files": 293,
    "from_common_files": 192,
    "new_test_files": [
      "test/commands/askAnswer.js",
      "test/commands/dialog.js",
      "test/commands/empty.js",
      "test/commands/focus.js",
      "test/commands/go.js",
      "test/commands/halt.js",
      "test/commands/morph.js",
      "test/commands/reset.js",
      "test/commands/scroll.js",
      "test/commands/select.js",
      "test/commands/swap.js",
      "test/core/asyncError.js",
      "test/core/dom-scope.js",
      "test/core/evalStatically.js",
      "test/core/liveTemplate.js",
      "test/expressions/assignableElements.js",
      "test/expressions/collectionExpressions.js",
      "test/expressions/dom-scope.js",
      "test/expressions/splitJoin.js",
      "test/ext/component.js",
      "test/features/bind.js",
      "test/features/live.js",
      "test/features/reactive-properties.js",
      "test/features/resize.js",
      "test/features/when.js"
    ],
    "common_files_with_additions": [
      "test/commands/add.js",
      "test/commands/append.js",
      "test/commands/default.js",
      "test/commands/fetch.js",
      "test/commands/hide.js",
      "test/commands/increment.js",
      "test/commands/js.js",
      "test/commands/measure.js",
      "test/commands/pick.js",
      "test/commands/put.js",
      "test/commands/remove.js",
      "test/commands/repeat.js",
      "test/commands/settle.js",
      "test/commands/show.js",
      "test/commands/toggle.js",
      "test/commands/transition.js",
      "test/core/bootstrap.js",
      "test/core/parser.js",
      "test/core/scoping.js",
      "test/expressions/asExpression.js",
      "test/expressions/attributeRef.js",
      "test/expressions/closest.js",
      "test/expressions/comparisonOperator.js",
      "test/expressions/cookies.js",
      "test/expressions/in.js",
      "test/expressions/logicalOperator.js",
      "test/expressions/mathOperator.js",
      "test/expressions/no.js",
      "test/expressions/objectLiteral.js",
      "test/expressions/queryRef.js",
      "test/expressions/relativePositionalExpression.js",
      "test/features/def.js",
      "test/features/on.js",
      "test/features/socket.js"
    ],
    "complexity_breakdown": {
      "simple": 178,
      "evaluate": 125,
      "run-eval": 83,
      "promise": 54,
      "eval-only": 38,
      "dialog": 5,
      "script-tag": 2
    },
    "category_breakdown": {
      "features/bind": 44,
      "features/when": 41,
      "expressions/comparisonOperator": 40,
      "features/live": 23,
      "expressions/collectionExpressions": 22,
      "expressions/dom-scope": 20,
      "ext/component": 19,
      "expressions/asExpression": 17,
      "core/bootstrap": 14,
      "commands/empty": 13,
      "commands/toggle": 11,
      "commands/dialog": 10,
      "commands/morph": 10,
      "core/liveTemplate": 10,
      "commands/repeat": 10,
      "commands/default": 9,
      "commands/reset": 8,
      "commands/scroll": 8,
      "core/evalStatically": 8,
      "expressions/assignableElements": 8,
      "commands/transition": 8,
      "commands/halt": 7,
      "expressions/splitJoin": 7,
      "commands/pick": 7,
      "core/parser": 7,
      "commands/fetch": 6,
      "commands/askAnswer": 5,
      "commands/go": 5,
      "core/dom-scope": 5,
      "commands/add": 5,
      "commands/increment": 5,
      "commands/remove": 5,
      "features/on": 5,
      "expressions/mathOperator": 5,
      "expressions/no": 5,
      "commands/select": 4,
      "commands/swap": 4,
      "features/reactive-properties": 4,
      "features/socket": 4,
      "expressions/relativePositionalExpression": 4,
      "commands/focus": 3,
      "features/resize": 3,
      "commands/put": 3,
      "expressions/closest": 3,
      "expressions/logicalOperator": 3,
      "core/asyncError": 2,
      "commands/measure": 2,
      "commands/show": 2,
      "features/def": 2,
      "commands/append": 1,
      "commands/hide": 1,
      "commands/js": 1,
      "commands/settle": 1,
      "core/scoping": 1,
      "expressions/attributeRef": 1,
      "expressions/cookies": 1,
      "expressions/in": 1,
      "expressions/objectLiteral": 1,
      "expressions/queryRef": 1
    }
  },
  "tests": [
    {
      "category": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "ext/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "commands/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "features/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "core/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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": "expressions/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');"
    }
  ]
}