From c103a65b8927bae173844efa30783a9acf282db9 Mon Sep 17 00:00:00 2001 From: David Arenas Date: Tue, 19 Sep 2023 17:15:32 +0200 Subject: [PATCH] Interactivity API: Add `timeout` option to `navigate()` (#54474) * Add timeout to the navigate function * Add test for timeout * Update changelog * Throw error if timeout finish * Revert "Throw error if timeout finish" This reverts commit 92f133317698c35bb55318ec0c1855875d0fffe9. * Do not reject when timeout ends --- .../router-navigate/render.php | 7 +++ .../router-navigate/view.js | 8 ++- packages/interactivity/CHANGELOG.md | 2 + packages/interactivity/src/router.js | 10 +++- .../interactivity/router-navigate.spec.ts | 50 ++++++++++++++++--- 5 files changed, 67 insertions(+), 10 deletions(-) diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php index 683e0eaff6e89..90246623ed997 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php +++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/render.php @@ -21,6 +21,13 @@ data-wp-text="state.router.status" >undefined + + $link ) { diff --git a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js index 468b4a64482d3..422750eec366e 100644 --- a/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js +++ b/packages/e2e-tests/plugins/interactive-blocks/router-navigate/view.js @@ -9,6 +9,7 @@ router: { status: 'idle', navigations: 0, + timeout: 10000, } }, actions: { @@ -20,8 +21,9 @@ state.router.status = 'busy'; const force = e.target.dataset.forceNavigation === 'true'; + const { timeout } = state.router; - await navigate( e.target.href, { force } ); + await navigate( e.target.href, { force, timeout } ); state.router.navigations -= 1; @@ -29,6 +31,10 @@ state.router.status = 'idle'; } }, + toggleTimeout: ( { state }) => { + state.router.timeout = + state.router.timeout === 10000 ? 0 : 10000; + } }, }, } ); diff --git a/packages/interactivity/CHANGELOG.md b/packages/interactivity/CHANGELOG.md index 19e82ff30471b..d55de04c0f858 100644 --- a/packages/interactivity/CHANGELOG.md +++ b/packages/interactivity/CHANGELOG.md @@ -6,6 +6,8 @@ - Improve `navigate()` to render only the result of the last call when multiple happen simultaneously. ([#54201](https://github.com/WordPress/gutenberg/pull/54201)) +- Add `timeout` option to `navigate()`, with a default value of `10000` milliseconds. ([#54474](https://github.com/WordPress/gutenberg/pull/54474)) + ## 2.2.0 (2023-08-31) ### Enhancements diff --git a/packages/interactivity/src/router.js b/packages/interactivity/src/router.js index 17b2b54e4d457..ee9755126994e 100644 --- a/packages/interactivity/src/router.js +++ b/packages/interactivity/src/router.js @@ -86,7 +86,14 @@ export const navigate = async ( href, options = {} ) => { const url = cleanUrl( href ); navigatingTo = href; prefetch( url, options ); - const page = await pages.get( url ); + + // Create a promise that resolves when the specified timeout ends. The + // timeout value is 10 seconds by default. + const timeoutPromise = new Promise( ( resolve ) => + setTimeout( resolve, options.timeout ?? 10000 ) + ); + + const page = await Promise.race( [ pages.get( url ), timeoutPromise ] ); // Once the page is fetched, the destination URL could have changed (e.g., // by clicking another link in the meantime). If so, bail out, and let the @@ -102,6 +109,7 @@ export const navigate = async ( href, options = {} ) => { ); } else { window.location.assign( href ); + await new Promise( () => {} ); } }; diff --git a/test/e2e/specs/interactivity/router-navigate.spec.ts b/test/e2e/specs/interactivity/router-navigate.spec.ts index 308bc6fd98618..552763ea40d5c 100644 --- a/test/e2e/specs/interactivity/router-navigate.spec.ts +++ b/test/e2e/specs/interactivity/router-navigate.spec.ts @@ -106,24 +106,58 @@ test.describe( 'Router navigate', () => { await expect( status ).toHaveText( 'busy' ); await expect( title ).toHaveText( 'Main' ); - { - const resolver = resolvers.pop(); - if ( resolver ) resolver(); - } + resolvers.pop()!(); await expect( navigations ).toHaveText( '1' ); await expect( status ).toHaveText( 'busy' ); await expect( title ).toHaveText( 'Link 1' ); await expect( page ).toHaveURL( href ); - { - const resolver = resolvers.pop(); - if ( resolver ) resolver(); - } + resolvers.pop()!(); await expect( navigations ).toHaveText( '0' ); await expect( status ).toHaveText( 'idle' ); await expect( title ).toHaveText( 'Link 1' ); await expect( page ).toHaveURL( href ); } ); + + test( 'should reload the next page when the timeout ends', async ( { + page, + interactivityUtils: utils, + } ) => { + const link1 = utils.getLink( 'router navigate - link 1' ); + + const title = page.getByTestId( 'title' ); + const toggleTimeout = page.getByTestId( 'toggle timeout' ); + + let resolver: Function; + + await page.route( link1, async ( route ) => { + // Only capture the first request. + if ( ! resolver ) { + await new Promise( ( r ) => ( resolver = r ) ); + await route.abort(); + } else { + await route.continue(); + } + } ); + + await expect( toggleTimeout ).toHaveText( 'Timeout 10000' ); + + // Set timeout to 0. + await toggleTimeout.click(); + await expect( toggleTimeout ).toHaveText( 'Timeout 0' ); + + // Navigation should timeout almost instantly. + await page.getByTestId( 'link 1' ).click(); + + await expect( page ).toHaveURL( link1 ); + await expect( title ).toHaveText( 'Link 1' ); + + // If timeout is 10000, that means the page has been reloaded. + await expect( toggleTimeout ).toHaveText( 'Timeout 10000' ); + + // Make the fetch abort, just in case. + resolver!(); + } ); } );