diff --git a/packages/plugins/src/components/test/plugin-area.js b/packages/plugins/src/components/test/plugin-area.js
new file mode 100644
index 0000000000000..68ed2b5368d03
--- /dev/null
+++ b/packages/plugins/src/components/test/plugin-area.js
@@ -0,0 +1,121 @@
+/**
+ * External dependencies
+ */
+import { act, render, cleanup } from '@testing-library/react';
+
+/**
+ * Internal dependencies
+ */
+import { getPlugins, unregisterPlugin, registerPlugin } from '../../api';
+import PluginArea from '../plugin-area';
+
+describe( 'PluginArea', () => {
+ afterEach( () => {
+ // Unmount components before unregistering the plugins.
+ // RTL uses top-level `afterEach` for cleanup, executed after this teardown.
+ cleanup();
+ getPlugins().forEach( ( plugin ) => {
+ unregisterPlugin( plugin.name );
+ } );
+ getPlugins( 'my-app' ).forEach( ( plugin ) => {
+ unregisterPlugin( plugin.name );
+ } );
+ } );
+
+ const TestComponent = ( { content } ) => {
+ return `plugin: ${ content }.`;
+ };
+
+ test( 'renders unscoped plugin', () => {
+ registerPlugin( 'unscoped', {
+ render: () => ,
+ icon: 'smiley',
+ } );
+
+ const { container } = render( );
+
+ expect( container ).toHaveTextContent( 'plugin: unscoped.' );
+ } );
+
+ test( 'renders scoped plugin', () => {
+ registerPlugin( 'scoped', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+
+ const { container } = render( );
+
+ expect( container ).toHaveTextContent( 'plugin: scoped.' );
+ } );
+
+ test( 'rerenders when a new plugin is registered', () => {
+ registerPlugin( 'foo', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+
+ const { container } = render( );
+
+ act( () => {
+ registerPlugin( 'bar', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+ } );
+
+ expect( container ).toHaveTextContent( 'plugin: bar.' );
+ } );
+
+ test( 'rerenders when a plugin is unregistered', () => {
+ registerPlugin( 'one', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+ registerPlugin( 'two', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+
+ const { container } = render( );
+
+ expect( container ).toHaveTextContent( 'plugin: one.plugin: two.' );
+
+ act( () => {
+ unregisterPlugin( 'one' );
+ } );
+
+ expect( container ).toHaveTextContent( 'plugin: two.' );
+ } );
+
+ test.failing(
+ 'does not rerender when a plugin is added to a different scope',
+ () => {
+ const ComponentSpy = jest.fn( ( { content } ) => {
+ return `plugin: ${ content }.`;
+ } );
+
+ registerPlugin( 'scoped', {
+ render: () => ,
+ icon: 'smiley',
+ scope: 'my-app',
+ } );
+
+ render( );
+
+ act( () => {
+ registerPlugin( 'unscoped', {
+ render: () => ,
+ icon: 'smiley',
+ } );
+ } );
+
+ // Any store update triggers setState and causes PluginArea to rerender.
+ expect( ComponentSpy ).toHaveBeenCalledTimes( 1 );
+ }
+ );
+} );