Files
2026-05-17 00:11:13 +05:00

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:

  1. Copy plugin to wp-content/plugins/wp-multi-city/.
  2. Go to admin → Plugins → activate "WP Multi City".

Expected:

  • No PHP errors in debug.log (with WP_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:

  1. Deactivate ACF Pro.
  2. 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:

  1. Open admin → Шорткоды → Города.
  2. Create term with slug spb, name "Санкт-Петербург".
  3. 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:

  1. Open admin → Шорткоды → Добавить.
  2. Observe the edit screen.
  3. Set title "Shortcode #1", save as Published (or Draft).
  4. 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:

  1. 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 = spb and 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:

  1. 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'));
    });
    
  2. Open /tyumen/about/ (front-end). Check wp-content/debug.log.

  3. 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:

  1. Create page "Test", slug test, content: [shortcode_dmr id=42]. Publish.
  2. Open /tyumen/test/.
  3. Open /test/ (no city prefix).
  4. 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:

  1. Open /tyumen/about/.
  2. View page source (or template debug output) — check get_queried_object_id() value.
  3. 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:

  1. 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:

  1. Open /tyumen/about/.
  2. 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:

  1. Open /tyumen/about/.
  2. 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/') and home_url('/tyumen/contact/') separately.
  3. Open / (main town context).

Expected on /tyumen/about/:

  • Custom menu link /uslugi/ rendered as https://<host>/tyumen/uslugi/ (or /tyumen/uslugi/).
  • About menu link (originally /about/) rendered with /tyumen/ prefix.
  • home_url('/contact/') returns https://<host>/tyumen/contact/.
  • home_url('/tyumen/contact/') returns https://<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:

  1. Open /privacy/.
  2. Open /tyumen/privacy/.
  3. 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:

  1. In a template, add: <?php echo esc_html(rest_url('wp/v2/posts')); ?>. Render any front-end page.
  2. 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_HOST differs from site_url()'s host, the rendered URL prefers HTTP_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: ______________