Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ export function LaunchForm( { site }: { site: Site } ) {
<SiteLaunchButton
site={ site }
tracksContext="site_settings"
backTo={ `/sites/${ site.slug }` }
flowDestination={ `/sites/${ site.slug }` }
/>
}
>
Expand Down
6 changes: 3 additions & 3 deletions client/dashboard/sites/site-launch-button/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ export function SiteLaunchButton( {
tracksContext,
launchUrl,
LaunchModal,
backTo,
flowDestination,
}: {
site: Site;
tracksContext: string;
launchUrl?: string;
LaunchModal?: A4aLaunchModalComponent;
backTo?: string;
flowDestination?: string;
} ) {
const { queries } = useAppContext();
const { recordTracksEvent } = useAnalytics();

const { isLoading, isExperimentLoading, isHidden, isDisabled, isBusy, href, onClick, modal } =
useSiteLaunch( site, {
tracksContext,
backTo,
flowDestination,
a4aLaunchUrl: launchUrl,
a4aLaunchModal: LaunchModal,
domainsOptions: queries.domainsQuery(),
Expand Down
81 changes: 81 additions & 0 deletions client/dashboard/sites/site-launch-button/test/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,87 @@ describe( '<SiteLaunchButton>', () => {
expect( screen.queryByRole( 'dialog' ) ).not.toBeInTheDocument();
} );

test( 'points Back at the page the launch started from, and post-launch elsewhere', async () => {
mockDomainsApi( [ createMockDomain( 'kaonashi.wordpress.com', false ) ] );
window.history.pushState( {}, '', '/sites/kaonashi.wordpress.com/settings/site-visibility' );

render(
<SiteLaunchButton
site={ createMockSite( {
plan: {
product_slug: 'free_plan',
product_name: 'Free',
is_free: true,
},
} as Partial< Site > ) }
tracksContext="test"
flowDestination="/sites/kaonashi.wordpress.com"
/>
);

const launchLink = await screen.findByRole( 'link', { name: 'Launch your site' } );
const query = new URL( launchLink.getAttribute( 'href' ) ?? '', window.location.origin )
.searchParams;

expect( query.get( 'back_to' ) ).toContain(
'/sites/kaonashi.wordpress.com/settings/site-visibility'
);
expect( query.get( 'redirect_to' ) ).toContain( '/sites/kaonashi.wordpress.com' );
expect( query.get( 'redirect_to' ) ).not.toContain( 'site-visibility' );

window.history.pushState( {}, '', '/' );
} );

test( 'an immediate launch stays on the page even when a flow destination is set', async () => {
const user = userEvent.setup();
mockDomainsApi( [ createMockDomain( 'kaonashi.wordpress.com', false ) ] );
const launchScope = mockLaunchApi();

const originalLocation = window.location;
const assign = jest.fn();
Object.defineProperty( window, 'location', {
writable: true,
value: {
href: 'http://localhost/sites/kaonashi.wordpress.com/settings/site-visibility',
origin: 'http://localhost',
pathname: '/sites/kaonashi.wordpress.com/settings/site-visibility',
search: '',
assign,
},
} );
const replaceState = jest
.spyOn( window.history, 'replaceState' )
.mockImplementation( () => {} );

try {
render(
<SiteLaunchButton
site={ createMockSite( {
plan: {
product_slug: 'wp_bundle_hosting_trial_monthly',
product_name: 'Hosting Trial',
is_free: false,
},
} as Partial< Site > ) }
tracksContext="test"
flowDestination="/sites/kaonashi.wordpress.com"
/>
);

const launchButton = await screen.findByRole( 'button', { name: 'Launch your site' } );
replaceState.mockClear();

await user.click( launchButton );

await waitFor( () => expect( launchScope.isDone() ).toBe( true ) );
await waitFor( () => expect( replaceState ).toHaveBeenCalled() );
expect( assign ).not.toHaveBeenCalled();
} finally {
replaceState.mockRestore();
Object.defineProperty( window, 'location', { writable: true, value: originalLocation } );
}
} );

test( 'renders a link to the launch flow for a free site without an immediate launch', async () => {
mockDomainsApi( [ createMockDomain( 'kaonashi.wordpress.com', false ) ] );

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,11 @@ type RecordTracksEvent = ( eventName: string, properties?: Record< string, unkno

export interface UseSiteLaunchOptions {
tracksContext: string;
/** Where the launch flow's Back button returns to. Defaults to the current page. */
backTo?: string;
/** Where the launch flow leaves the user once the site is live. Defaults to `backTo`. */
flowDestination?: string;
/** Where an immediate launch leaves the user. Defaults to staying on the current page. */
postLaunchUrl?: string;
a4aLaunchUrl?: string;
a4aLaunchModal?: A4aLaunchModalComponent;
Expand All @@ -51,6 +55,7 @@ export function useSiteLaunch(
{
tracksContext,
backTo,
flowDestination,
postLaunchUrl,
a4aLaunchUrl,
a4aLaunchModal: A4aLaunchModal,
Expand Down Expand Up @@ -98,9 +103,10 @@ export function useSiteLaunch(
back_to: backTo
? dashboardLinkWithBackport( backTo )
: redirectToDashboardLink( { supportBackport: true } ),
...( flowDestination ? { redirect_to: dashboardLinkWithBackport( flowDestination ) } : {} ),
dashboard: getCurrentDashboard(),
} );
}, [ site, backTo ] );
}, [ site, backTo, flowDestination ] );

const track = () => {
recordTracksEvent( 'calypso_dashboard_site_launch_button_click', { context: tracksContext } );
Expand Down
4 changes: 2 additions & 2 deletions client/signup/config/flows-pure.js
Original file line number Diff line number Diff line change
Expand Up @@ -310,8 +310,8 @@ export function generateFlows( {
steps: [ 'domains-launch', 'plans-launch', 'launch' ],
destination: getLaunchDestination,
description: 'A flow to launch a private site.',
providesDependenciesInQuery: [ 'siteSlug', 'back_to' ],
optionalDependenciesInQuery: [ 'back_to' ],
providesDependenciesInQuery: [ 'siteSlug', 'back_to', 'redirect_to' ],
optionalDependenciesInQuery: [ 'back_to', 'redirect_to' ],
hideProgressIndicator: true,
lastModified: '2019-11-22',
excludeFromManageSiteFlows: true,
Expand Down
6 changes: 6 additions & 0 deletions client/signup/config/flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,12 @@ export function getLaunchReturnUrl( dependencies ) {
}

function getLaunchDestination( dependencies ) {
// `redirect_to` lands the user somewhere other than where they came from once the site is live,
// so `back_to` is free to keep meaning "the page the Back button returns to".
if ( dependencies.redirect_to ) {
return addQueryArgs( { celebrateLaunch: 'true' }, dependencies.redirect_to );
}

const { url, celebrateArgs } = getLaunchReturnTarget( dependencies );

return addQueryArgs( celebrateArgs, url );
Expand Down
7 changes: 7 additions & 0 deletions client/signup/controller.js
Original file line number Diff line number Diff line change
Expand Up @@ -306,6 +306,13 @@ export default {
context.store.dispatch( updateDependencies( additionalDependencies ) );
}

if ( 'launch-site' === flowName ) {
// The dependency store persists between visits, and `redirect_to` is optional, so an
// abandoned launch would otherwise decide where the next one leaves the user. Re-read it
// from the query the flow was entered with.
context.store.dispatch( updateDependencies( { redirect_to: query?.redirect_to ?? null } ) );
}

context.primary = createElement( SignupComponent, {
store: context.store,
path: context.path,
Expand Down
39 changes: 39 additions & 0 deletions client/signup/test/flows.js
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,45 @@ describe( 'Signup Flows Configuration', () => {
} );
} );

describe( 'launch-site destination', () => {
beforeAll( () => {
// The suites above stub `getFlows` with fixtures; these assertions need the real config.
jest.restoreAllMocks();
} );

const getDestination = ( dependencies ) =>
flows.getFlows()[ 'launch-site' ].destination( dependencies );

test( 'returns the user to where the flow was started from', () => {
expect(
getDestination( {
siteSlug: 'test-site',
back_to: '/sites/test-site/settings/site-visibility',
} )
).toBe( '/sites/test-site/settings/site-visibility?celebrateLaunch=true' );
} );

test( 'prefers redirect_to, so the post-launch landing can differ from where Back goes', () => {
expect(
getDestination( {
siteSlug: 'test-site',
back_to: '/sites/test-site/settings/site-visibility',
redirect_to: '/sites/test-site',
} )
).toBe( '/sites/test-site?celebrateLaunch=true' );
} );

test( 'falls back to back_to when redirect_to was cleared on flow entry', () => {
expect(
getDestination( {
siteSlug: 'test-site',
back_to: '/sites/test-site/settings/site-visibility',
redirect_to: null,
} )
).toBe( '/sites/test-site/settings/site-visibility?celebrateLaunch=true' );
} );
} );

describe( 'filterDestination with checkout URLs', () => {
// Mock the required modules
beforeAll( () => {
Expand Down
Loading