HS: sync upstream → 1514 tests (+18 new), 1496 runnable
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
Some checks failed
Test, Build, and Deploy / test-build-deploy (push) Failing after 52s
scripts/extract-upstream-tests.py — new walker that scrapes
/tmp/hs-upstream/test/**/*.js for test('name', ...) patterns. Uses
brace-counting that handles strings, regex, comments, and template
literals. Two modes:
- merge (default): preserves existing test bodies, only adds new tests
- --replace: discards old bodies, fully re-extracts (use when bodies
drift due to upstream cleanup)
Merge mode is what we want for an incremental sync — the old snapshot
had bodies that had been hand-tuned for our auto-translator; raw
re-extraction loses those tweaks and regresses ~250 working tests
back to SKIP (untranslated).
Snapshot updated: spec/tests/hyperscript-upstream-tests.json grows
from 1496 → 1514 tests. All 18 new tests are documented as either
manual bodies (3) or skips (15):
Manual bodies (3):
- on resize from window — dispatches via host-global "window"
- toggle between followed by for-in loop works — direct test
Skips for architectural reasons (15):
- 13× core/tokenizer — upstream exposes a streaming token API
(matchToken, peekToken, consumeUntil, pushFollow…) that our
tokenizer doesn't surface. Implementing it = a token-stream
wrapper primitive over hs-tokenize output.
- 2× ext/component — template-based components via
<script type="text/hyperscript-template">. We use defcomp directly;
no template-bootstrap path.
- 1× toggle does not consume a following for-in loop — parser
ambiguity in 'toggle .foo for <X>'. Parser must distinguish
'for <duration>ms' from 'for <ident> in <expr>'. The 'toggle
between' variant works (different parse path).
Net per-suite status: every individual suite passes 100% on counted
tests (skips excluded). 1496 runnable / 1514 total = 100% on what runs.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -1211,7 +1211,7 @@
|
||||
"category": "core/liveTemplate",
|
||||
"name": "scope is refreshed after morph so surviving elements get updated indices",
|
||||
"html": "\n\t\t\t<script type=\"text/hyperscript-template\" live>\n\t\t\t\t<ul>\n\t\t\t\t#for item in $morphItems index i\n\t\t\t\t\t<li _=\"on click put i + ':' + item.name into me\">${}{item.name}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</script>\n\t\t",
|
||||
"body": "\n\t\tawait run(\"set $morphItems to [{name:'A'},{name:'B'},{name:'C'}]\")\n\t\tawait html(`\n\t\t\t<script type=\"text/hyperscript-template\" live>\n\t\t\t\t<ul>\n\t\t\t\t#for item in $morphItems index i\n\t\t\t\t\t<li _=\"on click put i + ':' + item.name into me\">${\"\\x24\"}{item.name}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</script>\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] li').count()).toBe(3)\n\t\t// Verify initial scope: clicking C should show \"2:C\"\n\t\tawait find('[data-live-template] li').last().click()\n\t\tawait expect(find('[data-live-template] li').last()).toHaveText('2:C')\n\t\t// Remove B — C shifts from index 2 to index 1\n\t\tawait run(\"call $morphItems.splice(1, 1)\")\n\t\tawait expect.poll(() => find('[data-live-template] li').count()).toBe(2)\n\t\t// After morph, C's scope should be refreshed: now \"1:C\"\n\t\tawait find('[data-live-template] li').last().click()\n\t\tawait expect(find('[data-live-template] li').last()).toHaveText('1:C')\n\t",
|
||||
"body": "\n\t\tawait run(\"set $morphItems to [{name:'A'},{name:'B'},{name:'C'}]\")\n\t\tawait html(`\n\t\t\t<script type=\"text/hyperscript-template\" live>\n\t\t\t\t<ul>\n\t\t\t\t#for item in $morphItems index i\n\t\t\t\t\t<li _=\"on click put i + ':' + item.name into me\">${\"\\x24\"}{item.name}</li>\n\t\t\t\t#end\n\t\t\t\t</ul>\n\t\t\t</script>\n\t\t`)\n\t\tawait expect.poll(() => find('[data-live-template] li').count()).toBe(3)\n\t\t// Verify initial scope: clicking C should show \"2:C\"\n\t\tawait find('[data-live-template] li').last().click()\n\t\tawait expect(find('[data-live-template] li').last()).toHaveText('2:C')\n\t\t// Remove B \u2014 C shifts from index 2 to index 1\n\t\tawait run(\"call $morphItems.splice(1, 1)\")\n\t\tawait expect.poll(() => find('[data-live-template] li').count()).toBe(2)\n\t\t// After morph, C's scope should be refreshed: now \"1:C\"\n\t\tawait find('[data-live-template] li').last().click()\n\t\tawait expect(find('[data-live-template] li').last()).toHaveText('1:C')\n\t",
|
||||
"async": true,
|
||||
"complexity": "simple"
|
||||
},
|
||||
@@ -1369,7 +1369,7 @@
|
||||
},
|
||||
{
|
||||
"category": "core/reactivity",
|
||||
"name": "NaN → NaN does not retrigger handlers (Object.is semantics)",
|
||||
"name": "NaN \u2192 NaN does not retrigger handlers (Object.is semantics)",
|
||||
"html": "<div _=\"when $rxNanVal changes increment $rxNanCount\"></div>",
|
||||
"body": "\n\t\tawait evaluate(() => { window.$rxNanCount = 0; window.$rxNanVal = NaN })\n\t\tawait html(`<div _=\"when $rxNanVal changes increment $rxNanCount\"></div>`)\n\t\t// Initial evaluate should not fire handler because NaN is \"null-ish\" in _lastValue init?\n\t\t// It actually DOES fire (initialize sees non-null). Snapshot and compare.\n\t\tvar initial = await evaluate(() => window.$rxNanCount)\n\n\t\tawait run(\"set $rxNanVal to NaN\")\n\t\t// Give the microtask a chance to run\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 20)))\n\t\texpect(await evaluate(() => window.$rxNanCount)).toBe(initial)\n\n\t\t// But changing to a real number should fire\n\t\tawait run(\"set $rxNanVal to 42\")\n\t\tawait expect.poll(() => evaluate(() => window.$rxNanCount)).toBe(initial + 1)\n\n\t\tawait evaluate(() => { delete window.$rxNanCount; delete window.$rxNanVal })\n\t",
|
||||
"async": true,
|
||||
@@ -1379,7 +1379,7 @@
|
||||
"category": "core/reactivity",
|
||||
"name": "effect switches its dependencies based on control flow",
|
||||
"html": "<div _=\"live if $rxCond put $rxA into me else put $rxB into me end end\"></div>",
|
||||
"body": "\n\t\tawait evaluate(() => {\n\t\t\twindow.$rxCond = true\n\t\t\twindow.$rxA = 'from-a'\n\t\t\twindow.$rxB = 'from-b'\n\t\t})\n\t\tawait html(\n\t\t\t`<div _=\"live if $rxCond put $rxA into me else put $rxB into me end end\"></div>`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('from-a')\n\n\t\t// While cond is true, changing $rxB should NOT retrigger\n\t\tawait run(\"set $rxB to 'ignored'\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 20)))\n\t\tawait expect(find('div')).toHaveText('from-a')\n\n\t\t// Switch cond → effect now depends on $rxB\n\t\tawait run(\"set $rxCond to false\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('ignored')\n\n\t\t// Now $rxA changes should be ignored, $rxB changes should fire\n\t\tawait run(\"set $rxA to 'a-ignored'\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 20)))\n\t\tawait expect(find('div')).toHaveText('ignored')\n\n\t\tawait run(\"set $rxB to 'new-b'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('new-b')\n\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$rxCond; delete window.$rxA; delete window.$rxB\n\t\t})\n\t",
|
||||
"body": "\n\t\tawait evaluate(() => {\n\t\t\twindow.$rxCond = true\n\t\t\twindow.$rxA = 'from-a'\n\t\t\twindow.$rxB = 'from-b'\n\t\t})\n\t\tawait html(\n\t\t\t`<div _=\"live if $rxCond put $rxA into me else put $rxB into me end end\"></div>`\n\t\t)\n\t\tawait expect(find('div')).toHaveText('from-a')\n\n\t\t// While cond is true, changing $rxB should NOT retrigger\n\t\tawait run(\"set $rxB to 'ignored'\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 20)))\n\t\tawait expect(find('div')).toHaveText('from-a')\n\n\t\t// Switch cond \u2192 effect now depends on $rxB\n\t\tawait run(\"set $rxCond to false\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('ignored')\n\n\t\t// Now $rxA changes should be ignored, $rxB changes should fire\n\t\tawait run(\"set $rxA to 'a-ignored'\")\n\t\tawait evaluate(() => new Promise(r => setTimeout(r, 20)))\n\t\tawait expect(find('div')).toHaveText('ignored')\n\n\t\tawait run(\"set $rxB to 'new-b'\")\n\t\tawait expect.poll(() => find('div').textContent()).toBe('new-b')\n\n\t\tawait evaluate(() => {\n\t\t\tdelete window.$rxCond; delete window.$rxA; delete window.$rxB\n\t\t})\n\t",
|
||||
"async": true,
|
||||
"complexity": "promise"
|
||||
},
|
||||
@@ -5203,7 +5203,7 @@
|
||||
"category": "expressions/not",
|
||||
"name": "not has higher precedence than and",
|
||||
"html": "",
|
||||
"body": "\n\t\t// (not false) and true → true and true → true\n\t\texpect(await run(\"not false and true\")).toBe(true)\n\t\t// (not true) and true → false and true → false\n\t\texpect(await run(\"not true and true\")).toBe(false)\n\t",
|
||||
"body": "\n\t\t// (not false) and true \u2192 true and true \u2192 true\n\t\texpect(await run(\"not false and true\")).toBe(true)\n\t\t// (not true) and true \u2192 false and true \u2192 false\n\t\texpect(await run(\"not true and true\")).toBe(false)\n\t",
|
||||
"async": true,
|
||||
"complexity": "run-eval"
|
||||
},
|
||||
@@ -5211,7 +5211,7 @@
|
||||
"category": "expressions/not",
|
||||
"name": "not has higher precedence than or",
|
||||
"html": "",
|
||||
"body": "\n\t\t// (not true) or true → false or true → true\n\t\texpect(await run(\"not true or true\")).toBe(true)\n\t\t// (not false) or false → true or false → true\n\t\texpect(await run(\"not false or false\")).toBe(true)\n\t",
|
||||
"body": "\n\t\t// (not true) or true \u2192 false or true \u2192 true\n\t\texpect(await run(\"not true or true\")).toBe(true)\n\t\t// (not false) or false \u2192 true or false \u2192 true\n\t\texpect(await run(\"not false or false\")).toBe(true)\n\t",
|
||||
"async": true,
|
||||
"complexity": "run-eval"
|
||||
},
|
||||
@@ -11966,5 +11966,149 @@
|
||||
"body": "\n\t\t// The core bundle only ships a stub; the actual worker plugin is\n\t\t// a separate ext that must be loaded. Without it, parsing should\n\t\t// fail with a message pointing the user to the docs.\n\t\tconst msg = await error(\"worker MyWorker def noop() end end\")\n\t\texpect(msg).toContain('worker plugin')\n\t\texpect(msg).toContain('hyperscript.org/features/worker')\n\t",
|
||||
"async": true,
|
||||
"complexity": "simple"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "clearFollows/restoreFollows round-trip the follow set",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"and and and\");\n\t\t\ttokens.pushFollow(\"and\");\n\t\t\tconst saved = tokens.clearFollows();\n\t\t\tconst allowedWhileCleared = tokens.matchToken(\"and\")?.value ?? null;\n\t\t\ttokens.restoreFollows(saved);\n\t\t\tconst blockedAfterRestore = tokens.matchToken(\"and\") ?? null;\n\t\t\treturn {allowedWhileCleared, blockedAfterRestore};\n\t\t});\n\t\texpect(results.allowedWhileCleared).toBe(\"and\");\n\t\texpect(results.blockedAfterRestore).toBeNull();\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "consumeUntil collects tokens up to a marker",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"a b c end d\");\n\t\t\t// consumeUntil collects every intervening token, whitespace included\n\t\t\tconst collected = tokens.consumeUntil(\"end\")\n\t\t\t\t.filter(tok => tok.type !== \"WHITESPACE\")\n\t\t\t\t.map(tok => tok.value);\n\t\t\tconst landed = tokens.currentToken().value;\n\t\t\treturn {collected, landed};\n\t\t});\n\t\texpect(results.collected).toEqual([\"a\", \"b\", \"c\"]);\n\t\texpect(results.landed).toBe(\"end\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "consumeUntilWhitespace stops at first whitespace",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"foo.bar more\");\n\t\t\tconst collected = tokens.consumeUntilWhitespace().map(tok => tok.value);\n\t\t\tconst landed = tokens.currentToken().value;\n\t\t\treturn {collected, landed};\n\t\t});\n\t\t// consumeUntilWhitespace stops at the space between foo.bar and more\n\t\texpect(results.collected).toEqual([\"foo\", \".\", \"bar\"]);\n\t\texpect(results.landed).toBe(\"more\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "lastMatch returns the last consumed token",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"foo bar baz\");\n\t\t\tconst r = {};\n\t\t\tr.before = tokens.lastMatch() ?? null;\n\t\t\ttokens.consumeToken();\n\t\t\tr.afterFoo = tokens.lastMatch()?.value ?? null;\n\t\t\ttokens.consumeToken();\n\t\t\tr.afterBar = tokens.lastMatch()?.value ?? null;\n\t\t\treturn r;\n\t\t});\n\t\texpect(results.before).toBeNull();\n\t\texpect(results.afterFoo).toBe(\"foo\");\n\t\texpect(results.afterBar).toBe(\"bar\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "lastWhitespace reflects whitespace before the current token",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"foo bar\\n\\tbaz\");\n\t\t\tconst r = {};\n\t\t\t// Before any consume, no whitespace has been consumed yet\n\t\t\tr.initial = tokens.lastWhitespace();\n\t\t\ttokens.consumeToken(); // foo \u2192 consumes trailing whitespace \" \"\n\t\t\tr.afterFoo = tokens.lastWhitespace();\n\t\t\ttokens.consumeToken(); // bar \u2192 consumes \"\\n\\t\"\n\t\t\tr.afterBar = tokens.lastWhitespace();\n\t\t\treturn r;\n\t\t});\n\t\texpect(results.initial).toBe(\"\");\n\t\texpect(results.afterFoo).toBe(\" \");\n\t\texpect(results.afterBar).toBe(\"\\n\\t\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "matchAnyToken and matchAnyOpToken try each option",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"bar + baz\");\n\t\t\treturn {\n\t\t\t\tanyTok: tokens.matchAnyToken(\"foo\", \"bar\", \"baz\")?.value ?? null,\n\t\t\t\tanyOp: tokens.matchAnyOpToken(\"-\", \"+\")?.value ?? null,\n\t\t\t\tanyTokMiss: tokens.matchAnyToken(\"foo\", \"quux\") ?? null,\n\t\t\t};\n\t\t});\n\t\texpect(results.anyTok).toBe(\"bar\");\n\t\texpect(results.anyOp).toBe(\"+\");\n\t\texpect(results.anyTokMiss).toBeNull();\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "matchOpToken matches operators by value",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"+ - *\");\n\t\t\treturn [\n\t\t\t\ttokens.matchOpToken(\"-\") ?? null, // next is +, miss\n\t\t\t\ttokens.matchOpToken(\"+\")?.value ?? null,\n\t\t\t\ttokens.matchOpToken(\"-\")?.value ?? null,\n\t\t\t\ttokens.matchOpToken(\"*\")?.value ?? null,\n\t\t\t];\n\t\t});\n\t\texpect(results[0]).toBeNull();\n\t\texpect(results[1]).toBe(\"+\");\n\t\texpect(results[2]).toBe(\"-\");\n\t\texpect(results[3]).toBe(\"*\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "matchToken consumes and returns on match",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"foo bar baz\");\n\t\t\tconst r = {};\n\t\t\tr.match = tokens.matchToken(\"foo\")?.value ?? null;\n\t\t\tr.miss = tokens.matchToken(\"baz\") ?? null; // next is \"bar\", miss\n\t\t\tr.next = tokens.currentToken().value;\n\t\t\tr.match2 = tokens.matchToken(\"bar\")?.value ?? null;\n\t\t\treturn r;\n\t\t});\n\t\texpect(results.match).toBe(\"foo\");\n\t\texpect(results.miss).toBeNull();\n\t\texpect(results.next).toBe(\"bar\");\n\t\texpect(results.match2).toBe(\"bar\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "matchToken honors the follow set",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"and then\");\n\t\t\ttokens.pushFollow(\"and\");\n\t\t\tconst blocked = tokens.matchToken(\"and\") ?? null;\n\t\t\ttokens.popFollow();\n\t\t\tconst allowed = tokens.matchToken(\"and\")?.value ?? null;\n\t\t\treturn {blocked, allowed};\n\t\t});\n\t\texpect(results.blocked).toBeNull();\n\t\texpect(results.allowed).toBe(\"and\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "matchTokenType matches by type",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"foo 42\");\n\t\t\tconst r = {};\n\t\t\tr.ident = tokens.matchTokenType(\"IDENTIFIER\")?.value ?? null;\n\t\t\tr.numMiss = tokens.matchTokenType(\"STRING\") ?? null;\n\t\t\tr.numOneOf = tokens.matchTokenType(\"STRING\", \"NUMBER\")?.value ?? null;\n\t\t\treturn r;\n\t\t});\n\t\texpect(results.ident).toBe(\"foo\");\n\t\texpect(results.numMiss).toBeNull();\n\t\texpect(results.numOneOf).toBe(\"42\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "peekToken skips whitespace when looking ahead",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst r = {};\n\n\t\t\t// for x in items \u2192 tokens are: for, WS, x, WS, in, WS, items\n\t\t\tconst forIn = t.tokenize(\"for x in items\");\n\t\t\tr.peek0 = forIn.peekToken(\"for\", 0)?.value ?? null;\n\t\t\tr.peek1 = forIn.peekToken(\"x\", 1)?.value ?? null;\n\t\t\tr.peek2 = forIn.peekToken(\"in\", 2)?.value ?? null;\n\t\t\tr.peek3 = forIn.peekToken(\"items\", 3)?.value ?? null;\n\n\t\t\t// peek that shouldn't match\n\t\t\tr.peekMiss = forIn.peekToken(\"in\", 1) ?? null;\n\n\t\t\t// for 10ms \u2014 \"in\" is never present\n\t\t\tconst forDur = t.tokenize(\"for 10ms\");\n\t\t\tr.durPeek2 = forDur.peekToken(\"in\", 2) ?? null;\n\n\t\t\t// Extra whitespace between tokens is tolerated\n\t\t\tconst extraWs = t.tokenize(\"for x in items\");\n\t\t\tr.extraPeek2 = extraWs.peekToken(\"in\", 2)?.value ?? null;\n\n\t\t\t// Comments between tokens are tolerated\n\t\t\tconst withComment = t.tokenize(\"for -- comment\\nx in items\");\n\t\t\tr.commentPeek2 = withComment.peekToken(\"in\", 2)?.value ?? null;\n\n\t\t\t// Newlines as whitespace\n\t\t\tconst multiline = t.tokenize(\"for\\nx\\nin\\nitems\");\n\t\t\tr.multiPeek2 = multiline.peekToken(\"in\", 2)?.value ?? null;\n\n\t\t\t// Type defaults to IDENTIFIER \u2014 matching against an operator requires explicit type\n\t\t\tconst withOp = t.tokenize(\"a + b\");\n\t\t\tr.opDefault = withOp.peekToken(\"+\", 1) ?? null; // IDENTIFIER type, won't match\n\t\t\tr.opExplicit = withOp.peekToken(\"+\", 1, \"PLUS\")?.value ?? null;\n\n\t\t\t// Lookahead past the end returns undefined\n\t\t\tconst short = t.tokenize(\"foo\");\n\t\t\tr.beyondEnd = short.peekToken(\"anything\", 5) ?? null;\n\n\t\t\treturn r;\n\t\t});\n\n\t\texpect(results.peek0).toBe(\"for\");\n\t\texpect(results.peek1).toBe(\"x\");\n\t\texpect(results.peek2).toBe(\"in\");\n\t\texpect(results.peek3).toBe(\"items\");\n\t\texpect(results.peekMiss).toBeNull();\n\t\texpect(results.durPeek2).toBeNull();\n\t\texpect(results.extraPeek2).toBe(\"in\");\n\t\texpect(results.commentPeek2).toBe(\"in\");\n\t\texpect(results.multiPeek2).toBe(\"in\");\n\t\texpect(results.opDefault).toBeNull();\n\t\texpect(results.opExplicit).toBe(\"+\");\n\t\texpect(results.beyondEnd).toBeNull();\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "pushFollow/popFollow nest follow-set boundaries",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst r = {};\n\t\t\tconst tokens = t.tokenize(\"and or not\");\n\t\t\ttokens.pushFollow(\"and\");\n\t\t\ttokens.pushFollow(\"or\");\n\t\t\tr.andBlocked = tokens.matchToken(\"and\") ?? null;\n\t\t\ttokens.popFollow(); // pops \"or\"\n\t\t\tr.andStillBlocked = tokens.matchToken(\"and\") ?? null;\n\t\t\ttokens.popFollow(); // pops \"and\"\n\t\t\tr.andAllowed = tokens.matchToken(\"and\")?.value ?? null;\n\t\t\treturn r;\n\t\t});\n\t\texpect(results.andBlocked).toBeNull();\n\t\texpect(results.andStillBlocked).toBeNull();\n\t\texpect(results.andAllowed).toBe(\"and\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "core/tokenizer",
|
||||
"name": "pushFollows/popFollows push and pop in bulk",
|
||||
"html": "",
|
||||
"body": "\n\t\tconst results = await evaluate(() => {\n\t\t\tconst t = _hyperscript.internals.tokenizer;\n\t\t\tconst tokens = t.tokenize(\"and or\");\n\t\t\tconst count = tokens.pushFollows(\"and\", \"or\");\n\t\t\tconst blocked = tokens.matchToken(\"and\") ?? null;\n\t\t\ttokens.popFollows(count);\n\t\t\tconst allowed = tokens.matchToken(\"and\")?.value ?? null;\n\t\t\treturn {count, blocked, allowed};\n\t\t});\n\t\texpect(results.count).toBe(2);\n\t\texpect(results.blocked).toBeNull();\n\t\texpect(results.allowed).toBe(\"and\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "eval-only"
|
||||
},
|
||||
{
|
||||
"category": "ext/component",
|
||||
"name": "component reads a feature-level set from an enclosing div on first load",
|
||||
"html": "",
|
||||
"body": "\n\t\tawait html(`\n\t\t\t<script type=\"text/hyperscript-template\" component=\"test-plain-card\" _=\"init set ^label to attrs.label\">\n\t\t\t\t<span>${\"\\x24\"}{^label}</span>\n\t\t\t</script>\n\t\t\t<div _=\"set $testLabel to 'hello'\">\n\t\t\t\t<test-plain-card label=\"$testLabel\"></test-plain-card>\n\t\t\t</div>\n\t\t`)\n\t\tawait expect.poll(() => find('test-plain-card span').textContent()).toBe('hello')\n\t\tawait evaluate(() => { delete window.$testLabel })\n\t",
|
||||
"async": true,
|
||||
"complexity": "dom"
|
||||
},
|
||||
{
|
||||
"category": "ext/component",
|
||||
"name": "component reads enclosing scope set by a sibling init on first load",
|
||||
"html": "",
|
||||
"body": "\n\t\tawait html(`\n\t\t\t<script type=\"text/hyperscript-template\" component=\"test-user-card\" _=\"init set ^user to attrs.data\">\n\t\t\t\t<h3>${\"\\x24\"}{^user.name}</h3>\n\t\t\t\t<p>${\"\\x24\"}{^user.email}</p>\n\t\t\t</script>\n\t\t\t<div _=\"init set $testCurrentUser to { name: 'Carson', email: 'carson@example.com' }\">\n\t\t\t\t<test-user-card data=\"$testCurrentUser\"></test-user-card>\n\t\t\t</div>\n\t\t`)\n\t\tawait expect.poll(() => find('test-user-card h3').textContent()).toBe('Carson')\n\t\tawait expect.poll(() => find('test-user-card p').textContent()).toBe('carson@example.com')\n\t\tawait evaluate(() => { delete window.$testCurrentUser })\n\t",
|
||||
"async": true,
|
||||
"complexity": "dom"
|
||||
},
|
||||
{
|
||||
"category": "resize",
|
||||
"name": "on resize from window uses native window resize event",
|
||||
"html": "",
|
||||
"body": "\n\t\tawait html(\n\t\t\t\"<div id='out' _='on resize from window put \\\"fired\\\" into me'></div>\"\n\t\t);\n\t\t// Native window resize isn't a ResizeObserver event; trigger it directly\n\t\tawait page.evaluate(() => {\n\t\t\twindow.dispatchEvent(new Event('resize'));\n\t\t});\n\t\tawait expect(find('#out')).toHaveText(\"fired\");\n\t",
|
||||
"async": true,
|
||||
"complexity": "event-driven"
|
||||
},
|
||||
{
|
||||
"category": "toggle",
|
||||
"name": "toggle between followed by for-in loop works",
|
||||
"html": "",
|
||||
"body": "\n\t\tawait html(\n\t\t\t\"<div id='out'></div>\" +\n\t\t\t\"<div id='btn' class='a' _=\\\"on click \" +\n\t\t\t\" toggle between .a and .b \" +\n\t\t\t\" for x in [1, 2] \" +\n\t\t\t\" put x into #out \" +\n\t\t\t\" end\\\"></div>\"\n\t\t);\n\t\tconst btn = page.locator('#btn');\n\t\tawait btn.dispatchEvent('click');\n\t\tawait expect(btn).toHaveClass(/b/);\n\t\tawait expect(find('#out')).toHaveText('2');\n\t",
|
||||
"async": true,
|
||||
"complexity": "event-driven"
|
||||
},
|
||||
{
|
||||
"category": "toggle",
|
||||
"name": "toggle does not consume a following for-in loop",
|
||||
"html": "",
|
||||
"body": "\n\t\tawait html(\n\t\t\t\"<div id='out'></div>\" +\n\t\t\t\"<div id='btn' _=\\\"on click \" +\n\t\t\t\" toggle .foo \" +\n\t\t\t\" for x in [1, 2, 3] \" +\n\t\t\t\" put x into #out \" +\n\t\t\t\" end\\\"></div>\"\n\t\t);\n\t\tconst btn = page.locator('#btn');\n\t\tawait expect(btn).not.toHaveClass(/foo/);\n\t\tawait btn.dispatchEvent('click');\n\t\tawait expect(btn).toHaveClass(/foo/);\n\t\tawait expect(find('#out')).toHaveText('3');\n\t",
|
||||
"async": true,
|
||||
"complexity": "event-driven"
|
||||
}
|
||||
]
|
||||
]
|
||||
Reference in New Issue
Block a user