Skip to content

Contributing ​

Read the root contributor guidelines before changing code. Use PHP 8.5, CakePHP 5.4 or later in the 5.x series, and Node 24.11+ or Node 26+. For installation and fixture setup, start with the demo.

For testing islands in an application, see Testing islands. The commands below verify the Interact repository itself.

Test organization ​

Find tests by the capability they exercise. PHP and JavaScript unit suites use corresponding feature folders such as Pagination, Composition, Uploads, and UrlState (lowercase kebab case in JavaScript paths). Playwright specs stay flat under tests/browser/, with descriptive filenames such as uploads.spec.ts and pagination-state.spec.ts.

LocationContents
tests/TestCase/<Feature>/PHPUnit cases for one capability, including its HTTP workflows.
tests/Fixture/<Feature>/Fixture classes, templates, data, and provider examples owned by that capability.
tests/js/<feature>/Browser-runtime unit tests with controlled transport and scheduling.
tests/browser/*.spec.tsPlaywright acceptance tests against the real demo application.
tests/TestSuite/Http/ and tests/TestSuite/Uploads/Shared PHP request and upload-isolation harnesses.
tests/js/support/Shared JavaScript unit-test helpers.

Name fixtures after the behavior they demonstrate. For example, tests/Fixture/Pagination/IndependentResourceQueriesIsland.php exercises two independently paginated queries against the same Table. Its template lives in that feature's templates/island/Pagination/ directory. Class docblocks explain the scenario and its ownership or security boundaries.

Rendering fixtures retain native CakePHP application, plugin, theme, and override layouts under tests/Fixture/Rendering/. The quickstart fixture and published documentation anchors live under tests/Fixture/Documentation/. Keep the bootable demo in tests/test_app/; it supplies complete interactions without depending on test-only fixture classes or templates.

Place cross-feature tests with the capability whose contract they verify. Import reusable fixtures from their owning feature, and split unrelated workflows into named test classes. Browser specs and shared runner entry points remain directly in tests/browser/; import the test API from tests/browser/fixtures.ts.

Run a feature in isolation:

sh
vendor/bin/phpunit tests/TestCase/Pagination
npm test -- tests/js/pagination
npm run test:browser -- tests/browser/pagination-state.spec.ts \
  --project=chromium

PHP checks ​

sh
composer validate --strict
composer check
composer test-coverage

check runs PHPUnit, Mago analysis, Mago lint, Rector dry run, and formatting verification. rector-fix applies refactorings followed by Mago formatting. Preserve approved Rector skips and CakePHP's Mago preset.

Coverage needs Xdebug in coverage mode or PCOV. Include unexecuted source and require at least 95% unrounded line coverage across production src/. Missing reports and empty executable-source measurements fail the explicit gate. PHPUnit 12.5 and 13 are supported. The committed lockfile uses PHPUnit 13; CI resolves the full dependency set with composer update --prefer-lowest --prefer-stable and composer update --prefer-stable, covering PHPUnit 12.5 and 13 respectively. The browser job installs the committed lockfile. PHPUnit fails on warnings, risky tests, and deprecations.

Optional branch coverage

With a compatible Xdebug build, composer test-coverage-branches writes method, line, branch, and path coverage to build/php-branches.txt. PHP 8.5.11 with Xdebug 3.5.3 crashes during branch/path instrumentation in this development environment; ordinary line coverage works. Branch reporting remains optional until that toolchain issue is resolved.

JavaScript and browser checks ​

sh
npm run check
npm run test:coverage
npx playwright install --with-deps chromium firefox webkit
npm run test:browser

check runs Vite+ format verification, Oxlint (including type-aware rules), native type checking, and Vitest. V8 coverage includes unexecuted runtime source and separately requires at least 95% unrounded line coverage. Statements, functions, and branches are also reported.

JavaScript tooling is configured together in vite.config.ts: lint owns Oxlint rules, fmt owns Oxfmt formatting, test owns Vitest and coverage, and pack builds both browser distributions. Use npm run lint, npm run format, or npm run typecheck for a focused check. Vite+ supplies these tools and Vitest, so no separate ESLint, Prettier, or Vitest installation is needed. Tests import their APIs from vite-plus/test.

TypeScript is a normal version 7 dependency. Vite+ uses its native type checker; npm run build uses vp pack to bundle the npm module and standalone CSP runtime, and emits declarations with the native TypeScript 7 generator. There are no TypeScript compatibility aliases or second compiler installations.

Playwright exercises the real CakePHP application in Chromium, Firefox, and WebKit with three parallel workers. Import test and expect from tests/browser/fixtures.ts; its worker-scoped fixture starts a FrankenPHP container with bundled Mercure on a free loopback port. Each worker has separate SQLite, session, cache, and upload storage. Teardown removes the container and temporary storage; combined app/hub diagnostics remain in test-results/worker-*.log. Docker is required. Browser tests create their own app instances.

Use Playwright's native filters and worker override for focused runs:

sh
npm run test:browser -- tests/browser/form-models.spec.ts --project=chromium
npm run test:browser -- --workers=1

Test observable behavior, security failures, races, validation, focus, and nested ownership even when coverage totals already pass. PHP integration tests exercise the middleware stack in process.

/forms is the FormHelper article demo. Playwright opens /demo/form-fixtures directly for plain HTML, external-control, and nested-island acceptance cases. That fixture page has no demo navigation entry, like /demo/polling.

Runtime modules live under resources/ts: bindings own controls, scheduling serializes requests, state modules validate and reconcile drafts, transport validates HTTP envelopes, and DOM modules own morphing and selection. client.ts coordinates them; index.ts installs the Alpine integration.

Keep examples trustworthy ​

Demo controller routes are connected explicitly in tests/test_app/config/routes.php. Islands assign Table properties in initialize() using the inherited fetchTable(); domain queries belong in named finders. Keep other behavior services injectable.

Give every PHP/TypeScript class an informative responsibility docblock, including tests and demos. Escape output, use Alpine shorthand, and preserve native form semantics. Do not suppress rules, weaken assertions, exclude production code, or add coverage-ignore annotations to reach a target.

For feature documentation, start with a direct definition and minimal syntax. Keep one page per topic. Small control examples use HTML-first tabs with a FormHelper alternative. Keep short examples within the relevant feature or API page. Complete interactions live in tests/test_app/ and are linked from Demo. HTTP and browser tests exercise that same application; do not maintain a separate documentation app. Check snippets, behavior, links, and desktop/mobile navigation.

Release branches ​

main targets CakePHP 5.4+ and currently releases Interact 0.2.x, progressing toward a stable 1.x API. cake6 targets CakePHP 6 and the future Interact 2.x line. Both require PHP 8.5. Composer aliases expose these branches as 0.2.x-dev and 2.x-dev; the API remains experimental.

Version 0.2.0 introduces CakePHP 5 support. Applications using CakePHP 6 with Interact 0.1.x should switch to dev-cake6 to retain compatibility.

Develop shared fixes on main, then merge main into cake6 with an ordinary merge commit. Adapt CakePHP 6 differences and run that branch's checks before completing the merge. Keep framework-specific changes separate from shared behavior changes. Preserve merge ancestry: do not squash or rebase merges between the maintained branches.

The initial CakePHP 5 backport is recorded on cake6 with a one-time git merge --no-ff -s ours <backport-tip>. That merge preserves the CakePHP 6 file tree while recording the backport as considered. Never repeat -s ours for ordinary updates: it would discard incoming fixes. Review dependency constraints, lockfiles, package versions, CI, and documentation during forward merges.

The published documentation follows the latest main branch and its current release line. Keep shared feature documentation synchronized through ordinary forward merges into cake6, adapting examples and setup for CakePHP 6 there. The cake6 documentation is built and checked on pushes and pull requests, but is not published until its release. Only main deploys to GitHub Pages. Release tags and npm publication are separate from branch preparation.

Documentation and releases ​

sh
npm ci
npm run docs:build
npm run package:composer
npm pack --dry-run

For local reading, run npm run docs:dev and use the URL it prints, including the /docs/ base path.

The published documentation is served through the website's Caddy proxy. GitHub Pages hosts the origin build under /cakephp-interact/_docs/; build/docs-pages contains that build plus redirects for the old public URLs. The VitePress public base remains /docs/, including during local development. Direct origin visits redirect to the canonical website URL; the origin itself remains publicly accessible. The build also exposes the unchanged native MiniSearch index at /docs/search-index.json for the website’s PHP documentation search. Its serialized format is checked during packaging; it is the same index used by VitePress’s local search.

The Documentation workflow builds documentation changes on pull requests and pushes to main and cake6. Only main deploys to the github-pages environment; pull requests build without deployment permissions. Manual workflow runs can publish main. Pages settings must use GitHub Actions as the source.

Package builds supply an npm module with declarations and a standalone webroot/js/runtime.js. Commit the standalone bundle and webroot/js/THIRD-PARTY-LICENSES.txt whenever runtime sources or bundled dependencies change. The Quality workflow rebuilds and checks both files against the committed versions, rejecting missing or stale assets. Node is contributor tooling; standard Composer consumers do not need it.

Packagist's default GitHub source archives include these committed assets for dev-main and future tagged releases. Build and commit the assets before tagging a release. Tagged releases beginning with 0.2.0 target CakePHP 5; older 0.1.x tags target CakePHP 6.

package:composer creates a reproducible archive under build/releases/ with PHP source, docs, the standalone bundle, and third-party licenses. This archive is an additional build artifact; attaching it to a GitHub release does not replace Packagist's default source archive. The Quality workflow validates packages but does not publish them. The npm module under build/js/ remains generated and untracked for the optional application-owned Alpine integration.

Quality checks run on pull requests and pushes to main. Feature-branch pushes are checked through their pull request, avoiding duplicate PHP and browser jobs.

Run affected checks and the full relevant workflow before handoff. For docs-only changes, verify documentation and examples without claiming unrelated PHP checks. Report actual results and unavailable tooling. Keep dependencies, caches, reports, and credentials out of commits.

Navigation lives in focused resources/ts/navigation modules installed by Interact's existing Alpine installer. Keep page visits separate from island request queues, and coordinate page history with URL-bound state. Persist only explicitly marked, compatible layout regions; incoming mounts and later updates must still authorize.

The page-level interactNavigation Alpine store reuses request feedback timing. Applications own indicators and styles, and choose delays in Alpine expressions. Keep document policy/version checks, external asset loading, cancellation, stale responses, scroll, and focus covered by real browser tests. Use the /navigation demo for both page-owned and retained island behavior.

Renderless actions ​

#[Action(render: false)] and action-only skipRender() share the ordinary request lifecycle and state response builder. Test signed state, child identities, upload references, events, URL updates, submission protection, and newer drafts without a morph. Validation must render, and the next successful action must render again to clear feedback. Initialization, synchronization, transfers, and history restoration always require HTML. Exercise the standalone FormHelper editor at /renderless through SQLite integration tests and all three browsers.

Realtime integration ​

Browser specs use one FrankenPHP container per worker, with the bundled Mercure hub on the same origin as CakePHP. The demo and tests share the PHP launcher in scripts/preview.php and the pinned image in tests/test_app/Dockerfile. Symfony Process, installed as a development dependency, handles Docker command execution. Test setup builds the image once, then workers receive independent ports, signing keys, SQLite databases, sessions, and upload storage.

sh
npm run test:browser
composer preview

Docker must be running and permitted to create loopback-bound containers. Missing infrastructure fails explicitly. Fixtures retain combined application/hub logs and remove their containers and temporary storage on teardown. The reconnect test restarts the app and bundled hub together while preserving that worker's storage. FrankenPHP also serves concurrent requests during action streaming, so the session release test uses the same app instead of a second PHP server. Its controlled TCP gate is released from inside the container.

The demo uses port 8765; tests reserve free loopback ports. See Demo for ngrok sharing. Demonstration identity endpoints are local fixtures; applications supply their own authentication.