Test a sibling plugin: snapshot harness, curl diffs, and the Previewer

Goal

You are building or changing a sibling plugin that injects data into the mobile JSON payload — the response served by local_app_get_phone_data. That payload is a frozen public contract: deployed mobile clients read it key-by-key, and a plugin that accidentally alters a section it doesn’t own can break apps in the field.

This article shows you how to prove the opposite: capture the phone-data JSON before your change, capture it again after, and confirm that the only difference is the key (or section) your plugin is responsible for. You’ll do it three ways, from quickest to most rigorous:

  1. A throwaway curl + diff for a one-off sanity check.
  2. The mam-main snapshot harness (tests/snapshot/snapshot-phone-data.php) for repeatable, normalized regression checks across multiple user scenarios.
  3. The Previewer to confirm the app actually renders what you shipped.

Prerequisites

  • A staging site running mam-main and your sibling plugin, reachable over HTTP from your machine. The snapshot tool needs no database access and no WP-CLI — it only needs to curl the staging URL.
  • PHP available on your local machine (the harness runs from the CLI: php snapshot-phone-data.php …).
  • At least one test user’s PID — the value of that user’s mam_app_token user-meta. This is what the mobile app sends as the pid query param. (Anonymous requests need no PID.)
  • The mam-main repo checked out locally, so you have mam-main/tests/snapshot/.
  • The Previewer app (or a test build) able to point at your staging site.

The phone-data endpoint accepts both local_app_get_phone_data (the frozen name older clients call) and the modern alias mam_get_phone_data. Both route to the same handler. Use local_app_get_phone_data for testing so you exercise exactly what deployed apps hit.


Why this matters before you start

The phone-data response assembles the entire screen-graph for a user: buttons, layouts, forms, notifications, content sections, and every sibling plugin’s contributed keys. A single cold request fans out to roughly 70 mam_get_phone_data_before_send subscribers. Your plugin is one of them.

The discipline this article enforces is simple: your diff should contain only your key. If your before/after comparison shows changes in left_menu, notifications, another plugin’s section, or anything you didn’t intend to touch, you have a regression — stop and investigate before you ship.


Step 1 — Quick check: curl before and after

For a fast, one-off look (you’re not yet setting up the harness), snapshot the raw JSON on either side of your change.

# Before activating / changing your plugin:
curl 'https://staging.example.com/wp-admin/admin-ajax.php?action=local_app_get_phone_data' > before.json

# Make your change (activate the plugin, or deploy the new build), then:
curl 'https://staging.example.com/wp-admin/admin-ajax.php?action=local_app_get_phone_data' > after.json

# Diff:
diff before.json after.json

Add a &pid=<test-user-pid> to the URL to capture a logged-in user’s payload instead of the anonymous one.

This is good enough to confirm “my key showed up.” Its weakness is noise: the raw response contains volatile fields (a cursor, timestamps, timing tracers) that change request-to-request, so a naive diff will flag differences that aren’t your fault. The next step removes that noise.


Step 2 — Capture a baseline with the snapshot harness

The harness lives at:

mam-main/tests/snapshot/snapshot-phone-data.php

It hits the same local_app_get_phone_data endpoint, but it normalizes the response before comparing — stripping volatile keys and sorting associative keys — so the only diffs you see are real contract changes.

Configure it

The tool is driven by a gitignored config file. Copy the example and fill it in:

cd mam-main/tests/snapshot
cp snapshot-config.example.php snapshot-config.php

Edit snapshot-config.php:

  • Set base_url to your staging site (no trailing /wp-admin).
  • Define one fixture per scenario you care about. Each fixture is just the query params for the AJAX call. Replace the REPLACE_WITH_* placeholders with real test PIDs.

The example ships with a useful set of scenarios you should keep:

Fixture What it exercises
anonymous No logged-in user — the public layout
logged_in_user A standard signed-in user
cloning_admin An admin cloning into another user’s view (a full rebuild of the cloned user’s payload)
cursor_no_new_data The delta path, where the response should be the “no new data” shape

Test the section your plugin owns under each of these. A common bug is a section that’s correct for a logged-in user but throws or appears for anonymous users, or one that leaks into the cloning-admin rebuild.

snapshot-config.php is gitignored, so your staging URL and test PIDs stay on your machine.

Capture the baseline (before your change)

With your plugin in its known-good / pre-change state on staging:

php snapshot-phone-data.php capture anonymous
php snapshot-phone-data.php capture logged_in_user
php snapshot-phone-data.php capture cloning_admin

Each capture writes a normalized JSON baseline into snapshots/ and prints the top-level key count. You can list what you’ve captured:

php snapshot-phone-data.php list

Step 3 — Make your change, then diff against the baseline

Deploy your plugin change to staging, then diff:

# One fixture:
php snapshot-phone-data.php diff logged_in_user

# Or every captured fixture in one pass:
php snapshot-phone-data.php diff-all

The tool exits 0 when the response matches the baseline and 1 when it differs, so you can wire diff-all into a release check.

A clean run prints a check per fixture:

✓ logged_in_user matches baseline

A differing run prints a path-addressed diff so you can see exactly which keys moved:

✗ logged_in_user DIFFERS from baseline

  + my_plugin_widget: [3 items]
  ~ settings.some_flag: false -> true
  - left_menu.2.label: "Old Label"

Read each line:

  • + <path> — a key your change added.
  • - <path> — a key your change removed.
  • ~ <path>: a -> b — a value that changed.

The pass/fail rule: the only lines should be additions under the key your plugin owns (e.g., + my_plugin_widget: …). If you see changes anywhere else — another plugin’s section, left_menu, notifications, settings.* you didn’t set — that’s a regression. Your subscriber is reaching outside its lane.

If a diff is expected

Sometimes your change is supposed to alter a section (you fixed a value, or intentionally added a key). Confirm the diff is exactly and only what you intended, then re-capture to make it the new baseline:

php snapshot-phone-data.php capture logged_in_user

Taming false positives

The harness strips a fixed set of volatile keys (the cursor, last-modified/timing fields, and a subscription-status field) so they don’t show up as phantom diffs. If you add a new field that legitimately changes every request, add its key to VOLATILE_KEYS at the top of snapshot-phone-data.php so it stops producing noise.

Note one normalization rule that matters for your plugin: associative-array keys are sorted, but numeric/list arrays keep their order. Order is meaningful for lists like button order and tab order — so if your plugin reorders a list, the harness will (correctly) flag it.


Step 4 — Respect the cursor, then re-verify the delta path

The phone-data response is gated by a single site-wide cursor. When the client sends back a cursor that still matches the server’s, the whole response short-circuits to a no_new_data shape (“keep your cache”) and the expensive build — every mam_get_phone_data_before_send subscriber included — is skipped. The regression to watch: a subscriber that writes post, user, or term meta while assembling its section bumps the global cursor on every request, so no client’s cursor ever matches again and the cache is defeated for the entire fleet — a performance regression even when the JSON looks correct.

After your change, diff the cursor_no_new_data fixture. When the client cursor matches the server cursor, the response should collapse to the no_new_data shape — your section (and every other) should be absent, not rebuilt. If a matched-cursor request still returns a full payload, something in your change is bumping the cursor on every read.

When you do mutate data that should invalidate caches, route the write through a hook that bumps the cursor — post, post-meta, user, user-meta, and term writes bump it automatically, and saving a MAM app setting resets it — so the app knows to fetch. Then re-capture your baselines, because the delta behavior is now part of the contract you’re testing.


Step 5 — Verify it renders in the Previewer

A clean JSON diff proves you didn’t break the contract. It does not prove the app shows what you intended — that’s what the Previewer is for, and it’s the primary test surface for sibling-plugin work.

  1. Point the Previewer at your staging site.
  2. Open as each of the scenarios you snapshotted — anonymous, a logged-in user, and (if relevant) a cloning admin — so the app exercises the same fixtures the harness compared.
  3. Confirm your section appears at the right level, with the right shape, on the right screen, and that nothing you don’t own visibly changed.

Remember: server-side injection is only half the job. The mobile client must already know how to consume your key. If the JSON is correct but nothing renders, the data is shipping but the client has no handler for it yet — coordinate with the mobile team.

For adjacent surfaces, the Previewer isn’t the only signal: sent notifications and queued emails land in the Sent Messages dashboard (the notification outbox), and push delivery shows up in the APNs/FCM dashboards plus the push-result errors.


Quick reference

Command Effect
php snapshot-phone-data.php capture <fixture> Save the current normalized response as the baseline
php snapshot-phone-data.php diff <fixture> Fetch fresh, diff vs. baseline (exit 0 = same, 1 = differs)
php snapshot-phone-data.php diff-all Diff every captured fixture; exit 1 if any differ
php snapshot-phone-data.php list List captured baselines and their sizes

Limitations

  • The harness exercises a single endpoint (local_app_get_phone_data). It does not verify side effects — DB writes, queued emails, push dispatch — only the response body. Test those manually via the test app and the Sent Messages (outbox) dashboard.
  • It requires staging to be reachable; it does no database or WP-install introspection.
  • A clean diff confirms the contract, not the client rendering. Always finish in the Previewer.

  • Extending mam-main: developer guide — the sibling-plugin pattern this article tests
  • AJAX action: local_app_get_phone_data — the frozen endpoint and its response shape
  • Recipe: Inject into the mobile JSON — how to add the key you’re verifying here
  • Hook: mam_get_phone_data_before_send — the subscriber hook your plugin uses
  • Cursor cache mechanism — why your section must honor the cursor
  • Frozen public contracts reference — what you must never rename
Was this article helpful?
Contents

    Need Support?

    Can't find the answer you're looking for? Don't worry we're here to help!