Skip to content

Commit 19c02c9

Browse files
update the custom url component, and fix tests for galleries
1 parent 9aac8dd commit 19c02c9

File tree

9 files changed

+579
-64
lines changed

9 files changed

+579
-64
lines changed

.dev/tests/cypress/helpers.js

Lines changed: 255 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -36,8 +36,7 @@ export function addFormChild( name ) {
3636
export function loginToSite() {
3737
return goTo( '/wp-login.php', true )
3838
.then( () => {
39-
cy.wait( 250 );
40-
39+
// Wait for login form to be ready
4140
cy.get( '#user_login' ).type( Cypress.env( 'wpUsername' ) );
4241
cy.get( '#user_pass' ).type( Cypress.env( 'wpPassword' ) );
4342
cy.get( '#wp-submit' ).click();
@@ -137,8 +136,31 @@ export function addBlockToPost( blockName, clearEditor = false ) {
137136
// Make sure the block was added to our page
138137
cy.get( `[class*="-visual-editor"] [data-type="${ blockName }"]` ).should( 'exist' );
139138

140-
// Give a short delay for blocks to render.
141-
cy.wait( 250 );
139+
// Instead of arbitrary wait, wait for the block to be fully rendered
140+
// This means waiting for the block to not have loading states
141+
cy.get( `[data-type="${ blockName }"]` ).should( ( $block ) => {
142+
// The block should be stable (not detaching/reattaching)
143+
expect( $block ).to.have.length.at.least( 1 );
144+
// The block should have its content loaded (not just a placeholder)
145+
expect( $block.html() ).to.have.length.greaterThan( 50 );
146+
} );
147+
148+
// Also ensure the block has finished its initial render cycle
149+
cy.get( `[data-type="${ blockName }"]` ).should( 'not.have.class', 'is-loading' );
150+
151+
// For gallery blocks specifically, wait for upload UI to be ready and stable
152+
if ( blockName.includes( 'gallery' ) ) {
153+
// Wait for the upload interface to be available
154+
cy.get( `[data-type="${ blockName }"]` ).should( ( $block ) => {
155+
const text = $block.text();
156+
expect( text ).to.satisfy( ( content ) =>
157+
content.includes( 'Upload' ) || content.includes( 'Select' ) || content.includes( 'Media Library' )
158+
);
159+
} );
160+
161+
// Gallery blocks need a small stabilization period to prevent DOM detachment.
162+
cy.wait( 150 );
163+
}
142164
}
143165

144166
export function addNewGroupToPost() {
@@ -154,8 +176,7 @@ export function addNewGroupToPost() {
154176
cy.get( '.block-editor-inserter__search-input,input.block-editor-inserter__search, .components-search-control__input' ).click().type( 'group' );
155177
}
156178

157-
cy.wait( 1000 );
158-
179+
// Wait for search results to appear
159180
cy.get( '.block-editor-block-types-list__list-item' ).contains( 'Group' ).click();
160181

161182
// Make sure the block was added to our page
@@ -172,11 +193,22 @@ export function addNewGroupToPost() {
172193
* From inside the WordPress editor open the CoBlocks Gutenberg editor panel
173194
*/
174195
export function savePage() {
175-
if ( isWP66AtLeast() ) {
176-
cy.get( '.editor-header__settings button.is-primary' ).click();
177-
} else {
178-
cy.get( '.edit-post-header__settings button.is-primary' ).click();
179-
}
196+
// Try multiple selectors for cross-version compatibility (WP 6.5-6.8.2)
197+
cy.get( 'body' ).then( ( $body ) => {
198+
if ( $body.find( '.editor-header__settings button.is-primary' ).length > 0 ) {
199+
// WP 6.6+ selector
200+
cy.get( '.editor-header__settings button.is-primary' ).click();
201+
} else if ( $body.find( '.edit-post-header__settings button.is-primary' ).length > 0 ) {
202+
// WP 6.5 and earlier selector
203+
cy.get( '.edit-post-header__settings button.is-primary' ).click();
204+
} else if ( $body.find( '.edit-post-header-toolbar__settings button.is-primary' ).length > 0 ) {
205+
// Alternative pattern
206+
cy.get( '.edit-post-header-toolbar__settings button.is-primary' ).click();
207+
} else {
208+
// Generic fallback
209+
cy.get( 'button.is-primary' ).contains( /save|publish|update/i ).click();
210+
}
211+
} );
180212

181213
cy.get( '.components-editor-notices__snackbar', { timeout: 120000 } ).should( 'not.be.empty' );
182214

@@ -208,23 +240,42 @@ export function checkForBlockErrors( blockName ) {
208240
* View the currently edited page on the front of site
209241
*/
210242
export function viewPage() {
211-
cy.get( 'button[aria-label="Settings"]' ).then( ( settingsButton ) => {
212-
if ( ! Cypress.$( settingsButton ).hasClass( 'is-pressed' ) && ! Cypress.$( settingsButton ).hasClass( 'is-toggled' ) ) {
213-
cy.get( settingsButton ).click();
243+
// Open settings panel if not already open
244+
cy.get( 'body' ).then( ( $body ) => {
245+
const settingsButton = $body.find( 'button[aria-label="Settings"]' );
246+
if ( settingsButton.length > 0 && ! settingsButton.hasClass( 'is-pressed' ) && ! settingsButton.hasClass( 'is-toggled' ) ) {
247+
cy.get( 'button[aria-label="Settings"]' ).click();
214248
}
215249
} );
216250

217-
if ( isWP65AtLeast() ) {
218-
cy.get( '[data-tab-id="edit-post/document"]' );
251+
// Wait for the settings panel to be visible
252+
cy.get( '.interface-interface-skeleton__sidebar' ).should( 'be.visible' );
219253

220-
cy.get( '.editor-post-url__panel-dropdown button' ).click();
221-
} else {
222-
cy.get( 'button[data-label="Post"]' );
254+
// Try multiple approaches to find the post URL
255+
cy.get( 'body' ).then( ( $body ) => {
256+
if ( $body.find( '[data-tab-id="edit-post/document"]' ).length > 0 ) {
257+
// WP 6.5+ approach
258+
cy.get( '[data-tab-id="edit-post/document"]' ).click();
259+
// Wait for document tab to become active
260+
cy.get( '[data-tab-id="edit-post/document"]' ).should( 'have.attr', 'aria-selected', 'true' );
261+
}
223262

224-
cy.get( '.edit-post-post-url__dropdown button' ).click();
225-
}
263+
// Try different selectors for the URL dropdown
264+
if ( $body.find( '.editor-post-url__panel-dropdown button' ).length > 0 ) {
265+
cy.get( '.editor-post-url__panel-dropdown button' ).click();
266+
} else if ( $body.find( '.edit-post-post-url__dropdown button' ).length > 0 ) {
267+
cy.get( '.edit-post-post-url__dropdown button' ).click();
268+
} else if ( $body.find( 'button[data-label="Post"]' ).length > 0 ) {
269+
cy.get( 'button[data-label="Post"]' ).click();
270+
cy.get( '.edit-post-post-url__dropdown button' ).click();
271+
} else {
272+
// Fallback: look for any URL-related dropdown
273+
cy.get( '[class*="url"], [class*="permalink"]' ).find( 'button' ).first().click();
274+
}
275+
} );
226276

227-
cy.get( '.editor-post-url__link' ).then( ( pageLink ) => {
277+
// Get the link and visit it
278+
cy.get( '.editor-post-url__link, .edit-post-post-url__link' ).then( ( pageLink ) => {
228279
const linkAddress = Cypress.$( pageLink ).attr( 'href' );
229280
cy.visit( linkAddress );
230281
} );
@@ -239,14 +290,17 @@ export function editPage() {
239290
}
240291

241292
/**
242-
* Clear all blocks from the editor
293+
* Clear all blocks from the editor and wait for them to be fully removed
243294
*/
244295
export function clearBlocks() {
245296
getWPDataObject().then( ( data ) => {
246297
data.dispatch( 'core/block-editor' ).removeBlocks(
247298
data.select( 'core/block-editor' ).getBlocks().map( ( block ) => block.clientId )
248299
);
249300
} );
301+
302+
// Simple wait and verify no blocks remain
303+
cy.get( '.block-editor-block-list__layout' ).should( 'not.contain', '[data-type]' );
250304
}
251305

252306
/**
@@ -294,12 +348,10 @@ export function setNewBlockStyle( style ) {
294348
*/
295349
export function selectBlock( name ) {
296350
/**
297-
* There are network requests taking place to the REST API to get the blocks and block patterns.
298-
* Sometimes these requests occur and other times they are cached and are not called.
299-
* For that reason is difficult to assert against those requests from core code.
300-
* We introduce an arbitrary wait to avoid a race condition by interacting too quickly.
351+
* Wait for the block to be available in the editor before attempting to select it.
352+
* This replaces the arbitrary wait with a proper assertion.
301353
*/
302-
cy.wait( 600 );
354+
cy.get( `[data-type*="${ name }"], [data-title*="${ name }"]` ).should( 'exist' );
303355

304356
let id = ''; // The block client ID.
305357
cy.window().then( ( win ) => {
@@ -321,7 +373,8 @@ export function selectBlock( name ) {
321373
cy.window().then( ( win ) => {
322374
win.wp.data.dispatch( 'core/edit-post' ).openGeneralSidebar( 'edit-post/block' );
323375
} );
324-
cy.wait( 600 );
376+
// Wait for sidebar to be visible
377+
cy.get( '.interface-interface-skeleton__sidebar' ).should( 'be.visible' );
325378
}
326379

327380
/**
@@ -533,11 +586,28 @@ export function openHeadingToolbarAndSelect( headingLevel ) {
533586
* @param {string} checkboxLabelText The checkbox label text. eg: Drop Cap
534587
*/
535588
export function toggleSettingCheckbox( checkboxLabelText ) {
536-
cy.get( '.components-toggle-control__label' )
589+
// Atomic approach using should() to avoid DOM detachment
590+
cy.get( 'label' )
537591
.contains( checkboxLabelText )
538-
.closest( '.components-base-control__field' )
539-
.find( '.components-form-toggle__input' )
540-
.click();
592+
.should( ( $label ) => {
593+
const forAttr = $label.attr( 'for' );
594+
if ( forAttr ) {
595+
// Find and click the associated input directly in the callback
596+
const input = Cypress.$( `#${ forAttr }` )[ 0 ];
597+
if ( input ) {
598+
input.click();
599+
}
600+
} else {
601+
// Fallback: find input within the same control
602+
const control = $label.closest( '.components-base-control, .components-toggle-control' )[ 0 ];
603+
if ( control ) {
604+
const input = Cypress.$( control ).find( '.components-form-toggle__input, input[type="checkbox"]' )[ 0 ];
605+
if ( input ) {
606+
input.click();
607+
}
608+
}
609+
}
610+
} );
541611
}
542612

543613
/**
@@ -638,3 +708,155 @@ export function getIframeBody( containerClass ) {
638708
export const sidebarClass = () => {
639709
return isWP66AtLeast() ? '.editor-sidebar__panel' : '.edit-post-sidebar';
640710
};
711+
712+
/**
713+
* Click the settings/inspector button with cross-version compatibility
714+
*/
715+
export function openInspectorPanel() {
716+
// Try multiple selectors for cross-version compatibility (WP 6.5-6.8.2)
717+
cy.get( 'body' ).then( ( $body ) => {
718+
if ( $body.find( '.editor-header__settings' ).length > 0 ) {
719+
// WP 6.6+ selector
720+
cy.get( '.editor-header__settings' ).click();
721+
} else if ( $body.find( '.edit-post-header__settings' ).length > 0 ) {
722+
// WP 6.5 and earlier selector
723+
cy.get( '.edit-post-header__settings' ).click();
724+
} else if ( $body.find( '.edit-post-header-toolbar__settings' ).length > 0 ) {
725+
// Alternative pattern
726+
cy.get( '.edit-post-header-toolbar__settings' ).click();
727+
} else {
728+
// Generic fallback using aria-label
729+
cy.get( '[aria-label*="Settings"], [aria-label*="Inspector"]' ).click();
730+
}
731+
} );
732+
733+
// Wait for sidebar to be visible and panels to load
734+
cy.get( '.interface-interface-skeleton__sidebar' ).should( 'be.visible' );
735+
736+
// In newer WP versions, we need to click the "Block" tab to see block settings
737+
cy.get( 'body' ).then( ( $body ) => {
738+
const blockTab = $body.find( '[data-tab-id="edit-post/block"]' );
739+
if ( blockTab.length > 0 ) {
740+
// Click Block tab if it exists and isn't already selected
741+
if ( ! blockTab.attr( 'aria-selected' ) || blockTab.attr( 'aria-selected' ) === 'false' ) {
742+
cy.get( '[data-tab-id="edit-post/block"]' ).click();
743+
// Wait for panels to appear after clicking Block tab
744+
cy.get( '.components-panel__body-title' ).should( 'have.length.at.least', 1 );
745+
}
746+
} else {
747+
// For older versions without tabs, wait for panels to be visible
748+
cy.get( '.components-panel__body-title' ).should( 'have.length.at.least', 1 );
749+
}
750+
} );
751+
}
752+
753+
/**
754+
* Open a specific panel in the inspector with cross-version compatibility
755+
*
756+
* @param {string} panelName - The name of the panel to open (case-insensitive)
757+
*/
758+
export function openInspectorPanelSection( panelName ) {
759+
// First ensure panels are loaded
760+
cy.get( '.components-panel__body-title' ).should( 'have.length.at.least', 1 );
761+
762+
// Find the panel button and expand if needed
763+
cy.get( '.components-panel__body-title button' )
764+
.contains( new RegExp( panelName, 'i' ) )
765+
.then( ( $button ) => {
766+
const isExpanded = $button.attr( 'aria-expanded' ) === 'true';
767+
768+
if ( ! isExpanded ) {
769+
cy.wrap( $button ).click();
770+
// Wait for panel to expand by checking aria-expanded again
771+
cy.get( '.components-panel__body-title button' )
772+
.contains( new RegExp( panelName, 'i' ) )
773+
.should( 'have.attr', 'aria-expanded', 'true' );
774+
}
775+
} );
776+
777+
// For Link Settings specifically, wait for the SelectControl to render
778+
if ( panelName.toLowerCase().includes( 'link' ) ) {
779+
// Simply check that a select element exists somewhere in the expanded panel area
780+
cy.get( '.components-panel__body select' ).should( 'exist' );
781+
}
782+
}
783+
784+
/**
785+
* Wait for and interact with the Link Settings dropdown
786+
* This is purpose-built for the gallery link functionality
787+
*
788+
* @param {string} linkType The type of link to select (e.g., 'custom', 'media', 'attachment', 'none')
789+
*/
790+
export function selectLinkOption( linkType ) {
791+
// Simple approach: find select element that should exist after panel expansion
792+
cy.get( '.components-panel__body select' )
793+
.should( 'be.visible' )
794+
.select( linkType );
795+
}
796+
797+
/**
798+
* Complete helper function for testing custom link functionality in gallery blocks
799+
* Handles the full flow from block selection to URL input and verification
800+
*
801+
* @param {string} blockName - The block name (e.g., 'coblocks/gallery-offset', 'coblocks/gallery-collage')
802+
* @param {string} customUrl - The URL to set for the custom link
803+
* @param {string} imageSelector - CSS selector for the image element to verify link on
804+
*/
805+
export function setGalleryCustomLink( blockName, customUrl, imageSelector = 'img' ) {
806+
// Use the exact working workflow from debug test
807+
808+
// Step 1: Block selection
809+
cy.get( `[data-type="${ blockName }"]` ).click();
810+
811+
// Step 2: Open inspector and ensure it stays open
812+
cy.get( 'body' ).then( ( $body ) => {
813+
const sidebar = $body.find( '.interface-interface-skeleton__sidebar' );
814+
if ( ! sidebar.is( ':visible' ) ) {
815+
cy.get( '.editor-header__settings, .edit-post-header__settings' ).click();
816+
}
817+
} );
818+
cy.get( '.interface-interface-skeleton__sidebar' ).should( 'be.visible' );
819+
820+
// Step 3: Ensure Block tab is active
821+
cy.get( '[data-tab-id="edit-post/block"]' ).then( ( $tab ) => {
822+
if ( $tab.attr( 'aria-selected' ) !== 'true' ) {
823+
cy.get( '[data-tab-id="edit-post/block"]' ).click();
824+
}
825+
} );
826+
cy.get( '[data-tab-id="edit-post/block"]' ).should( 'have.attr', 'aria-selected', 'true' );
827+
828+
// Step 4: Expand Link Settings panel
829+
cy.get( '.components-panel__body-title button' ).contains( /link/i ).then( ( $button ) => {
830+
const isExpanded = $button.attr( 'aria-expanded' ) === 'true';
831+
if ( ! isExpanded ) {
832+
cy.wrap( $button ).click();
833+
}
834+
} );
835+
cy.get( '.components-panel__body-title button' ).contains( /link/i ).should( 'have.attr', 'aria-expanded', 'true' );
836+
837+
// Step 5: Select custom link type
838+
cy.get( '.components-panel__body' ).contains( /link/i ).closest( '.components-panel__body' ).within( () => {
839+
cy.get( 'select' ).select( 'custom' );
840+
} );
841+
842+
// Step 6: Click the image to show URL input
843+
if ( blockName.includes( 'collage' ) ) {
844+
cy.get( `[data-type="${ blockName }"] .wp-block-coblocks-gallery-collage__item` ).first().click();
845+
cy.get( `[data-type="${ blockName }"] img` ).first().click( { force: true } );
846+
} else {
847+
cy.get( `[data-type="${ blockName }"]` ).within( () => {
848+
cy.get( imageSelector ).first().click( { force: true } );
849+
} );
850+
}
851+
852+
// Step 7: Find and use the URL input
853+
cy.get( '.block-editor-url-input input:visible' ).should( 'exist' ).clear().type( customUrl );
854+
cy.get( 'button[type="submit"]:visible' ).click();
855+
856+
// Step 8: Verify
857+
cy.get( `[data-type="${ blockName }"]` ).within( () => {
858+
cy.get( imageSelector ).first()
859+
.should( 'have.attr', 'data-imglink' )
860+
.and( 'include', customUrl );
861+
} );
862+
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,12 @@
11
module.exports = ( on ) => {
22
require( 'cypress-log-to-output' ).install( on, ( type, event ) => event.level === 'error' || event.type === 'error' );
3+
4+
// Simple task to print diagnostic info from specs into the Node process output
5+
on( 'task', {
6+
log( message ) {
7+
// eslint-disable-next-line no-console
8+
console.log( '[cy.task log]', typeof message === 'object' ? JSON.stringify( message, null, 2 ) : message );
9+
return null;
10+
},
11+
} );
312
};

0 commit comments

Comments
 (0)