From 25fa46441b7e62c42457fe686ce844f5319cef18 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 2 Oct 2026 09:04:12 +0545 Subject: [PATCH 1/4] Fix - Block editor ignores the Customizer primary color and never loads the theme fonts Co-Authored-By: Claude Opus 5.5 (1M context) --- .themegrill-qa/suite.json | 5 + inc/functions.php | 59 +++++++++- style-editor-block.css | 14 ++- .../block-editor/customizer-styles.spec.ts | 87 +++++++++++++++ tests/e2e/utils/wp.ts | 104 ++++++++++++++++++ 5 files changed, 264 insertions(+), 5 deletions(-) create mode 100644 tests/e2e/specs/block-editor/customizer-styles.spec.ts create mode 100644 tests/e2e/utils/wp.ts diff --git a/.themegrill-qa/suite.json b/.themegrill-qa/suite.json index 188a874..1ab5316 100644 --- a/.themegrill-qa/suite.json +++ b/.themegrill-qa/suite.json @@ -17,6 +17,11 @@ "demo": "@demo" }, "area_paths": { + "block-editor": [ + "inc/functions.php", + "style-editor-block.css", + "tests/e2e/utils/**" + ], "mobile-menu": [ "header.php", "js/navigation.js" diff --git a/inc/functions.php b/inc/functions.php index 608aad0..1def33d 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -123,12 +123,65 @@ function accelerate_scripts_styles_method() { } } +/** + * Enqueue the block editor styles and fonts, with the Customizer primary color. + * + * Hooked on `enqueue_block_assets` because styles enqueued on `enqueue_block_editor_assets` + * stay outside the editor iframe, or only reach it through a path WordPress warns about. + */ function accelerate_block_editor_styles() { - wp_enqueue_style( 'accelerate-editor-googlefonts', '//fonts.googleapis.com/css?family=Roboto:400,300,100|Roboto+Slab:700,400&display=swap' ); - wp_enqueue_style( 'accelerate-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css' ); + if ( ! is_admin() ) { + return; + } + + // Same fonts as the front end. Google Fonts ignores the version, so none is added. + wp_enqueue_style( 'accelerate-editor-googlefonts', '//fonts.googleapis.com/css?family=Roboto:400,300,100|Roboto+Slab:700,400&display=swap', array(), null ); + // Theme version, so a theme update busts the cached editor stylesheet. + wp_enqueue_style( 'accelerate-block-editor-styles', get_template_directory_uri() . '/style-editor-block.css', array(), ACCELERATE_THEME_VERSION ); + + $editor_css = accelerate_block_editor_dynamic_css(); + + if ( $editor_css ) { + wp_add_inline_style( 'accelerate-block-editor-styles', $editor_css ); + } } -add_action( 'enqueue_block_editor_assets', 'accelerate_block_editor_styles', 1, 1 ); +add_action( 'enqueue_block_assets', 'accelerate_block_editor_styles' ); + +if ( ! function_exists( 'accelerate_block_editor_dynamic_css' ) ) : + + /** + * Build block editor CSS from the Customizer primary color. + * + * Mirrors the post content rules of accelerate_custom_css(), scoped to the editor canvas. + * Like the front end, the color outputs nothing while it is at its default. + * + * @return string Editor CSS. + */ + function accelerate_block_editor_dynamic_css() { + $wrapper = '.editor-styles-wrapper'; + $screen = function_exists( 'get_current_screen' ) ? get_current_screen() : null; + $css = ''; + + $primary_color = accelerate_options( 'accelerate_primary_color', '#77CC6D' ); + + if ( '#77CC6D' !== (string) $primary_color ) { + $primary_color = wp_strip_all_tags( $primary_color ); + + // The front end's `.wp-block-quote` rule beats the primary color, so only classic quotes follow it. + $css .= $wrapper . ' blockquote:not(.wp-block-quote), ' . $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"] { background-color: ' . $primary_color . '; }'; + $css .= $wrapper . ' a { color: ' . $primary_color . '; }'; + } + + // The editor title is styled as a post title; a page title is an h1 on the front end. + if ( $screen && 'page' === $screen->post_type ) { + $css .= $wrapper . ' .editor-post-title__input { font-size: 30px; line-height: 36px; }'; + } + + return $css; + } + +endif; /****************************************************************************************/ diff --git a/style-editor-block.css b/style-editor-block.css index 6d130b0..e09b7ac 100644 --- a/style-editor-block.css +++ b/style-editor-block.css @@ -11,10 +11,10 @@ .editor-styles-wrapper > * { color: #666666; font: 16px 'Roboto', sans-serif; + font-weight: 300; } -.editor-styles-wrapper .wp-block.editor-post-title__block .editor-post-title__input { - color: #444444; +.editor-styles-wrapper .editor-post-title__input { font-size: 26px; line-height: 32px; font-weight: normal; @@ -260,6 +260,16 @@ } /* Forms */ +/* Like the front end's body, input, select, textarea rule; the editor's own code fields keep their font. */ +.editor-styles-wrapper input, +.editor-styles-wrapper select, +.editor-styles-wrapper textarea:not(.block-editor-plain-text) { + color: #666666; + font: 16px 'Roboto', sans-serif; + line-height: 20px; + font-weight: 300; +} + .editor-styles-wrapper input[type="text"], .editor-styles-wrapper input[type="email"], .editor-styles-wrapper input[type="search"], diff --git a/tests/e2e/specs/block-editor/customizer-styles.spec.ts b/tests/e2e/specs/block-editor/customizer-styles.spec.ts new file mode 100644 index 0000000..3f21fbf --- /dev/null +++ b/tests/e2e/specs/block-editor/customizer-styles.spec.ts @@ -0,0 +1,87 @@ +import { test, expect, type Frame, type Page } from "@playwright/test"; +import { createPage, deletePage, login, requireAdmin } from "../../utils/wp"; + +/** + * @area block-editor + * @tier fresh + * @guards accelerate-pro#47 + * @source accelerate-pro#47 (reported by iamprazol) + * @why The block editor never used the Customizer: it loaded a static stylesheet + * and a hardcoded Roboto link on a hook whose styles never reach the editor + * iframe, so no theme font loaded in the canvas, and the title, form fields + * and the Customizer primary color differed from the front end. Guards + * that the canvas carries the same fonts stylesheet as the front end, that + * the theme's editor styles reach the iframe the supported way, and that the + * title, a heading, text, a link and a search field compute the same font, + * size, weight and color in the editor as on the published page. Reads the + * stylesheet links, so it needs no network access to Google. + */ +const content = [ + '

TGQA heading

', + '

TGQA text with a link.

', + '', +].join("\n"); + +const targets = { + heading: "h2.wp-block-heading", + text: "p", + link: "p a", + search: ".wp-block-search__input", +}; + +async function styles(root: Page | Frame, scope: string, title: string) { + return root.locator(scope).first().evaluate( + (el, { targets, title }) => { + const read = (node: Element | null) => { + if (!node) return null; + const c = getComputedStyle(node); + return `${c.fontFamily.split(",")[0].replace(/["']/g, "")} ${c.fontSize} ${c.fontWeight} ${c.color}`; + }; + const out: Record = { title: read(document.querySelector(title)) }; + for (const [name, selector] of Object.entries(targets)) out[name] = read(el.querySelector(selector)); + return out; + }, + { targets, title }, + ); +} + +const fontsHref = (href: string | null) => (href ?? "").replace(/^https?:/, "").replace(/&(amp;|#038;)?ver=[^&]*/, ""); + +test("the block editor uses the front end's fonts, typography and colors @block-editor @fresh", async ({ + page, +}, testInfo) => { + requireAdmin(testInfo); + test.setTimeout(180_000); + const iframeWarnings: string[] = []; + page.on("console", (message) => { + if (/accelerate.*added to the iframe incorrectly/.test(message.text())) iframeWarnings.push(message.text()); + }); + await login(page); + let pageId = 0; + try { + const created = await createPage(page, "TGQA editor styles", content); + pageId = created.id; + + await page.goto(created.link); + const front = await styles(page, ".entry-content", ".entry-title"); + const frontFonts = await page.locator("#accelerate_googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); + + await page.goto(`/wp-admin/post.php?post=${pageId}&action=edit`, { waitUntil: "domcontentloaded" }); + const canvas = page.frameLocator('iframe[name="editor-canvas"]'); + await expect(canvas.locator(".editor-styles-wrapper .wp-block-search__input")).toBeVisible({ timeout: 90_000 }); + const frame = page.frame({ name: "editor-canvas" })!; + + await expect(canvas.locator("#accelerate-block-editor-styles-css"), "the theme's editor styles must reach the canvas").toBeAttached({ timeout: 15_000 }); + const editorFonts = await canvas.locator("#accelerate-editor-googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); + expect(editorFonts && fontsHref(editorFonts), "the editor canvas must load the front end's fonts stylesheet").toBe(frontFonts && fontsHref(frontFonts)); + + const editor = await styles(frame, ".editor-styles-wrapper", ".editor-post-title__input"); + for (const name of Object.keys(front)) { + expect(front[name], `${name} is missing on the front end`).not.toBeNull(); + expect(editor[name], `${name}: editor (font size weight color) differs from the front end`).toBe(front[name]); + } + expect(iframeWarnings).toEqual([]); + } finally { + if (pageId) await deletePage(page, pageId); + } +}); diff --git a/tests/e2e/utils/wp.ts b/tests/e2e/utils/wp.ts new file mode 100644 index 0000000..5b25b72 --- /dev/null +++ b/tests/e2e/utils/wp.ts @@ -0,0 +1,104 @@ +import { expect, type Page, type TestInfo } from "@playwright/test"; + +/** + * Shared helpers for specs that need wp-admin. Specs that change site state + * put it back in a finally block. + */ + +export const adminUser = process.env.TGQA_ADMIN_USER ?? ""; +export const adminPass = process.env.TGQA_ADMIN_PASS ?? ""; + +/** Skip, with a reason in the report, when no admin credentials were supplied. */ +export function requireAdmin(testInfo: TestInfo): void { + testInfo.skip( + !adminUser || !adminPass, + "needs TGQA_ADMIN_USER / TGQA_ADMIN_PASS in .themegrill-qa/.env.local", + ); +} + +export async function login(page: Page): Promise { + await page.goto("/wp-login.php"); + await page.locator("#user_login").fill(adminUser); + await page.locator("#user_pass").fill(adminPass); + await page.locator("#wp-submit").click(); + await expect(page.locator("#wpadminbar")).toBeVisible(); +} + +/** A REST nonce for the logged-in session, so page.request can call wp/v2. */ +export async function restNonce(page: Page): Promise { + const res = await page.request.get("/wp-admin/admin-ajax.php?action=rest-nonce"); + expect(res.ok(), "could not get a REST nonce — is the admin logged in?").toBeTruthy(); + return (await res.text()).trim(); +} + +/** Create a published page through the REST API and return its id and link. */ +export async function createPage( + page: Page, + title: string, + content: string, +): Promise<{ id: number; link: string }> { + const nonce = await restNonce(page); + const res = await page.request.post("/?rest_route=/wp/v2/pages", { + headers: { "X-WP-Nonce": nonce }, + data: { title, content, status: "publish" }, + }); + expect(res.ok(), `creating the test page failed: HTTP ${res.status()}`).toBeTruthy(); + const body = await res.json(); + return { id: body.id, link: body.link }; +} + +// POST with a method override rather than DELETE: some hosts (this Local site's +// nginx among them) answer a bare DELETE with 405 before WordPress sees it. +export async function deletePage(page: Page, id: number): Promise { + const nonce = await restNonce(page); + const res = await page.request.post(`/?rest_route=/wp/v2/pages/${id}&force=true`, { + headers: { "X-WP-Nonce": nonce, "X-HTTP-Method-Override": "DELETE" }, + }); + expect(res.ok(), `deleting test page ${id} failed: HTTP ${res.status()}`).toBeTruthy(); +} + +/** Remove a widget through the REST API (method override, as for pages above). */ +export async function removeWidget(page: Page, id: string): Promise { + const res = await page.request.post(`/?rest_route=/wp/v2/widgets/${id}&force=true`, { + headers: { "X-WP-Nonce": await restNonce(page), "X-HTTP-Method-Override": "DELETE" }, + }); + expect(res.ok(), `removing test widget ${id} failed: HTTP ${res.status()}`).toBeTruthy(); +} + +/** + * Add a classic (legacy) widget to a sidebar through the REST API, the way the block Widgets + * editor does: encode the form, create the widget, then place it (over HTTP a new widget + * lands in Inactive Widgets). `fields` are the widget form's field names and values. + */ +export async function addLegacyWidget( + page: Page, + idBase: string, + sidebar: string, + fields: Record, +): Promise { + const headers = { "X-WP-Nonce": await restNonce(page) }; + const form = Object.entries(fields) + .map(([k, v]) => `widget-${idBase}[1][${k}]=${encodeURIComponent(String(v))}`) + .join("&"); + const encoded = await page.request.post(`/?rest_route=/wp/v2/widget-types/${idBase}/encode`, { headers, data: { form_data: form } }); + expect(encoded.ok(), `encoding the ${idBase} widget failed: HTTP ${encoded.status()}`).toBeTruthy(); + const created = await page.request.post("/?rest_route=/wp/v2/widgets", { + headers, + data: { id_base: idBase, sidebar, instance: (await encoded.json()).instance }, + }); + expect(created.ok(), `creating the ${idBase} widget failed: HTTP ${created.status()}`).toBeTruthy(); + const id: string = (await created.json()).id; + try { + const placed = await page.request.post(`/?rest_route=/wp/v2/widgets/${id}`, { + headers: { ...headers, "X-HTTP-Method-Override": "PUT" }, + data: { sidebar }, + }); + expect(placed.ok(), `placing the ${idBase} widget failed: HTTP ${placed.status()}`).toBeTruthy(); + } catch (error) { + // The caller never gets the id, so its cleanup cannot remove this widget. + // A cleanup failure must not hide the placement error, which is the real cause. + await removeWidget(page, id).catch(() => {}); + throw error; + } + return id; +} From 4238015bc212675228b66a1e60675585cb049568 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 2 Oct 2026 10:08:35 +0545 Subject: [PATCH 2/4] Style plain buttons in the editor like the front end, and exercise a saved primary color on a post and a page in the editor spec Co-Authored-By: Claude Opus 5.5 (1M context) --- inc/functions.php | 2 +- style-editor-block.css | 15 ++- .../block-editor/customizer-styles.spec.ts | 116 +++++++++++++----- 3 files changed, 98 insertions(+), 35 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 1def33d..836aaca 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -169,7 +169,7 @@ function accelerate_block_editor_dynamic_css() { $primary_color = wp_strip_all_tags( $primary_color ); // The front end's `.wp-block-quote` rule beats the primary color, so only classic quotes follow it. - $css .= $wrapper . ' blockquote:not(.wp-block-quote), ' . $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"] { background-color: ' . $primary_color . '; }'; + $css .= $wrapper . ' blockquote:not(.wp-block-quote), ' . $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"], :where(' . $wrapper . ') button { background-color: ' . $primary_color . '; }'; $css .= $wrapper . ' a { color: ' . $primary_color . '; }'; } diff --git a/style-editor-block.css b/style-editor-block.css index e09b7ac..2225ac0 100644 --- a/style-editor-block.css +++ b/style-editor-block.css @@ -260,10 +260,11 @@ } /* Forms */ -/* Like the front end's body, input, select, textarea rule; the editor's own code fields keep their font. */ +/* Like the front end's body, button, input, select, textarea rule; the editor's own code fields keep their font. */ .editor-styles-wrapper input, .editor-styles-wrapper select, -.editor-styles-wrapper textarea:not(.block-editor-plain-text) { +.editor-styles-wrapper textarea:not(.block-editor-plain-text), +:where(.editor-styles-wrapper) button { color: #666666; font: 16px 'Roboto', sans-serif; line-height: 20px; @@ -302,7 +303,8 @@ .editor-styles-wrapper input[type="reset"], .editor-styles-wrapper input[type="button"], -.editor-styles-wrapper input[type="submit"] { +.editor-styles-wrapper input[type="submit"], +:where(.editor-styles-wrapper) button { margin-bottom: 30px; padding: 10px 20px; background-color: #77CC6D; @@ -321,10 +323,15 @@ .editor-styles-wrapper input[type="reset"]:hover, .editor-styles-wrapper input[type="button"]:hover, .editor-styles-wrapper input[type="submit"]:hover, -.editor-styles-wrapper button:hover { +:where(.editor-styles-wrapper) button:hover { background-color: #33363B; } +/* The Search block button is not a

', '', ].join("\n"); -const targets = { +const targets: Record = { heading: "h2.wp-block-heading", text: "p", link: "p a", - search: ".wp-block-search__input", + list: "li", + "classic quote": "blockquote:not(.wp-block-quote)", + submit: "input[type=submit]", + button: "p button", + "search field": ".wp-block-search__input", + "search button": ".wp-block-search__button", }; async function styles(root: Page | Frame, scope: string, title: string) { @@ -35,7 +48,7 @@ async function styles(root: Page | Frame, scope: string, title: string) { const read = (node: Element | null) => { if (!node) return null; const c = getComputedStyle(node); - return `${c.fontFamily.split(",")[0].replace(/["']/g, "")} ${c.fontSize} ${c.fontWeight} ${c.color}`; + return `${c.fontFamily.split(",")[0].replace(/["']/g, "")} ${c.fontSize} ${c.fontWeight} ${c.color} on ${c.backgroundColor}`; }; const out: Record = { title: read(document.querySelector(title)) }; for (const [name, selector] of Object.entries(targets)) out[name] = read(el.querySelector(selector)); @@ -45,43 +58,86 @@ async function styles(root: Page | Frame, scope: string, title: string) { ); } +/** Set Customizer values through the Customizer's own API and publish them; returns the previous values. */ +async function saveCustomizer(page: Page, values: Record): Promise> { + await page.goto("/wp-admin/customize.php", { waitUntil: "domcontentloaded" }); + await page.waitForFunction(() => (window as any).wp?.customize?.state?.("activated")?.get(), null, { timeout: 90_000 }); + return page.evaluate(async (values) => { + const api = (window as any).wp.customize; + const previous: Record = {}; + for (const [key, value] of Object.entries(values)) { + const setting = api(`accelerate[${key}]`); + if (!setting) throw new Error(`Customizer setting accelerate[${key}] is not registered`); + previous[key] = setting.get(); + setting.set(value); + } + await new Promise((resolve, reject) => api.previewer.save().done(resolve).fail(reject)); + return previous; + }, values); +} + +async function createPost(page: Page, type: "posts" | "pages", title: string): Promise<{ id: number; link: string }> { + const res = await page.request.post(`/?rest_route=/wp/v2/${type}`, { + headers: { "X-WP-Nonce": await restNonce(page) }, + data: { title, content, status: "publish" }, + }); + expect(res.ok(), `creating the test ${type} failed: HTTP ${res.status()}`).toBeTruthy(); + const body = await res.json(); + return { id: body.id, link: body.link }; +} + +// POST with a method override: some hosts answer a bare DELETE with 405. +async function deletePost(page: Page, type: "posts" | "pages", id: number): Promise { + const res = await page.request.post(`/?rest_route=/wp/v2/${type}/${id}&force=true`, { + headers: { "X-WP-Nonce": await restNonce(page), "X-HTTP-Method-Override": "DELETE" }, + }); + expect(res.ok(), `deleting test ${type} ${id} failed: HTTP ${res.status()}`).toBeTruthy(); +} + const fontsHref = (href: string | null) => (href ?? "").replace(/^https?:/, "").replace(/&(amp;|#038;)?ver=[^&]*/, ""); test("the block editor uses the front end's fonts, typography and colors @block-editor @fresh", async ({ page, }, testInfo) => { requireAdmin(testInfo); - test.setTimeout(180_000); + test.setTimeout(420_000); const iframeWarnings: string[] = []; page.on("console", (message) => { if (/accelerate.*added to the iframe incorrectly/.test(message.text())) iframeWarnings.push(message.text()); }); await login(page); - let pageId = 0; + let previous: Record | null = null; + const created: { type: "posts" | "pages"; id: number }[] = []; try { - const created = await createPage(page, "TGQA editor styles", content); - pageId = created.id; + previous = await saveCustomizer(page, settings); + + for (const type of ["posts", "pages"] as const) { + const item = await createPost(page, type, `TGQA editor styles ${type}`); + created.push({ type, id: item.id }); - await page.goto(created.link); - const front = await styles(page, ".entry-content", ".entry-title"); - const frontFonts = await page.locator("#accelerate_googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); + await page.goto(item.link); + const front = await styles(page, ".entry-content", ".entry-title"); + // No link when only standard (system) fonts are chosen. + const frontFonts = await page.locator("#accelerate_googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); - await page.goto(`/wp-admin/post.php?post=${pageId}&action=edit`, { waitUntil: "domcontentloaded" }); - const canvas = page.frameLocator('iframe[name="editor-canvas"]'); - await expect(canvas.locator(".editor-styles-wrapper .wp-block-search__input")).toBeVisible({ timeout: 90_000 }); - const frame = page.frame({ name: "editor-canvas" })!; + await page.goto(`/wp-admin/post.php?post=${item.id}&action=edit`, { waitUntil: "domcontentloaded" }); + const canvas = page.frameLocator('iframe[name="editor-canvas"]'); + await expect(canvas.locator(".editor-styles-wrapper .wp-block-search__input")).toBeVisible({ timeout: 90_000 }); + const frame = page.frame({ name: "editor-canvas" })!; - await expect(canvas.locator("#accelerate-block-editor-styles-css"), "the theme's editor styles must reach the canvas").toBeAttached({ timeout: 15_000 }); - const editorFonts = await canvas.locator("#accelerate-editor-googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); - expect(editorFonts && fontsHref(editorFonts), "the editor canvas must load the front end's fonts stylesheet").toBe(frontFonts && fontsHref(frontFonts)); + await expect(canvas.locator("#accelerate-block-editor-styles-css"), "the theme's editor styles must reach the canvas").toBeAttached({ timeout: 15_000 }); + const editorFonts = await canvas.locator("#accelerate-editor-googlefonts-css").evaluateAll((links) => links[0]?.getAttribute("href") ?? null); + expect.soft(editorFonts && fontsHref(editorFonts), `${type}: the editor canvas must load the front end's fonts stylesheet`).toBe(frontFonts && fontsHref(frontFonts)); - const editor = await styles(frame, ".editor-styles-wrapper", ".editor-post-title__input"); - for (const name of Object.keys(front)) { - expect(front[name], `${name} is missing on the front end`).not.toBeNull(); - expect(editor[name], `${name}: editor (font size weight color) differs from the front end`).toBe(front[name]); + const editor = await styles(frame, ".editor-styles-wrapper", ".editor-post-title__input"); + for (const name of Object.keys(front)) { + expect(front[name], `${type}: ${name} is missing on the front end`).not.toBeNull(); + expect.soft(editor[name], `${type}: ${name} (font size weight color on background) differs from the front end`).toBe(front[name]); + } } expect(iframeWarnings).toEqual([]); } finally { - if (pageId) await deletePage(page, pageId); + for (const { type, id } of created) await deletePost(page, type, id); + if (previous) await saveCustomizer(page, previous); } }); From ffd59ebf92e632e95a8e344d1808fc2a5e10322c Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 2 Oct 2026 10:13:43 +0545 Subject: [PATCH 3/4] Give the test login helper time for a slow dashboard Co-Authored-By: Claude Opus 5.5 (1M context) --- tests/e2e/utils/wp.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/tests/e2e/utils/wp.ts b/tests/e2e/utils/wp.ts index 5b25b72..32e37fd 100644 --- a/tests/e2e/utils/wp.ts +++ b/tests/e2e/utils/wp.ts @@ -21,7 +21,8 @@ export async function login(page: Page): Promise { await page.locator("#user_login").fill(adminUser); await page.locator("#user_pass").fill(adminPass); await page.locator("#wp-submit").click(); - await expect(page.locator("#wpadminbar")).toBeVisible(); + // The dashboard can take several seconds to load on a busy site. + await expect(page.locator("#wpadminbar")).toBeVisible({ timeout: 30_000 }); } /** A REST nonce for the logged-in session, so page.request can call wp/v2. */ From 7af4b0038c2e0addea25601827ad9af1969eb414 Mon Sep 17 00:00:00 2001 From: rg755421 Date: Fri, 2 Oct 2026 10:20:47 +0545 Subject: [PATCH 4/4] Output only valid colors, sizes and fonts in the editor CSS Co-Authored-By: Claude Opus 5.5 (1M context) --- inc/functions.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/inc/functions.php b/inc/functions.php index 836aaca..9c1ca44 100644 --- a/inc/functions.php +++ b/inc/functions.php @@ -165,9 +165,10 @@ function accelerate_block_editor_dynamic_css() { $primary_color = accelerate_options( 'accelerate_primary_color', '#77CC6D' ); - if ( '#77CC6D' !== (string) $primary_color ) { - $primary_color = wp_strip_all_tags( $primary_color ); + // Only a valid hex color reaches the CSS, so a stored value cannot break out of its rule. + $primary_color = '#77CC6D' !== (string) $primary_color ? sanitize_hex_color( $primary_color ) : ''; + if ( $primary_color ) { // The front end's `.wp-block-quote` rule beats the primary color, so only classic quotes follow it. $css .= $wrapper . ' blockquote:not(.wp-block-quote), ' . $wrapper . ' input[type="reset"], ' . $wrapper . ' input[type="button"], ' . $wrapper . ' input[type="submit"], :where(' . $wrapper . ') button { background-color: ' . $primary_color . '; }'; $css .= $wrapper . ' a { color: ' . $primary_color . '; }';