Skip to content

test(e2e): Add breakpoints and lazy load spec - #1342

Open
utkarshcloudinary wants to merge 4 commits into
developfrom
test/responsive-breakpoints-lazy-load-e2e
Open

utkarshcloudinary wants to merge 4 commits into
developfrom
test/responsive-breakpoints-lazy-load-e2e

Conversation

@utkarshcloudinary

Copy link
Copy Markdown
Collaborator

Adds an e2e spec for the responsive breakpoints and lazy load delivery settings on a synced image. It also fixes the shared srcsetUrls() helper, which split Cloudinary URLs apart at the commas inside their transformation segment (w_800,h_600,c_scale).

Approach

  • New tests/e2e/cloudinary-breakpoints-lazy-load.spec.js (@serial, real credentials needed for wp cloudinary sync). It uploads the 1200x900 fixture, embeds it in a post, and syncs once in beforeAll.
  • Settings are written through the plugin settings API. They are pinned in each test and restored in afterAll.
  • Server markup is read in a context with JavaScript off, because the lazy load script rewrites src/srcset after load.
  • Breakpoints: max_width=1100, min_width=500, pixel_step=300 must give srcset widths 1200w, 1100w, 800w, 500w. Each breakpoint URL is on Cloudinary and has the matching w_ transformation. Core never makes these widths for this image, so they can only come from the plugin.
  • Breakpoints are only written to srcset on the server when lazy load is off (Responsive_Breakpoints::add_features()), so the srcset test turns lazy load off.
  • Lazy load: checks the SVG placeholder src, data-cloudinary="lazy", data-size, the CLDBind onload, data-public-id, data-delivery, and the CLDLB config with the blur placeholder (e_blur:2000). With JS on, src changes to a Cloudinary URL.
  • Disabled: with breakpoints off, data-responsive and the breakpoint widths are gone. With lazy load off, the placeholder attributes and CLDLB are gone and src is the Cloudinary URL from the server.
  • utils/delivery.js: new parseSrcset() returns { url, descriptor } pairs. srcsetUrls() now uses it.

QA notes

  • npm run test:e2e:serial -- cloudinary-breakpoints-lazy-load
  • npm run test:e2e:serial -- cloudinary-gallery-delivery (uses the changed helper)

@gabrielcld2 gabrielcld2 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@utkarshcloudinary this is looking good, just a few minor issues

Comment thread tests/e2e/utils/delivery.js Outdated
.map( ( candidate ) => candidate.trim().split( /\s+/ )[ 0 ] )
.filter( Boolean );
const candidates = [];
const pattern = /\s*(\S+?)(?:\s+(\d+(?:\.\d+)?[wx]))?\s*(?:,(?=\s)|,?$)/gy;

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue: This regex only ends a candidate at , followed by whitespace, or at the end of the string. When exec fails, the loop breaks and quietly returns what it has so far:

Input Result
a.jpg 1x,b.jpg 2x (valid HTML) []
a.jpg 300w ,b.jpg 600w []
a.jpg 1.5x, b.jpg 100h only a.jpg

cloudinary-gallery-delivery.spec.js loops over [src, ...srcsetUrls(srcset)], so an empty result makes its srcset checks pass without checking anything.

Suggestion: follow the HTML parsing rule. A URL is a run of non-whitespace characters, and trailing commas on it end the candidate. Otherwise the descriptor runs up to the next comma:

function parseSrcset( srcset ) {
	const candidates = [];
	const input = srcset || '';
	const urlPattern = /[\s,]*(\S+)/y;
	const descriptorPattern = /\s*([^,]*?)\s*(?:,|$)/y;
	while ( true ) {
		const urlMatch = urlPattern.exec( input );
		if ( ! urlMatch ) {
			break;
		}
		let url = urlMatch[ 1 ];
		let descriptor = '';
		if ( /,$/.test( url ) ) {
			url = url.replace( /,+$/, '' );
		} else {
			descriptorPattern.lastIndex = urlPattern.lastIndex;
			descriptor = descriptorPattern.exec( input )[ 1 ];
			urlPattern.lastIndex = descriptorPattern.lastIndex;
		}
		candidates.push( { url, descriptor } );
	}
	return candidates;
}

It handles the cases above and the Cloudinary comma URLs (w_800,h_600,c_scale). If you keep the current regex, throw on leftover input instead of break, so a bad parse fails loudly.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch, fixed in 63da419. I used your parser: it follows the HTML parsing rule, so a.jpg 1x,b.jpg 2x, a.jpg 300w ,b.jpg 600w and a.jpg 1.5x, b.jpg 100h now give both candidates, and the Cloudinary URLs with commas stay whole.

* @param {Record<string, string|number>} values Setting slug to value.
* @return {Record<string, string|number>} Setting slug to the value before the write.
*/
function saveSettings( values ) {

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion (if-minor): This is the same code as saveSettings() in cloudinary-svg-delivery.spec.js. Could it move, with readSettings(), into a shared helper (for example tests/e2e/utils/settings.js)?

Both copies also put JSON inside a single-quoted PHP string (json_decode( '${ JSON.stringify(values) }' )), which breaks if a value contains '. The values here are safe, but a shared helper should escape ' and \\ before inserting the JSON (or base64-encode it).

@utkarshcloudinary utkarshcloudinary Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Done in c90d1d6. saveSettings() and readSettings() are now in tests/e2e/utils/settings.js, and the breakpoints, SVG and global transformations specs use them. The JSON is base64-encoded, so a ' or \ in a value cannot break the PHP string. #1353 uses the same helper.

// The first candidate is the original `src`; the rest
// carry the breakpoint width as a transformation.
if ( i > 0 ) {
expect( url, `transformation of ${ url }` ).toContain(

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (non-blocking, if-minor): toContain( 'w_1100' ) would also match w_11000, or a w_ in a different transformation segment. Match the parameter exactly:

expect( url ).toMatch( new RegExp( `[/,]w_${ EXPECTED_WIDTHS[ i ] }[,/]` ) );

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Fixed in c90d1d6 with your regex, so it matches the exact w_ parameter.


// Core may still emit its own srcset from the registered
// image sizes. None of the breakpoint-only widths may appear.
const widths = srcsetWidths( attrs.srcset );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

suggestion: If there is no srcset at all, srcsetWidths() returns [] and every not.toContain below passes. That is a valid outcome, but could we also check something positive? For example, that attrs.sizes, if present, isn't the plugin's (max-width: 1200px) 100vw, 1200px, or that every width found is one of core's (1200, 1024, 768, 300).

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added in c90d1d6. The test now checks that WordPress still writes a srcset, and that every width in it is a registered size of the image. It reads the sizes from the attachment metadata instead of a fixed list. The check passes on CI.


// The inline loader config carries the blur placeholder
// transformation.
const config = /var CLDLB = (\{.*?\});/.exec( html );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: .*? up to the first }; works today because wp_json_encode output has no }; and no newlines. Anchoring it to the script text that follows, or using [^\n]*?, would make it a bit less fragile.

@utkarshcloudinary utkarshcloudinary Nov 2, 2026 •

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Changed to [^\n]*? in c90d1d6.

browser,
baseURL,
} ) => {
configure( { breakpoints: false, lazyLoad: false } );

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: this is the same configure() call as the "disable breakpoints" test, but the title only talks about lazy load. A short comment that breakpoints are switched off here too would make that clear.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Added a comment in c90d1d6.

Cloudinary transformation segments such as w_800,h_600 contain commas,
so splitting srcset on every comma broke each candidate into pieces.
Parse candidates by their trailing descriptor instead and expose the
descriptor through parseSrcset().
Cover the responsive breakpoints and lazy load delivery settings on a
synced image:

- min/max/step produce the matching srcset width entries
- lazy load renders the SVG placeholder, data attributes and the
  CLDLB loader config with the blur placeholder
- turning either setting off removes its markup
The regex only ended a candidate at a comma followed by whitespace or
at the end of the string, and quietly returned what it had so far
when it did not match. Valid input such as "a.jpg 1x,b.jpg 2x" gave an
empty list, which made srcset checks pass without checking anything.

Read a URL as a run of non-whitespace characters, as the HTML spec
does, so commas inside Cloudinary URLs still stay part of the URL.
- Move saveSettings() and readSettings() to tests/e2e/utils/settings.js
  and base64-encode the JSON, so a quote in a value cannot break the
  PHP string.
- Match the breakpoint w_ parameter exactly.
- Check that every width left after breakpoints are off is a
  registered image size.
- Stop the CLDLB regex at the end of the line.
- Explain why the lazy load test also turns breakpoints off.
@utkarshcloudinary
utkarshcloudinary force-pushed the test/responsive-breakpoints-lazy-load-e2e branch from 78b1c9a to c90d1d6 Compare November 2, 2026 14:18

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants