Commit 41713d05517 for woocommerce
commit 41713d05517b95a3eba8c4ea1b39b1bc9527e666
Author: Darren Ethier <darren@roughsmootheng.in>
Date: Mon Aug 3 13:25:01 2026 -0400
Disclose Blueprint actions before import (#67349)
* Blueprint: disclose what an import will do before it runs
The runSql step's guards match the query as text, which cannot decide
what a statement will do — the same write can be expressed in forms the
patterns do not match. The class docblock nonetheless promised "no
modifications to admin users or roles" and "no unauthorized changes to
user capabilities", so the checks read as a security boundary they were
never able to be.
Rather than add more patterns, this states what the checks are and moves
the control to where it actually sits: the decision to import the file.
The import confirmation modal previously listed only the WooCommerce
Settings sections derived from setSiteOptions steps, so runSql,
installPlugin, installTheme, activatePlugin and activateTheme steps were
invisible — and a Blueprint made only of those produced the generic
"will overwrite the current configuration in WooCommerce Settings"
message, which understates what it does. The modal now also lists those
actions with counts, names any step it does not recognise instead of
dropping it, and always states that a Blueprint runs with the importing
administrator's access and should only come from a trusted source.
The counts are deliberately factual rather than alarming: WooCommerce's
own shipping and tax exporters emit runSql steps in bulk, so a legitimate
exported Blueprint routinely contains dozens of them and a warning tuned
for alarm would train people to click past it.
diff --git a/packages/php/blueprint/changelog/woo6-97-update-runsql-trust-boundary-docs b/packages/php/blueprint/changelog/woo6-97-update-runsql-trust-boundary-docs
new file mode 100644
index 00000000000..2ff72156f49
--- /dev/null
+++ b/packages/php/blueprint/changelog/woo6-97-update-runsql-trust-boundary-docs
@@ -0,0 +1,4 @@
+Significance: patch
+Type: update
+
+Document that the runSql step's query checks are not a security boundary, and warn WP-CLI users before a Blueprint is imported.
diff --git a/packages/php/blueprint/src/Cli.php b/packages/php/blueprint/src/Cli.php
index 61412de513e..927d445a98c 100644
--- a/packages/php/blueprint/src/Cli.php
+++ b/packages/php/blueprint/src/Cli.php
@@ -41,6 +41,12 @@ class Cli {
'optional' => true,
'options' => array( 'all', 'error', 'info', 'debug' ),
),
+ array(
+ 'type' => 'flag',
+ 'name' => 'yes',
+ 'optional' => true,
+ 'description' => 'Proceed without prompting for confirmation.',
+ ),
),
'when' => 'after_wp_load',
)
diff --git a/packages/php/blueprint/src/Cli/ImportCli.php b/packages/php/blueprint/src/Cli/ImportCli.php
index 2dc519c25ef..0bb8aa1d943 100644
--- a/packages/php/blueprint/src/Cli/ImportCli.php
+++ b/packages/php/blueprint/src/Cli/ImportCli.php
@@ -40,6 +40,9 @@ class ImportCli {
return;
}
+ \WP_CLI::warning( 'A Blueprint imported with WP-CLI can change anything this command can access on the site — including data that is not described in the file. Only import files from a source you trust.' );
+ \WP_CLI::confirm( 'Do you want to continue?', $optional_args );
+
$results = $blueprint->import();
$result_formatter = new CliResultFormatter( $results );
diff --git a/packages/php/blueprint/src/Importers/ImportRunSql.php b/packages/php/blueprint/src/Importers/ImportRunSql.php
index 337a96f50ba..24f9d02d967 100644
--- a/packages/php/blueprint/src/Importers/ImportRunSql.php
+++ b/packages/php/blueprint/src/Importers/ImportRunSql.php
@@ -11,8 +11,22 @@ use Automattic\WooCommerce\Blueprint\UseWPFunctions;
/**
* Processes SQL execution steps in the Blueprint.
*
- * Handles the execution of SQL queries with safety checks to prevent
- * unauthorized modifications to sensitive WordPress data.
+ * This step executes SQL supplied by the imported file against the site's
+ * database. Importing a Blueprint that contains it is equivalent to letting the
+ * author of that file run queries on the store, and the only access control is
+ * check_step_capabilities() below.
+ *
+ * The checks in this class inspect the query as text, so they cannot decide what
+ * a statement will actually do: the same write can be expressed in forms the
+ * patterns here do not match. They exist to catch mistakes and casual misuse and
+ * must not be relied on as a security boundary — do not extend them in the
+ * belief that enough patterns will make them one.
+ *
+ * The boundary is the decision to import the file. That decision is where the
+ * control belongs, so the import screen lists what a Blueprint will do —
+ * including how many queries it runs — before any step executes, and states that
+ * a Blueprint should only be imported from a trusted source. Anything that
+ * weakens that disclosure weakens the actual protection here.
*
* @package Automattic\WooCommerce\Blueprint\Importers
*/
@@ -35,10 +49,11 @@ class ImportRunSql implements StepProcessor {
/**
* Process the SQL execution step.
*
- * Validates and executes the SQL query while ensuring:
- * 1. Only allowed query types are executed
- * 2. No modifications to admin users or roles
- * 3. No unauthorized changes to user capabilities
+ * Runs the text-level checks — statement type, comment patterns, injection
+ * patterns, protected tables and capability-related option rows — and then
+ * executes the query in a transaction. See the class docblock for what those
+ * checks are and are not: they reject recognisable misuse, they do not
+ * constrain a determined author of the imported file.
*
* @param object $schema The schema containing the SQL query to execute.
* @return StepProcessorResult The result of the SQL execution.
diff --git a/packages/php/blueprint/tests/Unit/Cli/ImportCliTest.php b/packages/php/blueprint/tests/Unit/Cli/ImportCliTest.php
new file mode 100644
index 00000000000..dbdc1c39b35
--- /dev/null
+++ b/packages/php/blueprint/tests/Unit/Cli/ImportCliTest.php
@@ -0,0 +1,65 @@
+<?php
+
+namespace Automattic\WooCommerce\Blueprint\Tests\Unit\Cli;
+
+use Automattic\WooCommerce\Blueprint\Cli\ImportCli;
+use Automattic\WooCommerce\Blueprint\Tests\TestCase;
+
+/**
+ * Tests for ImportCli.
+ */
+class ImportCliTest extends TestCase {
+ /**
+ * The System Under Test.
+ *
+ * @var ImportCli
+ */
+ private $sut;
+
+ /**
+ * Blueprint fixture path.
+ *
+ * @var string
+ */
+ private $schema_path;
+
+ /**
+ * Set up the test case.
+ */
+ protected function setUp(): void {
+ parent::setUp();
+
+ \WP_CLI::$calls = array();
+ $this->schema_path = $this->get_fixture_path( 'empty-steps.json' );
+ $this->sut = new ImportCli( $this->schema_path );
+ }
+
+ /**
+ * Test that the command warns and asks for confirmation.
+ *
+ * @testdox Warns and asks for confirmation before importing a Blueprint.
+ */
+ public function test_warns_and_confirms_before_importing(): void {
+ $this->sut->run( array() );
+
+ $this->assertSame( 'warning', \WP_CLI::$calls[0][0] );
+ $this->assertStringContainsString( 'Only import files from a source you trust.', \WP_CLI::$calls[0][1] );
+ $this->assertSame( array( 'confirm', 'Do you want to continue?', array() ), \WP_CLI::$calls[1] );
+ $this->assertSame( array( 'success', "$this->schema_path imported successfully" ), \WP_CLI::$calls[2] );
+ }
+
+ /**
+ * Test that the command warns when confirmation is skipped.
+ *
+ * @testdox Displays the warning and passes the yes flag to confirmation.
+ */
+ public function test_warns_when_confirmation_is_skipped(): void {
+ $this->sut->run( array( 'yes' => true ) );
+
+ $this->assertSame( 'warning', \WP_CLI::$calls[0][0] );
+ $this->assertSame(
+ array( 'confirm', 'Do you want to continue?', array( 'yes' => true ) ),
+ \WP_CLI::$calls[1]
+ );
+ }
+}
diff --git a/packages/php/blueprint/tests/stubs/WPCli.php b/packages/php/blueprint/tests/stubs/WPCli.php
new file mode 100644
index 00000000000..c786e4f90a6
--- /dev/null
+++ b/packages/php/blueprint/tests/stubs/WPCli.php
@@ -0,0 +1,52 @@
+<?php
+
+if ( ! class_exists( 'WP_CLI' ) ) {
+ /**
+ * WP-CLI test double.
+ */
+ class WP_CLI {
+ /**
+ * Recorded calls.
+ *
+ * @var array
+ */
+ public static $calls = array();
+
+ /**
+ * Record a warning.
+ *
+ * @param string $message Warning message.
+ */
+ public static function warning( $message ) {
+ self::$calls[] = array( 'warning', $message );
+ }
+
+ /**
+ * Record a confirmation prompt.
+ *
+ * @param string $message Confirmation message.
+ * @param array $assoc_args Command arguments.
+ */
+ public static function confirm( $message, $assoc_args = array() ) {
+ self::$calls[] = array( 'confirm', $message, $assoc_args );
+ }
+
+ /**
+ * Record a success message.
+ *
+ * @param string $message Success message.
+ */
+ public static function success( $message ) {
+ self::$calls[] = array( 'success', $message );
+ }
+
+ /**
+ * Record an error message.
+ *
+ * @param string $message Error message.
+ */
+ public static function error( $message ) {
+ self::$calls[] = array( 'error', $message );
+ }
+ }
+}
diff --git a/packages/php/blueprint/tests/stubs/stubs.php b/packages/php/blueprint/tests/stubs/stubs.php
index 26b02dc70ce..4fdfbbd57a2 100644
--- a/packages/php/blueprint/tests/stubs/stubs.php
+++ b/packages/php/blueprint/tests/stubs/stubs.php
@@ -3,6 +3,8 @@
* Stubs for WooCommerce classes and interfaces.
*/
+require_once __DIR__ . '/WPCli.php';
+
if ( ! class_exists( 'WC_Log_Levels', false ) ) {
/**
* WC Log Levels Class
diff --git a/plugins/woocommerce/changelog/woo6-97-add-blueprint-import-disclosure b/plugins/woocommerce/changelog/woo6-97-add-blueprint-import-disclosure
new file mode 100644
index 00000000000..a7ba3425641
--- /dev/null
+++ b/plugins/woocommerce/changelog/woo6-97-add-blueprint-import-disclosure
@@ -0,0 +1,4 @@
+Significance: patch
+Type: add
+
+Blueprint: list what an imported Blueprint will do before it runs, including how many database queries it executes and which plugins and themes it installs or activates, and state that a Blueprint should only be imported from a trusted source.
diff --git a/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx b/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
index 6d9a15d0042..8893a896e75 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
+++ b/plugins/woocommerce/client/admin/client/blueprint/components/BlueprintUploadDropzone.tsx
@@ -31,6 +31,7 @@ import { getAdminLink } from '@woocommerce/settings';
import './style.scss';
import { OverwriteConfirmationModal } from '../settings/overwrite-confirmation-modal';
import { getOptionGroupsFromSteps } from './get-option-groups';
+import { getStepActions } from './get-step-actions';
import {
BlueprintQueueResponse,
BlueprintImportResponse,
@@ -189,7 +190,7 @@ const checkImportAllowed = async (): Promise< boolean > => {
method: 'GET',
} );
return response.import_allowed;
- } catch ( error ) {
+ } catch {
throw new Error(
__( 'Failed to check if imports are allowed.', 'woocommerce' )
);
@@ -201,6 +202,7 @@ interface FileUploadContext {
steps?: BlueprintStep[];
error?: Error;
settings_to_overwrite?: string[];
+ step_actions?: string[];
import_allowed?: boolean;
}
@@ -347,6 +349,8 @@ export const fileUploadMachine = setup( {
event.output
) as string[];
},
+ step_actions: ( { event } ) =>
+ getStepActions( event.output ),
} ),
},
onError: {
@@ -485,7 +489,7 @@ export const BlueprintUploadDropzone = () => {
{
br: <br />,
link: (
- // eslint-disable-next-line jsx-a11y/anchor-has-content, jsx-a11y/control-has-associated-label
+ // eslint-disable-next-line jsx-a11y/anchor-has-content
<a
href={ getAdminLink(
'admin.php?page=wc-settings&tab=site-visibility'
@@ -527,7 +531,7 @@ export const BlueprintUploadDropzone = () => {
<div className="blueprint-upload-dropzone">
<Icon icon={ upload } />
<p className="blueprint-upload-dropzone-text">
- { __( 'Drag and drop or ', 'woocommerce' ) }
+ { __( 'Drag and drop or', 'woocommerce' ) }{ ' ' }
<span>
{ __( 'choose a file', 'woocommerce' ) }
</span>
@@ -610,6 +614,7 @@ export const BlueprintUploadDropzone = () => {
overwrittenItems={
state.context.settings_to_overwrite || []
}
+ additionalActions={ state.context.step_actions || [] }
/>
) }
</>
diff --git a/plugins/woocommerce/client/admin/client/blueprint/components/get-step-actions.ts b/plugins/woocommerce/client/admin/client/blueprint/components/get-step-actions.ts
new file mode 100644
index 00000000000..cc1cf511cc6
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/blueprint/components/get-step-actions.ts
@@ -0,0 +1,128 @@
+/**
+ * External dependencies
+ */
+import { _n, sprintf } from '@wordpress/i18n';
+
+/**
+ * Internal dependencies
+ */
+import { BlueprintStep } from './types';
+
+/**
+ * Steps whose effects are already described to the user by the list of
+ * WooCommerce Settings sections the import will overwrite, so describing them
+ * again here would only repeat what the user has been told.
+ */
+const SETTINGS_STEPS = [ 'setSiteOptions' ];
+
+/**
+ * Human descriptions for the steps a Blueprint can contain, in the order they
+ * should be listed — most consequential first.
+ *
+ * A Blueprint step runs with the importing administrator's access, so the point
+ * of these descriptions is to let that administrator see what a file will do
+ * before they confirm it, rather than only seeing its name.
+ */
+const STEP_ACTIONS: Record< string, ( count: number ) => string > = {
+ runSql: ( count ) =>
+ sprintf(
+ /* translators: %d: number of database queries a Blueprint will run. */
+ _n(
+ 'Run %d database query',
+ 'Run %d database queries',
+ count,
+ 'woocommerce'
+ ),
+ count
+ ),
+ installPlugin: ( count ) =>
+ sprintf(
+ /* translators: %d: number of plugins a Blueprint will install. */
+ _n(
+ 'Install %d plugin',
+ 'Install %d plugins',
+ count,
+ 'woocommerce'
+ ),
+ count
+ ),
+ activatePlugin: ( count ) =>
+ sprintf(
+ /* translators: %d: number of plugins a Blueprint will activate. */
+ _n(
+ 'Activate %d plugin',
+ 'Activate %d plugins',
+ count,
+ 'woocommerce'
+ ),
+ count
+ ),
+ installTheme: ( count ) =>
+ sprintf(
+ /* translators: %d: number of themes a Blueprint will install. */
+ _n( 'Install %d theme', 'Install %d themes', count, 'woocommerce' ),
+ count
+ ),
+ activateTheme: ( count ) =>
+ sprintf(
+ /* translators: %d: number of themes a Blueprint will activate. */
+ _n(
+ 'Activate %d theme',
+ 'Activate %d themes',
+ count,
+ 'woocommerce'
+ ),
+ count
+ ),
+};
+
+/**
+ * Describe what a Blueprint will do beyond writing WooCommerce settings.
+ *
+ * Steps this function does not recognise are still counted and named rather
+ * than dropped, so a Blueprint cannot carry an action past the confirmation
+ * screen simply by using a step this list has not been taught about.
+ *
+ * @param steps a list of Blueprint steps
+ * @return string[] a list of descriptions, ready to show as a list
+ */
+export const getStepActions = ( steps: BlueprintStep[] ): string[] => {
+ const counts = steps.reduce< Map< string, number > >( ( acc, step ) => {
+ const name = step?.step;
+ if ( name && ! SETTINGS_STEPS.includes( name ) ) {
+ acc.set( name, ( acc.get( name ) || 0 ) + 1 );
+ }
+ return acc;
+ }, new Map() );
+
+ const actions = Object.keys( STEP_ACTIONS )
+ .filter( ( name ) => counts.has( name ) )
+ .map( ( name ) => STEP_ACTIONS[ name ]( counts.get( name ) || 0 ) );
+
+ const unrecognized = Array.from( counts.keys() )
+ .filter( ( name ) => ! Object.hasOwn( STEP_ACTIONS, name ) )
+ .sort();
+
+ if ( unrecognized.length ) {
+ const total = unrecognized.reduce(
+ ( sum, name ) => sum + ( counts.get( name ) || 0 ),
+ 0
+ );
+
+ actions.push(
+ sprintf(
+ /* translators: 1: number of steps a Blueprint will run. 2: comma separated list of step names. */
+ _n(
+ 'Run %1$d other step (%2$s)',
+ 'Run %1$d other steps (%2$s)',
+ total,
+ 'woocommerce'
+ ),
+ total,
+ unrecognized.join( ', ' )
+ )
+ );
+ }
+
+ return actions;
+};
diff --git a/plugins/woocommerce/client/admin/client/blueprint/components/test/get-step-actions.test.js b/plugins/woocommerce/client/admin/client/blueprint/components/test/get-step-actions.test.js
new file mode 100644
index 00000000000..343e74856bc
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/blueprint/components/test/get-step-actions.test.js
@@ -0,0 +1,91 @@
+/**
+ * Internal dependencies
+ */
+
+import { getStepActions } from '../get-step-actions';
+
+describe( 'getStepActions', () => {
+ it( 'should return nothing for a Blueprint that only writes settings', () => {
+ const steps = [
+ {
+ step: 'setSiteOptions',
+ options: { woocommerce_store_address: '' },
+ },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [] );
+ } );
+
+ it( 'should surface SQL steps, which are otherwise invisible before import', () => {
+ const steps = [
+ { step: 'runSql', sql: { contents: 'UPDATE wp_posts SET ID = 1' } },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [ 'Run 1 database query' ] );
+ } );
+
+ it( 'should count repeated steps rather than list them individually', () => {
+ const steps = [
+ { step: 'runSql' },
+ { step: 'runSql' },
+ { step: 'runSql' },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [
+ 'Run 3 database queries',
+ ] );
+ } );
+
+ it( 'should list every kind of action a Blueprint takes, most consequential first', () => {
+ const steps = [
+ { step: 'activateTheme' },
+ { step: 'installPlugin' },
+ { step: 'setSiteOptions', options: {} },
+ { step: 'runSql' },
+ { step: 'installPlugin' },
+ { step: 'activatePlugin' },
+ { step: 'installTheme' },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [
+ 'Run 1 database query',
+ 'Install 2 plugins',
+ 'Activate 1 plugin',
+ 'Install 1 theme',
+ 'Activate 1 theme',
+ ] );
+ } );
+
+ it( 'should report unrecognised steps rather than hide them', () => {
+ const steps = [
+ { step: 'runSql' },
+ { step: 'someFutureStep' },
+ { step: 'someFutureStep' },
+ { step: 'anotherUnknownStep' },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [
+ 'Run 1 database query',
+ 'Run 3 other steps (anotherUnknownStep, someFutureStep)',
+ ] );
+ } );
+
+ it( 'should report unrecognised steps named after object prototype properties', () => {
+ const steps = [
+ { step: '__proto__' },
+ { step: 'constructor' },
+ { step: 'toString' },
+ ];
+
+ expect( getStepActions( steps ) ).toEqual( [
+ 'Run 3 other steps (__proto__, constructor, toString)',
+ ] );
+ } );
+
+ it( 'should tolerate an empty or malformed step list', () => {
+ expect( getStepActions( [] ) ).toEqual( [] );
+ expect(
+ getStepActions( [ {}, { step: '' }, { step: null } ] )
+ ).toEqual( [] );
+ } );
+} );
diff --git a/plugins/woocommerce/client/admin/client/blueprint/settings/overwrite-confirmation-modal.tsx b/plugins/woocommerce/client/admin/client/blueprint/settings/overwrite-confirmation-modal.tsx
index afdc177a7b2..49eda97628c 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/settings/overwrite-confirmation-modal.tsx
+++ b/plugins/woocommerce/client/admin/client/blueprint/settings/overwrite-confirmation-modal.tsx
@@ -1,7 +1,7 @@
/**
* External dependencies
*/
-import { Modal, Button, Spinner } from '@wordpress/components';
+import { Modal, Button, Notice, Spinner } from '@wordpress/components';
import { __ } from '@wordpress/i18n';
import clsx from 'clsx';
@@ -11,6 +11,7 @@ type OverwriteConfirmationModalProps = {
onClose: () => void;
onConfirm: () => void;
overwrittenItems: string[];
+ additionalActions?: string[];
};
export const OverwriteConfirmationModal = ( {
@@ -19,14 +20,14 @@ export const OverwriteConfirmationModal = ( {
onClose,
onConfirm,
overwrittenItems,
+ additionalActions = [],
}: OverwriteConfirmationModalProps ) => {
- if ( ! isOpen ) return null;
+ if ( ! isOpen ) {
+ return null;
+ }
return (
<Modal
- title={ __(
- 'Your configuration will be overridden',
- 'woocommerce'
- ) }
+ title={ __( 'Review what this Blueprint will do', 'woocommerce' ) }
onRequestClose={ onClose }
className="woocommerce-blueprint-overwrite-modal"
isDismissible={ ! isImporting }
@@ -49,6 +50,30 @@ export const OverwriteConfirmationModal = ( {
) ) }
</ul>
+ { !! additionalActions.length && (
+ <>
+ <p className="woocommerce-blueprint-overwrite-modal__description woocommerce-blueprint-overwrite-modal__description--actions">
+ { __( 'It will also:', 'woocommerce' ) }
+ </p>
+ <ul className="woocommerce-blueprint-overwrite-modal__list">
+ { additionalActions.map( ( action ) => (
+ <li key={ action }>{ action }</li>
+ ) ) }
+ </ul>
+ </>
+ ) }
+
+ <Notice
+ status="warning"
+ isDismissible={ false }
+ className="woocommerce-blueprint-overwrite-modal__trust-notice"
+ >
+ { __(
+ 'A Blueprint runs with your administrator access, so it can change anything on your site — including data that is not listed above. Only import files from a source you trust.',
+ 'woocommerce'
+ ) }
+ </Notice>
+
<div className="woocommerce-blueprint-overwrite-modal__actions">
<Button
className="woocommerce-blueprint-overwrite-modal__actions-cancel"
diff --git a/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss b/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
index 968f3bcfe62..b5adf00befc 100644
--- a/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
+++ b/plugins/woocommerce/client/admin/client/blueprint/settings/style.scss
@@ -235,6 +235,22 @@
margin: 0 0 8px;
}
+ .woocommerce-blueprint-overwrite-modal__description--actions {
+ margin-top: 16px;
+ }
+
+ .woocommerce-blueprint-overwrite-modal__trust-notice {
+ margin: 24px 0 0;
+
+ .components-notice__content {
+ color: $gray-900;
+ font-size: 13px;
+ font-weight: 400;
+ line-height: 20px; /* 153.846% */
+ margin: 0;
+ }
+ }
+
ul {
margin: 0;
list-style: disc;
diff --git a/plugins/woocommerce/client/admin/client/blueprint/settings/test/overwrite-confirmation-modal.test.js b/plugins/woocommerce/client/admin/client/blueprint/settings/test/overwrite-confirmation-modal.test.js
new file mode 100644
index 00000000000..0838dad7a7f
--- /dev/null
+++ b/plugins/woocommerce/client/admin/client/blueprint/settings/test/overwrite-confirmation-modal.test.js
@@ -0,0 +1,72 @@
+/**
+ * External dependencies
+ */
+import { render, screen } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { OverwriteConfirmationModal } from '../overwrite-confirmation-modal';
+
+const defaultProps = {
+ isOpen: true,
+ isImporting: false,
+ onClose: () => {},
+ onConfirm: () => {},
+ overwrittenItems: [],
+};
+
+describe( 'OverwriteConfirmationModal', () => {
+ it( 'should always state that a Blueprint is only as trustworthy as its source', () => {
+ render( <OverwriteConfirmationModal { ...defaultProps } /> );
+
+ // The notice is also announced through a live region, so the copy is
+ // expected to appear more than once in the document.
+ expect(
+ screen.getAllByText( /Only import files from a source you trust/ )
+ .length
+ ).toBeGreaterThan( 0 );
+ } );
+
+ it( 'should list the actions a Blueprint takes beyond writing settings', () => {
+ render(
+ <OverwriteConfirmationModal
+ { ...defaultProps }
+ additionalActions={ [
+ 'Run 34 database queries',
+ 'Install 2 plugins',
+ ] }
+ />
+ );
+
+ expect( screen.getByText( 'It will also:' ) ).toBeInTheDocument();
+ expect(
+ screen.getByText( 'Run 34 database queries' )
+ ).toBeInTheDocument();
+ expect( screen.getByText( 'Install 2 plugins' ) ).toBeInTheDocument();
+ } );
+
+ it( 'should not show an empty actions list for a settings-only Blueprint', () => {
+ render(
+ <OverwriteConfirmationModal
+ { ...defaultProps }
+ overwrittenItems={ [ 'General' ] }
+ />
+ );
+
+ expect( screen.getByText( 'General' ) ).toBeInTheDocument();
+ expect( screen.queryByText( 'It will also:' ) ).not.toBeInTheDocument();
+ } );
+
+ it( 'should render nothing when closed', () => {
+ const { container } = render(
+ <OverwriteConfirmationModal
+ { ...defaultProps }
+ isOpen={ false }
+ additionalActions={ [ 'Run 34 database queries' ] }
+ />
+ );
+
+ expect( container ).toBeEmptyDOMElement();
+ } );
+} );