diff --git a/src/components/site-form.tsx b/src/components/site-form.tsx index f00225ff47..10e138ece0 100644 --- a/src/components/site-form.tsx +++ b/src/components/site-form.tsx @@ -72,7 +72,7 @@ function FormPathInputComponent( { ) } > { expect( screen.getByDisplayValue( 'My WordPress Website' ) ).toBeInTheDocument(); expect( screen.getByDisplayValue( '/default_path/my-wordpress-website' ) ).toBeInTheDocument(); } ); + + it( 'should reset to the proposed path when the path is set to default app directory', async () => { + const user = userEvent.setup(); + mockGenerateProposedSitePath.mockImplementation( ( name ) => { + const path = `/default_path/${ name.replace( /\s/g, '-' ).toLowerCase() }`; + return Promise.resolve( { + path, + name, + isEmpty: true, + isWordPress: false, + } ); + } ); + mockShowOpenFolderDialog.mockResolvedValue( { + path: 'populated-non-wordpress-directory', + name: 'My WordPress Website', + isEmpty: false, + isWordPress: false, + } ); + render( ); + + await user.click( screen.getByRole( 'button', { name: 'Add site' } ) ); + await user.click( screen.getByTestId( 'select-path-button' ) ); + + mockShowOpenFolderDialog.mockResolvedValue( { + path: '/default_path', + name: 'My WordPress Website', + isEmpty: false, + isWordPress: false, + } ); + await user.click( screen.getByTestId( 'select-path-button' ) ); + await user.click( screen.getByDisplayValue( 'My WordPress Website' ) ); + await user.type( screen.getByDisplayValue( 'My WordPress Website' ), ' mutated' ); + // screen.debug( screen.getByRole( 'dialog' ) ); + + expect( + screen.getByDisplayValue( '/default_path/my-wordpress-website-mutated' ) + ).toBeInTheDocument(); + } ); + + it( 'should display a helpful error message when an error occurs while creating the site', async () => { + const user = userEvent.setup(); + mockGenerateProposedSitePath.mockResolvedValue( { + path: '/default_path/my-wordpress-website', + name: 'My WordPress Website', + isEmpty: true, + isWordPress: false, + } ); + mockCreateSite.mockImplementation( () => { + throw new Error( 'Failed to create site' ); + } ); + render( ); + + await user.click( screen.getByRole( 'button', { name: 'Add site' } ) ); + await user.click( screen.getByRole( 'button', { name: 'Add site' } ) ); + + await waitFor( () => { + expect( screen.getByRole( 'alert' ) ).toHaveTextContent( + 'An error occurred while creating the site. Verify your selected local path is an empty directory or an existing WordPress folder and try again. If this problem persists, please contact support.' + ); + } ); + } ); } ); diff --git a/src/hooks/use-add-site.ts b/src/hooks/use-add-site.ts index d29831c9a9..40cbd87367 100644 --- a/src/hooks/use-add-site.ts +++ b/src/hooks/use-add-site.ts @@ -1,3 +1,4 @@ +import * as Sentry from '@sentry/electron/renderer'; import { useI18n } from '@wordpress/react-i18n'; import { useCallback, useMemo, useState } from 'react'; import { getIpcApi } from '../lib/get-ipc-api'; @@ -28,11 +29,13 @@ export function useAddSite() { const { path, name, isEmpty, isWordPress } = response; setDoesPathContainWordPress( false ); setError( '' ); - setSitePath( path ); + const pathResetToDefaultSitePath = + path === proposedSitePath.substring( 0, proposedSitePath.lastIndexOf( '/' ) ); + setSitePath( pathResetToDefaultSitePath ? '' : path ); if ( siteWithPathAlreadyExists( path ) ) { return; } - if ( ! isEmpty && ! isWordPress ) { + if ( ! isEmpty && ! isWordPress && ! pathResetToDefaultSitePath ) { setError( __( 'This directory is not empty. Please select an empty directory or an existing WordPress folder.' @@ -45,7 +48,7 @@ export function useAddSite() { setSiteName( name ?? null ); } } - }, [ __, siteWithPathAlreadyExists, siteName ] ); + }, [ __, siteWithPathAlreadyExists, siteName, proposedSitePath ] ); const handleAddSiteClick = useCallback( async () => { setIsAddingSite( true ); @@ -53,12 +56,17 @@ export function useAddSite() { const path = sitePath ? sitePath : proposedSitePath; await createSite( path, siteName ?? '' ); } catch ( e ) { - setError( ( e as Error )?.message ); + Sentry.captureException( e ); + setError( + __( + 'An error occurred while creating the site. Verify your selected local path is an empty directory or an existing WordPress folder and try again. If this problem persists, please contact support.' + ) + ); setIsAddingSite( false ); throw e; } setIsAddingSite( false ); - }, [ createSite, proposedSitePath, siteName, sitePath ] ); + }, [ createSite, proposedSitePath, siteName, sitePath, __ ] ); const handleSiteNameChange = useCallback( async ( name: string ) => { diff --git a/src/ipc-handlers.ts b/src/ipc-handlers.ts index e96bd8d346..37ef841c81 100644 --- a/src/ipc-handlers.ts +++ b/src/ipc-handlers.ts @@ -112,20 +112,15 @@ export async function createSite( ): Promise< SiteDetails[] > { const userData = await loadUserData(); const forceSetupSqlite = false; - let wasPathEmpty = false; - // We only try to create the directory recursively if the user has - // not selected a path from the dialog (and thus they use the "default" path) + // We only recursively create the directory if the user has not selected a + // path from the dialog (and thus they use the "default" or suggested path). if ( ! ( await pathExists( path ) ) && path.startsWith( DEFAULT_SITE_PATH ) ) { - try { - fs.mkdirSync( path, { recursive: true } ); - } catch ( err ) { - return userData.sites; - } + fs.mkdirSync( path, { recursive: true } ); } if ( ! ( await isEmptyDir( path ) ) && ! isWordPressDirectory( path ) ) { - wasPathEmpty = true; - userData.sites; + // Form validation should've prevented a non-empty directory from being selected + throw new Error( 'The selected directory is not empty nor an existing WordPress site.' ); } const allPaths = userData?.sites?.map( ( site ) => site.path ) || []; @@ -148,27 +143,18 @@ export async function createSite( const server = SiteServer.create( details ); if ( isWordPressDirectory( path ) ) { - try { - // If the directory contains a WordPress installation, and user wants to force SQLite - // integration, let's rename the wp-config.php file to allow WP Now to create a new one - // and initialize things properly. - if ( forceSetupSqlite && ( await pathExists( nodePath.join( path, 'wp-config.php' ) ) ) ) { - fs.renameSync( - nodePath.join( path, 'wp-config.php' ), - nodePath.join( path, 'wp-config-studio.php' ) - ); - } + // If the directory contains a WordPress installation, and user wants to force SQLite + // integration, let's rename the wp-config.php file to allow WP Now to create a new one + // and initialize things properly. + if ( forceSetupSqlite && ( await pathExists( nodePath.join( path, 'wp-config.php' ) ) ) ) { + fs.renameSync( + nodePath.join( path, 'wp-config.php' ), + nodePath.join( path, 'wp-config-studio.php' ) + ); + } - if ( ! ( await pathExists( nodePath.join( path, 'wp-config.php' ) ) ) ) { - await setupSqliteIntegration( path ); - } - } catch ( error ) { - Sentry.captureException( error ); - if ( wasPathEmpty ) { - // Clean the path to let the user try again - await shell.trashItem( path ); - } - throw new Error( 'Error creating the site. Please contact support.' ); + if ( ! ( await pathExists( nodePath.join( path, 'wp-config.php' ) ) ) ) { + await setupSqliteIntegration( path ); } }