BlueTheme Docs

Quality checks (Phase 10)

BlueTheme is sold as a commercial theme (its own site or a marketplace), not through the WordPress.org directory. So it keeps features WordPress.org counts as "plugin territory", and these are listed below as known exceptions.

Code checks (10a)

Check How Result
WordPress Coding Standards composer run lint (PHPCS, WPCS 3, PHPCompatibilityWP for PHP 8.0+) Clean
Unit tests composer run test 280 tests pass
PHP 8.0 syntax every PHP file of the theme and the child theme through php -l on PHP 8.0.30 (Docker php:8.0-cli) 423 files, no errors
Newer PHP functions search for functions added after PHP 8.0 (array_is_list, json_validate, mb_str_pad, array_find…) and 8.1+ syntax (enums, readonly, never) None used
Theme Check the Theme Check plugin's wp theme-check run bluetheme Only the exceptions below remain
Notices and errors pnpm qa:crawl -- --demos (below): both dev sites, plain and with each of the six demos, about 25 pages and 18 admin screens per run None from the theme in all 14 runs

Theme Check: known exceptions

Plugin territory, part of the product:

  • register_post_type() and register_taxonomy(): Portfolio, Clients, Testimonials, Offers, Slides and Templates.
  • add_shortcode(): [bluetheme_wishlist], the built-in wishlist page.
  • upload_mimes: uploading custom fonts (woff2) and Lottie files.
  • Removing the oEmbed discovery links and host script: optional performance switches in Theme Options → Global → Performance.
  • Hiding the admin bar: only inside the live builder's canvas and the template preview, which are editing views.

Intended, and safe:

  • base64_encode() in Options/Admin/OptionsPage.php: the admin menu icon as an SVG data URI, the way WordPress documents menu icons.
  • file_put_contents(): the generated CSS files. readfile(): sending the demo export download.
  • Menus without a theme location (Menu element and Menu widget): the user picks a menu. The widget passes the menu's id; Theme Check reads its menu key as a menu name.
  • "WordPress" spelling: the names of the WordPress logo icons in the bundled icon packs, and package names inside the minified builder script.
  • Favicon: the theme's favicon option replaces the Site Icon only when it is set (a BeTheme-style option).

Recommended features left out: block patterns and block styles (the theme has its own pre-built sections), and custom-header / custom-background support (Theme Options cover both).

Fixed in 10a

  • Text domain: six WooCommerce labels in the product elements used WooCommerce's text domain to reuse its translations. They now use bluetheme, with Persian translations.
  • Cookie notice: embeds held until consent were stored as base64 in an attribute; they are now kept in an inert <template> element.
  • Translate options: run-time lookups of the listed strings use translate() instead of __() with a variable.
  • WordPress content classes: styles for alignleft, alignright, aligncenter, wp-caption, wp-caption-text, gallery-caption, sticky and bypostauthor in classic content.

Accessibility (10b)

Target: WCAG 2.1 level AA.

Check How Result
Automated rules pnpm qa:a11y (axe-core, WCAG 2.1 A and AA rules) No failures from the theme or the demos in all 14 runs (both sites, plain and with each demo)
Keyboard use pnpm test:e2e (tests/e2e/keyboard.spec.js, 11 tests) All pass

The audit

pnpm qa:a11y                  # both dev sites
pnpm qa:a11y -- --site=fa     # one site
pnpm qa:a11y -- --demos       # also each bundled demo, imported and removed in turn

tools/qa-a11y.mjs uses the same address list as the crawl. It checks:

  • every kind of front-end page as a visitor, at desktop width, with reduced motion so faded-in content is not read as low contrast;
  • the front page at phone width (the mobile header);
  • the theme's admin screens: every subsection of Theme Options, the setup wizard and the Templates list. The admin bar, side menu, footer, update notice and the list table's own filters and titles are WordPress's and are left out.

It prints each failing rule once, with up to eight elements and four pages it failed on.

The live builder is an editing tool; it is covered by the keyboard tests rather than by axe.

Keyboard tests

tests/e2e/keyboard.spec.js runs on the English dev site, on the "Keyboard sample" page (tools/wp-env/keyboard-sample.php: a link that opens a popup, and a "Mega menu" item in the Main menu) and on "Elements sample":

  • the skip link is the first stop and moves focus past the header;
  • desktop submenus open with Enter, close with Escape (focus back on their button) and when focus leaves the menu;
  • the mega menu opens, its links are next in the tab order, Escape closes it;
  • the search panel puts focus in its field, closes with Escape and gives focus back; searching works from the keyboard;
  • the side cart and the mobile menu are dialogs that keep focus inside, close with Escape and give focus back;
  • a popup is modal: focus starts inside, never reaches the page behind, Escape closes it and focus returns to the link that opened it;
  • tabs follow the arrow keys, Home and End, with only the selected tab in the tab order;
  • accordion items open and close with Enter and Space;
  • slider arrows are named buttons that change the slide;
  • the login form's fields are labelled and an empty submit announces an error.

Fixed in 10b

  • Posts without an author: the author link was printed empty. The byline and the author box are now left out.

  • Linked images without alt text (Image, Hover Box, Zoom Box, Clients, Clients slider): the link now gets a name — the caption or the client's name, else the image's title in the Media Library.

  • Video play button: its name ("Play: title") did not contain its visible text ("Plays from youtube.com"). The name is now hidden text inside the button, followed by the visible note.

  • Product card buttons in Persian: WooCommerce's Persian translation names the "Read more" button with different words than its label. When a button's name does not contain its label, the theme names it "label: «product»".

  • Contrast: Feature Box text was faded to 90%, below 4.5:1 on the default blue; Slider captions were faded too, and a slide without an image sat on the light page color under white text. Both now use full color, and imageless slides use the dark heading color.

  • Theme Options: the slider fields (such as Site width) had no accessible name. The panel's toolbar also covered the first navigation item and the section title: the panel's overflow: hidden made the panel, not the window, the toolbar's scroll container. It now uses overflow: clip.

  • Search panel: focus now goes to the search field when the panel opens, so typing can start at once.

  • Muted text: the default muted gray (#6b7280, also the default for breadcrumbs, post meta and "Out of stock") was just under 4.5:1 on light tinted sections. It is now #656c79.

  • Transparent header preset: its button was an outline in the primary color, which can be too dark over the front page image. It is now a filled button. Over the image, the current menu item is now white like the other links (the menu style's underline or pill still marks it), not the primary color.

  • Demo colors: the primary color of four demos was too light for text and outline buttons on their light backgrounds, and for white text on their buttons. They were darkened and the packages rebuilt:

    Demo Was Now
    Agency #ff5a36 (with dark button text) #d22600 (with white button text, and a filled header button)
    Clothing shop #b45309 #ad5009
    Cosmetics shop #c2577a #ba436a
    Home decor shop #7c9473 #5f7257

    The product swatch colors and the illustrations keep their original colors: they are not text.

Not the theme's

  • The Persian dev site's own "test" header template is transparent with light text on every page, over light page sections. That is that template's setting.

Performance (10c)

Target: a Lighthouse performance score of 90 or more on the mobile profile (simulated slow 4G and a mid-range phone) for the front page of each demo, a blog post (Corporate demo) and a product page (Clothing shop demo), on both dev sites.

pnpm qa:perf                         # both dev sites, every demo
pnpm qa:perf -- --site=en            # one site
pnpm qa:perf -- --demo=corporate     # some demos (comma-separated)

tools/qa-perf.mjs imports each demo, measures its pages and removes it. Full reports are saved in test-results/lighthouse/.

What is measured. The dev sites run PHP in Docker on Windows, with no page cache: they take 4–10 seconds to build a page, which says nothing about the theme. So each page is measured on a static copy (tools/qa-mirror.mjs): a local server that fetches every file from the dev site once and then serves it from memory, compressed, the way a host with a page cache does. The dev site's own time is printed next to each score. WordPress's scripts are minified for the run (SCRIPT_DEBUG off, as on a live site).

Results (all 16 pages reach the target):

Page English Persian
Agency front page 94 99
Corporate front page 96 93
Corporate blog post 96 94
Clothing shop front page 94 91
Clothing shop product 99 99
Cosmetics shop front page 95 91
Home decor shop front page 94 92
Electronics shop front page 93 93

First paint is 1.7–1.8 s on every page. The largest paint is 1.7–3.4 s, and the slowest are the shop front pages, whose first image is a large product banner. Layout shift is under 0.06 and blocking time under 100 ms.

Before the fixes below, the demo front pages scored 83–87.

Fixed in 10c

  • Small stylesheets are inlined. Each element, each builder page or template and the Theme Options colors had their own stylesheet: a demo front page made about 30 blocking requests. The theme now gives WordPress the files of its small stylesheets, and WordPress prints them in the page, smallest first, up to its 40 KB limit. A new switch turns this off: Theme Options → Global → Performance → "Inline small stylesheets" (on).
  • Quick view's script only where products are. Quick view queued WooCommerce's variation script (with jQuery, Underscore and wp-util, in the page head) on every page. It is now queued with the first product card, and printed at the end of the page.
  • Inter in subsets. The bundled Inter was one 344 KB file with every alphabet, and it was preloaded ahead of the front page image. It is now seven files per style (Latin 48 KB, Latin Extended, Cyrillic, Cyrillic Extended, Greek, Greek Extended, Vietnamese) with unicode-range. Browsers download only the ones a page's text uses, and only Latin is preloaded. The files come from the @fontsource-variable/inter package (same font, same license) instead of inter-ui.

Left as they are

  • WooCommerce loads jQuery in the head and its own three stylesheets on every page. That is WooCommerce's choice.
  • Lighthouse reports about 33 KB of "unused CSS", mostly Bootstrap. That counts what the first screen doesn't use, and the scores reach the target without splitting the stylesheets.

Security (10d)

Every REST route and admin action, who may call it, how input and files are checked, the rate limits and what was fixed are in security.md.

Check How Result
Route permissions pnpm qa:security: every theme route as a visitor, a subscriber and with a wrong nonce; every admin action as a visitor and a subscriber Only the public routes answer, on both sites
Script injection the same script: payloads in every field of every element, section and wrap, saved as an Author and rendered None reach the page, on both sites (the self-test as an administrator finds 28)
Static analysis composer run analyse (PHPStan level 5, WordPress and WooCommerce stubs) No errors

Right to left (10e)

Check How Result
Direction-safe CSS pnpm lint:css now covers the element stylesheets too, and rejects physical sides (margin-left, padding-right, border-left, left:/right:, text-align/float/clear left or right) Clean; the few physical sides left are marked and explained (below)
Physical styles in PHP and JavaScript search for inline left/right styles, Bootstrap 4 side classes and arrow icons None; arrows mirror in RTL
Layout in both directions pnpm qa:rtl -- --demos Both sites and all 6 demos in each: no problems (after the fixes below)
Real devices, browsers and screen readers test-plan.md, by hand To do before a release

The layout check

pnpm qa:rtl                  # both dev sites
pnpm qa:rtl -- --site=fa     # one site
pnpm qa:rtl -- --demos       # also each bundled demo, imported and removed in turn
pnpm qa:rtl -- --demos=code,ai  # only these demos (the other QA scripts take it too)

tools/qa-rtl.mjs loads every kind of page at desktop and phone width, the theme's admin screens and the live builder, on the Persian site (RTL) and the English site (LTR). It reports:

  • a page wider than the window, naming the innermost elements that stick out;
  • headings, buttons, menu links and labels with text cut off;
  • text aligned to the physical side against the page direction (left in RTL, right in LTR), outside content marked with its own dir;
  • the logo on the end side of the header, a page that reads in the wrong direction or has no lang, and the builder's sidebar not on the start side.

Floats are not checked: browsers report a logical float (inline-start) as left or right too, so a float to the end side looks the same as a mistake.

Physical sides kept on purpose

  • .alignleft / .alignright: WordPress's classes mean the physical side in both directions.
  • The scaled previews in the header and footer gallery and in the builder's pre-built sections library: an iframe scaled from its top left corner.
  • The item being dragged in the builder: it follows the pointer with a transform, which works from the left edge.

Fixed in 10e

  • Sideways scrolling on phones with a wide gap. A boxed section whose gap between wraps was wider than the theme's gutter (such as 40px) was 4px wider than a phone screen: its rows reached past the edge. Boxed sections now get at least half the gap as side space on phones, as full-width sections already did.
  • The Code element used physical sides for its line numbers; it now uses logical ones (the block is always left to right, so nothing moves).
  • The advanced map's popups used text-align: right in RTL; they use start.
  • The shop price filter took its button-at-start, price-at-end layout from WooCommerce's physical sides (and its separate RTL stylesheet); the theme now sets it with logical sides.

The QA crawl

pnpm qa:crawl                  # both dev sites
pnpm qa:crawl -- --site=fa     # one site
pnpm qa:crawl -- --demos       # also each bundled demo, imported and removed in turn

For each site (tools/qa-crawl.mjs, with the address list from tools/wp-env/qa-urls.php) it:

  1. Collects one address of every kind:
    • front page, posts page, search with and without results, a 404 page and the feed;
    • items and archives of every public post type, a term of every public taxonomy, author and date archives;
    • the shop, cart, checkout and account pages;
    • the theme's admin screens and edit screens.
  2. Turns on WP_DEBUG and WP_DEBUG_LOG for the run and puts the previous settings back afterwards.
  3. Visits every page as a guest and as an administrator, then the admin screens.
  4. Reports server errors, PHP errors printed on a page, JavaScript errors, and every log line that names the theme, plus every fatal error.

It logs in with the wp-env test account (WP_USER and WP_PASS change it).

The Persian dev site's log has one known line that is not the theme's: Persian WooCommerce needs a PDO MySQL driver the wp-env image lacks. The crawl ignores that line.