Skip to content

Commit 044dd8d

Browse files
authored
Merge pull request #1316 from cloudinary/test/asset-edit-transformations-e2e
test(e2e): Add per-asset overlay and crop spec
2 parents aea85e0 + 42ba666 commit 044dd8d

1 file changed

Lines changed: 260 additions & 0 deletions

File tree

Lines changed: 260 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,260 @@
1+
/**
2+
* External dependencies
3+
*/
4+
const fs = require( 'fs' );
5+
const path = require( 'path' );
6+
const { test, expect } = require( './fixtures' );
7+
8+
/**
9+
* Internal dependencies
10+
*/
11+
const { ensureCloudinaryConnected } = require( './utils/connection' );
12+
const { expectCloudinaryUrl } = require( './utils/delivery' );
13+
const { wpCli } = require( './utils/wizard' );
14+
15+
const FIXTURE_PATH = path.join( __dirname, 'fixtures', 'test-image.jpg' );
16+
17+
const OVERLAY_TEXT = 'CldE2EOverlay';
18+
const CROP = 'c_crop,h_120,w_160';
19+
20+
let cloudName;
21+
22+
/**
23+
* State shared by the tests in this file. Every test edits the same
24+
* attachment, so the tests run in order (see `mode: 'serial'`).
25+
*
26+
* @type {{ postId: number, postLink: string, attachmentId: number }|null}
27+
*/
28+
let created = null;
29+
30+
/**
31+
* Locate an Edit Asset field by its setting slug. The plugin's UI builds
32+
* IDs such as `edit_asset.edit_affects.text_overlay_text`, which contain
33+
* dots, so an attribute selector is used instead of `#id`.
34+
*
35+
* @param {import('@playwright/test').Page} page
36+
* @param {string} slug Setting slug.
37+
* @return {import('@playwright/test').Locator} The input.
38+
*/
39+
function assetField( page, slug ) {
40+
return page.locator( `[id="edit_asset.edit_affects.${ slug }"]` );
41+
}
42+
43+
/**
44+
* Open the Edit Asset page for the test attachment and wait for the
45+
* preview script to initialise.
46+
*
47+
* @param {import('@playwright/test').Page} page
48+
*/
49+
async function openEditAsset( page ) {
50+
await page.goto(
51+
`/wp-admin/admin.php?page=cloudinary&section=edit-asset&asset=${ created.attachmentId }`
52+
);
53+
54+
// `asset-edit.js` appends the preview image on `window.load`; once it
55+
// is there, the save handlers are bound.
56+
await expect(
57+
page.locator( '#cld-asset-edit img' ),
58+
'the asset preview should render'
59+
).toBeVisible();
60+
}
61+
62+
/**
63+
* Click a save button on the Edit Asset page and wait for the REST call.
64+
*
65+
* @param {import('@playwright/test').Page} page
66+
* @param {import('@playwright/test').Locator} button
67+
* @return {Promise<Object>} The `save_asset` response body.
68+
*/
69+
async function saveAsset( page, button ) {
70+
const [ response ] = await Promise.all( [
71+
page.waitForResponse(
72+
( res ) =>
73+
res.url().includes( 'save_asset' ) &&
74+
res.request().method() === 'POST'
75+
),
76+
button.click(),
77+
] );
78+
expect( response.ok(), 'save_asset should succeed' ).toBe( true );
79+
80+
const body = await response.json();
81+
expect(
82+
body.note,
83+
'the plugin should accept the transformation as valid'
84+
).toBeUndefined();
85+
86+
return body;
87+
}
88+
89+
/**
90+
* Open the published post and read the delivery attributes of the inline
91+
* image once the lazy-loader has swapped in the Cloudinary URL. `src` in
92+
* the result is the URL the browser loaded.
93+
*
94+
* @param {import('@playwright/test').Page} page
95+
* @return {Promise<{ src: string, transformations: string }>} Delivery data.
96+
*/
97+
async function readDelivery( page ) {
98+
await page.goto( created.postLink );
99+
100+
const img = page
101+
.locator( `.wp-block-image img.wp-image-${ created.attachmentId }` )
102+
.first();
103+
await expect( img, 'the inline image should render' ).toBeVisible();
104+
await img.scrollIntoViewIfNeeded();
105+
106+
// The lazy-loader either writes the delivery URL to `src` or, when the
107+
// theme emits a srcset, restores `data-srcset` and leaves the SVG
108+
// placeholder in `src`. `currentSrc` is the URL the browser picked in
109+
// both cases.
110+
const currentSrc = () => img.evaluate( ( node ) => node.currentSrc );
111+
await expect
112+
.poll( currentSrc, {
113+
message: 'the lazy-loader should swap in the Cloudinary URL',
114+
} )
115+
.toMatch( /^https:\/\/res\.cloudinary\.com\// );
116+
117+
const src = await currentSrc();
118+
expectCloudinaryUrl( src, cloudName );
119+
120+
return {
121+
src,
122+
transformations:
123+
( await img.getAttribute( 'data-transformations' ) ) || '',
124+
};
125+
}
126+
127+
// @serial: needs real credentials in `cloudinary_connect` to sync assets,
128+
// while every analytics spec overwrites that option with fake ones.
129+
test.describe( 'Cloudinary per-asset editing', { tag: '@serial' }, () => {
130+
test.describe.configure( { mode: 'serial' } );
131+
132+
test.beforeAll( async ( { requestUtils } ) => {
133+
( { cloudName } = ensureCloudinaryConnected() );
134+
135+
const media = await requestUtils.rest( {
136+
method: 'POST',
137+
path: '/wp/v2/media',
138+
headers: {
139+
'Content-Type': 'image/jpeg',
140+
'Content-Disposition': `attachment; filename="asset-edit-e2e-${ Date.now() }.jpg"`,
141+
},
142+
data: fs.readFileSync( FIXTURE_PATH ),
143+
} );
144+
145+
const content =
146+
`<!-- wp:image {"id":${ media.id }} -->\n` +
147+
`<figure class="wp-block-image"><img src="${ media.source_url }" alt="" class="wp-image-${ media.id }"/></figure>\n` +
148+
`<!-- /wp:image -->`;
149+
150+
const post = await requestUtils.rest( {
151+
method: 'POST',
152+
path: '/wp/v2/posts',
153+
data: {
154+
status: 'publish',
155+
title: `Cloudinary asset edit e2e ${ Date.now() }`,
156+
content,
157+
},
158+
} );
159+
160+
created = {
161+
postId: post.id,
162+
postLink: post.link,
163+
attachmentId: media.id,
164+
};
165+
166+
// The Edit Asset page and the front-end rewrite both need the
167+
// asset to be synced to Cloudinary.
168+
wpCli( [ 'cloudinary', 'sync' ] );
169+
} );
170+
171+
test.afterAll( () => {
172+
if ( ! created ) {
173+
return;
174+
}
175+
const { postId, attachmentId } = created;
176+
created = null;
177+
178+
// WP-CLI rather than REST for the same reason as the image delivery
179+
// spec: `force=true` on a `?rest_route=` URL is fragile.
180+
for ( const id of [ postId, attachmentId ] ) {
181+
try {
182+
wpCli( [ 'post', 'delete', String( id ), '--force' ] );
183+
} catch ( e ) {
184+
console.warn( `Cleanup of post ${ id } failed:`, e.message );
185+
}
186+
}
187+
} );
188+
189+
test( 'text overlay updates the delivery URL and reverts when removed', async ( {
190+
page,
191+
} ) => {
192+
const baseline = await readDelivery( page );
193+
expect( baseline.src ).not.toContain( 'l_text:' );
194+
195+
await openEditAsset( page );
196+
await assetField( page, 'text_overlay_text' ).fill( OVERLAY_TEXT );
197+
const saved = await saveAsset(
198+
page,
199+
page.locator( '#cld-asset-save-text-overlay' )
200+
);
201+
expect( saved.text_overlay ).toContain( OVERLAY_TEXT );
202+
203+
const withOverlay = await readDelivery( page );
204+
for ( const value of [
205+
withOverlay.transformations,
206+
withOverlay.src,
207+
] ) {
208+
expect( value ).toContain( `l_text:Arial_20:${ OVERLAY_TEXT }` );
209+
expect( value ).toContain( 'fl_layer_apply' );
210+
}
211+
212+
// "Reset All" only clears the form; saving persists the empty overlay.
213+
await openEditAsset( page );
214+
await expect( assetField( page, 'text_overlay_text' ) ).toHaveValue(
215+
OVERLAY_TEXT
216+
);
217+
await page.locator( '#cld-asset-remove-text-overlay' ).click();
218+
await expect( assetField( page, 'text_overlay_text' ) ).toHaveValue(
219+
''
220+
);
221+
const cleared = await saveAsset(
222+
page,
223+
page.locator( '#cld-asset-save-text-overlay' )
224+
);
225+
expect( cleared.text_overlay ).toBe( '' );
226+
227+
expect( await readDelivery( page ) ).toEqual( baseline );
228+
} );
229+
230+
test( 'crop updates the delivery URL and reverts when removed', async ( {
231+
page,
232+
} ) => {
233+
const baseline = await readDelivery( page );
234+
expect( baseline.src ).not.toContain( CROP );
235+
236+
await openEditAsset( page );
237+
const input = assetField( page, 'transformations' );
238+
await input.fill( CROP );
239+
const saved = await saveAsset(
240+
page,
241+
page.locator( '#cld-asset-edit-save' )
242+
);
243+
expect( saved.transformations ).toBe( CROP );
244+
245+
const withCrop = await readDelivery( page );
246+
expect( withCrop.transformations ).toContain( CROP );
247+
expect( withCrop.src ).toContain( CROP );
248+
249+
await openEditAsset( page );
250+
await expect( input ).toHaveValue( CROP );
251+
await input.fill( '' );
252+
const cleared = await saveAsset(
253+
page,
254+
page.locator( '#cld-asset-edit-save' )
255+
);
256+
expect( cleared.transformations ).toBe( '' );
257+
258+
expect( await readDelivery( page ) ).toEqual( baseline );
259+
} );
260+
} );

0 commit comments

Comments
 (0)