Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
9.7 KiB
WP Multi City — Manual QA Checklist
End-to-end verification against a real WordPress installation. Run through every section before any release. Each section maps to a specific acceptance criterion from the MVP epic (cp-cw4).
Test environment:
- WordPress 6.0+ (clean install, no other plugins beyond ACF Pro)
- PHP 8.0+
- Advanced Custom Fields Pro 6.0+ activated
Tester: ______________ Date: ______________ Plugin version: ______________
1. Plugin activation
cp-cw4 acceptance: "Плагин активируется на чистом WP с ACF Pro"
Preconditions: clean WP 6.0+ with activated ACF Pro.
Steps:
- Copy plugin to
wp-content/plugins/wp-multi-city/. - Go to admin → Plugins → activate "WP Multi City".
Expected:
-
No PHP errors in
debug.log(withWP_DEBUG=true). -
No admin notice about missing ACF Pro.
-
Admin menu shows: "Шорткоды" (top-level), "WP Multi City" (top-level).
-
Section pass
2. ACF dependency notice
Preconditions: WP with ACF Pro deactivated.
Steps:
- Deactivate ACF Pro.
- Refresh admin.
Expected:
- Admin notice visible: "WP Multi City: требуется активный плагин Advanced Custom Fields Pro".
- No "Шорткоды" or "WP Multi City" menu items (plugin self-disabled).
After verification, reactivate ACF Pro for the rest of the checklist.
- Section pass
3. Taxonomy town admin UI
cp-cw4 acceptance: "В админке создаётся taxonomy town"
Preconditions: §1 passed.
Steps:
- Open admin → Шорткоды → Города.
- Create term with slug
spb, name "Санкт-Петербург". - Create term with slug
tyumen, name "Тюмень".
Expected:
-
Both terms appear in the list.
-
No "Hierarchical" parent-picker (taxonomy is flat).
-
No public archive page accessible at
/town/spb/(404 expected — taxonomy is non-public). -
Section pass
4. CPT shortcode-town admin UI
cp-cw4 acceptance: "В админке создаётся CPT shortcode-town"
Preconditions: §3 passed.
Steps:
- Open admin → Шорткоды → Добавить.
- Observe the edit screen.
- Set title "Shortcode #1", save as Published (or Draft).
- Note the post ID (e.g., 42) from the URL.
Expected:
-
Edit screen shows: title field + ACF repeater "Подстановки по городам" (
list_shortcode) with two sub-fields "Город" (taxonomy dropdown of town terms) + "Значение" (textarea). -
No editor / content / featured-image fields (CPT
supports=['title']only). -
Post saves successfully.
-
Section pass
5. Options page UI
Preconditions: §3 passed.
Steps:
- Open admin → WP Multi City.
Expected:
-
ACF options page renders with 4 fields:
main_town_slug— taxonomy dropdown of town terms.index_town_alt— page picker.page_unic— repeater with 3 sub-fields (page_old, page_new, town).page_no_rep— repeater with 1 sub-field (link).
-
Set
main_town_slug = spband save. -
Section pass
6. Helper functions smoke
cp-cw4 acceptance: "Шаблоны темы могут вызвать current_town(), _alt_get_field()"
Preconditions: §5 passed. Create page "About" via admin → Pages → Add New, slug about, note ID (e.g., 10).
Steps:
-
Add this to
wp-content/themes/<active-theme>/functions.php(or as a mu-plugin):add_action('wp_footer', function () { error_log('wpmc_current_town: ' . var_export(wpmc_current_town(), true)); error_log('wpmc_remove_domain_link: ' . wpmc_remove_domain_link('https://example.com/foo/')); $term = wpmc_get_term_by_slug('tyumen'); error_log('wpmc_get_term_by_slug: ' . ($term ? $term->slug : 'null')); }); -
Open
/tyumen/about/(front-end). Checkwp-content/debug.log. -
Open
/(front-end). Check log again.
Expected:
/tyumen/about/log:wpmc_current_town: 'tyumen',wpmc_remove_domain_link: '/foo/',wpmc_get_term_by_slug: 'tyumen'./log:wpmc_current_town: 'spb'(from main_town_slug option fallback).
Clean up: remove the debug snippet from functions.php.
- Section pass
7. [shortcode_dmr] resolves by current town
cp-cw4 acceptance: "[shortcode_dmr id=N] подменяется на значение для текущего города"
Preconditions: §6 passed. Open the shortcode-town post created in §4 (ID=42). Add two rows to list_shortcode:
- Row 1: Город = spb, Значение =
СПб-текст - Row 2: Город = tyumen, Значение =
Тюмень-текст
Save the shortcode-town post.
Steps:
- Create page "Test", slug
test, content:[shortcode_dmr id=42]. Publish. - Open
/tyumen/test/. - Open
/test/(no city prefix). - Open
/nonexistent-city/test/(no matching term).
Expected:
-
/tyumen/test/displaysТюмень-текст. -
/test/displaysСПб-текст(current_town falls back to main_town_slug=spb). -
/nonexistent-city/test/displaysСПб-текст(first segment not a town slug → main town fallback) or empty depending on permalink resolution. -
Section pass
8. URL router patches $wp_query
cp-cw4 acceptance: "На /{city-slug}/{page-slug}/ открывается соответствующая страница"
Preconditions: §6 passed (page About ID=10 exists). §5 with main_town_slug=spb.
Steps:
- Open
/tyumen/about/. - View page source (or template debug output) — check
get_queried_object_id()value. - Open
/spb/about/.
Expected:
-
/tyumen/about/loads page About (id=10) content;get_queried_object_id()= 10. -
In a template,
get_query_var('town')returns'tyumen'. -
/spb/about/returns 404 OR loads About — main town routing is a no-op (Router skips), so default WP rewrite rules govern. -
Section pass
9. /{slug}/ root with main_page_alt
Preconditions: §8 passed. Create page "Главная Тюмень", slug glavnaya-tyumen, note ID (e.g., 20). On the tyumen term (admin → Шорткоды → Города → tyumen → Edit), set ACF field main_page_alt = 20. Save term.
Steps:
- Open
/tyumen/.
Expected:
-
Page Главная Тюмень (id=20) loads.
-
is_front_page()returns true in template. -
get_queried_object_id()returns 20. -
Section pass
10. page_unic clone swap
cp-cw4 acceptance: "На /{city-slug}/{page-slug}/ открывается соответствующая страница" (with clone)
Preconditions: §8 passed (page About ID=10 exists). Create clone page "About-Тюмень", slug about-tyumen, ID=21, content "Тюменский About-content".
In admin → WP Multi City → page_unic, add one row: page_old=10 (About), page_new=21 (About-Тюмень), town=tyumen. Save options.
Steps:
- Open
/tyumen/about/. - Inspect page content.
Expected:
-
URL bar stays
/tyumen/about/. -
Content rendered is "Тюменский About-content" (from page 21, the clone).
-
get_queried_object_id()returns 21 (not 10). -
Section pass
11. URL filters — town prefix injection
cp-cw4 acceptance: (implied) Links in templates auto-prefix with city slug.
Preconditions: §8 passed. The active theme renders a primary navigation menu. Add menu items: link to /uslugi/ (custom link), link to page About.
Steps:
- Open
/tyumen/about/. - Inspect HTML of menu items (view source). Inspect any ACF link field used by the theme. Add a template snippet that outputs
home_url('/contact/')andhome_url('/tyumen/contact/')separately. - Open
/(main town context).
Expected on /tyumen/about/:
- Custom menu link
/uslugi/rendered ashttps://<host>/tyumen/uslugi/(or/tyumen/uslugi/). - About menu link (originally
/about/) rendered with/tyumen/prefix. home_url('/contact/')returnshttps://<host>/tyumen/contact/.home_url('/tyumen/contact/')returnshttps://<host>/tyumen/contact/(no double-prefix).- ACF link field
/internal/value rendered as/tyumen/internal/. - mailto: and tel: links unchanged.
- External
https://other-domain.com/links unchanged.
Expected on /:
-
All URLs unchanged (current_town == main_town_slug → no prefix).
-
Section pass
12. page_no_rep exclusions
Preconditions: §11 passed. In admin → WP Multi City → page_no_rep, add row: link = /privacy/. Save. Create page "Privacy", slug privacy, ID=30.
Steps:
- Open
/privacy/. - Open
/tyumen/privacy/. - Check menu/template link to
/privacy/from a/tyumen/...context.
Expected:
-
/privacy/loads page Privacy normally, no city routing. -
/tyumen/privacy/— Router does not patch$wp_query(path excluded); WP either 404s or falls back depending on rewrite rules. -
A link to
/privacy/rendered from/tyumen/about/stays/privacy/(NOT prefixed to/tyumen/privacy/). -
Section pass
13. REST API URL
cp-cw4 acceptance: (implied) REST API URL correct under host alias / proxy.
Preconditions: REST API enabled (default).
Steps:
- In a template, add:
<?php echo esc_html(rest_url('wp/v2/posts')); ?>. Render any front-end page. - Open the printed URL in browser or via curl.
Expected:
-
Rendered URL:
https://<HTTP_HOST>/wp-json/wp/v2/posts(HTTPS forced, HTTP_HOST used). -
HTTP request returns JSON array of posts (200 OK).
-
If
HTTP_HOSTdiffers fromsite_url()'s host, the rendered URL prefersHTTP_HOST. -
Section pass
Release sign-off
- All 13 sections passed
- PHPUnit suite green (
vendor/bin/phpunit→ OK 98 tests, 175 assertions) - PHP lint clean (
for f in includes/*.php; do php -l "$f"; done→ "No syntax errors detected" for every file) - Tested on WP 6.0+ with PHP 8.0+
- No PHP notices/warnings in
wp-content/debug.log
Tester signature: ______________ Date: ______________