Hyperscript compiler/runtime:
- query target support in set/fire/put commands
- hs-set-prolog-hook! / hs-prolog-hook / hs-prolog in runtime
- runtime log-capture cleanup
Scripts: sx-loops-up/down, sx-hs-e-up/down, sx-primitives-down
Plans: datalog, elixir, elm, go, koka, minikanren, ocaml, hs-bucket-f,
designs (breakpoint, null-safety, step-limit, tell, cookies, eval,
plugin-system)
lib/prolog/hs-bridge.sx: initial hook-based bridge draft
lib/common-lisp/tests/runtime.sx: CL runtime tests
WASM: regenerate sx_browser.bc.js from updated hs sources
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
129 lines
5.2 KiB
Markdown
129 lines
5.2 KiB
Markdown
# 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.
|