From de3247711c55311e85cb4bf784c1331fd515f1a1 Mon Sep 17 00:00:00 2001 From: lucadobrescu Date: Fri, 10 Jul 2026 13:39:30 +0300 Subject: [PATCH 1/2] fix: persist database results before saving charts Save the active database query before the chart form closes so one-time imports cannot retain stale preview data. Co-authored-by: Cursor --- .wp-env.json | 3 + js/frame.js | 14 ++ tests/e2e/config/enable-database-source.php | 34 ++++ tests/e2e/specs/database-source.spec.js | 182 ++++++++++++++++++++ 4 files changed, 233 insertions(+) create mode 100644 tests/e2e/config/enable-database-source.php create mode 100644 tests/e2e/specs/database-source.spec.js diff --git a/.wp-env.json b/.wp-env.json index 9d19ec37e..6dae32805 100644 --- a/.wp-env.json +++ b/.wp-env.json @@ -5,6 +5,9 @@ "." ], "themes": [], + "mappings": { + "wp-content/mu-plugins/visualizer-e2e-enable-database-source.php": "./tests/e2e/config/enable-database-source.php" + }, "config": { "WP_DEBUG": true, "WP_DEBUG_LOG": true, diff --git a/js/frame.js b/js/frame.js index e8596dca9..4d2d368f7 100644 --- a/js/frame.js +++ b/js/frame.js @@ -479,6 +479,20 @@ init_db_import_component(); + $( '#settings-button' ).on( 'click', function( event ){ + $('body').trigger('visualizer:db:query:update', {}); + if( $( '#db-chart-button' ).attr( 'data-current' ) !== 'filter' || $( '.visualizer-db-query' ).val().length === 0 ){ + return; + } + + event.preventDefault(); + event.stopImmediatePropagation(); + $( '#thehole' ).one( 'load', function(){ + $( '#settings-button' ).trigger( 'click' ); + } ); + $( '#db-chart-button' ).trigger( 'click' ); + } ); + $('#visualizer-query-fetch').on('click', function(e){ $('body').trigger('visualizer:db:query:update', {}); diff --git a/tests/e2e/config/enable-database-source.php b/tests/e2e/config/enable-database-source.php new file mode 100644 index 000000000..6e8aed261 --- /dev/null +++ b/tests/e2e/config/enable-database-source.php @@ -0,0 +1,34 @@ + { + const request = candidate.request(); + + return ( + candidate.url().includes( '/wp-admin/admin-ajax.php' ) && + request.method() === 'POST' && + request + .postData() + ?.includes( 'action=visualizer-fetch-db-data' ) + ); + } ), + chartEditor.getByRole( 'button', { name: 'Show Results' } ).click(), + ] ); + + expect( response.ok() ).toBe( true ); + await expect( chartEditor.locator( '.db-wizard-results' ) ).toContainText( + expectedValue + ); +} + +test.describe( 'Database source', () => { + const postIds = []; + + test.beforeEach( async ( { page, requestUtils } ) => { + await deleteAllCharts( requestUtils ); + page.setDefaultTimeout( 20000 ); + } ); + + test.afterEach( async ( { requestUtils } ) => { + await Promise.all( + postIds.splice( 0 ).map( ( postId ) => + requestUtils + .rest( { + method: 'DELETE', + path: `/wp/v2/posts/${ postId }`, + params: { force: true }, + } ) + .catch( () => {} ) + ) + ); + } ); + + test( 'saves previewed one-time data when saving the chart', async ( { + admin, + context, + page, + requestUtils, + }, testInfo ) => { + test.setTimeout( 180000 ); + + await context.addCookies( [ + { + name: 'visualizer_e2e_database_source', + value: '1', + url: testInfo.project.use.baseURL || 'http://localhost:8889', + }, + ] ); + + const sourcePost = await requestUtils.createPost( { + title: 'ISSUE_1329_OLD', + status: 'publish', + } ); + postIds.push( sourcePost.id ); + + await admin.visitAdminPage( + 'admin.php?page=visualizer&vaction=addnew' + ); + await page + .getByRole( 'button', { name: 'Classic Builder Step-by-step' } ) + .click(); + + const chartEditor = page.frameLocator( 'iframe' ); + await chartEditor + .locator( 'div#type-picker' ) + .waitFor( { state: 'visible', timeout: 20000 } ); + await selectChartAdmin( chartEditor, CHART_JS_LABELS.table ); + + await chartEditor + .locator( '#visualizer-chart-id' ) + .waitFor( { state: 'attached', timeout: 20000 } ); + const chartId = await chartEditor + .locator( '#visualizer-chart-id' ) + .getAttribute( 'data-id' ); + expect( chartId ).toMatch( /^\d+$/ ); + + await chartEditor + .getByRole( 'button', { name: /Import from database/ } ) + .click(); + await chartEditor + .locator( '#db-chart-button' ) + .evaluate( ( button ) => button.click() ); + + const query = `SELECT post_title AS value FROM wp_posts WHERE ID = ${ sourcePost.id }`; + await chartEditor + .locator( '.CodeMirror' ) + .evaluate( + ( editor, sql ) => editor.CodeMirror.setValue( sql ), + query + ); + + await showQueryResults( page, chartEditor, 'ISSUE_1329_OLD' ); + + const [ saveResponse ] = await Promise.all( [ + page.waitForResponse( + ( candidate ) => + candidate + .url() + .includes( 'action=visualizer-save-db-query' ) && + candidate.request().method() === 'POST' + ), + chartEditor + .locator( '#db-chart-button' ) + .evaluate( ( button ) => button.click() ), + ] ); + expect( saveResponse.ok() ).toBe( true ); + await expect( chartEditor.locator( '#canvas' ) ).toContainText( + 'ISSUE_1329_OLD', + { timeout: 20000 } + ); + await chartEditor + .getByRole( 'button', { name: 'Create Chart' } ) + .click(); + await expect( + page.getByRole( 'heading', { name: 'Visualizer Library' } ) + ).toBeVisible( { timeout: 20000 } ); + + const chartPost = await requestUtils.createPost( { + title: 'Issue 1329 chart', + content: `[visualizer id="${ chartId }"]`, + status: 'publish', + } ); + postIds.push( chartPost.id ); + + await page.goto( chartPost.link ); + await page.mouse.wheel( 0, 500 ); + await expect( + page.getByRole( 'cell', { name: 'ISSUE_1329_OLD' } ) + ).toBeVisible(); + + await requestUtils.rest( { + method: 'POST', + path: `/wp/v2/posts/${ sourcePost.id }`, + data: { title: 'ISSUE_1329_NEW' }, + } ); + + await admin.visitAdminPage( 'admin.php?page=visualizer' ); + await page + .locator( `.visualizer-chart-edit[data-chart="${ chartId }"]` ) + .click(); + await chartEditor + .locator( '#db-chart-button' ) + .evaluate( ( button ) => button.click() ); + await showQueryResults( page, chartEditor, 'ISSUE_1329_NEW' ); + + await chartEditor.getByRole( 'button', { name: 'Save Chart' } ).click(); + await expect( + page.getByRole( 'dialog', { name: 'Visualizer' } ) + ).toHaveCount( 0, { timeout: 30000 } ); + + await page.goto( chartPost.link ); + await page.mouse.wheel( 0, 500 ); + await expect( + page.getByRole( 'cell', { name: 'ISSUE_1329_NEW' } ) + ).toBeVisible(); + } ); +} ); From 58130b51cf0f49e52ce2a9b578e1c2a17ff05bf6 Mon Sep 17 00:00:00 2001 From: lucadobrescu Date: Fri, 10 Jul 2026 14:41:04 +0300 Subject: [PATCH 2/2] fix: wait for database save before chart save Co-authored-by: Cursor --- js/frame.js | 38 +++++++++++------ tests/e2e/config/enable-database-source.php | 42 ++++++++----------- tests/e2e/specs/database-source.spec.js | 45 +++++++++++++++++---- 3 files changed, 80 insertions(+), 45 deletions(-) diff --git a/js/frame.js b/js/frame.js index 4d2d368f7..6fd62e269 100644 --- a/js/frame.js +++ b/js/frame.js @@ -479,19 +479,33 @@ init_db_import_component(); - $( '#settings-button' ).on( 'click', function( event ){ - $('body').trigger('visualizer:db:query:update', {}); - if( $( '#db-chart-button' ).attr( 'data-current' ) !== 'filter' || $( '.visualizer-db-query' ).val().length === 0 ){ - return; - } + var settings_button = document.querySelector( '#settings-button' ); + if ( settings_button ) { + settings_button.addEventListener( 'click', function( event ){ + $('body').trigger('visualizer:db:query:update', {}); + if( $( '#db-chart-button' ).attr( 'data-current' ) !== 'filter' || $( '.visualizer-db-query' ).val().length === 0 ){ + return; + } - event.preventDefault(); - event.stopImmediatePropagation(); - $( '#thehole' ).one( 'load', function(){ - $( '#settings-button' ).trigger( 'click' ); - } ); - $( '#db-chart-button' ).trigger( 'click' ); - } ); + event.preventDefault(); + event.stopImmediatePropagation(); + + var query_saved = false; + var resume_save = function(){ + query_saved = true; + settings_button.click(); + }; + + $('body').one( 'visualizer:render:currentchart:update', resume_save ); + $( '#thehole' ).one( 'load', function(){ + $('body').off( 'visualizer:render:currentchart:update', resume_save ); + if ( ! query_saved ) { + $( '#db-chart-button' ).trigger( 'click' ); + } + } ); + $( '#db-chart-button' ).trigger( 'click' ); + }, true ); + } $('#visualizer-query-fetch').on('click', function(e){ diff --git a/tests/e2e/config/enable-database-source.php b/tests/e2e/config/enable-database-source.php index 6e8aed261..ecc22bb77 100644 --- a/tests/e2e/config/enable-database-source.php +++ b/tests/e2e/config/enable-database-source.php @@ -3,32 +3,24 @@ * Enable database-source E2E tests for requests carrying the test cookie. */ -defined( 'VISUALIZER_PRO_VERSION' ) || define( 'VISUALIZER_PRO_VERSION', '2.0.1' ); +if ( defined( 'TI_E2E_TESTING' ) && isset( $_COOKIE['visualizer_e2e_database_source'] ) ) { + defined( 'VISUALIZER_PRO_VERSION' ) || define( 'VISUALIZER_PRO_VERSION', '2.0.1' ); -if ( ! class_exists( 'Visualizer_Pro' ) ) { - class Visualizer_Pro { - const ACTION_FETCH_DATA = 'visualizer-fetch-data'; - const CF_PERMISSIONS = 'visualizer-permissions'; + if ( ! class_exists( 'Visualizer_Pro' ) ) { + class Visualizer_Pro { + const ACTION_FETCH_DATA = 'visualizer-fetch-data'; + const CF_PERMISSIONS = 'visualizer-permissions'; + } } -} - -function visualizer_e2e_database_source_enabled() { - return defined( 'TI_E2E_TESTING' ) && isset( $_COOKIE['visualizer_e2e_database_source'] ); -} -add_filter( - 'visualizer_is_pro', - function ( $enabled ) { - return visualizer_e2e_database_source_enabled() ? true : $enabled; - }, - PHP_INT_MAX -); + add_filter( 'visualizer_is_pro', '__return_true', PHP_INT_MAX ); -add_filter( - 'visualizer_pro_upsell_class', - function ( $class, $feature = '' ) { - return visualizer_e2e_database_source_enabled() && 'db-query' === $feature ? '' : $class; - }, - PHP_INT_MAX, - 2 -); + add_filter( + 'visualizer_pro_upsell_class', + function ( $class, $feature = '' ) { + return 'db-query' === $feature ? '' : $class; + }, + PHP_INT_MAX, + 2 + ); +} diff --git a/tests/e2e/specs/database-source.spec.js b/tests/e2e/specs/database-source.spec.js index 3ddd4a7e4..1512d519a 100644 --- a/tests/e2e/specs/database-source.spec.js +++ b/tests/e2e/specs/database-source.spec.js @@ -12,6 +12,15 @@ const { selectChartAdmin, } = require( '../utils/common' ); +async function setQuery( chartEditor, query ) { + await chartEditor + .locator( '#visualizer-db-query .CodeMirror' ) + .evaluate( + ( editor, sql ) => editor.CodeMirror.setValue( sql ), + query + ); +} + async function showQueryResults( page, chartEditor, expectedValue ) { const [ response ] = await Promise.all( [ page.waitForResponse( ( candidate ) => { @@ -61,14 +70,15 @@ test.describe( 'Database source', () => { context, page, requestUtils, - }, testInfo ) => { + } ) => { test.setTimeout( 180000 ); + await page.goto( '/' ); await context.addCookies( [ { name: 'visualizer_e2e_database_source', value: '1', - url: testInfo.project.use.baseURL || 'http://localhost:8889', + url: page.url(), }, ] ); @@ -107,12 +117,7 @@ test.describe( 'Database source', () => { .evaluate( ( button ) => button.click() ); const query = `SELECT post_title AS value FROM wp_posts WHERE ID = ${ sourcePost.id }`; - await chartEditor - .locator( '.CodeMirror' ) - .evaluate( - ( editor, sql ) => editor.CodeMirror.setValue( sql ), - query - ); + await setQuery( chartEditor, query ); await showQueryResults( page, chartEditor, 'ISSUE_1329_OLD' ); @@ -178,5 +183,29 @@ test.describe( 'Database source', () => { await expect( page.getByRole( 'cell', { name: 'ISSUE_1329_NEW' } ) ).toBeVisible(); + + await admin.visitAdminPage( 'admin.php?page=visualizer' ); + await page + .locator( `.visualizer-chart-edit[data-chart="${ chartId }"]` ) + .click(); + await chartEditor + .locator( '#db-chart-button' ) + .evaluate( ( button ) => button.click() ); + await setQuery( + chartEditor, + 'SELECT visualizer_missing_column FROM wp_posts' + ); + + const errorDialogPromise = page.waitForEvent( 'dialog' ); + await chartEditor.getByRole( 'button', { name: 'Save Chart' } ).click(); + const errorDialog = await errorDialogPromise; + await errorDialog.dismiss(); + + await expect( + chartEditor.locator( '#visualizer-db-query' ) + ).toBeVisible(); + await expect( + page.getByRole( 'dialog', { name: 'Visualizer' } ) + ).toBeVisible(); } ); } );