Skip to content
Open
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
3 changes: 3 additions & 0 deletions .wp-env.json
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,9 @@
"."
],
"themes": [],
"mappings": {
"wp-content/mu-plugins/visualizer-e2e-force-lazy-render.php": "./tests/e2e/config/force-lazy-render.php"
},
"config": {
"WP_DEBUG": true,
"WP_DEBUG_LOG": true,
Expand Down
16 changes: 11 additions & 5 deletions classes/Visualizer/Module/Frontend.php
Original file line number Diff line number Diff line change
Expand Up @@ -803,14 +803,20 @@ function visualizerTriggerScriptLoader() {

function visualizerLoadScripts() {
document.querySelectorAll("script[data-visualizer-script]").forEach(function(elem) {
jQuery.getScript( elem.getAttribute("data-visualizer-script") )
.done( function( script, textStatus ) {
elem.setAttribute("src", elem.getAttribute("data-visualizer-script"));
elem.removeAttribute("data-visualizer-script");
// Replace the placeholder with a real script tag using async=false:
// scripts download in parallel but execute in insertion order, which
// preserves the WordPress dependency order. Parallel jQuery.getScript
// calls could execute render-facade.js before the chart renderer had
// registered its render event listener, leaving charts blank.
var script = document.createElement("script");
script.src = elem.getAttribute("data-visualizer-script");
script.async = false;
script.onload = function() {
setTimeout( function() {
visualizerRefreshChart();
} );
} );
};
elem.parentNode.replaceChild(script, elem);
});
}

Expand Down
10 changes: 10 additions & 0 deletions tests/e2e/config/force-lazy-render.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
<?php
/**
* E2E test helper (loaded as an mu-plugin via .wp-env.json).
*
* Charts created through the quick wizard have no `lazy_load_chart` setting,
* so force lazy rendering on the frontend to exercise the delayed script
* loader in lazy-render.spec.js. Elementor previews are unaffected: the
* widget force-disables lazy rendering with the same filter during render.
*/
add_filter( 'visualizer_lazy_load_chart', '__return_true' );
76 changes: 76 additions & 0 deletions tests/e2e/specs/lazy-render.spec.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
/**
* WordPress dependencies
*/
const { test, expect } = require( '@wordpress/e2e-test-utils-playwright' );

/**
* Internal dependencies
*/
const { deleteAllCharts, createChartWithAdmin } = require( '../utils/common' );

/**
* Regression tests for https://github.com/Codeinwp/visualizer/issues/1319
*
* Charts with lazy rendering enabled load their scripts only after the first
* user interaction, via `script[data-visualizer-script]` placeholders. The
* loader must preserve the WordPress dependency order: if render-facade.js
* executes before the chart renderer (render-google.js etc.) has registered
* its `visualizer:render:chart:start` listener, the render event is lost and
* the chart stays blank forever.
*/
test.describe( 'Lazy rendered charts (frontend)', () => {
test.beforeEach( async ( { requestUtils } ) => {
await deleteAllCharts( requestUtils );
} );

async function createChartOnPost( admin, page, requestUtils ) {
// Lazy rendering is forced on by tests/e2e/config/force-lazy-render.php
// (mapped as an mu-plugin in .wp-env.json).
const chartId = await createChartWithAdmin( admin, page );

const post = await requestUtils.createPost( {
title: 'Lazy render',
content: `[visualizer id="${ chartId }" lazy="no" class=""]`,
status: 'publish',
} );

return { chartId, post };
}

async function triggerLazyLoader( page ) {
// Scripts are swapped to `data-visualizer-script` placeholders.
await expect( page.locator( 'script[data-visualizer-script]' ).first() ).toBeAttached();

Check failure on line 42 in tests/e2e/specs/lazy-render.spec.js

View workflow job for this annotation

GitHub Actions / Playwright E2E tests

[chromium] › tests/e2e/specs/lazy-render.spec.js:66:2 › Lazy rendered charts (frontend) › chart renders without artificial script delay

1) [chromium] › tests/e2e/specs/lazy-render.spec.js:66:2 › Lazy rendered charts (frontend) › chart renders without artificial script delay Retry #1 ─────────────────────────────────────────────────────────────────────────────────────── Error: expect(locator).toBeAttached() failed Locator: locator('script[data-visualizer-script]').first() Expected: attached Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeAttached" with timeout 5000ms - waiting for locator('script[data-visualizer-script]').first() 40 | async function triggerLazyLoader( page ) { 41 | // Scripts are swapped to `data-visualizer-script` placeholders. > 42 | await expect( page.locator( 'script[data-visualizer-script]' ).first() ).toBeAttached(); | ^ 43 | 44 | // The loader starts on the first user interaction. 45 | await page.evaluate( () => window.dispatchEvent( new Event( 'scroll' ) ) ); at triggerLazyLoader (/home/runner/work/visualizer/visualizer/tests/e2e/specs/lazy-render.spec.js:42:76) at /home/runner/work/visualizer/visualizer/tests/e2e/specs/lazy-render.spec.js:70:9

Check failure on line 42 in tests/e2e/specs/lazy-render.spec.js

View workflow job for this annotation

GitHub Actions / Playwright E2E tests

[chromium] › tests/e2e/specs/lazy-render.spec.js:66:2 › Lazy rendered charts (frontend) › chart renders without artificial script delay

1) [chromium] › tests/e2e/specs/lazy-render.spec.js:66:2 › Lazy rendered charts (frontend) › chart renders without artificial script delay Error: expect(locator).toBeAttached() failed Locator: locator('script[data-visualizer-script]').first() Expected: attached Timeout: 5000ms Error: element(s) not found Call log: - Expect "toBeAttached" with timeout 5000ms - waiting for locator('script[data-visualizer-script]').first() 40 | async function triggerLazyLoader( page ) { 41 | // Scripts are swapped to `data-visualizer-script` placeholders. > 42 | await expect( page.locator( 'script[data-visualizer-script]' ).first() ).toBeAttached(); | ^ 43 | 44 | // The loader starts on the first user interaction. 45 | await page.evaluate( () => window.dispatchEvent( new Event( 'scroll' ) ) ); at triggerLazyLoader (/home/runner/work/visualizer/visualizer/tests/e2e/specs/lazy-render.spec.js:42:76) at /home/runner/work/visualizer/visualizer/tests/e2e/specs/lazy-render.spec.js:70:9

// The loader starts on the first user interaction.
await page.evaluate( () => window.dispatchEvent( new Event( 'scroll' ) ) );
}

test( 'chart renders when the renderer script loads after render-facade.js', async ( { admin, page, requestUtils } ) => {
const { chartId, post } = await createChartOnPost( admin, page, requestUtils );

// Delay the chart renderer scripts so render-facade.js would win the
// load race — the deterministic reproduction of issue #1319.
await page.route( /js\/render-(google|chartjs|datatables)\.js/, async ( route ) => {
await new Promise( ( resolve ) => setTimeout( resolve, 2000 ) );
await route.continue();
} );

await page.goto( `/?p=${ post.id }` );
await triggerLazyLoader( page );

const chart = page.locator( `.visualizer-front-${ chartId }` ).first();
await expect( chart ).toHaveClass( /visualizer-chart-loaded/, { timeout: 15000 } );
await expect( chart.locator( 'svg, canvas, table' ).first() ).toBeVisible();
} );

test( 'chart renders without artificial script delay', async ( { admin, page, requestUtils } ) => {
const { chartId, post } = await createChartOnPost( admin, page, requestUtils );

await page.goto( `/?p=${ post.id }` );
await triggerLazyLoader( page );

const chart = page.locator( `.visualizer-front-${ chartId }` ).first();
await expect( chart ).toHaveClass( /visualizer-chart-loaded/, { timeout: 15000 } );
await expect( chart.locator( 'svg, canvas, table' ).first() ).toBeVisible();
} );
} );
Loading