Skip to content

Close the A-Z Filters automated-coverage gap (GVAZ-9/10/11/12) - #66

Merged
Mwalek merged 6 commits into
developfrom
test/az-coverage-gap
Aug 6, 2026
Merged

Close the A-Z Filters automated-coverage gap (GVAZ-9/10/11/12)#66
Mwalek merged 6 commits into
developfrom
test/az-coverage-gap

Conversation

@Mwalek

@Mwalek Mwalek commented Aug 6, 2026

Copy link
Copy Markdown

Closes the automated-coverage gap on the A-Z Entry Filter widget: 19 new tests, taking the suite from 13 to 32 (17 PHPUnit, 15 Playwright).

Important

This branch contains #65 rebased onto develop. The first four commits are the GVAZ-1 work already under review there, so this diff includes them. The new coverage needs both #65's Playwright harness and the GVAZ-9/10/11/12 fixes on develop, and neither base carries both. Merge #65 first, or close it in favour of this branch.

Why the harness moved

The Playwright harness lived only on fix/az-search-request; the fixes lived only on develop. The rebase applied with zero conflictstests/bootstrap.php was already byte-identical on both branches, so the expected conflict never materialised.

PHPUnit (6)

Test Issue
test_zero_nine_bucket_on_a_form_field_matches_field_values GVAZ-9
test_zero_nine_bucket_created_by_without_digit_display_names_matches_nothing GVAZ-9
test_query_building_leaves_the_sql_filter_registry_at_its_baseline GVAZ-11
test_spent_letter_expressions_do_not_rewrite_a_later_query GVAZ-11
test_accented_display_name_groups_under_its_base_letter GVAZ-12
test_swedish_a_ring_and_umlauts_stay_separate_letters_under_a_binary_collation GVAZ-12

Playwright (13)

Bug-derived: an active letter leaves another View on the same page untouched; the same View embedded twice returns the same rows; a letter plus a Search Bar search keeps the search matching its own field; the Created By 0-9 bucket resolves through the author display name.

Unguarded paths: the bar renders the whole configured alphabet; Russian, Greek and Swedish alphabets render and filter; the localization filter replaces the letters; "Show All" clears the filter and is offered only while one is active; a letter with no matches shows the empty state; the letter survives paging.

Catch-the-bug gate

Each bug-derived test was run against the pre-fix widget (widget file only — a plain revert also deletes the test files it builds on).

Red pre-fix, green post-fix: ..._created_by_without_digit_display_names_matches_nothing (3 rows vs 0) · ..._sql_filter_registry_at_its_baseline (leaked callback) · ..._swedish_a_ring_and_umlauts... (3 vs 1) · azSearchBarCombination · azMultiViewIsolation → untouched View · azCreatedByDigits.

Reported honestly rather than counted as coverage:

  • test_zero_nine_bucket_on_a_form_field_matches_field_valuesgreen both ways. Guards the control-flow restructure; does not catch the original bug.
  • test_accented_display_name_groups_under_its_base_lettergreen both ways. Accent folding is database collation behaviour that neither fix changed; characterisation only.
  • test_spent_letter_expressions_do_not_rewrite_a_later_query — red pre-fix, but structurally: the once-registration is new API, so it fails on "not registered" rather than on an observed double-wrap. Weaker signal than the others.
  • azMultiViewIsolationsame View twicegreen both ways. A stacked LOWER(LOWER(x)) is functionally idempotent, so rows stay correct pre-fix. Regression guard, not a bug-catcher.

Happy-path tests have no fix to revert, so they were proven by mutation: truncating every alphabet to a,b,c turned all five alphabet tests red; forcing get_filter_letter() to false turned all four navigation tests red.

Flakiness gate

Three consecutive clean runs each — Playwright 15/15 ×3, PHPUnit 17/17 ×3 on PHP 7.4 and 8.0 — plus one E2E_WORKERS=1 run matching CI's serialised config.

One genuine flake was found and fixed at the root, not with a longer timeout: getByRole('link', { name: 'Show All' }) matches accessible names by substring, and the fixture View titles ("AZ Show All Absent") render as title links, so the locator was matching the page title instead of the widget. Replaced with a markup-scoped showAllLink() helper and verified 20/20 under --repeat-each=5.

Harness additions

tests/E2E/mu-plugins/e2e-view-fields.php gains two routes — publish a page of View shortcodes (the fixtures API exposes one View per call and can't express two Views rendering in one request), and attribute entries to an author with a known display name — plus two opt-in levers: a custom alphabet for the localization-filter test, and a binary collation forced by ?e2e_collation=bin. That last one matters: without a forced collation the letter rewrite only adds LOWER(), which is harmless on a case-insensitive column, so a leaked rewrite is invisible from the browser. It is what makes the GVAZ-10 and GVAZ-11 specs able to fail at all.

Not covered

A-Z View + DataTables View on one page. DataTables is a separate commercial GravityView extension, absent from this harness and from package.json, so the scenario is not reproducible here. Substituted the same View embedded twice test, which exercises the same multi-query rewrite path — with the caveat above that it is green both ways.

Reviewer notes

  • Specs use ESM import to match the existing specs, not the CommonJS used by the helpers.
  • Running the suite locally needs cp .env.example .env once (gitignored; CI already does this), otherwise the fixtures API has no baseUrl.
  • The mu-plugin is bind-mounted as a single file, so editing it swaps the inode and silently 404s every gk-e2e/v1 route until npm run tests:e2e:setup re-mounts it.

💾 Build file (d71c483).

zackkatz and others added 6 commits August 6, 2026 14:25
The widget filters by the `letter` GET parameter and applies its own
gravityview/view/query GF_Query condition, but never told GravityView that
`letter` is a search. So core's is_search() ignored `?letter=`, and a "Hide
entries until search" View stayed hidden when a visitor clicked a letter
(ViewRenderer/Widget gate entry loading on is_search()). Downstream consumers
missed it too (DataTables' filtered-count total, GVDT-44/GVDT-45).

The widget now registers with core:
- gk/gravityview/search/request/search-arguments: adds `letter` so the request
  counts as a search (GravityView 2.55+).
- gk/gravityview/search/request/filters: strips `letter` back out of the built
  filters, since it is not a form field (gf_query_filter applies the filtering).
- gravityview/widget/hide_until_searched (@SInCE 1.5.4): reveals the View when a
  letter is active, covering GravityView older than the search-request pipeline.

Adds the extension's first PHPUnit suite (gkunit): reproduces the miss and
covers all three hooks. Green on PHP 7.4 and 8.0.
…hed Views

Adds the extension's first browser E2E setup (wp-env + Playwright via
@gravitykit/e2e-bootstrap/e2e-fixtures), mounting AZ-Filters, GravityView,
and Gravity Forms. Seeds a Cities form, entries, a List View, and the
az_filter widget through the fixtures REST routes (no editor UI).

Two specs:
- hideUntilSearchedAZReveal: an active ?letter= reveals only the matching
  entries on a hide-until-searched View (fails on the pre-fix widget).
- azFilterNoHideRegression: plain A-Z filtering still returns the correct
  entries per letter on a View without hide-until-searched.
Wires the test suites into CI, mirroring the sibling GravityView-extension
pattern (DataTables): a `prepare` job provisions Gravity Forms and a
GravityView source checkout (git clone + composer install, so its tests/
bootstrap and prefixed Foundation exist), then:
- run_unit_tests: npx @gravitykit/phpunit test --parallel (PHP 7.4 + 8.0)
- run_e2e_tests: boots wp-env and runs the Playwright specs

GravityView + Gravity Forms reach wp-env via WP_ENV_PLUGINS from .env in CI;
locally, wp-env.config.js auto-mounts the sibling repos when present. Release
build gates on the unit tests, matching the sibling repos.

Validated locally: phpunit test --parallel passes on 7.4 and 8.0, the E2E
specs pass, and `circleci config validate` passes.
Adds the cases the GVAZ-9 and GVAZ-10/11/12 fixes did not take:

- The 0-9 bucket on a normal form field still matches field values, so the
  Created By branch does not capture it.
- A Created By 0-9 bucket that matches no display name returns nothing rather
  than every entry.
- Building a View query leaves the gform_gf_query_sql registry at its baseline,
  asserted on the callback count rather than on results.
- Comparisons spent by one query do not rewrite a later query in the request.
- An accented display name groups under its base letter where the collation
  folds accents.
- Swedish a-ring and umlauts stay separate letters under a binary collation.
Takes the A-Z front end from two specs to fifteen.

Bug-derived: an active letter leaves another View on the same page untouched,
the same View embedded twice returns the same rows, a letter combined with a
Search Bar search keeps the search matching its own field, and the Created By
0-9 bucket resolves through the author display name.

Unguarded paths: the bar renders the whole configured alphabet, three localized
alphabets (Russian, Greek, Swedish) render and filter, an alphabet added through
the localization filter replaces the letters, "Show All" clears the filter and
appears only while one is active, a letter with no matches shows the empty
state, and the letter survives paging.

The harness mu-plugin gains routes to publish a page of View shortcodes and to
attribute entries to an author with a known display name, plus two opt-in test
levers: a custom alphabet, and a binary collation forced by ?e2e_collation=bin.
The collation lever is what makes a leaked rewrite observable from the browser.
@linear-code

linear-code Bot commented Aug 6, 2026

Copy link
Copy Markdown

GVAZ-9

@coderabbitai

coderabbitai Bot commented Aug 6, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • main

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8c58a0f5-0969-4a8a-a0f8-6af990b2cbde

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@Mwalek
Mwalek merged commit 49c831e into develop Aug 6, 2026
2 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants