@@ -29,6 +29,14 @@ const EDITOR_VALIDITY_WARNING_SELECTORS = [
2929 ".components-notice" ,
3030]
3131
32+ export function editorCommandWordPressUrl ( server : PlaygroundCliServer ) : string {
33+ return server . wordpressUrl ?? server . serverUrl
34+ }
35+
36+ function editorCommandPreviewTopology ( args : string [ ] , runtimeSpec : RuntimeCreateSpec , server : PlaygroundCliServer ) {
37+ return browserPreviewTopology ( [ "preview-mode=local" , ...args ] , runtimeSpec , editorCommandWordPressUrl ( server ) )
38+ }
39+
3240export async function runEditorCanvasProbeCommand ( {
3341 artifactRoot,
3442 runtimeSpec,
@@ -551,7 +559,7 @@ export async function runEditorOpenCommand({
551559 }
552560
553561 const waitTimeoutMs = durationArg ( args , "wait-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
554- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl , server . previewProxyDiagnostics ?. targetOrigin )
562+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
555563 const { preview, networkPolicy } = topology
556564 const routeTracker = createBrowserPreviewRouteTracker ( )
557565 const targetUrl = topology . resolveUrl ( target . url )
@@ -842,7 +850,7 @@ export async function runEditorActionsCommand({
842850 const waitTimeoutMs = durationArg ( args , "wait-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
843851 const stepTimeoutMs = durationArg ( args , "step-timeout" , BROWSER_STEP_DEFAULT_TIMEOUT_MS )
844852 const totalTimeoutMs = durationArg ( args , "timeout" , BROWSER_SCRIPT_DEFAULT_TIMEOUT_MS )
845- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl , server . previewProxyDiagnostics ?. targetOrigin )
853+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
846854 const { preview, networkPolicy } = topology
847855 const routeTracker = createBrowserPreviewRouteTracker ( )
848856 const targetUrl = topology . resolveUrl ( target . url )
@@ -1218,11 +1226,14 @@ async function executeEditorBlockMutation(page: import("playwright").Page, step:
12181226 let siblings = blocks
12191227 let block : Block | undefined
12201228 if ( typeof input . clientId === "string" ) {
1221- const locate = ( items : Block [ ] , parent ?: string ) : boolean => items . some ( ( item ) => {
1222- if ( item . clientId === input . clientId ) { block = item ; parentClientId = parent ; siblings = items ; return true }
1223- return locate ( item . innerBlocks ?? [ ] , item . clientId )
1224- } )
1225- locate ( blocks )
1229+ const pending : Array < { items : Block [ ] ; parent ?: string } > = [ { items : blocks } ]
1230+ while ( ! block && pending . length > 0 ) {
1231+ const current = pending . pop ( ) !
1232+ for ( const item of current . items ) {
1233+ if ( item . clientId === input . clientId ) { block = item ; parentClientId = current . parent ; siblings = current . items ; break }
1234+ if ( item . innerBlocks ?. length ) pending . push ( { items : item . innerBlocks , parent : item . clientId } )
1235+ }
1236+ }
12261237 } else {
12271238 const path = input . path ?? [ input . index ?? - 1 ]
12281239 for ( const position of path ) {
@@ -1239,22 +1250,33 @@ async function executeEditorBlockMutation(page: import("playwright").Page, step:
12391250 }
12401251 }
12411252 if ( ! block ?. clientId ) throw new Error ( "wp-codebox-editor-target-not-found: clientId, index, or path did not resolve a block" )
1242- const requireAction = ( name : string ) : ( ( ...args : unknown [ ] ) => unknown ) => {
1243- const action = actions [ name ]
1244- if ( typeof action !== "function" ) throw new Error ( `wp-codebox-editor-${ input . kind } -unsupported: core/block-editor.${ name } is unavailable` )
1245- return action as ( ...args : unknown [ ] ) => unknown
1246- }
1247- const create = ( spec : { name : string ; attributes ?: Record < string , unknown > ; innerBlocks ?: unknown [ ] } ) : unknown => {
1248- if ( typeof wp ?. blocks ?. createBlock !== "function" ) throw new Error ( "wp-codebox-editor-create-block-unsupported: wp.blocks.createBlock is unavailable" )
1249- return wp . blocks . createBlock ( spec . name , spec . attributes ?? { } , ( spec . innerBlocks ?? [ ] ) . map ( ( child ) => create ( child as typeof spec ) ) )
1250- }
1251- if ( input . kind === "selectBlock" ) return void requireAction ( "selectBlock" ) ( block . clientId )
1252- if ( input . kind === "updateBlockAttributes" ) return void requireAction ( "updateBlockAttributes" ) ( block . clientId , input . attributes )
1253- if ( input . kind === "removeBlock" ) return void requireAction ( "removeBlocks" ) ( [ block . clientId ] )
1254- if ( input . kind === "duplicateBlock" ) return void requireAction ( "duplicateBlocks" ) ( [ block . clientId ] )
1255- if ( input . kind === "moveBlock" ) return void requireAction ( "moveBlocksToPosition" ) ( [ block . clientId ] , parentClientId , parentClientId , input . position )
1256- if ( input . kind === "replaceBlock" ) return void requireAction ( "replaceBlock" ) ( block . clientId , create ( input . block ) )
1257- if ( input . kind === "replaceInnerBlocks" ) return void requireAction ( "replaceInnerBlocks" ) ( block . clientId , input . blocks . map ( create ) )
1253+ let actionName : string = input . kind
1254+ let actionArgs : unknown [ ] = [ block . clientId ]
1255+ if ( input . kind === "updateBlockAttributes" ) actionArgs . push ( input . attributes )
1256+ if ( input . kind === "removeBlock" ) { actionName = "removeBlocks" ; actionArgs = [ [ block . clientId ] ] }
1257+ if ( input . kind === "duplicateBlock" ) { actionName = "duplicateBlocks" ; actionArgs = [ [ block . clientId ] ] }
1258+ if ( input . kind === "moveBlock" ) { actionName = "moveBlocksToPosition" ; actionArgs = [ [ block . clientId ] , parentClientId , parentClientId , input . position ] }
1259+ if ( input . kind === "replaceBlock" || input . kind === "replaceInnerBlocks" ) {
1260+ const createBlock = wp ?. blocks ?. createBlock
1261+ if ( typeof createBlock !== "function" ) throw new Error ( "wp-codebox-editor-create-block-unsupported: wp.blocks.createBlock is unavailable" )
1262+ const specs = input . kind === "replaceBlock" ? [ input . block ] : input . blocks
1263+ const created = new Map < object , unknown > ( )
1264+ const pending = specs . map ( ( spec ) => ( { spec, visited : false } ) )
1265+ while ( pending . length > 0 ) {
1266+ const current = pending . pop ( ) !
1267+ if ( ! current . visited ) {
1268+ pending . push ( { spec : current . spec , visited : true } )
1269+ for ( const child of current . spec . innerBlocks ?? [ ] ) pending . push ( { spec : child , visited : false } )
1270+ continue
1271+ }
1272+ created . set ( current . spec , createBlock ( current . spec . name , current . spec . attributes ?? { } , ( current . spec . innerBlocks ?? [ ] ) . map ( ( child ) => created . get ( child ) ) ) )
1273+ }
1274+ actionName = input . kind
1275+ actionArgs = input . kind === "replaceBlock" ? [ block . clientId , created . get ( input . block ) ] : [ block . clientId , input . blocks . map ( ( spec ) => created . get ( spec ) ) ]
1276+ }
1277+ const action = actions [ actionName ]
1278+ if ( typeof action !== "function" ) throw new Error ( `wp-codebox-editor-${ input . kind } -unsupported: core/block-editor.${ actionName } is unavailable` )
1279+ action ( ...actionArgs )
12581280 } , step )
12591281}
12601282
@@ -1333,7 +1355,7 @@ async function waitForEditorSemanticReadiness(page: import("playwright").Page, t
13331355}
13341356
13351357async function saveEditorPost ( page : import ( "playwright" ) . Page , step : Extract < EditorActionStep , { kind : "savePost" } > , timeoutMs : number ) : Promise < BrowserEditorSaveSummary > {
1336- const save = await page . evaluate ( async ( input ) => {
1358+ await page . evaluate ( async ( input ) => {
13371359 const win = window as unknown as {
13381360 wp ?: {
13391361 blocks ?: { createBlock ?: ( name : string , attributes ?: Record < string , unknown > ) => unknown }
@@ -1363,31 +1385,19 @@ async function saveEditorPost(page: import("playwright").Page, step: Extract<Edi
13631385 blockEditor . insertBlocks ( [ createBlock ( "core/paragraph" , { content : input . content ?? input . marker } ) ] )
13641386 }
13651387 await Promise . resolve ( editorDispatch . savePost ( ) )
1366- const deadline = Date . now ( ) + input . timeoutMs
1367- await new Promise < void > ( ( resolve , reject ) => {
1368- const done = ( ) => {
1369- const isSaving = typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : false
1370- const didSucceed = typeof editor . didPostSaveRequestSucceed === "function" ? Boolean ( editor . didPostSaveRequestSucceed ( ) ) : undefined
1371- const didFail = typeof editor . didPostSaveRequestFail === "function" ? Boolean ( editor . didPostSaveRequestFail ( ) ) : false
1372- if ( ! isSaving && didSucceed !== false ) {
1373- cleanup ( )
1374- resolve ( )
1375- } else if ( didFail ) {
1376- cleanup ( )
1377- reject ( new Error ( "wp-codebox-editor-save-failed: core/editor savePost reported a failed request" ) )
1378- } else if ( Date . now ( ) > deadline ) {
1379- cleanup ( )
1380- reject ( new Error ( "wp-codebox-editor-save-timeout: timed out waiting for core/editor savePost to settle" ) )
1381- }
1382- }
1383- const unsubscribe = typeof wpData ?. subscribe === "function" ? wpData . subscribe ( done ) : undefined
1384- const interval = window . setInterval ( done , 250 )
1385- const cleanup = ( ) => {
1386- window . clearInterval ( interval )
1387- if ( unsubscribe ) unsubscribe ( )
1388- }
1389- done ( )
1390- } )
1388+ } , { marker : step . marker , content : step . content } )
1389+ const settled = await page . waitForFunction ( ( ) => {
1390+ const editor = ( window as unknown as { wp ?: { data ?: { select ?: ( store : string ) => Record < string , unknown > } } } ) . wp ?. data ?. select ?.( "core/editor" )
1391+ if ( ! editor ) return false
1392+ const saving = typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : false
1393+ const failed = typeof editor . didPostSaveRequestFail === "function" ? Boolean ( editor . didPostSaveRequestFail ( ) ) : false
1394+ const succeeded = typeof editor . didPostSaveRequestSucceed === "function" ? Boolean ( editor . didPostSaveRequestSucceed ( ) ) : undefined
1395+ return ! saving && ( failed || succeeded !== false ) ? { failed } : false
1396+ } , undefined , { timeout : timeoutMs } ) . then ( ( handle ) => handle . jsonValue ( ) as Promise < { failed : boolean } > )
1397+ if ( settled . failed ) throw new Error ( "wp-codebox-editor-save-failed: core/editor savePost reported a failed request" )
1398+ const save = await page . evaluate ( ( input ) => {
1399+ const editor = ( window as unknown as { wp ?: { data ?: { select ?: ( store : string ) => Record < string , unknown > } } } ) . wp ?. data ?. select ?.( "core/editor" )
1400+ if ( ! editor ) throw new Error ( "wp-codebox-editor-readiness-unavailable: core/editor store is unavailable" )
13911401 const editedContent = typeof editor . getEditedPostContent === "function" ? String ( editor . getEditedPostContent ( ) ?? "" ) : ""
13921402 return {
13931403 schema : "wp-codebox/editor-save/v1" ,
@@ -1398,7 +1408,7 @@ async function saveEditorPost(page: import("playwright").Page, step: Extract<Edi
13981408 markerPresent : input . marker ? editedContent . includes ( input . marker ) : undefined ,
13991409 content : editedContent ,
14001410 }
1401- } , { marker : step . marker , content : step . content , timeoutMs } )
1411+ } , { marker : step . marker } )
14021412
14031413 const { content, ...summary } = save as BrowserEditorSaveSummary & { content ?: string }
14041414 return {
@@ -1493,13 +1503,22 @@ export async function captureEditorState(page: import("playwright").Page, target
14931503 const currentPost = typeof editor . getCurrentPost === "function" ? editor . getCurrentPost ( ) as Record < string , unknown > | null : null
14941504 const blocks = typeof blockEditor . getBlocks === "function" ? blockEditor . getBlocks ( ) as Array < Record < string , unknown > > : [ ]
14951505 const serialize = ( window as unknown as { wp ?: { blocks ?: { serialize ?: ( items : unknown [ ] ) => string } } } ) . wp ?. blocks ?. serialize
1496- const toTree = ( items : Array < Record < string , unknown > > ) : NonNullable < EditorStateSnapshot [ "blocks" ] > => items . map ( ( block ) => ( {
1497- name : typeof block . name === "string" ? block . name : "" ,
1498- clientId : typeof block . clientId === "string" ? block . clientId : undefined ,
1499- attributes : typeof block . attributes === "object" && block . attributes ? block . attributes as Record < string , unknown > : undefined ,
1500- isValid : typeof block . isValid === "boolean" ? block . isValid : undefined ,
1501- innerBlocks : Array . isArray ( block . innerBlocks ) ? toTree ( block . innerBlocks as Array < Record < string , unknown > > ) : undefined ,
1502- } ) )
1506+ const tree : NonNullable < EditorStateSnapshot [ "blocks" ] > = [ ]
1507+ const pending : Array < { items : Array < Record < string , unknown > > ; output : NonNullable < EditorStateSnapshot [ "blocks" ] > } > = [ { items : blocks , output : tree } ]
1508+ while ( pending . length > 0 ) {
1509+ const current = pending . pop ( ) !
1510+ for ( const block of current . items ) {
1511+ const innerBlocks : NonNullable < EditorStateSnapshot [ "blocks" ] > = [ ]
1512+ current . output . push ( {
1513+ name : typeof block . name === "string" ? block . name : "" ,
1514+ clientId : typeof block . clientId === "string" ? block . clientId : undefined ,
1515+ attributes : typeof block . attributes === "object" && block . attributes ? block . attributes as Record < string , unknown > : undefined ,
1516+ isValid : typeof block . isValid === "boolean" ? block . isValid : undefined ,
1517+ innerBlocks,
1518+ } )
1519+ if ( Array . isArray ( block . innerBlocks ) ) pending . push ( { items : block . innerBlocks as Array < Record < string , unknown > > , output : innerBlocks } )
1520+ }
1521+ }
15031522 const savedContent = typeof currentPost ?. content === "object" && currentPost . content
15041523 ? stringValue ( ( currentPost . content as Record < string , unknown > ) . raw ?? ( currentPost . content as Record < string , unknown > ) . rendered )
15051524 : undefined
@@ -1511,7 +1530,7 @@ export async function captureEditorState(page: import("playwright").Page, target
15111530 status : typeof currentPost ?. status === "string" ? currentPost . status : undefined ,
15121531 title : typeof currentPost ?. title === "object" && currentPost . title ? ( currentPost . title as Record < string , unknown > ) . raw ?? ( currentPost . title as Record < string , unknown > ) . rendered : undefined ,
15131532 } ,
1514- blocks : toTree ( blocks ) ,
1533+ blocks : tree ,
15151534 serializedContent : typeof serialize === "function" ? serialize ( blocks ) : typeof editor . getEditedPostContent === "function" ? String ( editor . getEditedPostContent ( ) ?? "" ) : undefined ,
15161535 dirty : typeof editor . isEditedPostDirty === "function" ? Boolean ( editor . isEditedPostDirty ( ) ) : undefined ,
15171536 saving : typeof editor . isSavingPost === "function" ? Boolean ( editor . isSavingPost ( ) ) : undefined ,
@@ -1957,7 +1976,7 @@ export async function runEditorValidateBlocksCommand({
19571976 const content = await editorValidateContentFromArgs ( args )
19581977 const provider = editorValidateProviderFromArgs ( args )
19591978 const waitTimeoutMs = durationArg ( args , "wait-timeout" , EDITOR_VALIDATE_BLOCKS_READY_TIMEOUT_MS )
1960- const topology = browserPreviewTopology ( args , runtimeSpec , server . serverUrl , server . previewProxyDiagnostics ?. targetOrigin )
1979+ const topology = editorCommandPreviewTopology ( args , runtimeSpec , server )
19611980 const { preview, networkPolicy } = topology
19621981 const routeTracker = createBrowserPreviewRouteTracker ( )
19631982 const targetUrl = topology . resolveUrl ( target . url )
0 commit comments