diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml new file mode 100644 index 0000000..6261a7d --- /dev/null +++ b/.github/workflows/test.yml @@ -0,0 +1,86 @@ +name: test + +on: + push: + branches: [main] + pull_request: + +permissions: + contents: read + +jobs: + # Layer 1: platform-neutral logic — widgets' rendered output, theme tokens, + # Notifier, assets, options, SetState batching. Plus formatting, vet on both + # targets, and a WASM build smoke check. + host: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + - name: gofmt + run: test -z "$(gofmt -l .)" || (echo "gofmt needed:"; gofmt -l .; exit 1) + - name: vet (host) + run: go vet ./... + - name: vet (wasm) + run: GOOS=js GOARCH=wasm go vet ./... + - name: test (host, race) + run: go test -race -cover ./... + - name: build (wasm) + run: GOOS=js GOARCH=wasm go build ./... + + # Layer 2: the reconciler (element_wasm.go) running as real WASM against a + # real DOM, via wasmbrowsertest in headless Chrome. + wasm: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + - uses: browser-actions/setup-chrome@v2 + - name: allow Chrome's user-namespace sandbox + # ubuntu-24.04 runners restrict unprivileged user namespaces via + # AppArmor, which crashes Chrome's zygote sandbox (wasmbrowsertest runs + # Chrome with the sandbox on and exposes no flag to disable it). Lifting + # the restriction lets the sandbox initialize. `|| true` keeps it a + # no-op on images where the key doesn't exist. + run: sudo sysctl -w kernel.apparmor_restrict_unprivileged_userns=0 || true + - name: install wasmbrowsertest + run: | + go install github.com/agnivade/wasmbrowsertest@latest + cp "$(go env GOPATH)/bin/wasmbrowsertest" "$(go env GOPATH)/bin/go_js_wasm_exec" + echo "$(go env GOPATH)/bin" >> "$GITHUB_PATH" + - name: test (wasm runtime) + run: GOOS=js GOARCH=wasm go test -count=1 ./... + + # Layer 3: full end-to-end — the testapp built and served by the gutter CLI, + # driven through a real browser by Playwright. + e2e: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + - uses: actions/setup-go@v6 + with: + go-version-file: go.mod + cache: true + - uses: actions/setup-node@v6 + with: + node-version: 20 + - name: install playwright + working-directory: e2e + run: | + npm install + npx playwright install --with-deps chromium + - name: e2e + working-directory: e2e + run: npx playwright test + - uses: actions/upload-artifact@v7 + if: failure() + with: + name: playwright-report + path: e2e/playwright-report + retention-days: 7 diff --git a/CLAUDE.md b/CLAUDE.md index 78f759f..c642c05 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -13,7 +13,7 @@ Gutter is a Go library for building web applications declaratively, inspired by - `github.com/Runway-Club/gutter/widgets` — the single widget catalog. Three flavors live here side by side: - **App shell + themed** (StatelessWidgets that read `ctx.Theme`): `Scaffold` (the recommended root — `Title`/`Theme`/`AppBar`/`StickyAppBar`/`Body`/`Footer`; when `StickyAppBar` is true the bar is wrapped in `position:sticky; top:0; z-index:900` so it pins to the viewport while the body scrolls past — z-index sits below the 1000 overlay tier so Popup/Drawer/BottomSheet still cover it), `AppBar`, `Heading`, `Body`, `Caption`, `Link`, `Button`, `IconButton` (square Button variant rendering an `Icon` as its only content), `Card`, `Surface`, `Badge`, `Image` (HTML `` with `Asset` resolved via `gutter.AssetURL` or absolute `Src`; supports `Fit` for object-fit), `Icon` (Google Material Symbols glyph; `Style: IconOutlined|IconRounded|IconSharp`, `Filled`, `Weight`, `Grade` — drives the FILL/wght/GRAD/opsz axes via font-variation-settings; the scaffolded `index.html` preloads all three stylesheets), `File` (themed file picker — `Label`/`Child` for the trigger styled like a Button, `Accept`/`Multiple`, callback receives `[]FilePick{Name, Size, MimeType, Data []byte}` with bytes pre-read via FileReader; reading is WASM-only, `file_wasm.go`/`file_stub.go` split). - **Input family** (all themed via `theme.Components.Input` / `theme.Colors.Primary`; controlled — declarative `Value`/`Checked`/`Selected` field is the source of truth, change callback fires on user edits, parent rebuilds with the new value): `Input` (single-line; `Type: InputText|Password|Email|Number|Tel|URL|Search|Date|Time|DateTimeLocal|Month|Week|Color` plus `Min/Max/Step/Pattern/AutoComplete/Disabled/ReadOnly/Name`), `TextArea` (multi-line; `Rows`, `Resize`, `MaxLength`), `Checkbox` (label-wrapped native checkbox themed via `accent-color`), `Switch` (custom CSS sliding pill — implemented as a styled `