# F5 — Cookie API (+5) **Suite:** `hs-upstream-expressions/cookies` **Target:** All 5 tests are `SKIP (untranslated)`. ## 1. The 5 tests From upstream `test/expressions/cookies.js`: | Test | What it checks | |------|---------------| | `length is 0 when no cookies are set` | `cookies.length == 0` with no cookies set | | `basic set cookie values work` | `set cookies.name to "value"` then `cookies.name == "value"` | | `update cookie values work` | set, then set again, value updates | | `basic clear cookie values work` | `set cookies.name to "value"` then `clear cookies.name`, then `cookies.name == undefined` | | `iterate cookies values work` | `for name in cookies` iterates cookie names | ## 2. HyperScript cookie syntax `cookies` is a special global expression in HyperScript backed by `document.cookie`. The upstream implementation wraps `document.cookie` in a proxy: - `cookies.name` → read cookie by name (returns string or `undefined`) - `set cookies.name to val` → write cookie (sets `document.cookie = "name=val"`) - `clear cookies.name` → delete cookie (sets max-age=-1) - `cookies.length` → number of cookies set - `for name in cookies` → iterate over cookie names ## 3. Test runner mock All 5 tests are untranslated — no SX test bodies exist yet. The generator needs patterns for the cookie expressions, and `hs-run-filtered.js` needs a `document.cookie` mock. ### Mock in `tests/hs-run-filtered.js` Add a simple in-memory cookie store to the `dom` mock: ```js let _cookieStore = {}; Object.defineProperty(global.document, 'cookie', { get() { return Object.entries(_cookieStore) .map(([k,v]) => `${k}=${v}`) .join('; '); }, set(str) { const [pair, ...attrs] = str.split(';'); const [name, val] = pair.split('=').map(s => s.trim()); const maxAge = attrs.find(a => a.trim().startsWith('max-age=')); if (maxAge && parseInt(maxAge.split('=')[1]) < 0) { delete _cookieStore[name]; } else { _cookieStore[name] = val; } }, configurable: true }); ``` Add `_cookieStore = {}` reset to `hs-cleanup!` equivalent in the runner. ## 4. SX runtime additions in `lib/hyperscript/runtime.sx` HS needs a `cookies` special expression that the compiler resolves. Two approaches: **Option A (simpler):** Treat `cookies` as a built-in variable bound to a proxy dict at runtime. When property access `cookies.name` is evaluated, dispatch to cookie read/write helpers. **Option B (upstream-faithful):** Parse `cookies` as a special primary expression, emit runtime calls `hs-cookie-get`, `hs-cookie-set`, `hs-cookie-delete`, `hs-cookie-length`, `hs-cookie-names`. Option A is less invasive. The runtime env gets a `cookies` binding pointing to a special object; property access and assignment on it dispatch to the cookie helpers, which call `(platform-cookie-get name)` / `(platform-cookie-set name val)` / `(platform-cookie-delete name)`. Platform cookie operations map to `document.cookie` reads/writes in JS. ## 5. Generator patterns (`tests/playwright/generate-sx-tests.py`) The upstream tests use patterns like: ```js await page.evaluate(() => { _hyperscript.evaluate("set cookies.foo to 'bar'") }); expect(await page.evaluate(() => _hyperscript.evaluate("cookies.foo"))).toBe("bar"); ``` In our SX harness these become direct `eval-hs` calls. Since all 5 tests are untranslated, hand-write them rather than extending the generator (similar to E39). ## 6. Translated test bodies ```lisp (deftest "length is 0 when no cookies are set" (hs-cleanup!) (assert= (eval-hs "cookies.length") 0)) (deftest "basic set cookie values work" (hs-cleanup!) (eval-hs "set cookies.foo to 'bar'") (assert= (eval-hs "cookies.foo") "bar")) (deftest "update cookie values work" (hs-cleanup!) (eval-hs "set cookies.foo to 'bar'") (eval-hs "set cookies.foo to 'baz'") (assert= (eval-hs "cookies.foo") "baz")) (deftest "basic clear cookie values work" (hs-cleanup!) (eval-hs "set cookies.foo to 'bar'") (eval-hs "clear cookies.foo") (assert= (eval-hs "cookies.foo") nil)) (deftest "iterate cookies values work" (hs-cleanup!) (eval-hs "set cookies.a to '1'") (eval-hs "set cookies.b to '2'") (let ((names (eval-hs "for name in cookies collect name"))) (assert (contains? names "a")) (assert (contains? names "b")))) ``` ## 7. Implementation checklist 1. Add cookie mock to `tests/hs-run-filtered.js`. Wire reset into test cleanup. 2. Add `hs-cookie-get`, `hs-cookie-set`, `hs-cookie-delete`, `hs-cookie-length`, `hs-cookie-names` to `lib/hyperscript/runtime.sx`. 3. Add `cookies` as a special expression in the HS parser/evaluator that dispatches to the above. 4. Replace 5 `SKIP` bodies in `spec/tests/test-hyperscript-behavioral.sx` with translated test bodies above. 5. Run `hs_test_run suite="hs-upstream-expressions/cookies"` — expect 5/5. 6. Run smoke 0–195 — no regressions. 7. Commit: `HS: cookie API — document.cookie proxy + 5 tests` ## 8. Risk Medium. The mock is simple. The main risk is the `cookies` expression integration in the parser — it needs to hook into property-access and assignment paths that are already well-exercised. Keep the implementation thin: `cookies` is a runtime value with a special type, not a new parse form.