Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .themegrill-qa/suite.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"header.php",
"js/navigation.js"
],
"elementor": ["inc/elementor/**", "style.css", "inc/functions.php"],
"search": [
"searchform.php",
"search.php",
Expand Down
13 changes: 12 additions & 1 deletion inc/elementor/elementor.php
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

function accelerate_elementor_widget_render_filter( $args ) {
return array(
'before_widget' => '<aside class="widget ' . accelerate_widget_class_names( $args['widget_id'] ) . '">',
'before_widget' => '<aside class="widget ' . esc_attr( accelerate_widget_class_names( $args['widget_id'] ) ) . '">',
'after_widget' => '</aside>',
'before_title' => '<h3 class="widget-title"><span>',
'after_title' => '</span></h3>',
Expand All @@ -44,6 +44,17 @@ function accelerate_widget_class_names( $widgets_id ) {

$return_value = isset( $classes[ $widgets_id ] ) ? $classes[ $widgets_id ] : '';

if ( '' === $return_value ) {
global $wp_widget_factory;

foreach ( $wp_widget_factory->widgets as $widget ) {
if ( $widget->id_base === $widgets_id && ! empty( $widget->widget_options['classname'] ) ) {
$return_value = $widget->widget_options['classname'];
break;
}
}
}

return $return_value;
}

Expand Down
2 changes: 1 addition & 1 deletion inc/functions.php
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ function accelerate_custom_css() {
$primary_color = accelerate_options( 'accelerate_primary_color', '#77CC6D' );
$primary_dark = accelerate_darkcolor( $primary_color, -50 );
if ( $primary_color != '#77CC6D' ) {
$accelerate_internal_css .= ' .accelerate-button,blockquote,button,input[type=button],input[type=reset],input[type=submit]{background-color:' . $primary_color . '}a{color:' . $primary_color . '}#page{border-top:3px solid ' . $primary_color . '}#site-title a:hover{color:' . $primary_color . '}#search-form span,.main-navigation a:hover,.main-navigation ul li ul li a:hover,.main-navigation ul li ul li:hover>a,.main-navigation ul li.current-menu-ancestor a,.main-navigation ul li.current-menu-item a,.main-navigation ul li.current-menu-item ul li a:hover,.main-navigation ul li.current_page_ancestor a,.main-navigation ul li.current_page_item a,.main-navigation ul li:hover>a,.main-small-navigation li:hover > a{background-color:' . $primary_color . '}.site-header .menu-toggle:before{color:' . $primary_color . '}.main-small-navigation li:hover{background-color:' . $primary_color . '}.main-small-navigation ul>.current-menu-item,.main-small-navigation ul>.current_page_item{background:' . $primary_color . '}.footer-menu a:hover,.footer-menu ul li.current-menu-ancestor a,.footer-menu ul li.current-menu-item a,.footer-menu ul li.current_page_ancestor a,.footer-menu ul li.current_page_item a,.footer-menu ul li:hover>a{color:' . $primary_color . '}#featured-slider .slider-read-more-button,.slider-title-head .entry-title a{background-color:' . $primary_color . '}a.slide-prev,a.slide-next,.slider-title-head .entry-title a{background-color:' . $primary_color . '}#controllers a.active,#controllers a:hover{background-color:' . $primary_color . ';color:' . $primary_color . '}.format-link .entry-content a{background-color:' . $primary_color . '}#secondary .widget_featured_single_post h3.widget-title a:hover,.widget_image_service_block .entry-title a:hover{color:' . $primary_color . '}.pagination span{background-color:' . $primary_color . '}.pagination a span:hover{color:' . $primary_color . ';border-color:' . $primary_color . '}#content .comments-area a.comment-edit-link:hover,#content .comments-area a.comment-permalink:hover,#content .comments-area article header cite a:hover,.comments-area .comment-author-link a:hover{color:' . $primary_color . '}.comments-area .comment-author-link span{background-color:' . $primary_color . '}#wp-calendar #today,.comment .comment-reply-link:hover,.nav-next a,.nav-previous a{color:' . $primary_color . '}.widget-title span{border-bottom:2px solid ' . $primary_color . '}#secondary h3 span:before,.footer-widgets-area h3 span:before{color:' . $primary_color . '}#secondary .accelerate_tagcloud_widget a:hover,.footer-widgets-area .accelerate_tagcloud_widget a:hover{background-color:' . $primary_color . '}.footer-widgets-area a:hover{color:' . $primary_color . '}.footer-socket-wrapper{border-top:3px solid ' . $primary_color . '}.footer-socket-wrapper .copyright a:hover{color:' . $primary_color . '}a#scroll-up{background-color:' . $primary_color . '}.entry-meta .byline i,.entry-meta .cat-links i,.entry-meta a,.post .entry-title a:hover{color:' . $primary_color . '}.entry-meta .post-format i{background-color:' . $primary_color . '}.entry-meta .comments-link a:hover,.entry-meta .edit-link a:hover,.entry-meta .posted-on a:hover,.main-navigation li.menu-item-has-children:hover,.entry-meta .tag-links a:hover{color:' . $primary_color . '}.more-link span,.read-more{background-color:' . $primary_color . '}.woocommerce #respond input#submit, .woocommerce a.button, .woocommerce button.button, .woocommerce input.button, .woocommerce #respond input#submit.alt, .woocommerce a.button.alt, .woocommerce button.button.alt, .woocommerce input.button.alt,.woocommerce span.onsale {background-color: ' . $primary_color . ';}.woocommerce ul.products li.product .price .amount,.entry-summary .price .amount,.woocommerce .woocommerce-message::before{color: ' . $primary_color . ';},.woocommerce .woocommerce-message { border-top-color: ' . $primary_color . ';}';
$accelerate_internal_css .= ' .accelerate-button,blockquote,button,input[type=button],input[type=reset],input[type=submit]{background-color:' . $primary_color . '}a{color:' . $primary_color . '}#page{border-top:3px solid ' . $primary_color . '}#site-title a:hover{color:' . $primary_color . '}#search-form span,.main-navigation a:hover,.main-navigation ul li ul li a:hover,.main-navigation ul li ul li:hover>a,.main-navigation ul li.current-menu-ancestor a,.main-navigation ul li.current-menu-item a,.main-navigation ul li.current-menu-item ul li a:hover,.main-navigation ul li.current_page_ancestor a,.main-navigation ul li.current_page_item a,.main-navigation ul li:hover>a,.main-small-navigation li:hover > a{background-color:' . $primary_color . '}.site-header .menu-toggle:before{color:' . $primary_color . '}.main-small-navigation li:hover{background-color:' . $primary_color . '}.main-small-navigation ul>.current-menu-item,.main-small-navigation ul>.current_page_item{background:' . $primary_color . '}.footer-menu a:hover,.footer-menu ul li.current-menu-ancestor a,.footer-menu ul li.current-menu-item a,.footer-menu ul li.current_page_ancestor a,.footer-menu ul li.current_page_item a,.footer-menu ul li:hover>a{color:' . $primary_color . '}#featured-slider .slider-read-more-button,.slider-title-head .entry-title a{background-color:' . $primary_color . '}a.slide-prev,a.slide-next,.slider-title-head .entry-title a{background-color:' . $primary_color . '}#controllers a.active,#controllers a:hover{background-color:' . $primary_color . ';color:' . $primary_color . '}.format-link .entry-content a{background-color:' . $primary_color . '}#secondary .widget_featured_single_post h3.widget-title a:hover,.widget_image_service_block .entry-title a:hover{color:' . $primary_color . '}.pagination span{background-color:' . $primary_color . '}.pagination a span:hover{color:' . $primary_color . ';border-color:' . $primary_color . '}#content .comments-area a.comment-edit-link:hover,#content .comments-area a.comment-permalink:hover,#content .comments-area article header cite a:hover,.comments-area .comment-author-link a:hover{color:' . $primary_color . '}.comments-area .comment-author-link span{background-color:' . $primary_color . '}#wp-calendar #today,.comment .comment-reply-link:hover,.nav-next a,.nav-previous a{color:' . $primary_color . '}.widget-title span{border-bottom:2px solid ' . $primary_color . '}#secondary h3 span:before,.footer-widgets-area h3 span:before,.elementor-widget-container .widget h3 span:before{color:' . $primary_color . '}#secondary .accelerate_tagcloud_widget a:hover,.footer-widgets-area .accelerate_tagcloud_widget a:hover{background-color:' . $primary_color . '}.footer-widgets-area a:hover{color:' . $primary_color . '}.footer-socket-wrapper{border-top:3px solid ' . $primary_color . '}.footer-socket-wrapper .copyright a:hover{color:' . $primary_color . '}a#scroll-up{background-color:' . $primary_color . '}.entry-meta .byline i,.entry-meta .cat-links i,.entry-meta a,.post .entry-title a:hover{color:' . $primary_color . '}.entry-meta .post-format i{background-color:' . $primary_color . '}.entry-meta .comments-link a:hover,.entry-meta .edit-link a:hover,.entry-meta .posted-on a:hover,.main-navigation li.menu-item-has-children:hover,.entry-meta .tag-links a:hover{color:' . $primary_color . '}.more-link span,.read-more{background-color:' . $primary_color . '}.woocommerce #respond input#submit, .woocommerce a.button, .woocommerce button.button, .woocommerce input.button, .woocommerce #respond input#submit.alt, .woocommerce a.button.alt, .woocommerce button.button.alt, .woocommerce input.button.alt,.woocommerce span.onsale {background-color: ' . $primary_color . ';}.woocommerce ul.products li.product .price .amount,.entry-summary .price .amount,.woocommerce .woocommerce-message::before{color: ' . $primary_color . ';},.woocommerce .woocommerce-message { border-top-color: ' . $primary_color . ';}';
}

if ( ! empty( $accelerate_internal_css ) ) {
Expand Down
3 changes: 2 additions & 1 deletion rtl.css
Original file line number Diff line number Diff line change
Expand Up @@ -292,7 +292,8 @@ a.slide-prev {
}

#secondary h3 span:before,
.footer-widgets-area h3 span:before {
.footer-widgets-area h3 span:before,
.elementor-widget-container .widget h3 span:before {
padding-left: 5px;
}

Expand Down
4 changes: 3 additions & 1 deletion style.css
Original file line number Diff line number Diff line change
Expand Up @@ -1908,7 +1908,9 @@ a.slide-next:hover {

/* =WIDGETS ICON
----------------------------------------------- */
#secondary h3 span:before, .footer-widgets-area h3 span:before {
#secondary h3 span:before,
.footer-widgets-area h3 span:before,
.elementor-widget-container .widget h3 span:before {
font-family: FontAwesome;
font-style: normal;
font-weight: normal;
Expand Down
155 changes: 155 additions & 0 deletions tests/e2e/specs/elementor/widget-wrapper.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
import { test, expect, type Page, type TestInfo } from "@playwright/test";
import { login, requireAdmin, restNonce, createPage, deletePage } from "../../utils/wp";

const PLUGIN = "elementor/elementor";

const contextOptions = ({ project }: TestInfo) => ({
baseURL: project.use.baseURL,
ignoreHTTPSErrors: project.use.ignoreHTTPSErrors,
});

type Provisioned ={ pageId: number; previous: "active" | "inactive" | "absent" };

// Elementor is not part of the fresh blueprint, so the spec brings it in and puts the site back.
async function provisionElementor(page: Page): Promise<Provisioned["previous"]> {
const nonce = await restNonce(page);
const headers = { "X-WP-Nonce": nonce };

const current = await page.request.get(`/?rest_route=/wp/v2/plugins/${PLUGIN}`, { headers });
if (current.ok()) {
const status = (await current.json()).status as string;
if (status === "active") return "active";
const res = await page.request.post(`/?rest_route=/wp/v2/plugins/${PLUGIN}`, {
headers,
data: { status: "active" },
});
expect(res.ok(), `activating Elementor failed: HTTP ${res.status()}`).toBeTruthy();
return "inactive";
}

const res = await page.request.post("/?rest_route=/wp/v2/plugins", {
headers,
data: { slug: "elementor", status: "active" },
});
expect(res.ok(), `installing Elementor failed: HTTP ${res.status()}`).toBeTruthy();
return "absent";
}

async function restoreElementor(page: Page, previous: Provisioned["previous"]): Promise<void> {
if (previous === "active") return;
const nonce = await restNonce(page);
const headers = { "X-WP-Nonce": nonce };
await page.request.post(`/?rest_route=/wp/v2/plugins/${PLUGIN}`, {
headers,
data: { status: "inactive" },
});
if (previous === "absent") {
await page.request.post(`/?rest_route=/wp/v2/plugins/${PLUGIN}`, {
headers: { ...headers, "X-HTTP-Method-Override": "DELETE" },
});
}
}

// Saves the page through Elementor's own editor endpoint, the only supported way to give a page widget data.
async function saveElementorPage(page: Page, id: number): Promise<void> {
const editor = await page.request.get(`/wp-admin/post.php?post=${id}&action=elementor`);
expect(editor.ok(), `opening the Elementor editor failed: HTTP ${editor.status()}`).toBeTruthy();
const nonce = (await editor.text()).match(/admin-ajax\.php\\?",\\?"nonce\\?":\\?"([a-f0-9]+)/)?.[1];
expect(nonce, "could not read Elementor's ajax nonce from the editor page").toBeTruthy();

const elements = [
{
id: "a1b2c3d",
elType: "container",
settings: {},
isInner: false,
elements: [
{
id: "b1c2d3e",
elType: "widget",
widgetType: "wp-widget-search",
settings: { wp: { title: "Search Title" } },
elements: [],
},
],
},
];
const res = await page.request.post("/wp-admin/admin-ajax.php", {
form: {
action: "elementor_ajax",
_nonce: nonce!,
editor_post_id: String(id),
actions: JSON.stringify({
save: { action: "save_builder", data: { status: "publish", elements, settings: {} } },
}),
},
});
expect(res.ok(), `saving the Elementor page failed: HTTP ${res.status()}`).toBeTruthy();
expect((await res.json()).success, "Elementor refused to save the page").toBe(true);
}

test.describe("Elementor WordPress widgets @elementor @fresh", () => {
test.describe.configure({ mode: "serial" });

let state: Provisioned | undefined;

test.beforeAll(async ({ browser }, testInfo) => {
requireAdmin(testInfo);
const context = await browser.newContext(contextOptions(testInfo));
const page = await context.newPage();
await login(page);
const previous = await provisionElementor(page);
const { id, link } = await createPage(page, "tgqa-elementor-widget-wrapper", "");
state = { pageId: id, previous };
await saveElementorPage(page, id);
testInfo.annotations.push({ type: "page", description: link });
await context.close();
});

test.afterAll(async ({ browser }, testInfo) => {
if (!state) return;
const context = await browser.newContext(contextOptions(testInfo));
const page = await context.newPage();
await login(page);
await deletePage(page, state.pageId);
await restoreElementor(page, state.previous);
await context.close();
});

/**
* @area elementor
* @tier fresh
* @source write-spec 2026-10-05
* @why The theme's elementor/widgets/wordpress/widget_args filter built
* `<aside class="widget ">` for any widget outside the theme's own list,
* so core and WooCommerce widgets lost the class their styles hang off
* (widget_search, widget_shopping_cart). Asserts the class from the
* widget's own registered classname; says nothing about the styling it
* unlocks or about the theme's own widgets.
*/
test("a core widget in Elementor keeps its widget class @elementor @fresh", async ({ page }) => {
await page.goto(`/?page_id=${state!.pageId}`);
const wrapper = page.locator(".elementor-widget-wp-widget-search aside");
await expect(wrapper).toHaveCount(1);
await expect(wrapper).toHaveClass(/\bwidget_search\b/);
});

/**
* @area elementor
* @tier fresh
* @source write-spec 2026-10-05
* @why With the widget class restored, the sidebar title-icon rules start to
* match inside Elementor, but they only set the Font Awesome family
* under #secondary and the footer, so the icon rendered as a missing
* glyph box. Asserts the family only, not the glyph or its colour.
*/
test("a titled widget in Elementor draws its title icon with Font Awesome @elementor @fresh", async ({
page,
}) => {
await page.goto(`/?page_id=${state!.pageId}`);
const title = page.locator(".elementor-widget-wp-widget-search aside h3 span");
await expect(title).toHaveText("Search Title");
const family = await title.evaluate((el) => getComputedStyle(el, "::before").fontFamily);
expect(family).toContain("FontAwesome");
});
});
Loading