From b8f343b0da323dc7a0f1688d3b310c9293399eff Mon Sep 17 00:00:00 2001 From: lucadobrescu Date: Wed, 8 Jul 2026 14:30:32 +0300 Subject: [PATCH 1/2] fix: preserve script order for lazy-rendered charts so the renderer registers before render-facade fires (#1319) Co-Authored-By: Claude Fable 5 --- classes/Visualizer/Module/Frontend.php | 16 ++++-- tests/e2e/specs/lazy-render.spec.js | 75 ++++++++++++++++++++++++++ 2 files changed, 86 insertions(+), 5 deletions(-) create mode 100644 tests/e2e/specs/lazy-render.spec.js diff --git a/classes/Visualizer/Module/Frontend.php b/classes/Visualizer/Module/Frontend.php index 1d3bbabd..eca006a9 100644 --- a/classes/Visualizer/Module/Frontend.php +++ b/classes/Visualizer/Module/Frontend.php @@ -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); }); } diff --git a/tests/e2e/specs/lazy-render.spec.js b/tests/e2e/specs/lazy-render.spec.js new file mode 100644 index 00000000..0e682c2e --- /dev/null +++ b/tests/e2e/specs/lazy-render.spec.js @@ -0,0 +1,75 @@ +/** + * 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 ) { + // New charts have lazy rendering enabled by default. + 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(); + + // 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(); + } ); +} ); From f7a18e6f8c06146362730c97c5d5c0a294420d4f Mon Sep 17 00:00:00 2001 From: lucadobrescu Date: Wed, 8 Jul 2026 15:12:19 +0300 Subject: [PATCH 2/2] test: force lazy rendering in e2e env so the lazy-render spec exercises the delayed script loader Co-Authored-By: Claude Fable 5 --- .wp-env.json | 3 +++ tests/e2e/config/force-lazy-render.php | 10 ++++++++++ tests/e2e/specs/lazy-render.spec.js | 3 ++- 3 files changed, 15 insertions(+), 1 deletion(-) create mode 100644 tests/e2e/config/force-lazy-render.php diff --git a/.wp-env.json b/.wp-env.json index 9d19ec37..61758738 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -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, diff --git a/tests/e2e/config/force-lazy-render.php b/tests/e2e/config/force-lazy-render.php new file mode 100644 index 00000000..2de83bd0 --- /dev/null +++ b/tests/e2e/config/force-lazy-render.php @@ -0,0 +1,10 @@ + { } ); async function createChartOnPost( admin, page, requestUtils ) { - // New charts have lazy rendering enabled by default. + // 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( {