Skip to content

Add lint/test tooling and CI, make GTM configurable, document bindings - #25

Merged
Herm71 merged 11 commits into
mainfrom
v1.2.0-tooling-gtm-docs
Oct 7, 2026
Merged

Herm71 merged 11 commits into
mainfrom
v1.2.0-tooling-gtm-docs

Conversation

@Herm71

@Herm71 Herm71 commented Oct 7, 2026

Copy link
Copy Markdown
Owner

Completes the v1.2.0 milestone: working lint and test tooling with CI (#14), a configurable GTM container that skips admins (#15), and the missing documentation (#16).

Closes #14
Closes #15
Closes #16

What changed

Tooling (#14)

  • phpcs.xml.dist: the WordPress standard, jc prefix, jc-core-functionality text domain and minimum_wp_version 6.5. It excludes vendor/, node_modules/ and tests/. composer lint and composer lint-fix now use it.
  • The 23 remaining PHPCS errors are fixed: docblocks, comment punctuation, $quoteTitle → $quote_title, an unused parameter, and hook registrations moved below their functions.
  • Lint tools updated (lock only): WPCS 3.4.1, PHP_CodeSniffer 3.13.6 and PHPCSUtils 1.2.3. composer audit is clean.
  • husky + lint-staged pre-commit hook: PHPCS on staged PHP, and wp-scripts format on staged JS, JSON, YAML and Markdown.
  • npm test now runs node:test, then PHPUnit. lint:php is added.
  • .prettierignore: excludes acf-json/ and CHANGELOG.md.
  • .github/workflows/ci.yml runs on every PR and on main, with three jobs:
    • php-lint (PHP 8.0): php -l on every plugin file, composer validate, composer lint.
    • tests (PHP 8.3): npm test.
    • js: lint:pkg-json, plus a check that wp-scripts format changes nothing.
  • Plugin header: adds Requires at least: 6.5 and Requires PHP: 8.0.
  • package.json: keys reordered to satisfy lint:pkg-json; no values changed.

GTM (#15): lib/functions/gtm.php

  • jc_gtm_container_id() defaults to GTM-WNP9BDSD. It can be overridden with a JC_GTM_ID constant or the jc_gtm_container_id filter. '', or anything that isn't GTM-[A-Z0-9]+, disables GTM.
  • Neither snippet is printed for users with manage_options.
  • The ID is escaped for each context: wp_json_encode() in the inline script, and add_query_arg() + esc_url() on the <noscript> iframe.

Docs (#16)

  • Settings info page:
    • It now documents jc/user-data: the key/userId arguments, what each key returns, and an example block.
    • It shows the active GTM container, or "none (disabled)", and how to change it.
  • jc_add_json_load_point(): its docblock explains that removing the theme's ACF load path is deliberate. The plugin is the only source of the content model, so a theme's leftover acf-json/ can't load duplicates or override it.
  • README, CLAUDE.md and ROADMAP: updated for all of the above.

Tests

  • GtmTest: 13 tests, with the JC_GTM_ID constant tests in separate processes.
  • SettingsPageTest: 2 more tests.
  • The bootstrap gains current_user_can(), add_query_arg(), and a wp_print_inline_script_tag() that runs the attributes filter like core does.

Why

PHP versions: PHPUnit 12 needs PHP 8.3, but the live server runs 8.0, so CI tests on 8.3 and checks that the plugin's own files parse on 8.0. The php-lint job installs dev tools with --ignore-platform-req=php; PHPUnit is never run there.

How to test

composer install && npm ci
npm test                # 27 node + 57 PHPUnit, all pass
composer lint           # exits 0
composer audit          # no advisories
npm run format && git diff --exit-code   # tree unchanged

CI: this PR is the workflow's first run. All three jobs should pass.

GTM regression check: to see the new GTM tests catch the old code, run git show main:lib/functions/gtm.php > lib/functions/gtm.php && composer test. Expect 10 GTM failures/errors; restore with git checkout lib/functions/gtm.php. Against main's gtm.php, settings.php and general.php together, 12 of the new tests fail or error.

In WordPress, after deploying:

  • Logged out, view the page source. Both GTM snippets should be present with GTM-WNP9BDSD, and the inline loader should carry a nonce.
  • Logged in as an administrator, neither snippet should appear.
  • Settings → Jason Chafin Custom Functionality Info should show both block-binding examples as text, and the active container.

Breaking changes

  • Admin visits: they're no longer sent to GA4, so reported traffic may drop slightly. That's intended.
  • New minimum versions: WordPress refuses to activate the plugin below WordPress 6.5 or PHP 8.0. The live site runs WordPress 7.1 and PHP 8.0.30.
  • Pre-commit hook: npm install now installs it. Commits with badly formatted PHP fail locally until fixed, and git commit --no-verify bypasses it.

Migration steps

  • None required. The GTM default is the live container.
  • Optional: on staging, add define( 'JC_GTM_ID', '' ); to wp-config.php to keep test traffic out of analytics.

🤖 Generated with Claude Code

Herm71 and others added 10 commits October 7, 2026 11:20
composer audit flagged high-severity advisories in squizlabs/
php_codesniffer, phpcsstandards/phpcsutils and wp-coding-standards/wpcs.
All three are dev-only and never shipped in the release zip, but they
run on every lint, locally and soon in CI.

Lock-only update within the existing constraints: WPCS 3.4.1,
PHP_CodeSniffer 3.13.6, PHPCSUtils 1.2.3. composer audit is now clean.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
composer lint ran bare phpcs with no ruleset and no path, so it exited 3
without checking anything. phpcs.xml.dist selects the WordPress
standard, the jc prefix and the jc-core-functionality text domain, and
excludes vendor/, node_modules/ and tests/ (the PHPUnit bootstrap stubs
core functions by their real, unprefixed names).

Only the prefix-length check is disabled: renaming every function and
the public jc_csp_* filters to satisfy a 3-character minimum would break
existing hooks.

Declares Requires at least 6.5 (block bindings, Requires Plugins and
script modules all need it) and Requires PHP 8.0 (the live server), so
WordPress refuses to activate on anything older and the ruleset's
minimum_wp_version has a source. lint and lint-fix now resolve phpcs
and phpcbf from vendor/bin through Composer.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The 23 errors left after the phpcbf pass needed judgment rather than
auto-fixing: function docblocks with @param/@return, sentence-case
inline comments, $quoteTitle -> $quote_title, and the plugin header's
@Package tag.

jc_add_json_save_point() drops its unused $path parameter; WordPress
still passes it, and PHP ignores extra arguments to userland callbacks.
Hook registrations move below the functions they register, which is
where WPCS expects them; they still run at file load, so behavior is
unchanged.

composer lint now exits 0.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Brings the two test files that drifted in line with wp-scripts format,
so the CI formatting check starts from a clean tree.

acf-json/ is ignored: ACF rewrites those files in its own layout every
time a field group or post type is saved in the admin, so reformatting
them would be undone on the next save and then fail CI. CHANGELOG.md is
generated by commit-and-tag-version and is ignored for the same reason.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
npm test ran lint-staged, which had no config, so it did nothing and
passed. It now runs both suites (node:test, then PHPUnit). lint:php
is added alongside the existing JS and package.json linters.

husky installs a pre-commit hook on npm install (prepare) that runs
lint-staged: phpcs on staged PHP, wp-scripts format on staged JS,
JSON, YAML and Markdown. Same tools CI runs, so the hook and CI cannot
disagree. Matches rcid-core-functionality.

package.json keys are reordered to satisfy lint:pkg-json, which CI
will run; no values change.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
Nothing checked a change before it was tagged: release.yml only runs on
a v*.*.* tag. CI now runs on every pull request and on main:

- php-lint on PHP 8.0, the plugin's minimum and the live server's
  version: php -l on every plugin file, composer validate, composer
  lint. PHPUnit 12 needs 8.3, so this job relaxes the platform check
  to install the dev tools; PHPUnit is never run here.
- tests on PHP 8.3: npm test (node:test, then PHPUnit).
- js: lint:pkg-json, and wp-scripts format must leave the tree
  unchanged, the same command the pre-commit hook runs.

Based on rcid-core-functionality's lint.yml, plus the tests job.

Closes the optional CI item in #14.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The container ID was hard-coded twice and GTM loaded for everyone,
including the site owner, so every admin visit was counted in
analytics. Staging had no way to turn it off.

jc_gtm_container_id() defaults to the live container (GTM-WNP9BDSD),
so updating changes nothing on the live site. Override it with a
JC_GTM_ID constant in wp-config.php or the jc_gtm_container_id
filter; '' disables GTM. Anything that is not a GTM-[A-Z0-9]+ ID is
treated as '', so a typo turns GTM off rather than printing an
arbitrary string into a script.

Users with manage_options get neither snippet. The ID is escaped for
each context: wp_json_encode() in the inline script, esc_url() on the
noscript iframe.

Fixes #15

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
The info page documented only jc/copyright. It now covers jc/user-data
(its key and userId arguments, what each key returns, and an example
block), and shows the active GTM container, that admins are skipped,
and how to change or disable it with JC_GTM_ID.

jc_add_json_load_point() removed ACF's theme load path behind a comment
reading "(optional)". It is deliberate: the plugin is the single source
of the content model, so a theme's leftover acf-json/ must not load
duplicates or overrides. The docblock now says so, and how to add a
theme path without undoing it.

Fixes #16

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
GtmTest pins the behavior #15 changed:

- the default stays the live container, so updating changes nothing
- the loader goes through the script API and gets the CSP nonce
- administrators get neither snippet; other logged-in users still do
- the filter and the JC_GTM_ID constant override the ID (the constant
  tests run in separate processes, since a constant cannot be unset)
- '' and invalid IDs, including script-breakout and markup payloads,
  print nothing at all

SettingsPageTest now checks the jc/user-data example (#16) and the GTM
line, including its disabled state.

Against main, 12 of these fail or error.

Refs #15, #16

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
README gains the JC_GTM_ID / filter configuration and the admin skip,
and a Development section covering npm test, composer lint, the
pre-commit hook and what CI checks. CLAUDE.md drops the stale "no
phpcs.xml" and "npm test does nothing" notes, records the PHP 8.0 vs
PHPUnit 8.3 split, and the deliberate ACF load-path removal.
ROADMAP ticks sections 5 and 6.

Refs #14, #15, #16

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Herm71 Herm71 added documentation Improvements or additions to documentation enhancement New feature or request tooling Build, lint, CI, release tooling labels Oct 7, 2026
node --test only understands glob patterns from Node 21; Node 20, which
CI uses, read "tests/**/*.test.js" as a literal path and failed with
"Could not find". The test files all sit directly in tests/, so an
unquoted tests/*.test.js, expanded by the shell, works on any Node.

Refs #14

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
@Herm71
Herm71 merged commit cddf46f into main Oct 7, 2026
3 checks passed
@Herm71
Herm71 deleted the v1.2.0-tooling-gtm-docs branch October 7, 2026 18:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation enhancement New feature or request tooling Build, lint, CI, release tooling

Projects

None yet

1 participant