diff --git a/app/soapbox/features/groups/__tests__/discover.test.tsx b/app/soapbox/features/groups/__tests__/discover.test.tsx
new file mode 100644
index 000000000..b2485cdde
--- /dev/null
+++ b/app/soapbox/features/groups/__tests__/discover.test.tsx
@@ -0,0 +1,77 @@
+import userEvent from '@testing-library/user-event';
+import { Map as ImmutableMap } from 'immutable';
+import React from 'react';
+
+import { render, screen, waitFor } from 'soapbox/jest/test-helpers';
+import { normalizeAccount, normalizeInstance } from 'soapbox/normalizers';
+
+import Discover from '../discover';
+
+jest.mock('../../../hooks/useDimensions', () => ({
+ useDimensions: () => [{ scrollWidth: 190 }, null, { width: 300 }],
+}));
+
+(window as any).ResizeObserver = class ResizeObserver {
+
+ observe() { }
+ disconnect() { }
+
+};
+
+const userId = '1';
+const store: any = {
+ me: userId,
+ accounts: ImmutableMap({
+ [userId]: normalizeAccount({
+ id: userId,
+ acct: 'justin-username',
+ display_name: 'Justin L',
+ avatar: 'test.jpg',
+ chats_onboarded: false,
+ }),
+ }),
+ instance: normalizeInstance({
+ version: '3.4.1 (compatible; TruthSocial 1.0.0)',
+ software: 'TRUTHSOCIAL',
+ }),
+};
+
+const renderApp = () => (
+ render(
+ ,
+ undefined,
+ store,
+ )
+);
+
+describe('', () => {
+ describe('before the user starts searching', () => {
+ it('it should render popular groups', async () => {
+ renderApp();
+
+ await waitFor(() => {
+ expect(screen.getByTestId('popular-groups')).toBeInTheDocument();
+ expect(screen.getByTestId('suggested-groups')).toBeInTheDocument();
+ expect(screen.getByTestId('popular-tags')).toBeInTheDocument();
+ expect(screen.queryAllByTestId('recent-searches')).toHaveLength(0);
+ expect(screen.queryAllByTestId('group-search-icon')).toHaveLength(0);
+
+ });
+ });
+ });
+
+ describe('when the user focuses on the input', () => {
+ it('should render the search experience', async () => {
+ const user = userEvent.setup();
+ renderApp();
+
+ await user.click(screen.getByTestId('search'));
+
+ await waitFor(() => {
+ expect(screen.getByTestId('group-search-icon')).toBeInTheDocument();
+ expect(screen.getByTestId('recent-searches')).toBeInTheDocument();
+ expect(screen.queryAllByTestId('popular-groups')).toHaveLength(0);
+ });
+ });
+ });
+});
\ No newline at end of file
diff --git a/app/soapbox/features/groups/components/discover/__tests__/group-grid-item.test.tsx b/app/soapbox/features/groups/components/discover/__tests__/group-grid-item.test.tsx
new file mode 100644
index 000000000..09be8da84
--- /dev/null
+++ b/app/soapbox/features/groups/components/discover/__tests__/group-grid-item.test.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import { buildGroup } from 'soapbox/jest/factory';
+import { render, screen } from 'soapbox/jest/test-helpers';
+
+import GroupGridItem from '../group-grid-item';
+
+describe(' {
+ it('should render correctly', () => {
+ const group = buildGroup({
+ display_name: 'group name here',
+ locked: false,
+ members_count: 6,
+ });
+ render();
+
+ expect(screen.getByTestId('group-grid-item')).toHaveTextContent(group.display_name);
+ expect(screen.getByTestId('group-grid-item')).toHaveTextContent('Public');
+ expect(screen.getByTestId('group-grid-item')).toHaveTextContent('6 members');
+ });
+});
\ No newline at end of file
diff --git a/app/soapbox/features/groups/components/discover/__tests__/group-list-item.test.tsx b/app/soapbox/features/groups/components/discover/__tests__/group-list-item.test.tsx
new file mode 100644
index 000000000..f78b9da43
--- /dev/null
+++ b/app/soapbox/features/groups/components/discover/__tests__/group-list-item.test.tsx
@@ -0,0 +1,21 @@
+import React from 'react';
+
+import { buildGroup } from 'soapbox/jest/factory';
+import { render, screen } from 'soapbox/jest/test-helpers';
+
+import GroupListItem from '../group-list-item';
+
+describe(' {
+ it('should render correctly', () => {
+ const group = buildGroup({
+ display_name: 'group name here',
+ locked: false,
+ members_count: 6,
+ });
+ render();
+
+ expect(screen.getByTestId('group-list-item')).toHaveTextContent(group.display_name);
+ expect(screen.getByTestId('group-list-item')).toHaveTextContent('Public');
+ expect(screen.getByTestId('group-list-item')).toHaveTextContent('6 members');
+ });
+});
\ No newline at end of file
diff --git a/app/soapbox/features/groups/components/discover/__tests__/layout-buttons.test.tsx b/app/soapbox/features/groups/components/discover/__tests__/layout-buttons.test.tsx
new file mode 100644
index 000000000..c6d1ea514
--- /dev/null
+++ b/app/soapbox/features/groups/components/discover/__tests__/layout-buttons.test.tsx
@@ -0,0 +1,38 @@
+import userEvent from '@testing-library/user-event';
+import React from 'react';
+
+import { render, screen, within } from 'soapbox/jest/test-helpers';
+
+import LayoutButtons, { GroupLayout } from '../layout-buttons';
+
+describe(' {
+ describe('when LIST view', () => {
+ it('should render correctly', async () => {
+ const onSelectFn = jest.fn();
+ const user = userEvent.setup();
+
+ render();
+
+ expect(within(screen.getByTestId('layout-list-action')).getByTestId('svg-icon-loader')).toHaveClass('text-primary-600');
+ expect(within(screen.getByTestId('layout-grid-action')).getByTestId('svg-icon-loader')).not.toHaveClass('text-primary-600');
+
+ await user.click(screen.getByTestId('layout-grid-action'));
+ expect(onSelectFn).toHaveBeenCalled();
+ });
+ });
+
+ describe('when GRID view', () => {
+ it('should render correctly', async () => {
+ const onSelectFn = jest.fn();
+ const user = userEvent.setup();
+
+ render();
+
+ expect(within(screen.getByTestId('layout-list-action')).getByTestId('svg-icon-loader')).not.toHaveClass('text-primary-600');
+ expect(within(screen.getByTestId('layout-grid-action')).getByTestId('svg-icon-loader')).toHaveClass('text-primary-600');
+
+ await user.click(screen.getByTestId('layout-grid-action'));
+ expect(onSelectFn).toHaveBeenCalled();
+ });
+ });
+});
\ No newline at end of file
diff --git a/app/soapbox/features/groups/components/discover/__tests__/tag-list-item.test.tsx b/app/soapbox/features/groups/components/discover/__tests__/tag-list-item.test.tsx
new file mode 100644
index 000000000..c180c9234
--- /dev/null
+++ b/app/soapbox/features/groups/components/discover/__tests__/tag-list-item.test.tsx
@@ -0,0 +1,16 @@
+import React from 'react';
+
+import { buildGroupTag } from 'soapbox/jest/factory';
+import { render, screen } from 'soapbox/jest/test-helpers';
+
+import TagListItem from '../tag-list-item';
+
+describe(' {
+ it('should render correctly', () => {
+ const tag = buildGroupTag({ name: 'tag 1', groups: 5 });
+ render();
+
+ expect(screen.getByTestId('tag-list-item')).toHaveTextContent(tag.name);
+ expect(screen.getByTestId('tag-list-item')).toHaveTextContent('Number of groups: 5');
+ });
+});
\ No newline at end of file
diff --git a/app/soapbox/features/groups/components/discover/group-grid-item.tsx b/app/soapbox/features/groups/components/discover/group-grid-item.tsx
index c30055971..1f4920bde 100644
--- a/app/soapbox/features/groups/components/discover/group-grid-item.tsx
+++ b/app/soapbox/features/groups/components/discover/group-grid-item.tsx
@@ -25,6 +25,7 @@ const GroupGridItem = forwardRef((props: IGroup, ref: React.ForwardedRef
{
diff --git a/app/soapbox/features/groups/components/discover/layout-buttons.tsx b/app/soapbox/features/groups/components/discover/layout-buttons.tsx
index eecdfaf83..9b98ba5e7 100644
--- a/app/soapbox/features/groups/components/discover/layout-buttons.tsx
+++ b/app/soapbox/features/groups/components/discover/layout-buttons.tsx
@@ -15,7 +15,10 @@ interface ILayoutButtons {
const LayoutButtons = ({ layout, onSelect }: ILayoutButtons) => (
-