diff --git a/app/soapbox/features/ui/__tests__/index.test.tsx b/app/soapbox/features/ui/__tests__/index.test.tsx
new file mode 100644
index 000000000..e4a360dff
--- /dev/null
+++ b/app/soapbox/features/ui/__tests__/index.test.tsx
@@ -0,0 +1,87 @@
+import { Map as ImmutableMap } from 'immutable';
+import React from 'react';
+import { Route, Switch } from 'react-router-dom';
+
+import { render, screen, waitFor } from '../../../jest/test-helpers';
+import { normalizeAccount } from '../../../normalizers';
+import UI from '../index';
+
+const TestableComponent = () => (
+
+
+
+
+ Sign in
+
+);
+
+describe('', () => {
+ let store;
+
+ beforeEach(() => {
+ store = {
+ me: false,
+ accounts: ImmutableMap({
+ '1': normalizeAccount({
+ id: '1',
+ acct: 'username',
+ display_name: 'My name',
+ avatar: 'test.jpg',
+ }),
+ }),
+ };
+ });
+
+ describe('when logged out', () => {
+ describe('with guest experience disabled', () => {
+ beforeEach(() => {
+ store = { ...store, soapbox: ImmutableMap({ guestExperience: false }) };
+ });
+
+ describe('when viewing a Profile Page', () => {
+ it('should render the Profile page', async() => {
+ render(
+ ,
+ {},
+ store,
+ { initialEntries: ['/@username'] },
+ );
+
+ await waitFor(() => {
+ expect(screen.getByTestId('cta-banner')).toHaveTextContent('Sign up now to discuss');
+ });
+ });
+ });
+
+ describe('when viewing a Status Page', () => {
+ it('should render the Status page', async() => {
+ render(
+ ,
+ {},
+ store,
+ { initialEntries: ['/@username/posts/12'] },
+ );
+
+ await waitFor(() => {
+ expect(screen.getByTestId('cta-banner')).toHaveTextContent('Sign up now to discuss');
+ });
+ });
+ });
+
+ describe('when viewing any other page', () => {
+ it('should redirect to the login page', async() => {
+ render(
+ ,
+ {},
+ store,
+ { initialEntries: ['/@username/media'] },
+ );
+
+ await waitFor(() => {
+ expect(screen.getByTestId('sign-in')).toHaveTextContent('Sign in');
+ });
+ });
+ });
+ });
+ });
+});
diff --git a/app/soapbox/features/ui/index.tsx b/app/soapbox/features/ui/index.tsx
index 1ebfc30a0..06591af82 100644
--- a/app/soapbox/features/ui/index.tsx
+++ b/app/soapbox/features/ui/index.tsx
@@ -5,8 +5,7 @@ import React, { useState, useEffect, useRef, useCallback } from 'react';
import { HotKeys } from 'react-hotkeys';
import { defineMessages, useIntl } from 'react-intl';
import { useDispatch } from 'react-redux';
-import { Switch, useHistory } from 'react-router-dom';
-import { Redirect } from 'react-router-dom';
+import { Switch, useHistory, matchPath, Redirect } from 'react-router-dom';
import { fetchFollowRequests } from 'soapbox/actions/accounts';
import { fetchReports, fetchUsers, fetchConfig } from 'soapbox/actions/admin';
@@ -608,7 +607,14 @@ const UI: React.FC = ({ children }) => {
// Wait for login to succeed or fail
if (me === null) return null;
- if (!me && !guestExperience) {
+ const isProfileOrStatusPage = !!matchPath(
+ history.location.pathname,
+ ['/@:username', '/@:username/posts/:statusId'],
+ );
+
+ // Require login if Guest Experience is disabled and we're not trying
+ // to render a Profile or Status.
+ if (!me && (!guestExperience && !isProfileOrStatusPage)) {
cacheCurrentUrl(history.location);
return ;
}
diff --git a/app/soapbox/jest/test-setup.ts b/app/soapbox/jest/test-setup.ts
index 6456e3e84..0052388b0 100644
--- a/app/soapbox/jest/test-setup.ts
+++ b/app/soapbox/jest/test-setup.ts
@@ -15,3 +15,17 @@ jest.mock('uuid', () => ({ v4: jest.fn(() => '1') }));
const intersectionObserverMock = () => ({ observe: () => null, disconnect: () => null });
window.IntersectionObserver = jest.fn().mockImplementation(intersectionObserverMock);
+
+Object.defineProperty(window, 'matchMedia', {
+ writable: true,
+ value: jest.fn().mockImplementation(query => ({
+ matches: false,
+ media: query,
+ onchange: null,
+ addListener: jest.fn(), // Deprecated
+ removeListener: jest.fn(), // Deprecated
+ addEventListener: jest.fn(),
+ removeEventListener: jest.fn(),
+ dispatchEvent: jest.fn(),
+ })),
+});