Skip to content

Commit 277e077

Browse files
committed
enhance mental model documentation with detailed explanations of slottable component children, key differences from React, and testing environments; update Valhalla README for clarity on testing purposes and conventions
1 parent 8bdce8c commit 277e077

File tree

25 files changed

+288
-5
lines changed

25 files changed

+288
-5
lines changed

docs/MENTAL-MODEL.md

Lines changed: 169 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -698,6 +698,96 @@ return <>
698698

699699
---
700700

701+
## Component Children (Slottable)
702+
703+
Azoth components can receive children, but the mechanism differs from React.
704+
705+
### Component Signature
706+
707+
```jsx
708+
// Props as first arg, slottable (children) as second arg
709+
const Card = ({ class: className }, slottable) => (
710+
<div class={`card${className ? ` ${className}` : ''}`}>
711+
{slottable}
712+
</div>
713+
);
714+
```
715+
716+
### Key Differences from React
717+
718+
1. **Second argument, not a prop:** Children are passed as the second argument to the component function, not as `props.children`.
719+
720+
2. **Called "slottable":** The convention is to name it `slottable` rather than `children` to emphasize the difference.
721+
722+
3. **You receive DOM, not virtual DOM:** In React, `children` are React elements you can manipulate (clone, map, filter). In Azoth, `slottable` is actual DOM content. Don't try to "muck about" with it — just render it.
723+
724+
4. **Composition, not manipulation:** Achieve the same patterns by composing components (nesting) rather than manipulating children programmatically.
725+
726+
### Usage
727+
728+
```jsx
729+
// Definition
730+
const Card = ({ class: className }, slottable) => (
731+
<div class={`card${className ? ` ${className}` : ''}`}>
732+
{slottable}
733+
</div>
734+
);
735+
736+
// Usage - content becomes slottable
737+
<Card class="stats">
738+
<h2 class="card-title">Title</h2>
739+
<div class="content">...</div>
740+
</Card>
741+
```
742+
743+
### Compiled Output
744+
745+
Looking at the compiler tests, component children compile to:
746+
747+
```javascript
748+
// Input:
749+
const c = <Component><p>content</p></Component>;
750+
751+
// Output:
752+
const c = __rC(Component, null, templateFn());
753+
// ^Component ^props ^children template
754+
```
755+
756+
The third argument to `__rC` is a template function that creates the DOM for the children.
757+
758+
### Testing and Development Environments
759+
760+
Azoth has several testing environments, each with a specific purpose:
761+
762+
**Valhalla (`packages/vahalla/`):**
763+
- Browser-based tests via Vitest browser mode + Chrome
764+
- Tests the **developer-facing JSX API** at the integration level
765+
- Use for component patterns, rendering behavior, JSX-to-DOM verification
766+
- Add tests here when investigating bugs at the API level
767+
768+
**vite-test (`vite-test/`):**
769+
- A minimal Vite bootstrap project
770+
- Verifies that Azoth works correctly in a standard Vite build environment
771+
- **Build system integration test** — does Azoth + Vite produce a working app?
772+
- Keep minimal; it's not a test suite, it's a smoke test for the build
773+
774+
**Thoth compiler tests (`packages/thoth/compiler.test.js`):**
775+
- Tests the compiler/transpiler output directly
776+
- Use for testing compilation behavior, generated code, template structure
777+
- Add tests here for parser/transpiler-level issues
778+
779+
**Happy-dom vs real browsers:** When encountering errors in Node-based test environments (like happy-dom), verify the pattern works in a real browser first. Some DOM behaviors differ between implementations.
780+
781+
### See Also
782+
783+
- `packages/vahalla/components.test.tsx` — API-level tests for component patterns
784+
- `packages/vahalla/README.md` — Testing conventions and inline snapshot format
785+
- `packages/thoth/compiler.test.js` — "component child templates" and "compose component" tests
786+
- `packages/thoth/transform/Analyzer.js` — How children are analyzed
787+
- `packages/thoth/transform/Transpiler.js` — How children are transpiled
788+
789+
---
790+
701791
## Known Issues
702792

703793
*Bugs and unexpected behaviors discovered during development:*
@@ -747,4 +837,82 @@ if(!jsxOnly && expr === null) {
747837

748838
**Status:** Fixed — Boolean shorthand now works like JSX/React.
749839

750-
**Context:** Discovered while refactoring `FubClientsStage.jsx` where `<FubSimpleRow muted />` caused the error.
840+
**Context:** Discovered while refactoring `FubClientsStage.jsx` where `<FubSimpleRow muted />` caused the error.
841+
842+
### Dynamic Bindings Require DOM Property Names (Not Attribute Names)
843+
844+
**Issue:** Dynamic attribute bindings use DOM property assignment, not `setAttribute()`. This means you must use the DOM property name (`className`) rather than the HTML attribute name (`class`).
845+
846+
**Root Cause:** In `makeBind()` (template-generators.js), dynamic values are assigned directly to DOM properties:
847+
```javascript
848+
t0.className = v0; // Works - className is a DOM property
849+
t0["class"] = v0; // Fails - "class" is not a valid DOM property
850+
```
851+
852+
**Reproduction:**
853+
```jsx
854+
// Static attribute — WORKS (goes into HTML template directly)
855+
<div class="card">content</div>
856+
// Output: <div class="card">content</div>
857+
858+
// Dynamic with attribute name — BROKEN
859+
<div class={myClass}>content</div>
860+
// Output: <div>content</div> ← class missing!
861+
862+
// Dynamic with property name — WORKS
863+
<div className={myClass}>content</div>
864+
// Output: <div class="highlighted">content</div>
865+
```
866+
867+
**Common attribute → property mappings:**
868+
- `class``className`
869+
- `for``htmlFor`
870+
- `readonly``readOnly`
871+
- `tabindex``tabIndex`
872+
873+
**Documented behavior:** See line ~359: "Attributes need attribute names, properties need DOM property names."
874+
875+
**Future work:** A translation layer could allow developers to use either name interchangeably (90% complete in separate project).
876+
877+
**Workaround:** Use `className` for dynamic class bindings, `class` for static.
878+
879+
**Status:** Known limitation — documented, workaround available. Future: attribute-to-property translation layer.
880+
881+
### Component with Dynamic Binding Inside Slottable (Under Investigation)
882+
883+
**Issue:** A component with dynamic bindings (e.g., `{title}`) used inside another component's slottable crashes at runtime.
884+
885+
**Reproduction:**
886+
```jsx
887+
const CardTitle = ({ title }) => (
888+
<h2 class="card-title">{title}</h2>
889+
);
890+
891+
const Card = (props, slottable) => (
892+
<div class="card">{slottable}</div>
893+
);
894+
895+
// This crashes:
896+
<Card>
897+
<CardTitle title="Performance Stats" />
898+
</Card>
899+
```
900+
901+
**Error:** `TypeError: Cannot read properties of undefined (reading 'data')` at compose.js line 201 in `clear(anchor)` function.
902+
903+
**Observations:**
904+
- Works: Same pattern in Valhalla tests (components defined inline in same file)
905+
- Fails: wre-dashboards (components imported from separate file)
906+
907+
**Under investigation:** Need to isolate the variable (inline vs imported) with controlled experiment in Valhalla.
908+
909+
**Workaround:** Use imperative DOM manipulation:
910+
```jsx
911+
export const CardTitle = ({ title }) => {
912+
const h2 = <h2 class="card-title"></h2>;
913+
h2.textContent = title;
914+
return h2;
915+
};
916+
```
917+
918+
**Status:** Open — root cause unknown, investigating.

packages/thoth/transform/template-generators.test.js

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -173,7 +173,6 @@ describe('bind generator', () => {
173173
);
174174
});
175175

176-
177176
test('edge case', ({ expect }) => {
178177
const input = `
179178
export const Loading = () => <p>loading...</p>;

packages/vahalla/Card.tsx

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
/// <reference path="../azoth/jsx.d.ts" />
2+
3+
// Separate file to test import behavior
4+
// Same components as inline test, but in separate module
5+
6+
export const Card = (props, slottable) => (
7+
<div class="card">{slottable}</div>
8+
);
9+
10+
export const CardTitle = ({ title }) => (
11+
<h2 class="card-title">{title}</h2>
12+
);

packages/vahalla/README.md

Lines changed: 34 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,19 @@
11
# Valhalla - Azoth End-to-End Testing
22

3-
Browser-based tests verifying Azoth JSX compilation and rendering.
3+
Browser-based tests verifying Azoth JSX compilation and rendering at the API level.
4+
5+
## Purpose
6+
7+
Valhalla tests the **developer-facing JSX interface** — treating JSX as HTML that returns DOM. This is distinct from:
8+
9+
- **vite-test/**: A minimal Vite bootstrap project that verifies Azoth works correctly in a standard Vite build environment. Use for build system integration testing.
10+
- **packages/thoth/compiler.test.js**: Compiler-level tests for the Thoth transpiler. Use for testing compilation output.
11+
12+
**When to add tests here:**
13+
- Testing component patterns (props, slottable, nesting)
14+
- Verifying JSX-to-DOM behavior
15+
- Documenting idiomatic Azoth patterns
16+
- Investigating rendering bugs at the API level
417

518
## Setup
619

@@ -28,11 +41,30 @@ test('element', ({ expect }) => {
2841

2942
## Conventions
3043

31-
- Use `/* HTML */` comment for snapshot syntax highlighting
44+
- **Use `/* HTML */` comment** before inline snapshots for syntax highlighting
3245
- Type assertions for specific element types: `as HTMLParagraphElement`
3346
- `document.body` as fixture container (universal, always available)
3447
- Inline snapshots keep expected output visible with test code
3548

49+
## Test Files
50+
51+
- **smoke.test.tsx**: Core JSX-to-DOM behavior (elements, DOM APIs)
52+
- **components.test.tsx**: Component patterns (props, slottable, nesting)
53+
- **sandbox.test.tsx**: Scratch file for quick experiments
54+
55+
## Running Tests
56+
57+
```bash
58+
# Run all Valhalla tests
59+
pnpm test packages/vahalla/
60+
61+
# Run specific test file
62+
pnpm test packages/vahalla/components.test.tsx
63+
64+
# Update snapshots
65+
pnpm test packages/vahalla/components.test.tsx -- -u
66+
```
67+
3668
## Sandbox: Empirical JSX Testing
3769

3870
`sandbox.test.tsx` is a scratch file for quickly testing Azoth JSX behavior.
3.76 KB
Loading
4.17 KB
Loading
5.16 KB
Loading
5.82 KB
Loading
4.79 KB
Loading
5.03 KB
Loading

0 commit comments

Comments
 (0)