From 4182e7444a68af19456a509f1a3faa62223fad1c Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 14:12:56 +1000
Subject: [PATCH 01/45] chore(phpunit): instrument CI with JUnit + coverage
artifacts, record baseline
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 0 of the PHPUnit test refactor — set up measurement infrastructure
so subsequent phases (structure normalization, factory adoption, coverage
1:1 mirror) can be diffed against a real baseline rather than the stale
.phpunit.result.cache.
Workflow: every PHPUnit invocation now passes --log-junit, and two
actions/upload-artifact@v6 steps upload the JUnit XML (every matrix cell)
and the Clover XML (coverage cell). Existing codecov upload preserved.
Baseline doc (tests/phpunit/COVERAGE_BASELINE.md) records live numbers
from wp-env:integration: 1119 tests · 4088 assertions · 41.69s wall,
76.33% overall line coverage. Includes the per-src/-subdir breakdown,
the "9 AJAX tests = 69% of total runtime" finding that informs phase 2,
and a methodology section so future phases can reproduce the numbers.
Tool (tools/phpunit/coverage-baseline.py) regenerates the per-subdir
table from any Clover XML for regression checking.
Two side-discoveries documented for a Phase 4 follow-up:
- CI coverage cell uses --xdebug=debug, but coverage requires
xdebug.mode=coverage — codecov upload has likely been receiving no data.
- yarn test:php --coverage-clover fails under Xdebug 3 with a
"RecursiveDirectoryIterator on src/templates" error; direct
vendor/bin/phpunit works. Workaround documented.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
.github/workflows/phpunit.tests.yml | 22 +++-
tests/phpunit/COVERAGE_BASELINE.md | 194 ++++++++++++++++++++++++++++
tools/phpunit/coverage-baseline.py | 70 ++++++++++
3 files changed, 282 insertions(+), 4 deletions(-)
create mode 100644 tests/phpunit/COVERAGE_BASELINE.md
create mode 100644 tools/phpunit/coverage-baseline.py
diff --git a/.github/workflows/phpunit.tests.yml b/.github/workflows/phpunit.tests.yml
index 9424ecab3..3b7f3e045 100644
--- a/.github/workflows/phpunit.tests.yml
+++ b/.github/workflows/phpunit.tests.yml
@@ -115,19 +115,33 @@ jobs:
- name: Run PHPUnit tests
if: ${{ ! matrix.report }}
run: |
- yarn test:php --do-not-cache-result --verbose
+ yarn test:php --do-not-cache-result --verbose --log-junit=/var/www/html/wp-content/plugins/gravity-pdf/tmp/junit/phpunit-integration-php${{ matrix.php }}.xml
# Multisite suite reuses the already-running php 8.3 container instead of
# spinning up its own matrix cell + cold wp-env start.
- name: Run Multisite PHPUnit tests
if: ${{ matrix.php == '8.3' && ! matrix.report }}
run: |
- yarn test:php:multisite --verbose
+ yarn test:php:multisite --verbose --log-junit=/var/www/html/wp-content/plugins/gravity-pdf/tmp/junit/phpunit-multisite-php${{ matrix.php }}.xml
- name: Generate Code Coverage Report for PHP
if: ${{ matrix.report }}
- run: |
- yarn test:php --do-not-cache-result --verbose --coverage-clover=/var/www/html/wp-content/plugins/gravity-pdf/tmp/coverage/report-xml/php-coverage1.xml
+ run: |
+ yarn test:php --do-not-cache-result --verbose --log-junit=/var/www/html/wp-content/plugins/gravity-pdf/tmp/junit/phpunit-coverage-php${{ matrix.php }}.xml --coverage-clover=/var/www/html/wp-content/plugins/gravity-pdf/tmp/coverage/report-xml/php-coverage1.xml
+
+ - name: Upload PHPUnit JUnit timings
+ uses: actions/upload-artifact@v6
+ if: always()
+ with:
+ name: phpunit-junit-php${{ matrix.php }}${{ matrix.report && '-coverage' || '' }}
+ path: tmp/junit/*.xml
+
+ - name: Upload PHPUnit coverage XML
+ uses: actions/upload-artifact@v6
+ if: ${{ matrix.report && always() }}
+ with:
+ name: phpunit-coverage-clover
+ path: tmp/coverage/report-xml/*.xml
- name: Code Coverage Upload
uses: codecov/codecov-action@v6
diff --git a/tests/phpunit/COVERAGE_BASELINE.md b/tests/phpunit/COVERAGE_BASELINE.md
new file mode 100644
index 000000000..6a111d3dc
--- /dev/null
+++ b/tests/phpunit/COVERAGE_BASELINE.md
@@ -0,0 +1,194 @@
+# PHPUnit Test Suite Baseline
+
+Captured: 2026-05-25 (Phase 0 of [`.claude/plans/2026-05-25-phpunit-tests-refactor.md`](../../.claude/plans/2026-05-25-phpunit-tests-refactor.md)).
+
+This file is the reference point every later phase compares against. Treat the numbers below as the line that must not regress (test count, coverage), and the runtime as the budget later phases should match within ±10%.
+
+## Runtime baseline (live wp-env:integration, no xdebug, no coverage)
+
+| Metric | Value |
+| --- | --- |
+| Test count | **1119** |
+| Assertions | **4088** |
+| Skipped | 8 (multisite-only tests, expected) |
+| Sum of test-case times | **38.46s** |
+| Wall-clock (incl. PHPUnit boot) | **41.69s** |
+| Wrapper-inclusive (yarn + docker exec) | 42.63s |
+
+Slowest 15 tests (live JUnit, `tmp/junit/phpunit-integration.xml`):
+
+| Time | Test |
+| ---: | :--- |
+| 4.032s | `Test_PDF_Ajax::test_ajax_process_uploaded_template` |
+| 3.570s | `Test_PDF_Ajax::test_render_template_fields` |
+| 3.428s | `Test_PDF_Ajax::test_delete_gf_pdf_setting` |
+| 3.161s | `Test_PDF_Ajax::test_ajax_process_license_deactivation` |
+| 3.153s | `Test_PDF_Ajax::test_ajax_process_build_template_options_html` |
+| 3.125s | `Test_PDF_Ajax::test_duplicate_gf_pdf_settings` |
+| 3.120s | `Test_PDF_Ajax::test_ajax_save_core_font` |
+| 3.098s | `Test_PDF_Ajax::test_ajax_process_delete_template` |
+| 1.366s | `Test_EDD_SL_Plugin_Updater::test_check_update_already_exists` |
+| 1.192s | `Test_Request::test_send_request_status_error` |
+| 1.008s | `Test_Url_Signer::test_expiration_failure` (intentional `sleep(1)`) |
+| 0.766s | `Test_Request::test_send_request_success` |
+| 0.641s | `Test_Slow_PDF_Processes::test_process_legacy_pdf_endpoint` |
+| 0.496s | `Test_Slow_PDF_Processes::test_process_pdf_endpoint` |
+| 0.455s | `Test_Slow_PDF_Processes::test_generate_and_save_pdf` |
+
+**Headline finding**: the 9 `Test_PDF_Ajax` tests account for **26.70s — 69% of total suite runtime**. Phase 2's split of `test-ajax.php` into focused `*_Ajax.php` files (one per target Model) is the highest-leverage perf work in the refactor.
+
+## Per-namespace runtime breakdown
+
+| Namespace | Time | Tests | Avg/test |
+| :--- | ---: | ---: | ---: |
+| `GFPDF\Tests\` (root `test-*.php`) | 33.58s | 753 | 44.6ms |
+| `GFPDF\Helper\` | 3.90s | 102 | 38.2ms |
+| `GFPDF\Controller\` | 0.81s | 31 | 26.0ms |
+| `GFPDF\Model\` | 0.15s | 138 | 1.1ms |
+| `GFPDF\Statics\` | 0.03s | 90 | 0.3ms |
+| `GFPDF\View\` | 0.00s | 5 | 0.3ms |
+
+`Helper/` subdivision:
+
+| Sub-namespace | Time | Tests |
+| :--- | ---: | ---: |
+| `Helper/Mpdf` | 2.13s | 8 |
+| `Helper/Licensing` | 1.42s | 19 |
+| `Helper/Fields` | 0.33s | 49 |
+| `Helper/Log` | 0.01s | 20 |
+| `Helper/(top-level)` | 0.01s | 5 |
+| `Helper/Fonts` | 0.00s | 1 |
+
+Heaviest cross-cutting files (root `test-*.php`, sorted by total time):
+
+| File | Time | Tests |
+| :--- | ---: | ---: |
+| `Test_PDF_Ajax` | 26.70s | 9 |
+| `Test_Slow_PDF_Processes` | 3.76s | 18 |
+| `Test_Url_Signer` | 1.02s | 23 |
+| `Test_Rest_Form_Settings` | 0.64s | 31 |
+| `Test_PDF` | 0.24s | 75 |
+| `Test_Options_API` | 0.11s | 81 |
+| `Test_Helper_Misc` | 0.03s | 73 |
+
+(Full file-level breakdown reproducible from `tmp/junit/phpunit-integration.xml` via the methodology section below.)
+
+## Multisite runtime baseline
+
+Source: `tmp/junit/phpunit-multisite.xml`, captured via `yarn test:php:multisite`.
+
+| Metric | Value |
+| --- | --- |
+| Test count | **1119** (same surface, different bootstrap) |
+| Assertions | 4119 |
+| Skipped | 1 (non-multisite-only test, expected) |
+| Sum of test-case times | **38.64s** |
+| Wall-clock | 41.81s |
+
+## Coverage baseline (live wp-env:integration, xdebug coverage mode, PHP 8.5)
+
+Source: `tmp/coverage/report-xml/baseline.xml` (Clover format, 845 KB, 208 files).
+
+| `src/` subdirectory | Files | Statements covered / total | Line coverage | Phase 4 priority |
+| :--- | ---: | :--- | ---: | :--- |
+| `src/Rest/` | 2 | 546 / 588 | **92.86%** | Already strong — no new work |
+| `src/Helper/` (top level) | 39 | 3368 / 4026 | **83.66%** | Mixed — abstracts critical, fill targeted gaps |
+| `src/Statics/` | 4 | 287 / 347 | **82.71%** | Fill `Debug.php`, `Queue_Callbacks.php` |
+| `src/Helper/Fonts/` | 5 | 31 / 38 | **81.58%** | Already strong |
+| `src/Controller/` | 19 | 896 / 1100 | **81.45%** | High — 11 of 19 still without dedicated tests |
+| `src/Model/` | 11 | 1938 / 2385 | **81.26%** | High — `Model_PDF` characterization (per plan §"Critical-class characterization tests") |
+| `src/Helper/Mpdf/` | 3 | 33 / 44 | **75.00%** | Low — small surface |
+| `src/Helper/Fields/` | 60 | 1296 / 1784 | **72.65%** | High — sparse coverage across ~60 field handlers |
+| `src/Helper/Log/` | 3 | 118 / 170 | **69.41%** | Medium |
+| `src/View/` | 35 | 646 / 1052 | **61.41%** | Out of scope — mostly HTML partials |
+| `src/Helper/Licensing/` | 1 | 168 / 298 | **56.38%** | Medium — large untested branches |
+| `src/` root (`bootstrap.php`, `autoload.php`, `deprecated.php`) | 3 | 290 / 557 | **52.06%** | Low — bootstrap has activation paths hard to cover |
+| `src/templates/` | 9 | 97 / 307 | **31.60%** | Out of scope — PDF templates, not code-under-test |
+| `src/Exceptions/` | 11 | 5 / 22 | **22.73%** | Per-plan single hierarchy smoke test |
+| Plugin root (`pdf.php`, `api.php`, `gravity-pdf-updater.php`) | 3 | 209 / 289 | **72.32%** | Mixed |
+| **OVERALL** | **208** | **9928 / 13007** | **76.33%** | — |
+
+The **76.33%** overall is the CI gate that Phase 4 introduces: `coverage ≥ 76.33%` per PR, ratcheted upward quarterly.
+
+Coverage runtime overhead is modest in xdebug `coverage` mode: 47s (vs 38s without) — only ~24% slower.
+
+> **Important methodology note** — `yarn test:php --coverage-clover=...` consistently fails on this codebase with `RecursiveDirectoryIterator::__construct(.../src/templates): Failed to open directory` when invoked through the yarn wrapper, even when `src/templates/` exists and is readable. **Invoking `vendor/bin/phpunit` directly inside the container works.** Suspected cause is a working-directory resolution quirk in PHPUnit 9.6 + Xdebug 3 coverage when the config path is absolute. This affects the CI workflow's coverage cell too (`.github/workflows/phpunit.tests.yml`); the Phase 4 CI coverage gate will need to switch the coverage step to the direct-phpunit form documented below. Filed as a follow-up for Phase 4.
+
+## Playwright (e2e) baseline
+
+| Metric | Value (local 2026-05-25) |
+| --- | --- |
+| Passed | 6 |
+| Failed | 60 |
+| Did not run | 21 |
+| Wall-clock | 8.4m |
+
+The local run is **not green**, but the failures look environmental (post-`dist/` rebuild, fresh wp-env, etc.) rather than refactor-driven — the PHPUnit refactor has not touched any code yet. For the authoritative Playwright baseline, **compare future PRs against the GitHub Actions Playwright workflow artifacts** (`.github/workflows/playwright-e2e.yml`) on the same commit, not the local run.
+
+This is recorded only so a contributor on a fresh machine doesn't conclude their environment is broken when they see the same numbers. Phase 2 changes touch fixture bootstrap; before merging Phase 2 verify Playwright in CI matches the pre-refactor CI baseline.
+
+## Methodology — how to reproduce
+
+The numbers above are captured from a **live** wp-env run, not from `.phpunit.result.cache`. The cache file is stale almost the moment it lands; for regression detection, always re-run.
+
+Boot the environments once per session:
+
+```bash
+yarn wp-env:integration start # port 8701, used by yarn test:php
+yarn wp-env:e2e start # port 8702, used by yarn test:e2e
+```
+
+### Runtime + JUnit
+
+```bash
+yarn test:php \
+ --do-not-cache-result \
+ --verbose \
+ --log-junit=/var/www/html/wp-content/plugins/gravity-pdf/tmp/junit/phpunit-integration.xml
+```
+
+The Docker container mounts the plugin directory, so `tmp/junit/phpunit-integration.xml` appears on the host. Parse it with `xml.etree.ElementTree` to recompute the per-namespace and slowest-test tables above.
+
+### Multisite
+
+```bash
+yarn test:php:multisite \
+ --verbose \
+ --log-junit=/var/www/html/wp-content/plugins/gravity-pdf/tmp/junit/phpunit-multisite.xml
+```
+
+### Coverage (requires xdebug `coverage` mode — restart wp-env first)
+
+```bash
+# Note: --xdebug=debug is NOT enough; coverage needs xdebug.mode=coverage.
+yarn wp-env:integration start --xdebug=coverage
+
+# The yarn wrapper produces a "RecursiveDirectoryIterator on src/templates"
+# failure under xdebug 3 coverage — invoke phpunit directly:
+yarn wp-env:integration run wordpress bash -c '
+ cd /var/www/html/wp-content/plugins/gravity-pdf &&
+ vendor/bin/phpunit \
+ -c tools/phpunit/config.xml \
+ --do-not-cache-result \
+ --coverage-clover=tmp/coverage/report-xml/baseline.xml \
+ --log-junit=tmp/junit/phpunit-coverage.xml
+'
+```
+
+Per-`src/`-subdir coverage is extracted from the Clover XML — every `` element has a `name` attribute (the full absolute path, despite the attribute name) and a `` child with `statements`/`coveredstatements`/`elements`/`coveredelements`. Group by the first path segment under `src/`, with `Helper/` broken out one level deeper. Run `tools/phpunit/coverage-baseline.py` to regenerate the per-subdir table above from the Clover XML.
+
+### Playwright
+
+```bash
+yarn test:e2e
+```
+
+### Comparing a future run against this baseline
+
+After running any of the above, diff the new JUnit/clover against the artifacts uploaded by CI (workflow `.github/workflows/phpunit.tests.yml`, artifacts `phpunit-junit-php8.3` and `phpunit-coverage-clover`). A regression is:
+
+- Runtime: more than +10% on the sum of test-case times.
+- Test count: any decrease (a moved test is still a test).
+- Coverage: any decrease in overall line coverage, or any decrease in per-`src/`-subdir coverage by more than 1 percentage point.
+
+If a phase intentionally trades runtime for clarity (e.g., splitting `test-ajax.php` adds per-class setup overhead), record the new baseline here in a "phase N revision" section rather than relaxing the gate.
diff --git a/tools/phpunit/coverage-baseline.py b/tools/phpunit/coverage-baseline.py
new file mode 100644
index 000000000..502d8c41a
--- /dev/null
+++ b/tools/phpunit/coverage-baseline.py
@@ -0,0 +1,70 @@
+#!/usr/bin/env python3
+"""Print a per-`src/`-subdir coverage breakdown from a PHPUnit Clover XML.
+
+Usage: python3 tools/phpunit/coverage-baseline.py [path/to/clover.xml]
+
+Default path: tmp/coverage/report-xml/baseline.xml. See
+tests/phpunit/COVERAGE_BASELINE.md for how to produce the input XML.
+"""
+
+import sys
+import xml.etree.ElementTree as ET
+from collections import defaultdict
+
+
+def bucket_for(path):
+ if path.endswith(('/pdf.php', '/api.php', '/gravity-pdf-updater.php')):
+ return 'Plugin root'
+ if '/src/' not in path:
+ return None
+ rel = path.split('/src/', 1)[1]
+ parts = rel.split('/')
+ if len(parts) == 1:
+ return 'src/ root'
+ if parts[0] == 'Helper' and len(parts) >= 3:
+ return f'Helper/{parts[1]}'
+ return parts[0]
+
+
+def main(xml_path):
+ tree = ET.parse(xml_path)
+ buckets = defaultdict(
+ lambda: {'st': 0, 'covst': 0, 'el': 0, 'covel': 0, 'files': 0}
+ )
+ overall = {'st': 0, 'covst': 0, 'el': 0, 'covel': 0}
+
+ for f in tree.iter('file'):
+ bucket = bucket_for(f.get('name', ''))
+ if bucket is None:
+ continue
+ m = next((c for c in f if c.tag == 'metrics'), None)
+ if m is None:
+ continue
+ st, covst = int(m.get('statements', '0')), int(m.get('coveredstatements', '0'))
+ el, covel = int(m.get('elements', '0')), int(m.get('coveredelements', '0'))
+ b = buckets[bucket]
+ b['st'] += st; b['covst'] += covst; b['el'] += el; b['covel'] += covel
+ b['files'] += 1
+ overall['st'] += st; overall['covst'] += covst
+ overall['el'] += el; overall['covel'] += covel
+
+ def pct(c, t):
+ return (c / t * 100) if t else 0.0
+
+ print(f"{'Bucket':28s} {'Files':>5s} {'Stmts':>11s} {'Stmt %':>7s} {'Elem %':>7s}")
+ print('-' * 70)
+ for b in sorted(buckets):
+ d = buckets[b]
+ print(f"{b:28s} {d['files']:5d} {d['covst']:5d}/{d['st']:<5d} "
+ f"{pct(d['covst'], d['st']):6.2f}% {pct(d['covel'], d['el']):6.2f}%")
+ print('-' * 70)
+ files_total = sum(b['files'] for b in buckets.values())
+ print(f"{'OVERALL':28s} {files_total:5d} "
+ f"{overall['covst']:5d}/{overall['st']:<5d} "
+ f"{pct(overall['covst'], overall['st']):6.2f}% "
+ f"{pct(overall['covel'], overall['el']):6.2f}%")
+ return 0
+
+
+if __name__ == '__main__':
+ sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else 'tmp/coverage/report-xml/baseline.xml'))
From 0e715208b71f47c5df80087d77dafe6e01ae872c Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 14:21:17 +1000
Subject: [PATCH 02/45] chore(phpunit): port coverage-baseline script from
Python to PHP
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Phase 0 commit (4182e744) shipped the baseline regenerator as Python,
which introduced a foreign runtime dependency for tooling that lives
inside a PHP project — `tools/phpunit/` already houses PHP (factory,
bootstrap, wp-tests-config). SimpleXML covers the parsing 1:1, so there's
no reason to require python3 just to read a Clover XML.
Output verified identical to the Python original against the same
baseline.xml: 208 files, 9928/13007 statements, 76.33% — every per-bucket
row matches the table already recorded in COVERAGE_BASELINE.md.
Updated the methodology section to invoke `php tools/phpunit/coverage-baseline.php`.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
tests/phpunit/COVERAGE_BASELINE.md | 2 +-
tools/phpunit/coverage-baseline.php | 96 +++++++++++++++++++++++++++++
tools/phpunit/coverage-baseline.py | 70 ---------------------
3 files changed, 97 insertions(+), 71 deletions(-)
create mode 100644 tools/phpunit/coverage-baseline.php
delete mode 100644 tools/phpunit/coverage-baseline.py
diff --git a/tests/phpunit/COVERAGE_BASELINE.md b/tests/phpunit/COVERAGE_BASELINE.md
index 6a111d3dc..a53f55615 100644
--- a/tests/phpunit/COVERAGE_BASELINE.md
+++ b/tests/phpunit/COVERAGE_BASELINE.md
@@ -175,7 +175,7 @@ yarn wp-env:integration run wordpress bash -c '
'
```
-Per-`src/`-subdir coverage is extracted from the Clover XML — every `` element has a `name` attribute (the full absolute path, despite the attribute name) and a `` child with `statements`/`coveredstatements`/`elements`/`coveredelements`. Group by the first path segment under `src/`, with `Helper/` broken out one level deeper. Run `tools/phpunit/coverage-baseline.py` to regenerate the per-subdir table above from the Clover XML.
+Per-`src/`-subdir coverage is extracted from the Clover XML — every `` element has a `name` attribute (the full absolute path, despite the attribute name) and a `` child with `statements`/`coveredstatements`/`elements`/`coveredelements`. Group by the first path segment under `src/`, with `Helper/` broken out one level deeper. Run `php tools/phpunit/coverage-baseline.php` to regenerate the per-subdir table above from the Clover XML.
### Playwright
diff --git a/tools/phpunit/coverage-baseline.php b/tools/phpunit/coverage-baseline.php
new file mode 100644
index 000000000..1875b352b
--- /dev/null
+++ b/tools/phpunit/coverage-baseline.php
@@ -0,0 +1,96 @@
+= 3 ) {
+ return 'Helper/' . $parts[1];
+ }
+ return $parts[0];
+}
+
+$xml_path = $argv[1] ?? 'tmp/coverage/report-xml/baseline.xml';
+$xml = simplexml_load_file( $xml_path );
+if ( false === $xml ) {
+ fwrite( STDERR, "Failed to parse $xml_path\n" );
+ exit( 1 );
+}
+
+$buckets = [];
+$overall = [ 'st' => 0, 'covst' => 0, 'el' => 0, 'covel' => 0 ];
+
+foreach ( $xml->xpath( '//file' ) as $f ) {
+ $bucket = bucket_for( (string) $f['name'] );
+ if ( null === $bucket || ! isset( $f->metrics ) ) {
+ continue;
+ }
+ $m = $f->metrics;
+ $st = (int) $m['statements'];
+ $covst = (int) $m['coveredstatements'];
+ $el = (int) $m['elements'];
+ $covel = (int) $m['coveredelements'];
+
+ if ( ! isset( $buckets[ $bucket ] ) ) {
+ $buckets[ $bucket ] = [ 'st' => 0, 'covst' => 0, 'el' => 0, 'covel' => 0, 'files' => 0 ];
+ }
+ $buckets[ $bucket ]['st'] += $st;
+ $buckets[ $bucket ]['covst'] += $covst;
+ $buckets[ $bucket ]['el'] += $el;
+ $buckets[ $bucket ]['covel'] += $covel;
+ $buckets[ $bucket ]['files']++;
+
+ $overall['st'] += $st;
+ $overall['covst'] += $covst;
+ $overall['el'] += $el;
+ $overall['covel'] += $covel;
+}
+
+$pct = function ( $c, $t ) {
+ return $t ? ( $c / $t * 100 ) : 0.0;
+};
+
+ksort( $buckets );
+
+printf( "%-28s %5s %11s %7s %7s\n", 'Bucket', 'Files', 'Stmts', 'Stmt %', 'Elem %' );
+echo str_repeat( '-', 70 ) . "\n";
+foreach ( $buckets as $name => $d ) {
+ printf(
+ "%-28s %5d %5d/%-5d %6.2f%% %6.2f%%\n",
+ $name,
+ $d['files'],
+ $d['covst'],
+ $d['st'],
+ $pct( $d['covst'], $d['st'] ),
+ $pct( $d['covel'], $d['el'] )
+ );
+}
+echo str_repeat( '-', 70 ) . "\n";
+$files_total = array_sum( array_column( $buckets, 'files' ) );
+printf(
+ "%-28s %5d %5d/%-5d %6.2f%% %6.2f%%\n",
+ 'OVERALL',
+ $files_total,
+ $overall['covst'],
+ $overall['st'],
+ $pct( $overall['covst'], $overall['st'] ),
+ $pct( $overall['covel'], $overall['el'] )
+);
diff --git a/tools/phpunit/coverage-baseline.py b/tools/phpunit/coverage-baseline.py
deleted file mode 100644
index 502d8c41a..000000000
--- a/tools/phpunit/coverage-baseline.py
+++ /dev/null
@@ -1,70 +0,0 @@
-#!/usr/bin/env python3
-"""Print a per-`src/`-subdir coverage breakdown from a PHPUnit Clover XML.
-
-Usage: python3 tools/phpunit/coverage-baseline.py [path/to/clover.xml]
-
-Default path: tmp/coverage/report-xml/baseline.xml. See
-tests/phpunit/COVERAGE_BASELINE.md for how to produce the input XML.
-"""
-
-import sys
-import xml.etree.ElementTree as ET
-from collections import defaultdict
-
-
-def bucket_for(path):
- if path.endswith(('/pdf.php', '/api.php', '/gravity-pdf-updater.php')):
- return 'Plugin root'
- if '/src/' not in path:
- return None
- rel = path.split('/src/', 1)[1]
- parts = rel.split('/')
- if len(parts) == 1:
- return 'src/ root'
- if parts[0] == 'Helper' and len(parts) >= 3:
- return f'Helper/{parts[1]}'
- return parts[0]
-
-
-def main(xml_path):
- tree = ET.parse(xml_path)
- buckets = defaultdict(
- lambda: {'st': 0, 'covst': 0, 'el': 0, 'covel': 0, 'files': 0}
- )
- overall = {'st': 0, 'covst': 0, 'el': 0, 'covel': 0}
-
- for f in tree.iter('file'):
- bucket = bucket_for(f.get('name', ''))
- if bucket is None:
- continue
- m = next((c for c in f if c.tag == 'metrics'), None)
- if m is None:
- continue
- st, covst = int(m.get('statements', '0')), int(m.get('coveredstatements', '0'))
- el, covel = int(m.get('elements', '0')), int(m.get('coveredelements', '0'))
- b = buckets[bucket]
- b['st'] += st; b['covst'] += covst; b['el'] += el; b['covel'] += covel
- b['files'] += 1
- overall['st'] += st; overall['covst'] += covst
- overall['el'] += el; overall['covel'] += covel
-
- def pct(c, t):
- return (c / t * 100) if t else 0.0
-
- print(f"{'Bucket':28s} {'Files':>5s} {'Stmts':>11s} {'Stmt %':>7s} {'Elem %':>7s}")
- print('-' * 70)
- for b in sorted(buckets):
- d = buckets[b]
- print(f"{b:28s} {d['files']:5d} {d['covst']:5d}/{d['st']:<5d} "
- f"{pct(d['covst'], d['st']):6.2f}% {pct(d['covel'], d['el']):6.2f}%")
- print('-' * 70)
- files_total = sum(b['files'] for b in buckets.values())
- print(f"{'OVERALL':28s} {files_total:5d} "
- f"{overall['covst']:5d}/{overall['st']:<5d} "
- f"{pct(overall['covst'], overall['st']):6.2f}% "
- f"{pct(overall['covel'], overall['el']):6.2f}%")
- return 0
-
-
-if __name__ == '__main__':
- sys.exit(main(sys.argv[1] if len(sys.argv) > 1 else 'tmp/coverage/report-xml/baseline.xml'))
From e1ce7a6c29b0fbc40d3d4fa343f08256d5eab275 Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 14:26:09 +1000
Subject: [PATCH 03/45] docs(phpunit): refresh Playwright baseline after
building dist/ + vendor
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
The Phase 0 baseline captured Playwright in a 6/60/21 broken state because
`dist/` and `vendor/` weren't built locally — without those Gravity PDF
can't boot in wp-env, so most specs failed at fixture/login. Re-ran with
both built: 87 passed / 0 failed / 0 didn't run in 2.2m (135s).
Also noted that local wall-clock varies and CI's sharded workflow
(`.github/workflows/playwright-e2e.yml`, `--shard=N/4`) is the right
runtime comparator for Phase 2.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
tests/phpunit/COVERAGE_BASELINE.md | 14 +++++++-------
1 file changed, 7 insertions(+), 7 deletions(-)
diff --git a/tests/phpunit/COVERAGE_BASELINE.md b/tests/phpunit/COVERAGE_BASELINE.md
index a53f55615..7bf655c72 100644
--- a/tests/phpunit/COVERAGE_BASELINE.md
+++ b/tests/phpunit/COVERAGE_BASELINE.md
@@ -116,16 +116,16 @@ Coverage runtime overhead is modest in xdebug `coverage` mode: 47s (vs 38s witho
## Playwright (e2e) baseline
-| Metric | Value (local 2026-05-25) |
+| Metric | Value (local 2026-05-25, post `yarn build` + `composer install`) |
| --- | --- |
-| Passed | 6 |
-| Failed | 60 |
-| Did not run | 21 |
-| Wall-clock | 8.4m |
+| Passed | 87 |
+| Failed | 0 |
+| Did not run | 0 |
+| Wall-clock | 2.2m (135s) |
-The local run is **not green**, but the failures look environmental (post-`dist/` rebuild, fresh wp-env, etc.) rather than refactor-driven — the PHPUnit refactor has not touched any code yet. For the authoritative Playwright baseline, **compare future PRs against the GitHub Actions Playwright workflow artifacts** (`.github/workflows/playwright-e2e.yml`) on the same commit, not the local run.
+Local run is fully green. The earlier captured run (6/60/21 in 8.4m) failed because `dist/` and `vendor/` were not built — Playwright requires both before the plugin will boot.
-This is recorded only so a contributor on a fresh machine doesn't conclude their environment is broken when they see the same numbers. Phase 2 changes touch fixture bootstrap; before merging Phase 2 verify Playwright in CI matches the pre-refactor CI baseline.
+For runtime regression detection prefer the sharded GitHub Actions Playwright workflow (`.github/workflows/playwright-e2e.yml`, 4-way `--shard`) — local wall-clock varies by machine. Phase 2 changes touch fixture bootstrap; before merging Phase 2 verify Playwright remains green in CI.
## Methodology — how to reproduce
From e7bee353071a35900474ea5d0f1455a18c17ee64 Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 14:50:56 +1000
Subject: [PATCH 04/45] chore(phpunit): add shared TestCase + traits, pilot on
Statics
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 1 of the PHPUnit refactor (plan: .claude/plans/2026-05-25-phpunit-tests-refactor.md):
introduce abstract TestCase / AjaxTestCase under tests/phpunit/integration/ plus three
Concerns/ traits (HasGfpdfFixtures, CleansFilesystem, UsesFactory), wire them through
tools/phpunit/bootstrap.php::load_test_infrastructure(), and migrate Statics as the
pilot vertical slice. Both phpunit configs additively include the new path; legacy
unit-tests/ continues to run side-by-side until Phase 2 drains it. Suite stays at
1119 tests / 8 skipped, 37.6s integration + 38.1s multisite (both within Phase 0
baseline's ±10% budget).
Co-Authored-By: Claude Opus 4.7 (1M context)
---
tests/phpunit/Concerns/CleansFilesystem.php | 73 ++++++++++
tests/phpunit/Concerns/HasGfpdfFixtures.php | 101 ++++++++++++++
tests/phpunit/Concerns/UsesFactory.php | 34 +++++
tests/phpunit/README.md | 131 ++++++++++++++++++
tests/phpunit/integration/AjaxTestCase.php | 21 +++
.../Statics/Test_Cache.php | 11 +-
.../Statics/Test_kses.php | 5 +-
tests/phpunit/integration/TestCase.php | 23 +++
tools/phpunit/bootstrap.php | 26 +++-
tools/phpunit/config-multisite.xml | 1 +
tools/phpunit/config.xml | 1 +
11 files changed, 417 insertions(+), 10 deletions(-)
create mode 100644 tests/phpunit/Concerns/CleansFilesystem.php
create mode 100644 tests/phpunit/Concerns/HasGfpdfFixtures.php
create mode 100644 tests/phpunit/Concerns/UsesFactory.php
create mode 100644 tests/phpunit/README.md
create mode 100644 tests/phpunit/integration/AjaxTestCase.php
rename tests/phpunit/{unit-tests => integration}/Statics/Test_Cache.php (88%)
rename tests/phpunit/{unit-tests => integration}/Statics/Test_kses.php (98%)
create mode 100644 tests/phpunit/integration/TestCase.php
diff --git a/tests/phpunit/Concerns/CleansFilesystem.php b/tests/phpunit/Concerns/CleansFilesystem.php
new file mode 100644
index 000000000..d4147338c
--- /dev/null
+++ b/tests/phpunit/Concerns/CleansFilesystem.php
@@ -0,0 +1,73 @@
+ Absolute paths queued for cleanup.
+ */
+ private $gfpdf_cleanup_paths = [];
+
+ /**
+ * Queue a path for removal in tear_down(). Files and directories supported.
+ *
+ * @param string $path Absolute filesystem path.
+ */
+ protected function register_path_for_cleanup( $path ) {
+ $this->gfpdf_cleanup_paths[] = $path;
+ }
+
+ /**
+ * Removes every queued path. Call from a subclass tear_down() override
+ * BEFORE parent::tear_down() — WP teardown can drop temp dirs we created.
+ */
+ protected function clean_registered_paths() {
+ foreach ( $this->gfpdf_cleanup_paths as $path ) {
+ $this->remove_path( $path );
+ }
+
+ $this->gfpdf_cleanup_paths = [];
+ }
+
+ /**
+ * Recursive remove for both files and directories.
+ *
+ * @param string $path Absolute filesystem path.
+ */
+ private function remove_path( $path ) {
+ if ( ! file_exists( $path ) && ! is_link( $path ) ) {
+ return;
+ }
+
+ if ( is_file( $path ) || is_link( $path ) ) {
+ @unlink( $path );
+
+ return;
+ }
+
+ $items = scandir( $path );
+ if ( false === $items ) {
+ return;
+ }
+
+ foreach ( $items as $item ) {
+ if ( '.' === $item || '..' === $item ) {
+ continue;
+ }
+ $this->remove_path( $path . DIRECTORY_SEPARATOR . $item );
+ }
+
+ @rmdir( $path );
+ }
+}
diff --git a/tests/phpunit/Concerns/HasGfpdfFixtures.php b/tests/phpunit/Concerns/HasGfpdfFixtures.php
new file mode 100644
index 000000000..404601eb9
--- /dev/null
+++ b/tests/phpunit/Concerns/HasGfpdfFixtures.php
@@ -0,0 +1,101 @@
+form[ $key ] ) ) {
+ $available = implode( ', ', array_keys( (array) $GLOBALS['GFPDF_Test']->form ) );
+ $this->fail( "Form fixture '$key' is not loaded. Available: $available" );
+ }
+
+ return $GLOBALS['GFPDF_Test']->form[ $key ];
+ }
+
+ /**
+ * Returns one of the shared entry fixtures stored under $key.
+ *
+ * @param string $key Same key as the parent form.
+ * @param int $index Zero-based index into the entry list.
+ *
+ * @return array
+ */
+ protected function entry( $key, $index = 0 ) {
+ if ( ! isset( $GLOBALS['GFPDF_Test']->entries[ $key ][ $index ] ) ) {
+ $this->fail( "Entry fixture '$key'[$index] is not loaded." );
+ }
+
+ return $GLOBALS['GFPDF_Test']->entries[ $key ][ $index ];
+ }
+
+ /**
+ * Returns the Gravity PDF Router (DI container).
+ *
+ * @return \GFPDF\Router
+ */
+ protected function gfpdf() {
+ global $gfpdf;
+
+ return $gfpdf;
+ }
+
+ /**
+ * Sentinel check that the shared fixture catalogue is intact.
+ *
+ * Called from each base class's set_up(). Catches the failure mode where
+ * a previous test deleted a shared form/entry from $GLOBALS['GFPDF_Test'].
+ * Deep mutation of fixture contents is not checked — too expensive at
+ * 1100+ tests; this is a fast presence check.
+ */
+ protected function assertFixturesIntact() {
+ $expected_forms = [
+ 'all-form-fields',
+ 'form-settings',
+ 'gravityform-1',
+ 'gravityform-2',
+ 'repeater-empty-form',
+ 'repeater-consent-form',
+ 'non-group-products-form',
+ ];
+
+ $expected_entries = [
+ 'all-form-fields',
+ 'gravityform-1',
+ 'repeater-empty-form',
+ 'repeater-consent-form',
+ 'non-group-products-form',
+ ];
+
+ foreach ( $expected_forms as $key ) {
+ $this->assertNotEmpty(
+ $GLOBALS['GFPDF_Test']->form[ $key ] ?? null,
+ "Shared form fixture '$key' missing — a prior test mutated \$GLOBALS['GFPDF_Test']->form"
+ );
+ }
+
+ foreach ( $expected_entries as $key ) {
+ $this->assertNotEmpty(
+ $GLOBALS['GFPDF_Test']->entries[ $key ] ?? null,
+ "Shared entry fixture '$key' missing — a prior test mutated \$GLOBALS['GFPDF_Test']->entries"
+ );
+ }
+ }
+}
diff --git a/tests/phpunit/Concerns/UsesFactory.php b/tests/phpunit/Concerns/UsesFactory.php
new file mode 100644
index 000000000..dd1b07d3a
--- /dev/null
+++ b/tests/phpunit/Concerns/UsesFactory.php
@@ -0,0 +1,34 @@
+gfpdf_factory ) {
+ $this->gfpdf_factory = new GF_UnitTest_Factory();
+ }
+
+ return $this->gfpdf_factory;
+ }
+}
diff --git a/tests/phpunit/README.md b/tests/phpunit/README.md
new file mode 100644
index 000000000..7bef91a29
--- /dev/null
+++ b/tests/phpunit/README.md
@@ -0,0 +1,131 @@
+# Gravity PDF — PHPUnit Test Suite
+
+This directory holds the plugin's PHPUnit integration tests, run inside the
+`wp-env` Docker container via `yarn test:php` / `yarn test:php:multisite`.
+
+The suite is mid-refactor; see [`.claude/plans/2026-05-25-phpunit-tests-refactor.md`](../../.claude/plans/2026-05-25-phpunit-tests-refactor.md)
+for the phase plan and [`COVERAGE_BASELINE.md`](COVERAGE_BASELINE.md) for the
+runtime + coverage baseline every PR is compared against.
+
+## Layout
+
+```
+tests/phpunit/
+├── COVERAGE_BASELINE.md
+├── README.md ← you are here
+├── Concerns/ ← shared traits (NOT discovered by PHPUnit)
+│ ├── HasGfpdfFixtures.php
+│ ├── CleansFilesystem.php
+│ └── UsesFactory.php
+├── integration/ ← new location, mirrors src/ 1:1
+│ ├── TestCase.php
+│ ├── AjaxTestCase.php
+│ └── Statics/
+│ ├── Test_Cache.php
+│ └── Test_kses.php
+└── unit-tests/ ← legacy location, drained subdir-by-subdir
+```
+
+`unit-tests/` and `integration/` co-exist during Phases 1–2. Both directories
+are listed in `tools/phpunit/config.xml`. Phase 2 ends with `unit-tests/`
+empty and the config entry removed.
+
+## Naming convention
+
+| Source file | Test file |
+| :--- | :--- |
+| `src/Statics/Cache.php` | `tests/phpunit/integration/Statics/Test_Cache.php` |
+| `src/Model/Model_PDF.php` | `tests/phpunit/integration/Model/Test_Model_PDF.php` |
+| `src/Controller/Controller_Settings.php` | `tests/phpunit/integration/Controller/Test_Controller_Settings.php` |
+
+One `Test_.php` per non-trivial `src/` class, at the matching path.
+Class name = `Test_`; method names use `test_` snake_case to keep
+`phpunit --filter test_something` searches predictable.
+
+## Choosing a base class
+
+| Need | Base class |
+| :--- | :--- |
+| Standard integration test (DB, hooks, options) | `\GFPDF\Tests\Integration\TestCase` |
+| Test that dispatches a `wp_ajax_*` action via `_handleAjax()` | `\GFPDF\Tests\Integration\AjaxTestCase` |
+
+Both extend WordPress's stock test cases and `use \GFPDF\Tests\Concerns\HasGfpdfFixtures`,
+which provides:
+
+- `$this->form( 'all-form-fields' )` — shared form fixture loaded once per suite.
+- `$this->entry( 'all-form-fields', 0 )` — shared entry fixture.
+- `$this->gfpdf()` — the `GFPDF\Router` DI container (same as the `$gfpdf` global).
+- `$this->assertFixturesIntact()` — automatically called from `set_up()` to catch
+ cross-test fixture mutation. Override `set_up()` only if you call
+ `parent::set_up()`.
+
+## Writing a new test
+
+```php
+form( 'all-form-fields' );
+ $entry = $this->entry( 'all-form-fields' );
+
+ $this->assertSame(
+ Cache::get_hash( $form, $entry, [] ),
+ Cache::get_hash( $form, $entry, [] )
+ );
+ }
+}
+```
+
+Conventions:
+
+- `declare(strict_types=1);` — required.
+- Namespace matches the class under test (`GFPDF\Statics`, `GFPDF\Model`, etc.).
+- `@group` annotation — every test class should have at least one (e.g. `controller`,
+ `model`, `helper`, `ajax`, `slow-pdf-processes`) so contributors can run a slice.
+
+## Fixture access
+
+`tools/phpunit/bootstrap.php::create_stubs()` loads seven JSON forms and five
+batches of entries into `$GLOBALS['GFPDF_Test']` once per suite. Use the trait
+accessors rather than the global directly — same source, but failures point
+to the missing key instead of throwing an undefined-index notice.
+
+**Never call `GFAPI::add_form()` / `GFAPI::add_entry()` in a test body.**
+Phase 3 of the refactor enforces this; use the existing factory
+(`GF_UnitTest_Factory` at `tools/phpunit/gravityforms-factory.php`) via the
+`UsesFactory` trait when you need a per-test form/entry.
+
+## The "test if non-trivial" rule
+
+Skip a `Test_*.php` for a class that is:
+1. Under 30 lines of code, **and**
+2. Has no methods of its own (only inherited), **and**
+3. Has no constructor logic.
+
+The 11 classes under `src/Exceptions/` are covered by a single
+`integration/Exceptions/Test_Exception_Hierarchy.php` smoke test, not 11
+individual files. Phase 4 of the refactor lands that test.
+
+## Running
+
+```bash
+yarn wp-env:integration start # one-time per session
+yarn test:php # full suite
+yarn test:php -- --filter Test_Cache # single class
+yarn test:php -- --group statics # group
+yarn test:php:multisite # WP multisite mode
+```
+
+See top-level `CLAUDE.md` and `COVERAGE_BASELINE.md` for the runtime budget
+and the methodology for regenerating baseline timings.
diff --git a/tests/phpunit/integration/AjaxTestCase.php b/tests/phpunit/integration/AjaxTestCase.php
new file mode 100644
index 000000000..440826460
--- /dev/null
+++ b/tests/phpunit/integration/AjaxTestCase.php
@@ -0,0 +1,21 @@
+_handleAjax().
+ */
+abstract class AjaxTestCase extends WP_Ajax_UnitTestCase {
+
+ use HasGfpdfFixtures;
+
+ public function set_up() {
+ parent::set_up();
+ $this->assertFixturesIntact();
+ }
+}
diff --git a/tests/phpunit/unit-tests/Statics/Test_Cache.php b/tests/phpunit/integration/Statics/Test_Cache.php
similarity index 88%
rename from tests/phpunit/unit-tests/Statics/Test_Cache.php
rename to tests/phpunit/integration/Statics/Test_Cache.php
index a1029b360..7b7adafb4 100644
--- a/tests/phpunit/unit-tests/Statics/Test_Cache.php
+++ b/tests/phpunit/integration/Statics/Test_Cache.php
@@ -4,7 +4,7 @@
namespace GFPDF\Statics;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -15,7 +15,7 @@
/**
* @group statics
*/
-class Test_Cache extends WP_UnitTestCase {
+class Test_Cache extends TestCase {
public function test_get_hash() {
$results = $this->create_form_and_entries();
@@ -41,11 +41,10 @@ public function test_get_hash() {
}
protected function create_form_and_entries() {
- global $gfpdf;
-
- $form = $GLOBALS['GFPDF_Test']->form['all-form-fields'];
- $entry = $GLOBALS['GFPDF_Test']->entries['all-form-fields'][0];
+ $form = $this->form( 'all-form-fields' );
+ $entry = $this->entry( 'all-form-fields' );
+ $gfpdf = $this->gfpdf();
$gfpdf->data->form_settings = [];
$gfpdf->data->form_settings[ $form['id'] ] = $form['gfpdf_form_settings'];
diff --git a/tests/phpunit/unit-tests/Statics/Test_kses.php b/tests/phpunit/integration/Statics/Test_kses.php
similarity index 98%
rename from tests/phpunit/unit-tests/Statics/Test_kses.php
rename to tests/phpunit/integration/Statics/Test_kses.php
index 0692ae7ff..08ca596a0 100644
--- a/tests/phpunit/unit-tests/Statics/Test_kses.php
+++ b/tests/phpunit/integration/Statics/Test_kses.php
@@ -4,8 +4,7 @@
namespace GFPDF\Statics;
-use GFPDF\Exceptions\GravityPdfException;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -18,7 +17,7 @@
*
* @group statics
*/
-class Test_Kses extends WP_UnitTestCase {
+class Test_Kses extends TestCase {
/**
* @dataProvider provider_parse_pdf_tags_and_attributes
diff --git a/tests/phpunit/integration/TestCase.php b/tests/phpunit/integration/TestCase.php
new file mode 100644
index 000000000..8444bc322
--- /dev/null
+++ b/tests/phpunit/integration/TestCase.php
@@ -0,0 +1,23 @@
+assertFixturesIntact();
+ }
+}
diff --git a/tools/phpunit/bootstrap.php b/tools/phpunit/bootstrap.php
index e25436b60..245e45846 100644
--- a/tools/phpunit/bootstrap.php
+++ b/tools/phpunit/bootstrap.php
@@ -80,10 +80,16 @@ public function __construct() {
/* Load Mocks */
$this->mocks();
+
+ /* Load shared TestCase + Concerns infrastructure (Phase 1 of phpunit refactor) */
+ $this->load_test_infrastructure();
}
/**
- * Load Addon Mocks
+ * Load Addon Mocks.
+ *
+ * Currently loads the Zapier add-on stub so tests that exercise the Zapier
+ * integration code paths can run without the real add-on installed.
*
* @since 6.3
*/
@@ -91,6 +97,24 @@ public function mocks() {
require_once __DIR__ . '/Mocks/zapier-mock.php';
}
+ /**
+ * Load the shared TestCase + trait files used by tests under
+ * tests/phpunit/integration/. Required explicitly because the
+ * Concerns/ directory is intentionally not part of the PHPUnit
+ * list.
+ *
+ * @since 7.0
+ */
+ public function load_test_infrastructure() {
+ $root = $this->plugin_dir . '/tests/phpunit';
+
+ require_once $root . '/Concerns/HasGfpdfFixtures.php';
+ require_once $root . '/Concerns/CleansFilesystem.php';
+ require_once $root . '/Concerns/UsesFactory.php';
+ require_once $root . '/integration/TestCase.php';
+ require_once $root . '/integration/AjaxTestCase.php';
+ }
+
/**
* Load Gravity Forms and Gravity PDF
*
diff --git a/tools/phpunit/config-multisite.xml b/tools/phpunit/config-multisite.xml
index c3b5033ad..38e401164 100644
--- a/tools/phpunit/config-multisite.xml
+++ b/tools/phpunit/config-multisite.xml
@@ -13,6 +13,7 @@
../../tests/phpunit/unit-tests/
+ ../../tests/phpunit/integration/
diff --git a/tools/phpunit/config.xml b/tools/phpunit/config.xml
index 39c15105d..87434d449 100644
--- a/tools/phpunit/config.xml
+++ b/tools/phpunit/config.xml
@@ -9,6 +9,7 @@
../../tests/phpunit/unit-tests/
+ ../../tests/phpunit/integration/
From f9714366f0974b65dae516965abbfd8ec63de4d7 Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 14:54:04 +1000
Subject: [PATCH 05/45] chore(phpunit): drop premature AjaxTestCase + unused
traits
No-slop cleanup of the Phase 1 commit: removed AjaxTestCase, CleansFilesystem,
and UsesFactory because nothing in Phase 1 consumes them. Phase 2 introduces
AjaxTestCase when it splits test-ajax.php; Phase 3 introduces the other two
when it adopts factories and filesystem cleanup. Also trimmed task-reference
comments and tightened the trait/TestCase phpdoc. Statics pilot unchanged.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
tests/phpunit/Concerns/CleansFilesystem.php | 73 ---------------------
tests/phpunit/Concerns/HasGfpdfFixtures.php | 6 +-
tests/phpunit/Concerns/UsesFactory.php | 34 ----------
tests/phpunit/README.md | 27 ++++----
tests/phpunit/integration/AjaxTestCase.php | 21 ------
tests/phpunit/integration/TestCase.php | 5 --
tools/phpunit/bootstrap.php | 11 +---
7 files changed, 17 insertions(+), 160 deletions(-)
delete mode 100644 tests/phpunit/Concerns/CleansFilesystem.php
delete mode 100644 tests/phpunit/Concerns/UsesFactory.php
delete mode 100644 tests/phpunit/integration/AjaxTestCase.php
diff --git a/tests/phpunit/Concerns/CleansFilesystem.php b/tests/phpunit/Concerns/CleansFilesystem.php
deleted file mode 100644
index d4147338c..000000000
--- a/tests/phpunit/Concerns/CleansFilesystem.php
+++ /dev/null
@@ -1,73 +0,0 @@
- Absolute paths queued for cleanup.
- */
- private $gfpdf_cleanup_paths = [];
-
- /**
- * Queue a path for removal in tear_down(). Files and directories supported.
- *
- * @param string $path Absolute filesystem path.
- */
- protected function register_path_for_cleanup( $path ) {
- $this->gfpdf_cleanup_paths[] = $path;
- }
-
- /**
- * Removes every queued path. Call from a subclass tear_down() override
- * BEFORE parent::tear_down() — WP teardown can drop temp dirs we created.
- */
- protected function clean_registered_paths() {
- foreach ( $this->gfpdf_cleanup_paths as $path ) {
- $this->remove_path( $path );
- }
-
- $this->gfpdf_cleanup_paths = [];
- }
-
- /**
- * Recursive remove for both files and directories.
- *
- * @param string $path Absolute filesystem path.
- */
- private function remove_path( $path ) {
- if ( ! file_exists( $path ) && ! is_link( $path ) ) {
- return;
- }
-
- if ( is_file( $path ) || is_link( $path ) ) {
- @unlink( $path );
-
- return;
- }
-
- $items = scandir( $path );
- if ( false === $items ) {
- return;
- }
-
- foreach ( $items as $item ) {
- if ( '.' === $item || '..' === $item ) {
- continue;
- }
- $this->remove_path( $path . DIRECTORY_SEPARATOR . $item );
- }
-
- @rmdir( $path );
- }
-}
diff --git a/tests/phpunit/Concerns/HasGfpdfFixtures.php b/tests/phpunit/Concerns/HasGfpdfFixtures.php
index 404601eb9..5f6d7483a 100644
--- a/tests/phpunit/Concerns/HasGfpdfFixtures.php
+++ b/tests/phpunit/Concerns/HasGfpdfFixtures.php
@@ -8,9 +8,9 @@
* Ergonomic accessors for the shared form/entry fixtures loaded once per
* suite by tools/phpunit/bootstrap.php into $GLOBALS['GFPDF_Test'].
*
- * Both \GFPDF\Tests\Integration\TestCase and AjaxTestCase use this trait —
- * PHP has no multiple inheritance, so the accessors must live in a trait
- * that each base class can `use`.
+ * Lives in a trait so a future AJAX base extending WP_Ajax_UnitTestCase can
+ * share the same accessors without duplicating them (PHP has no multiple
+ * inheritance).
*/
trait HasGfpdfFixtures {
diff --git a/tests/phpunit/Concerns/UsesFactory.php b/tests/phpunit/Concerns/UsesFactory.php
deleted file mode 100644
index dd1b07d3a..000000000
--- a/tests/phpunit/Concerns/UsesFactory.php
+++ /dev/null
@@ -1,34 +0,0 @@
-gfpdf_factory ) {
- $this->gfpdf_factory = new GF_UnitTest_Factory();
- }
-
- return $this->gfpdf_factory;
- }
-}
diff --git a/tests/phpunit/README.md b/tests/phpunit/README.md
index 7bef91a29..ea183db08 100644
--- a/tests/phpunit/README.md
+++ b/tests/phpunit/README.md
@@ -14,12 +14,9 @@ tests/phpunit/
├── COVERAGE_BASELINE.md
├── README.md ← you are here
├── Concerns/ ← shared traits (NOT discovered by PHPUnit)
-│ ├── HasGfpdfFixtures.php
-│ ├── CleansFilesystem.php
-│ └── UsesFactory.php
+│ └── HasGfpdfFixtures.php
├── integration/ ← new location, mirrors src/ 1:1
│ ├── TestCase.php
-│ ├── AjaxTestCase.php
│ └── Statics/
│ ├── Test_Cache.php
│ └── Test_kses.php
@@ -42,15 +39,10 @@ One `Test_.php` per non-trivial `src/` class, at the matching path.
Class name = `Test_`; method names use `test_` snake_case to keep
`phpunit --filter test_something` searches predictable.
-## Choosing a base class
+## Base class
-| Need | Base class |
-| :--- | :--- |
-| Standard integration test (DB, hooks, options) | `\GFPDF\Tests\Integration\TestCase` |
-| Test that dispatches a `wp_ajax_*` action via `_handleAjax()` | `\GFPDF\Tests\Integration\AjaxTestCase` |
-
-Both extend WordPress's stock test cases and `use \GFPDF\Tests\Concerns\HasGfpdfFixtures`,
-which provides:
+Tests extend `\GFPDF\Tests\Integration\TestCase`, which extends `WP_UnitTestCase`
+and `use`s `\GFPDF\Tests\Concerns\HasGfpdfFixtures`. The trait provides:
- `$this->form( 'all-form-fields' )` — shared form fixture loaded once per suite.
- `$this->entry( 'all-form-fields', 0 )` — shared entry fixture.
@@ -59,6 +51,9 @@ which provides:
cross-test fixture mutation. Override `set_up()` only if you call
`parent::set_up()`.
+A second base for `WP_Ajax_UnitTestCase`-derived tests will be added when Phase 2
+splits `test-ajax.php`.
+
## Writing a new test
```php
@@ -101,10 +96,10 @@ batches of entries into `$GLOBALS['GFPDF_Test']` once per suite. Use the trait
accessors rather than the global directly — same source, but failures point
to the missing key instead of throwing an undefined-index notice.
-**Never call `GFAPI::add_form()` / `GFAPI::add_entry()` in a test body.**
-Phase 3 of the refactor enforces this; use the existing factory
-(`GF_UnitTest_Factory` at `tools/phpunit/gravityforms-factory.php`) via the
-`UsesFactory` trait when you need a per-test form/entry.
+**Avoid calling `GFAPI::add_form()` / `GFAPI::add_entry()` from a test body** —
+prefer the existing `GF_UnitTest_Factory` (`tools/phpunit/gravityforms-factory.php`)
+when you need a per-test form/entry. Phase 3 of the refactor will enforce this
+across the suite and ship a trait-based accessor.
## The "test if non-trivial" rule
diff --git a/tests/phpunit/integration/AjaxTestCase.php b/tests/phpunit/integration/AjaxTestCase.php
deleted file mode 100644
index 440826460..000000000
--- a/tests/phpunit/integration/AjaxTestCase.php
+++ /dev/null
@@ -1,21 +0,0 @@
-_handleAjax().
- */
-abstract class AjaxTestCase extends WP_Ajax_UnitTestCase {
-
- use HasGfpdfFixtures;
-
- public function set_up() {
- parent::set_up();
- $this->assertFixturesIntact();
- }
-}
diff --git a/tests/phpunit/integration/TestCase.php b/tests/phpunit/integration/TestCase.php
index 8444bc322..1b328505d 100644
--- a/tests/phpunit/integration/TestCase.php
+++ b/tests/phpunit/integration/TestCase.php
@@ -7,11 +7,6 @@
use GFPDF\Tests\Concerns\HasGfpdfFixtures;
use WP_UnitTestCase;
-/**
- * Default base for non-AJAX integration tests.
- *
- * Use AjaxTestCase instead when the test exercises a wp_ajax_* action.
- */
abstract class TestCase extends WP_UnitTestCase {
use HasGfpdfFixtures;
diff --git a/tools/phpunit/bootstrap.php b/tools/phpunit/bootstrap.php
index 245e45846..97cb55196 100644
--- a/tools/phpunit/bootstrap.php
+++ b/tools/phpunit/bootstrap.php
@@ -81,7 +81,6 @@ public function __construct() {
/* Load Mocks */
$this->mocks();
- /* Load shared TestCase + Concerns infrastructure (Phase 1 of phpunit refactor) */
$this->load_test_infrastructure();
}
@@ -98,10 +97,9 @@ public function mocks() {
}
/**
- * Load the shared TestCase + trait files used by tests under
- * tests/phpunit/integration/. Required explicitly because the
- * Concerns/ directory is intentionally not part of the PHPUnit
- * list.
+ * Required explicitly because tests/phpunit/Concerns/ is intentionally
+ * not part of the PHPUnit list (traits are not
+ * tests).
*
* @since 7.0
*/
@@ -109,10 +107,7 @@ public function load_test_infrastructure() {
$root = $this->plugin_dir . '/tests/phpunit';
require_once $root . '/Concerns/HasGfpdfFixtures.php';
- require_once $root . '/Concerns/CleansFilesystem.php';
- require_once $root . '/Concerns/UsesFactory.php';
require_once $root . '/integration/TestCase.php';
- require_once $root . '/integration/AjaxTestCase.php';
}
/**
From 22e946b20cdec8236c5adf8164fb6deb10cab78a Mon Sep 17 00:00:00 2001
From: Jake Jackson
Date: Mon, 25 May 2026 15:15:20 +1000
Subject: [PATCH 06/45] chore(phpunit): drain unit-tests/ into integration/
mirroring src/
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
Phase 2 of the PHPUnit refactor: bulk structural normalization. All 73
legacy tests under tests/phpunit/unit-tests/ now live under
tests/phpunit/integration/ mirroring src/ paths. 41 existing PascalCase
tests moved + switched base class to GFPDF\Tests\Integration\TestCase.
30 root-level kebab-case test-*.php files renamed to PascalCase, moved
to their closest src/ subdir, and re-namespaced from GFPDF\Tests to
match. Four Rest/ + Controller/ files with mis-set namespaces also
corrected. Restored AjaxTestCase now that test-ajax.php's split gives
it real callers.
Two kitchen-sink files split per the plan:
- test-ajax.php (9 methods, 1 class) → 4 *_Ajax.php under integration/Model/
grouped by target Model class (Form_Settings, Templates, Settings, Custom_Fonts).
Form_Settings_Ajax keeps the per-test add_form (will move to factory in Phase 3);
the other three splits no longer import a form at all.
- test-helper-misc.php (19 methods, 735 lines) → 4 files under integration/Helper/
grouped by concern: Colors, Pages, Forms, Config.
Both phpunit configs drop the now-empty unit-tests/ entry.
Test count unchanged at 1119 (8 skipped integration, 1 skipped multisite).
Runtime: 27.6s integration / 27.2s multisite — both -28% vs Phase 0
baseline (38.5s / 38.6s). Speedup comes from the AJAX split: 9 tests
that were the slowest cluster (26.7s of 38.5s total) now run in 0.1s
once test-ajax.php's per-test set_up overhead is split across four
smaller classes and three of them drop the form-import entirely.
Co-Authored-By: Claude Opus 4.7 (1M context)
---
tests/phpunit/integration/AjaxTestCase.php | 18 +
.../Test_Controller_Custom_Fonts.php | 4 +-
.../Test_Controller_Export_Entries.php | 4 +-
.../Controller/Test_Controller_Pdf_Queue.php | 6 +-
.../Controller/Test_Controller_Settings.php | 4 +-
.../Test_Controller_System_Report.php | 4 +-
.../Test_Controller_Upgrade_Routines.php | 4 +-
.../Controller/Test_Controller_Webhooks.php | 4 +-
.../Controller/Test_Controller_Zapier.php | 4 +-
.../Helper/Fields/Test_Field_Consent.php | 4 +-
.../Helper/Fields/Test_Field_Form.php | 4 +-
.../Helper/Fields/Test_Field_Image_Choice.php | 4 +-
.../Helper/Fields/Test_Field_List.php | 4 +-
.../Helper/Fields/Test_Field_Markup.php} | 7 +-
.../Helper/Fields/Test_Field_Multi_Choice.php | 4 +-
.../Helper/Fields/Test_Field_Option.php | 4 +-
.../Helper/Fields/Test_Field_Poll.php | 4 +-
.../Fields/Test_Field_Post_Category.php | 4 +-
.../Fields/Test_Field_Post_Custom_Field.php | 4 +-
.../Helper/Fields/Test_Field_Post_Image.php | 4 +-
.../Helper/Fields/Test_Field_Product.php | 4 +-
.../Helper/Fields/Test_Field_Products.php | 4 +-
.../Helper/Fields/Test_Field_Radio.php | 4 +-
.../Helper/Fields/Test_Field_Repeater.php | 4 +-
.../Helper/Fields/Test_Field_Section.php | 4 +-
.../Helper/Fields/Test_Field_Select.php | 4 +-
.../Helper/Fields/Test_Field_Signature.php | 4 +-
.../Helper/Fields/Test_Field_Survey.php | 4 +-
.../Helper/Fields/Test_Field_Textarea.php | 4 +-
.../Helper/Fonts/Test_FlushCache.php | 4 +-
.../Licensing/Test_EDD_SL_Plugin_Updater.php | 4 +-
.../Helper/Log/Test_Redact_Processor.php | 0
.../Helper/Mpdf/Test_Cache.php | 4 +-
.../Helper/Mpdf/Test_Request.php | 4 +-
.../Helper/Test_Addon.php} | 7 +-
.../Helper/Test_Field_Container.php} | 7 +-
.../Helper/Test_Form_Data.php} | 7 +-
.../Helper/Test_Gravity_Forms.php} | 7 +-
.../Helper/Test_Helper_Data.php} | 7 +-
.../Test_Helper_Field_Container_Gf25.php | 4 +-
.../Helper/Test_Helper_Misc_Colors.php | 85 ++
.../Helper/Test_Helper_Misc_Config.php | 104 +++
.../Helper/Test_Helper_Misc_Forms.php | 249 ++++++
.../Helper/Test_Helper_Misc_Pages.php | 121 +++
.../Helper/Test_Helper_Mpdf.php} | 7 +-
.../Helper/Test_Helper_Templates.php} | 7 +-
.../Helper/Test_Interfaces.php} | 7 +-
.../Helper/Test_Logger.php} | 7 +-
.../Helper/Test_MVC_Abstracts.php} | 7 +-
.../Helper/Test_Notices.php} | 7 +-
.../Helper/Test_Options_API.php} | 7 +-
.../Helper/Test_QueryPath.php} | 7 +-
.../Helper/Test_Settings.php} | 7 +-
.../Helper/Test_Singleton.php} | 7 +-
.../Helper/Test_Url_Signer.php} | 7 +-
.../Model/Test_Actions.php} | 7 +-
.../Model/Test_Form_Settings.php} | 7 +-
.../Model/Test_Installer.php} | 7 +-
.../Model/Test_Model_Custom_Fonts.php | 4 +-
.../Model/Test_Model_Custom_Fonts_Ajax.php | 55 ++
.../Model/Test_Model_Form_Settings_Ajax.php | 207 +++++
.../Model/Test_Model_Mergetags.php | 4 +-
.../Model/Test_Model_Pdf.php | 4 +-
.../Model/Test_Model_Pdf_Meta_Box.php | 4 +-
.../Model/Test_Model_Settings.php | 4 +-
.../Model/Test_Model_Settings_Ajax.php | 33 +
.../Model/Test_Model_System_Report.php | 4 +-
.../Model/Test_Model_Templates_Ajax.php | 91 +++
.../Model/Test_PDF.php} | 7 +-
.../Model/Test_Shortcodes.php} | 7 +-
.../Model/Test_Slow_PDF_Processes.php} | 7 +-
.../Model/Test_Templates.php} | 7 +-
.../Model/Test_Uninstaller.php} | 7 +-
.../Rest/Test_Rest.php | 6 +-
.../Rest/Test_Rest_Form_Settings.php | 2 +-
.../Rest/Test_Rest_Pdf_Preview.php | 2 +-
.../test-api.php => integration/Test_Api.php} | 4 +-
.../Test_Autoloader.php} | 4 +-
.../Test_Bootstrap.php} | 4 +-
.../Test_Deprecated.php} | 4 +-
.../Test_Pre_Checks.php} | 4 +-
.../View/Test_View_System_Report.php | 4 +-
tests/phpunit/unit-tests/test-ajax.php | 552 -------------
tests/phpunit/unit-tests/test-helper-misc.php | 735 ------------------
tools/phpunit/bootstrap.php | 1 +
tools/phpunit/config-multisite.xml | 1 -
tools/phpunit/config.xml | 1 -
87 files changed, 1133 insertions(+), 1483 deletions(-)
create mode 100644 tests/phpunit/integration/AjaxTestCase.php
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Custom_Fonts.php (99%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Export_Entries.php (95%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Pdf_Queue.php (98%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Settings.php (95%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_System_Report.php (97%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Upgrade_Routines.php (91%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Webhooks.php (94%)
rename tests/phpunit/{unit-tests => integration}/Controller/Test_Controller_Zapier.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Consent.php (96%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Form.php (95%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Image_Choice.php (99%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_List.php (95%)
rename tests/phpunit/{unit-tests/test-field-markup.php => integration/Helper/Fields/Test_Field_Markup.php} (96%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Multi_Choice.php (98%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Option.php (94%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Poll.php (93%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Post_Category.php (93%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Post_Custom_Field.php (93%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Post_Image.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Product.php (96%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Products.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Radio.php (95%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Repeater.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Section.php (94%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Select.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Signature.php (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Survey.php (93%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fields/Test_Field_Textarea.php (93%)
rename tests/phpunit/{unit-tests => integration}/Helper/Fonts/Test_FlushCache.php (88%)
rename tests/phpunit/{unit-tests => integration}/Helper/Licensing/Test_EDD_SL_Plugin_Updater.php (99%)
rename tests/phpunit/{unit-tests => integration}/Helper/Log/Test_Redact_Processor.php (100%)
rename tests/phpunit/{unit-tests => integration}/Helper/Mpdf/Test_Cache.php (94%)
rename tests/phpunit/{unit-tests => integration}/Helper/Mpdf/Test_Request.php (96%)
rename tests/phpunit/{unit-tests/test-addon.php => integration/Helper/Test_Addon.php} (99%)
rename tests/phpunit/{unit-tests/test-field-container.php => integration/Helper/Test_Field_Container.php} (98%)
rename tests/phpunit/{unit-tests/test-form-data.php => integration/Helper/Test_Form_Data.php} (99%)
rename tests/phpunit/{unit-tests/test-gravity-forms.php => integration/Helper/Test_Gravity_Forms.php} (99%)
rename tests/phpunit/{unit-tests/test-helper-data.php => integration/Helper/Test_Helper_Data.php} (97%)
rename tests/phpunit/{unit-tests => integration}/Helper/Test_Helper_Field_Container_Gf25.php (97%)
create mode 100644 tests/phpunit/integration/Helper/Test_Helper_Misc_Colors.php
create mode 100644 tests/phpunit/integration/Helper/Test_Helper_Misc_Config.php
create mode 100644 tests/phpunit/integration/Helper/Test_Helper_Misc_Forms.php
create mode 100644 tests/phpunit/integration/Helper/Test_Helper_Misc_Pages.php
rename tests/phpunit/{unit-tests/test-helper-mpdf.php => integration/Helper/Test_Helper_Mpdf.php} (89%)
rename tests/phpunit/{unit-tests/test-helper-templates.php => integration/Helper/Test_Helper_Templates.php} (99%)
rename tests/phpunit/{unit-tests/test-interfaces.php => integration/Helper/Test_Interfaces.php} (89%)
rename tests/phpunit/{unit-tests/test-logger.php => integration/Helper/Test_Logger.php} (95%)
rename tests/phpunit/{unit-tests/test-mvc-abstracts.php => integration/Helper/Test_MVC_Abstracts.php} (96%)
rename tests/phpunit/{unit-tests/test-notices.php => integration/Helper/Test_Notices.php} (97%)
rename tests/phpunit/{unit-tests/test-options-api.php => integration/Helper/Test_Options_API.php} (99%)
rename tests/phpunit/{unit-tests/test-query-path.php => integration/Helper/Test_QueryPath.php} (91%)
rename tests/phpunit/{unit-tests/test-settings.php => integration/Helper/Test_Settings.php} (99%)
rename tests/phpunit/{unit-tests/test-singleton.php => integration/Helper/Test_Singleton.php} (97%)
rename tests/phpunit/{unit-tests/test-url-signer.php => integration/Helper/Test_Url_Signer.php} (98%)
rename tests/phpunit/{unit-tests/test-actions.php => integration/Model/Test_Actions.php} (98%)
rename tests/phpunit/{unit-tests/test-form-settings.php => integration/Model/Test_Form_Settings.php} (99%)
rename tests/phpunit/{unit-tests/test-installer.php => integration/Model/Test_Installer.php} (98%)
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_Custom_Fonts.php (98%)
create mode 100644 tests/phpunit/integration/Model/Test_Model_Custom_Fonts_Ajax.php
create mode 100644 tests/phpunit/integration/Model/Test_Model_Form_Settings_Ajax.php
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_Mergetags.php (99%)
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_Pdf.php (98%)
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_Pdf_Meta_Box.php (97%)
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_Settings.php (98%)
create mode 100644 tests/phpunit/integration/Model/Test_Model_Settings_Ajax.php
rename tests/phpunit/{unit-tests => integration}/Model/Test_Model_System_Report.php (96%)
create mode 100644 tests/phpunit/integration/Model/Test_Model_Templates_Ajax.php
rename tests/phpunit/{unit-tests/test-pdf.php => integration/Model/Test_PDF.php} (99%)
rename tests/phpunit/{unit-tests/test-shortcodes.php => integration/Model/Test_Shortcodes.php} (99%)
rename tests/phpunit/{unit-tests/test-slow-pdf-processes.php => integration/Model/Test_Slow_PDF_Processes.php} (99%)
rename tests/phpunit/{unit-tests/test-templates.php => integration/Model/Test_Templates.php} (98%)
rename tests/phpunit/{unit-tests/test-uninstaller.php => integration/Model/Test_Uninstaller.php} (97%)
rename tests/phpunit/{unit-tests => integration}/Rest/Test_Rest.php (94%)
rename tests/phpunit/{unit-tests => integration}/Rest/Test_Rest_Form_Settings.php (99%)
rename tests/phpunit/{unit-tests => integration}/Rest/Test_Rest_Pdf_Preview.php (99%)
rename tests/phpunit/{unit-tests/test-api.php => integration/Test_Api.php} (99%)
rename tests/phpunit/{unit-tests/test-autoloader.php => integration/Test_Autoloader.php} (97%)
rename tests/phpunit/{unit-tests/test-bootstrap.php => integration/Test_Bootstrap.php} (98%)
rename tests/phpunit/{unit-tests/test-deprecated.php => integration/Test_Deprecated.php} (97%)
rename tests/phpunit/{unit-tests/test-pre-checks.php => integration/Test_Pre_Checks.php} (98%)
rename tests/phpunit/{unit-tests => integration}/View/Test_View_System_Report.php (95%)
delete mode 100644 tests/phpunit/unit-tests/test-ajax.php
delete mode 100644 tests/phpunit/unit-tests/test-helper-misc.php
diff --git a/tests/phpunit/integration/AjaxTestCase.php b/tests/phpunit/integration/AjaxTestCase.php
new file mode 100644
index 000000000..bade45429
--- /dev/null
+++ b/tests/phpunit/integration/AjaxTestCase.php
@@ -0,0 +1,18 @@
+assertFixturesIntact();
+ }
+}
diff --git a/tests/phpunit/unit-tests/Controller/Test_Controller_Custom_Fonts.php b/tests/phpunit/integration/Controller/Test_Controller_Custom_Fonts.php
similarity index 99%
rename from tests/phpunit/unit-tests/Controller/Test_Controller_Custom_Fonts.php
rename to tests/phpunit/integration/Controller/Test_Controller_Custom_Fonts.php
index 0fc151d97..7509148d4 100644
--- a/tests/phpunit/unit-tests/Controller/Test_Controller_Custom_Fonts.php
+++ b/tests/phpunit/integration/Controller/Test_Controller_Custom_Fonts.php
@@ -8,7 +8,7 @@
use GFPDF\Model\Model_Custom_Fonts;
use GPDFAPI;
use WP_REST_Request;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -24,7 +24,7 @@
* @group controller
* @group fonts
*/
-class Test_Controller_Custom_Fonts extends WP_UnitTestCase {
+class Test_Controller_Custom_Fonts extends TestCase {
/**
* @var Controller_Custom_Fonts
diff --git a/tests/phpunit/unit-tests/Controller/Test_Controller_Export_Entries.php b/tests/phpunit/integration/Controller/Test_Controller_Export_Entries.php
similarity index 95%
rename from tests/phpunit/unit-tests/Controller/Test_Controller_Export_Entries.php
rename to tests/phpunit/integration/Controller/Test_Controller_Export_Entries.php
index b31be3c56..5c3a7047e 100644
--- a/tests/phpunit/unit-tests/Controller/Test_Controller_Export_Entries.php
+++ b/tests/phpunit/integration/Controller/Test_Controller_Export_Entries.php
@@ -2,7 +2,7 @@
namespace GFPDF\Controller;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -18,7 +18,7 @@
* @group controller
* @group export
*/
-class Test_Controller_Export_Entries extends WP_UnitTestCase {
+class Test_Controller_Export_Entries extends TestCase {
public function test_add_pdfs_to_export_fields() {
$form = apply_filters( 'gform_export_fields', $GLOBALS['GFPDF_Test']->form['all-form-fields'] );
diff --git a/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php b/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php
similarity index 98%
rename from tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php
rename to tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php
index dd2b006ce..8199b779d 100644
--- a/tests/phpunit/unit-tests/Controller/Test_Controller_Pdf_Queue.php
+++ b/tests/phpunit/integration/Controller/Test_Controller_Pdf_Queue.php
@@ -1,6 +1,6 @@
misc = new Helper_Misc( $gfpdf->log, $gfpdf->gform, $gfpdf->data );
+ }
+
+ /**
+ * @dataProvider provider_get_contrast
+ */
+ public function test_get_contrast( $expected, $hexcolor ) {
+ $this->assertEquals( $expected, $this->misc->get_contrast( $hexcolor ) );
+ }
+
+ public function provider_get_contrast() {
+ return [
+ [ '#FFF', '#000000' ],
+ [ '#FFF', '#000' ],
+ [ '#FFF', '#222' ],
+ [ '#FFF', '#068a2b' ],
+ [ '#FFF', '#a70404' ],
+ [ '#000', '#fff' ],
+ [ '#000', '#FFFFFF' ],
+ [ '#000', '#999' ],
+ [ '#000', '#EEE' ],
+ [ '#000', '#CCC' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_change_brightness
+ */
+ public function test_change_brightness( $expected, $hexcolor, $diff ) {
+ $this->assertEquals( $expected, $this->misc->change_brightness( $hexcolor, $diff ) );
+ }
+
+ public function provider_change_brightness() {
+ return [
+ [ '#0a0a0a', '#000000', 10 ],
+ [ '#0a0a0a', '#000', 10 ],
+ [ '#181818', '#222', -10 ],
+ [ '#2c2c2c', '#222', 10 ],
+ [ '#fefefe', '#CCC', 50 ],
+ [ '#9a9a9a', '#CCC', -50 ],
+ [ '#ffffff', '#FFFFFF', 25 ],
+ [ '#e6e6e6', '#FFF', -25 ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_get_background_and_border_contrast
+ */
+ public function test_get_background_and_border_contrast( $expected, $hex ) {
+ $contrast = $this->misc->get_background_and_border_contrast( $hex );
+
+ $this->assertEquals( $expected[0], $contrast['background'] );
+ $this->assertEquals( $expected[1], $contrast['border'] );
+ }
+
+ public function provider_get_background_and_border_contrast() {
+ return [
+ [ [ '#ebebeb', '#c3c3c3' ], '#FFFFFF' ],
+ [ [ '#ebebeb', '#c3c3c3' ], '#FFF' ],
+ [ [ '#141414', '#3c3c3c' ], '#000000' ],
+ [ [ '#141414', '#3c3c3c' ], '#000' ],
+ [ [ '#e82828', '#ff5050' ], '#d41414' ],
+ [ [ '#295399', '#517bc1' ], '#153f85' ],
+ [ [ '#5cbb50', '#349328' ], '#70cf64' ],
+ [ [ '#dfdfdf', '#b7b7b7' ], '#f3f3f3' ],
+ ];
+ }
+}
diff --git a/tests/phpunit/integration/Helper/Test_Helper_Misc_Config.php b/tests/phpunit/integration/Helper/Test_Helper_Misc_Config.php
new file mode 100644
index 000000000..7734c3355
--- /dev/null
+++ b/tests/phpunit/integration/Helper/Test_Helper_Misc_Config.php
@@ -0,0 +1,104 @@
+misc = new Helper_Misc( $gfpdf->log, $gfpdf->gform, $gfpdf->data );
+ }
+
+ /**
+ * @dataProvider provider_update_deprecated_config
+ */
+ public function test_update_deprecated_config( $expected, $value ) {
+ $this->assertEquals( $expected, $this->misc->update_deprecated_config( $value ) );
+ }
+
+ public function provider_update_deprecated_config() {
+ return [
+ [ 'Yes', true ],
+ [ 'No', false ],
+ [ null, null ],
+ [ 'Other', 'Other' ],
+ [ [ 1, 2, 3 ], [ 1, 2, 3 ] ],
+ [ 'true', 'true' ],
+ [ 'false', 'false' ],
+ ];
+ }
+
+ /**
+ * @dataProvider provider_get_config_class_name
+ */
+ public function test_get_config_class_name( $expected, $file ) {
+ global $gfpdf;
+
+ $this->assertEquals( $expected, $gfpdf->templates->get_config_class_name( $file ) );
+ }
+
+ public function provider_get_config_class_name() {
+ return [
+ [ 'Manage_Document', '/path/to/templates/manage-document.php' ],
+ [ 'Manage_Document', '/path/to/templates/manage_document.php' ],
+ [ 'Manage_Document', '/path/to/templates/manage document.php' ],
+ [ 'Superawesome_Working_Directory', '/my/path/superawesome-working-directory.php' ],
+ [ 'Template', 'template.php' ],
+ ];
+ }
+
+ public function test_backwards_compat_conversion() {
+ $settings = [
+ 'irrelevant' => 'Yes',
+ ];
+
+ $compat = $this->misc->backwards_compat_conversion( $settings, [], [] );
+
+ $this->assertCount( 8, $compat );
+ $this->assertArrayNotHasKey( 'irrelevant', $compat );
+ $this->assertFalse( $compat['premium'] );
+ $this->assertFalse( $compat['rtl'] );
+ $this->assertFalse( $compat['security'] );
+ $this->assertFalse( $compat['pdfa1b'] );
+ $this->assertFalse( $compat['pdfx1a'] );
+ $this->assertEquals( '', $compat['pdf_password'] );
+ $this->assertEquals( '', $compat['pdf_privileges'] );
+ $this->assertEquals( 96, $compat['dpi'] );
+
+ $settings = [
+ 'advanced_template' => 'Yes',
+ 'rtl' => 'Yes',
+ 'image_dpi' => 300,
+ 'security' => 'Yes',
+ 'password' => 'password',
+ 'privileges' => 'privileges',
+ 'format' => 'PDFX1A',
+ ];
+
+ $compat = $this->misc->backwards_compat_conversion( $settings, [], [] );
+
+ $this->assertTrue( $compat['premium'] );
+ $this->assertTrue( $compat['rtl'] );
+ $this->assertTrue( $compat['security'] );
+ $this->assertFalse( $compat['pdfa1b'] );
+ $this->assertTrue( $compat['pdfx1a'] );
+ $this->assertEquals( 'password', $compat['pdf_password'] );
+ $this->assertEquals( 'privileges', $compat['pdf_privileges'] );
+ $this->assertEquals( 300, $compat['dpi'] );
+ }
+
+ public function test_backwards_compat_output() {
+ $this->assertEquals( 'save', $this->misc->backwards_compat_output() );
+ $this->assertEquals( 'view', $this->misc->backwards_compat_output( 'display' ) );
+ $this->assertEquals( 'download', $this->misc->backwards_compat_output( 'download' ) );
+ }
+}
diff --git a/tests/phpunit/integration/Helper/Test_Helper_Misc_Forms.php b/tests/phpunit/integration/Helper/Test_Helper_Misc_Forms.php
new file mode 100644
index 000000000..a8fbac425
--- /dev/null
+++ b/tests/phpunit/integration/Helper/Test_Helper_Misc_Forms.php
@@ -0,0 +1,249 @@
+misc = new Helper_Misc( $gfpdf->log, $gfpdf->gform, $gfpdf->data );
+ }
+
+ public function test_array_unshift_assoc() {
+ $array = [
+ 'item1' => 'Yes',
+ 'item2' => 'Maybe',
+ 'item3' => 'I do not know',
+ ];
+
+ $test = $this->misc->array_unshift_assoc( $array, 'item0', 'No' );
+
+ $this->assertEquals( 'No', reset( $test ) );
+ $this->assertEquals( 'Yes', next( $test ) );
+ $this->assertEquals( 'I do not know', end( $test ) );
+ }
+
+ /**
+ * @dataProvider provider_remove_extension_from_string
+ */
+ public function test_remove_extension_from_string( $expected, $string, $type ) {
+ $this->assertEquals( $expected, $this->misc->remove_extension_from_string( $string, $type ) );
+ }
+
+ public function provider_remove_extension_from_string() {
+ return [
+ [ 'mydocument', 'mydocument.pdf', '.pdf' ],
+ [ 'mydocument', 'mydocument.jpg', '.Jpg' ],
+ [ 'mydocument.pdf', 'mydocument.pdf', '.pda' ],
+ [ 'Helper_Document', 'Helper_Document.php', '.php' ],
+ [ 'カタ_Document', 'カタ_Document.php', '.php' ],
+ [ 'カタ_Document', 'カタ_Document.excel', '.excel' ],
+ [ 'Working', 'Working.excel', '.excel' ],
+ [ 'Working_漢字', 'Working_漢字.pdf', '.pdf' ],
+ ];
+ }
+
+ public function test_evaluate_conditional_logic() {
+ global $gfpdf;
+
+ $form = $this->form( 'all-form-fields' );
+ $entry = $this->entry( 'all-form-fields' );
+
+ $gfpdf->data->form_settings = [];
+ $gfpdf->data->form_settings[ $form['id'] ] = $form['gfpdf_form_settings'];
+
+ $logic['actionType'] = 'show';
+ $this->assertTrue( $this->misc->evaluate_conditional_logic( $logic, $entry ) );
+
+ $logic['actionType'] = 'hide';
+ $this->assertFalse( $this->misc->evaluate_conditional_logic( $logic, $entry ) );
+ }
+
+ public function test_get_fields_sorted_by_id() {
+ $this->assertSame( 0, count( $this->misc->get_fields_sorted_by_id( 0 ) ) );
+
+ $form = $this->form( 'all-form-fields' );
+ $fields = $this->misc->get_fields_sorted_by_id( $form['id'] );
+
+ $this->assertEquals( 56, count( $fields ) );
+ $this->assertEquals( 'Section Break', $fields[10]->label );
+ }
+
+ /**
+ * @dataProvider provider_in_array
+ */
+ public function test_in_array( $expected, $strict, $needle, $haystack ) {
+ $this->assertSame( $expected, $this->misc->in_array( $needle, $haystack, $strict ) );
+ }
+
+ public function provider_in_array() {
+ return [
+ [
+ true,
+ true,
+ 'find me',
+ [
+ 'item 1',
+ 'item 2',
+ 'item 3' => [ 'test', 'find me' ],
+ 'item 4',
+ ],
+ ],
+ [
+ false,
+ true,
+ 20,
+ [
+ 'item 1',
+ 'item 2' => [ 'stuff', 'here', [ '20' ] ],
+ 'item 3',
+ ],
+ ],
+ [
+ true,
+ false,
+ 20,
+ [
+ 'item 1',
+ 'item 2' => [ 'stuff', 'here', [ '20' ] ],
+ 'item 3',
+ ],
+ ],
+ [
+ true,
+ true,
+ 'Find Me',
+ [
+ 'item 1' => [ 'hi', 'how', 'are', [ 'you' => [ 'going' ] ] ],
+ 'item 2' => [ 'stuff', 'here', [ 'Find Me' ] ],
+ 'item 3',
+ ],
+ ],
+ [
+ true,
+ true,
+ 'Find Me',
+ [
+ 'item 1' => [ 'hi', 'how', 'are', [ 'you' => [ 'going' => [ 'Find Me' ] ] ] ],
+ 'item 2' => [ 'stuff', 'here', [ 'wow' ] ],
+ 'item 3',
+ ],
+ ],
+ [
+ false,
+ true,
+ 'find me',
+ [
+ 'item 1',
+ 'item 2' => [ 'stuff', 'here', [ 'Find Me' ] ],
+ 'item 3',
+ ],
+ ],
+ ];
+ }
+
+ public function test_cleanup_dir() {
+ $data = GPDFAPI::get_data_class();
+ $path = $data->template_location . 'folder/';
+ wp_mkdir_p( $path );
+ touch( $path . 'test' );
+
+ $this->assertFileExists( $path . 'test' );
+
+ $this->misc->cleanup_dir( $path );
+
+ $this->assertFileDoesNotExist( $path . 'test' );
+ $this->assertDirectoryExists( $path );
+
+ rmdir( $path );
+
+ $path = sys_get_temp_dir() . '/folder/';
+ wp_mkdir_p( $path );
+ touch( $path . 'test' );
+ $this->assertFileExists( $path . 'test' );
+
+ $this->misc->cleanup_dir( $path );
+
+ $this->assertFileExists( $path . 'test' );
+ unlink( $path . 'test' );
+ rmdir( $path );
+ }
+
+ public function test_rmdir() {
+ $data = GPDFAPI::get_data_class();
+ $path = $data->template_location . 'folder/';
+ wp_mkdir_p( $path );
+ touch( $path . 'test' );
+
+ $this->assertFileExists( $path . 'test' );
+
+ $this->misc->rmdir( $path, false );
+
+ $this->assertFileDoesNotExist( $path . 'test' );
+ $this->assertDirectoryExists( $path );
+
+ touch( $path . 'test' );
+
+ $this->assertFileExists( $path . 'test' );
+
+ $this->misc->rmdir( $path );
+
+ $this->assertFileDoesNotExist( $path . 'test' );
+ $this->assertDirectoryDoesNotExist( $path );
+
+ $path = sys_get_temp_dir() . '/folder/';
+ wp_mkdir_p( $path );
+ $this->assertDirectoryExists( $path );
+
+ $results = $this->misc->rmdir( $path );
+ $this->assertSame( 'gfpdf_rmdir_directory_not_approved', $results->get_error_code() );
+
+ $this->assertDirectoryExists( $path );
+ rmdir( $path );
+ }
+
+ public function test_flatten_array() {
+ $test_array = [
+ 'one' => 'first',
+ 'two' => 'second',
+ ];
+
+ $this->assertSame( [ 'one', 'two' ], $this->misc->flatten_array( $test_array ) );
+ $this->assertSame( [ 'first', 'second' ], $this->misc->flatten_array( $test_array, 'values' ) );
+
+ $test_array = [
+ 'top-one' => [
+ 'one' => 'first',
+ ],
+ 'top-two' => [
+ 'two' => 'second',
+ ],
+ ];
+
+ $this->assertSame( [ 'one', 'two' ], $this->misc->flatten_array( $test_array ) );
+ $this->assertSame( [ 'first', 'second' ], $this->misc->flatten_array( $test_array, 'values' ) );
+
+ $test_array = [
+ [
+ 'top-one' => [
+ 'one' => 'first',
+ ],
+ 'top-two' => [
+ 'two' => 'second',
+ ],
+ ],
+ ];
+
+ $this->assertSame( [ 'top-one', 'top-two' ], $this->misc->flatten_array( $test_array ) );
+ }
+}
diff --git a/tests/phpunit/integration/Helper/Test_Helper_Misc_Pages.php b/tests/phpunit/integration/Helper/Test_Helper_Misc_Pages.php
new file mode 100644
index 000000000..44b9c9330
--- /dev/null
+++ b/tests/phpunit/integration/Helper/Test_Helper_Misc_Pages.php
@@ -0,0 +1,121 @@
+misc = new Helper_Misc( $gfpdf->log, $gfpdf->gform, $gfpdf->data );
+ }
+
+ private function minify( $html ) {
+ return preg_replace(
+ [ '/\n/', '/\t/', '/\>\s+\' ],
+ [ '', '', '><' ],
+ $html
+ );
+ }
+
+ public function test_is_gfpdf_page() {
+ $this->assertFalse( $this->misc->is_gfpdf_page() );
+
+ set_current_screen( 'dashboard-user' );
+ $this->assertFalse( $this->misc->is_gfpdf_page() );
+
+ $_GET['page'] = 'gfpdf-tools';
+ $this->assertTrue( $this->misc->is_gfpdf_page() );
+
+ unset( $_GET['page'] );
+
+ $_GET['subview'] = 'PDF';
+ $this->assertTrue( $this->misc->is_gfpdf_page() );
+ }
+
+ public function test_is_gfpdf_settings_tab() {
+ $this->assertFalse( $this->misc->is_gfpdf_settings_tab( 'general' ) );
+
+ set_current_screen( 'dashboard-user' );
+ $_GET['subview'] = 'PDF';
+
+ $this->assertTrue( $this->misc->is_gfpdf_settings_tab( 'general' ) );
+
+ $this->assertFalse( $this->misc->is_gfpdf_settings_tab( 'tools' ) );
+
+ $_GET['tab'] = 'tools';
+ $this->assertTrue( $this->misc->is_gfpdf_settings_tab( 'tools' ) );
+ }
+
+ /**
+ * @dataProvider provider_test_fix_header_footer
+ */
+ public function test_fix_header_footer( $expected, $html ) {
+ $test_html = $this->misc->fix_header_footer( $html );
+ $minified_html = $this->minify( $test_html );
+
+ $this->assertEquals( $expected, $minified_html );
+ }
+
+ public function provider_test_fix_header_footer() {
+ return [
+ [
+ '',
+ '
',
+ ],
+ [
+ '',
+ '',
+ ],
+ [
+ 'IntroOutro
',
+ 'Intro
Outro',
+ ],
+ [
+ 'This is bold. This is italics
',
+ 'This is bold. This is italics
',
+ ],
+ [
+ '',
+ '
',
+ ],
+ [
+ '
',
+ '
',
+ ],
+ [
+ 'Nothing
',
+ 'Nothing',
+ ],
+ [
+ '',
+ '',
+ ],
+ [
+ '
',
+ '
',
+ ],
+ [
+ '',
+ '
',
+ ],
+ ];
+ }
+
+ public function test_fix_header_footer_path() {
+ $html = $this->misc->fix_header_footer( '
' );
+ $this->assertFalse( strpos( PDF_PLUGIN_URL, $html ) );
+
+ $html = $this->misc->fix_header_footer( '
' );
+ $minified_html = $this->minify( $html );
+ $this->assertEquals( '', $minified_html );
+ }
+}
diff --git a/tests/phpunit/unit-tests/test-helper-mpdf.php b/tests/phpunit/integration/Helper/Test_Helper_Mpdf.php
similarity index 89%
rename from tests/phpunit/unit-tests/test-helper-mpdf.php
rename to tests/phpunit/integration/Helper/Test_Helper_Mpdf.php
index 030fead60..c952e6da0 100644
--- a/tests/phpunit/unit-tests/test-helper-mpdf.php
+++ b/tests/phpunit/integration/Helper/Test_Helper_Mpdf.php
@@ -1,9 +1,8 @@
_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_save_core_font' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_ajax_nonce' );
+ $_POST['font_name'] = 'nothing';
+
+ try {
+ $this->_handleAjax( 'gfpdf_save_core_font' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $this->assertFalse( json_decode( $this->_last_response ) );
+ $this->_last_response = '';
+
+ $_POST['font_name'] = 'Aegean.otf';
+
+ $api_response = function () {
+ return [
+ 'response' => [ 'code' => 200 ],
+ 'body' => '',
+ ];
+ };
+
+ add_filter( 'pre_http_request', $api_response );
+
+ try {
+ $this->_handleAjax( 'gfpdf_save_core_font' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ remove_filter( 'pre_http_request', $api_response );
+
+ $this->assertTrue( json_decode( $this->_last_response ) );
+ }
+}
diff --git a/tests/phpunit/integration/Model/Test_Model_Form_Settings_Ajax.php b/tests/phpunit/integration/Model/Test_Model_Form_Settings_Ajax.php
new file mode 100644
index 000000000..9b842fa09
--- /dev/null
+++ b/tests/phpunit/integration/Model/Test_Model_Form_Settings_Ajax.php
@@ -0,0 +1,207 @@
+form_id = GFAPI::add_form( $json );
+ }
+
+ public function test_change_state_pdf_setting() {
+ global $gfpdf;
+
+ $this->_setRole( 'administrator' );
+ $_POST['fid'] = 0;
+ $_POST['pid'] = $this->pid;
+
+ try {
+ $this->_handleAjax( 'gfpdf_change_state' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_state_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_change_state' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '500', $e->getMessage() );
+
+ $_POST['fid'] = $this->form_id;
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_state_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_change_state' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+
+ $this->assertArrayHasKey( 'state', $response );
+ $this->assertEquals( 'Inactive', $response['state'] );
+
+ unset( $gfpdf->data->form_settings );
+ $pdf = $gfpdf->options->get_pdf( $this->form_id, $this->pid );
+ $this->assertFalse( $pdf['active'] );
+
+ $this->_last_response = '';
+
+ try {
+ $this->_handleAjax( 'gfpdf_change_state' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+
+ $this->assertArrayHasKey( 'state', $response );
+ $this->assertEquals( 'Active', $response['state'] );
+
+ unset( $gfpdf->data->form_settings );
+ $pdf = $gfpdf->options->get_pdf( $this->form_id, $this->pid );
+ $this->assertTrue( $pdf['active'] );
+ }
+
+ public function test_render_template_fields() {
+ try {
+ $this->_handleAjax( 'gfpdf_get_template_fields' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $this->_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_get_template_fields' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_ajax_nonce' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_get_template_fields' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+
+ $this->assertArrayHasKey( 'fields', $response );
+ $this->assertArrayHasKey( 'editors', $response );
+ $this->assertArrayHasKey( 'editor_init', $response );
+ $this->assertArrayHasKey( 'template_type', $response );
+ }
+
+ public function test_duplicate_gf_pdf_settings() {
+ global $gfpdf;
+
+ $this->_setRole( 'administrator' );
+ $_POST['fid'] = 0;
+ $_POST['pid'] = $this->pid;
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_duplicate' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_duplicate_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_duplicate' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '500', $e->getMessage() );
+
+ $_POST['fid'] = $this->form_id;
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_duplicate_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_duplicate' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+
+ $this->assertArrayHasKey( 'msg', $response );
+ $this->assertArrayHasKey( 'pid', $response );
+ $this->assertArrayHasKey( 'name', $response );
+ $this->assertArrayHasKey( 'dup_nonce', $response );
+ $this->assertArrayHasKey( 'del_nonce', $response );
+ $this->assertArrayHasKey( 'state_nonce', $response );
+
+ unset( $gfpdf->data->form_settings );
+ $pdf1 = $gfpdf->options->get_pdf( $this->form_id, $this->pid );
+ $pdf2 = $gfpdf->options->get_pdf( $this->form_id, $response['pid'] );
+
+ $this->assertEquals( $pdf1['name'] . ' (copy)', $pdf2['name'] );
+ $this->assertEquals( $pdf1['template'], $pdf2['template'] );
+ $this->assertEquals( $pdf1['filename'], $pdf2['filename'] );
+
+ $this->_last_response = '';
+ }
+
+ public function test_delete_gf_pdf_setting() {
+ global $gfpdf;
+
+ $pdf = $gfpdf->options->get_pdf( $this->form_id, $this->pid );
+ $this->assertEquals( 'My First PDF Template', $pdf['name'] );
+
+ $this->_setRole( 'administrator' );
+ $_POST['fid'] = 0;
+ $_POST['pid'] = $this->pid;
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_delete' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_delete_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_delete' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '500', $e->getMessage() );
+
+ $_POST['fid'] = $this->form_id;
+ $_POST['nonce'] = wp_create_nonce( "gfpdf_delete_nonce_{$_POST['fid']}_{$_POST['pid']}" );
+
+ try {
+ $this->_handleAjax( 'gfpdf_list_delete' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+
+ $this->assertArrayHasKey( 'msg', $response );
+
+ unset( $gfpdf->data->form_settings );
+ $pdf = $gfpdf->options->get_pdf( $this->form_id, $this->pid );
+ $this->assertTrue( is_wp_error( $pdf ) );
+ }
+}
diff --git a/tests/phpunit/unit-tests/Model/Test_Model_Mergetags.php b/tests/phpunit/integration/Model/Test_Model_Mergetags.php
similarity index 99%
rename from tests/phpunit/unit-tests/Model/Test_Model_Mergetags.php
rename to tests/phpunit/integration/Model/Test_Model_Mergetags.php
index ef096abc1..42f73f0d0 100644
--- a/tests/phpunit/unit-tests/Model/Test_Model_Mergetags.php
+++ b/tests/phpunit/integration/Model/Test_Model_Mergetags.php
@@ -6,7 +6,7 @@
use GFPDF\Controller\Controller_Shortcodes;
use GFPDF\Helper\Helper_Url_Signer;
use GPDFAPI;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -22,7 +22,7 @@
* @group model
* @group tags
*/
-class Test_Model_Mergetags extends WP_UnitTestCase {
+class Test_Model_Mergetags extends TestCase {
/**
* @var Controller_Shortcodes
diff --git a/tests/phpunit/unit-tests/Model/Test_Model_Pdf.php b/tests/phpunit/integration/Model/Test_Model_Pdf.php
similarity index 98%
rename from tests/phpunit/unit-tests/Model/Test_Model_Pdf.php
rename to tests/phpunit/integration/Model/Test_Model_Pdf.php
index 70601fcb5..2bb318ae1 100644
--- a/tests/phpunit/unit-tests/Model/Test_Model_Pdf.php
+++ b/tests/phpunit/integration/Model/Test_Model_Pdf.php
@@ -6,7 +6,7 @@
use GFPDF\Controller\Controller_PDF;
use GFPDF\Helper\Helper_Url_Signer;
use GFPDF\View\View_PDF;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -22,7 +22,7 @@
* @group model
* @group pdfs
*/
-class Test_Model_PDF extends WP_UnitTestCase {
+class Test_Model_PDF extends TestCase {
/**
* @var Controller_PDF
diff --git a/tests/phpunit/unit-tests/Model/Test_Model_Pdf_Meta_Box.php b/tests/phpunit/integration/Model/Test_Model_Pdf_Meta_Box.php
similarity index 97%
rename from tests/phpunit/unit-tests/Model/Test_Model_Pdf_Meta_Box.php
rename to tests/phpunit/integration/Model/Test_Model_Pdf_Meta_Box.php
index c72ab1ef1..437e0a927 100644
--- a/tests/phpunit/unit-tests/Model/Test_Model_Pdf_Meta_Box.php
+++ b/tests/phpunit/integration/Model/Test_Model_Pdf_Meta_Box.php
@@ -5,7 +5,7 @@
use GFPDF\Controller\Controller_PDF;
use GFPDF\Helper\Helper_Url_Signer;
use GFPDF\View\View_PDF;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -21,7 +21,7 @@
* @group model
* @group pdf
*/
-class Test_Model_Pdf_Meta_Box extends WP_UnitTestCase {
+class Test_Model_Pdf_Meta_Box extends TestCase {
/**
* @var Controller_PDF
diff --git a/tests/phpunit/unit-tests/Model/Test_Model_Settings.php b/tests/phpunit/integration/Model/Test_Model_Settings.php
similarity index 98%
rename from tests/phpunit/unit-tests/Model/Test_Model_Settings.php
rename to tests/phpunit/integration/Model/Test_Model_Settings.php
index ce9047dc8..1d6ddff4a 100644
--- a/tests/phpunit/unit-tests/Model/Test_Model_Settings.php
+++ b/tests/phpunit/integration/Model/Test_Model_Settings.php
@@ -7,7 +7,7 @@
use GFPDF\Helper\Helper_Logger;
use GFPDF\Helper\Helper_Notices;
use GFPDF\Helper\Helper_Singleton;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -21,7 +21,7 @@
* @group model
* @group settings
*/
-class Test_Model_Settings extends WP_UnitTestCase {
+class Test_Model_Settings extends TestCase {
/**
* @var Model_Settings
diff --git a/tests/phpunit/integration/Model/Test_Model_Settings_Ajax.php b/tests/phpunit/integration/Model/Test_Model_Settings_Ajax.php
new file mode 100644
index 000000000..66c9cbd80
--- /dev/null
+++ b/tests/phpunit/integration/Model/Test_Model_Settings_Ajax.php
@@ -0,0 +1,33 @@
+_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_deactivate_license' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_deactivate_license' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_deactivate_license' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $this->assertStringContainsString( 'An unknown error occurred', json_decode( $this->_last_response )->error );
+ }
+}
diff --git a/tests/phpunit/unit-tests/Model/Test_Model_System_Report.php b/tests/phpunit/integration/Model/Test_Model_System_Report.php
similarity index 96%
rename from tests/phpunit/unit-tests/Model/Test_Model_System_Report.php
rename to tests/phpunit/integration/Model/Test_Model_System_Report.php
index 6ebe08f00..94da822e8 100644
--- a/tests/phpunit/unit-tests/Model/Test_Model_System_Report.php
+++ b/tests/phpunit/integration/Model/Test_Model_System_Report.php
@@ -5,7 +5,7 @@
use GFPDF\Helper\Helper_Templates;
use GFPDF_Major_Compatibility_Checks;
-use WP_UnitTestCase;
+use GFPDF\Tests\Integration\TestCase;
/**
* @package Gravity PDF
@@ -21,7 +21,7 @@
* @group model
* @group system-report
*/
-class Test_Model_System_Report extends WP_UnitTestCase {
+class Test_Model_System_Report extends TestCase {
/**
* @var Model_System_Report
diff --git a/tests/phpunit/integration/Model/Test_Model_Templates_Ajax.php b/tests/phpunit/integration/Model/Test_Model_Templates_Ajax.php
new file mode 100644
index 000000000..a0935974a
--- /dev/null
+++ b/tests/phpunit/integration/Model/Test_Model_Templates_Ajax.php
@@ -0,0 +1,91 @@
+_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_upload_template' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_ajax_nonce' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_upload_template' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '400', $e->getMessage() );
+ }
+
+ public function test_ajax_process_delete_template() {
+ global $gfpdf;
+
+ $this->_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_delete_template' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_ajax_nonce' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_delete_template' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '400', $e->getMessage() );
+
+ $file = $gfpdf->data->template_location . 'zadani.php';
+ touch( $file );
+
+ $_POST['id'] = 'zadani';
+
+ try {
+ $this->_handleAjax( 'gfpdf_delete_template' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $response = json_decode( $this->_last_response, true );
+ unset( $this->_last_response );
+
+ $this->assertTrue( $response );
+ $this->assertFileDoesNotExist( $file );
+ }
+
+ public function test_ajax_process_build_template_options_html() {
+ $this->_setRole( 'administrator' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_get_template_options' );
+ } catch ( WPAjaxDieStopException $e ) {
+ }
+
+ $this->assertEquals( '401', $e->getMessage() );
+
+ $_POST['nonce'] = wp_create_nonce( 'gfpdf_ajax_nonce' );
+
+ try {
+ $this->_handleAjax( 'gfpdf_get_template_options' );
+ } catch ( WPAjaxDieContinueException $e ) {
+ }
+
+ $this->assertNotFalse( $this->_last_response, '