# SpeedyGo regression suite

Every test in here exists because the behaviour it checks **broke a real customer site**. The
goal is not coverage for its own sake — it is that none of these specific failures can ever
come back silently.

## Running

```bash
php tests/run-tests.php
```

No WordPress, no Composer, no database. `tests/bootstrap.php` stubs the handful of WordPress
functions the tested code touches, so the whole suite runs in well under a second and is safe
to put in CI. It exits `1` on any failure.

## What is covered, and why

| Area | The bug it prevents |
|---|---|
| **Presets** | A preset re-enabling `minify_js` or `load_js_on_interaction` — the two options that can corrupt a site's JavaScript. Also that `font_display_swap` stays on (it was in no preset, so every site failed the font audit). |
| **Critical CSS** | An incomplete capture (only `:root` variables) triggering async CSS, which made pages paint unstyled and then reflow — CLS 1.94 and a 33s LCP on a live site. |
| **Nonce pages** | AJAX forms (login, add-to-cart, comments) failing because a cached page outlived its security token. |
| **PHP sessions** | Custom `session_start()` logins being served a shared, session-less cached page and logged straight back out. |
| **Page builders** | Elementor/Divi/WPBakery/Beaver scripts being minified, combined, deferred or delayed — while ordinary theme JS still gets optimized. |
| **Attribute parsing** | `s.async=false` inside an `onerror` **value** being read as an `async` **attribute**, which silently disabled defer on every minified script and made `combine_js` a no-op. |
| **Atomic writes** | A half-written CSS/JS file served with a `200 OK` — the "text only, no CSS" first render. An empty 200 cannot be caught by the `onerror` fallback, so the guard must stay. |
| **WebP validation** | A `.webp` whose bytes are not WebP being served as an image (broken logo). |
| **Drop-in** | The pre-WordPress serve path losing its logged-in / PHP-session bypass or its nonce expiry. |
| **Compatibility rules** | LMS / membership / form / multilingual / slider JS being minified, combined, deferred or delayed. Rules are remote-updatable, so a new incompatibility is fixed fleet-wide without a plugin release. |

## Two kinds of test

- **Behaviour** — calls the real plugin function and asserts what it returns.
- **Guard** — asserts a critical safety check is still present in the source. These cover logic
  inside private class methods (atomic writes, WebP header validation) where the realistic
  regression is somebody deleting the guard during a refactor.

## Adding a test

When a bug is found and fixed, add a test here **in the same change**. Name it after the
failure, not the function — `"variables-only blob is REJECTED (keeps CSS blocking = safe)"`
tells the next person why the line exists; `"test_is_useful_2"` does not.

Verify the test genuinely fails before the fix: break the code deliberately, run the suite,
confirm it goes red, then restore. A test that has never failed proves nothing.

## These do not ship

`tests/` is marked `export-ignore` in `.gitattributes`, so `./build.sh` (which uses
`git archive`) leaves it out of the plugin zip. The build refuses to finish if it finds test
files in the archive, because a build that silently ships them is worse than no build script at
all - it looks safe.

Two reasons the exclusion matters more than saving 44 KB:

- `bootstrap.php` **defines `ABSPATH` itself**. That is exactly what the
  `if (!defined('ABSPATH')) exit;` guard at the top of every plugin file checks, so a copy of
  `run-tests.php` sitting on a live server is reachable over HTTP and will execute plugin code
  outside WordPress. Both test files now refuse to run unless `PHP_SAPI === 'cli'`, but not
  shipping them is the real fix.
- Every extra PHP file on a customer's server is attack surface, and reviewers for plugin
  directories flag test harnesses in a release.

Build a release with:

```bash
./build.sh          # from HEAD
./build.sh v2.1.11  # from a tag
```

## Before a release

A green suite is the gate for merging, not for shipping. See
[VERIFICATION-MATRIX.md](VERIFICATION-MATRIX.md) for the ten real-site archetypes that must be
checked before a version is published.
