diff --git a/frontend/src/metabase/public/components/LogoBadge.jsx b/frontend/src/metabase/public/components/LogoBadge.jsx
index a007d73bd40b3768b22dba30b26e7a227e25b461..1dbf8989079646443131d09bf18686a0a214aa01 100644
--- a/frontend/src/metabase/public/components/LogoBadge.jsx
+++ b/frontend/src/metabase/public/components/LogoBadge.jsx
@@ -13,7 +13,7 @@ const LogoBadge = ({ dark }) => (
     <LogoIcon height={28} dark={dark} />
     <span className="text-small">
       <span className="ml1 md-ml2 text-medium">{jt`Powered by ${(
-        <span className={dark ? "text-white" : "text-brand"}>
+        <span key="metabase" className={dark ? "text-white" : "text-brand"}>
           {t`Metabase`}
         </span>
       )}`}</span>
diff --git a/frontend/src/metabase/public/containers/PublicApp.jsx b/frontend/src/metabase/public/containers/PublicApp.jsx
deleted file mode 100644
index 45bfd28d448ee1924487c93581069dda3dd7f0d7..0000000000000000000000000000000000000000
--- a/frontend/src/metabase/public/containers/PublicApp.jsx
+++ /dev/null
@@ -1,27 +0,0 @@
-/* eslint-disable react/prop-types */
-import React, { Component } from "react";
-import { connect } from "react-redux";
-
-import PublicNotFound from "metabase/public/components/PublicNotFound";
-import PublicError from "metabase/public/components/PublicError";
-
-const mapStateToProps = (state, props) => ({
-  errorPage: state.app.errorPage,
-});
-
-class PublicApp extends Component {
-  render() {
-    const { children, errorPage } = this.props;
-    if (errorPage) {
-      if (errorPage.status === 404) {
-        return <PublicNotFound />;
-      } else {
-        return <PublicError />;
-      }
-    } else {
-      return children;
-    }
-  }
-}
-
-export default connect(mapStateToProps)(PublicApp);
diff --git a/frontend/src/metabase/public/containers/PublicApp/PublicApp.tsx b/frontend/src/metabase/public/containers/PublicApp/PublicApp.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..e5131395f9e88a9a8c3b4a8bd92dfc12a503222a
--- /dev/null
+++ b/frontend/src/metabase/public/containers/PublicApp/PublicApp.tsx
@@ -0,0 +1,36 @@
+import React from "react";
+import { connect } from "react-redux";
+
+import { getErrorPage } from "metabase/selectors/app";
+
+import PublicNotFound from "metabase/public/components/PublicNotFound";
+import PublicError from "metabase/public/components/PublicError";
+
+import type { AppErrorDescriptor, State } from "metabase-types/store";
+
+interface OwnProps {
+  children: JSX.Element;
+}
+
+interface StateProps {
+  errorPage?: AppErrorDescriptor | null;
+}
+
+type Props = OwnProps & StateProps;
+
+function mapStateToProps(state: State) {
+  return {
+    errorPage: getErrorPage(state),
+  };
+}
+
+function PublicApp({ errorPage, children }: Props) {
+  if (errorPage) {
+    return errorPage.status === 404 ? <PublicNotFound /> : <PublicError />;
+  }
+  return children;
+}
+
+export default connect<StateProps, unknown, OwnProps, State>(mapStateToProps)(
+  PublicApp,
+);
diff --git a/frontend/src/metabase/public/containers/PublicApp/PublicApp.unit.spec.tsx b/frontend/src/metabase/public/containers/PublicApp/PublicApp.unit.spec.tsx
new file mode 100644
index 0000000000000000000000000000000000000000..a3496e513f61ca16839e9d9ad434e5535f879f86
--- /dev/null
+++ b/frontend/src/metabase/public/containers/PublicApp/PublicApp.unit.spec.tsx
@@ -0,0 +1,104 @@
+import React from "react";
+import userEvent from "@testing-library/user-event";
+
+import { getIcon, renderWithProviders, screen } from "__support__/ui";
+import { mockSettings } from "__support__/settings";
+
+import { AppErrorDescriptor } from "metabase-types/store";
+import { createMockAppState } from "metabase-types/store/mocks";
+
+import EmbedFrame from "../../components/EmbedFrame";
+import PublicApp from "./PublicApp";
+
+type SetupOpts = {
+  name?: string;
+  description?: string;
+  actionButtons?: JSX.Element[];
+  error?: AppErrorDescriptor;
+  hasEmbedBranding?: boolean;
+};
+
+function setup({
+  error,
+  hasEmbedBranding = true,
+  ...embedFrameProps
+}: SetupOpts = {}) {
+  const app = createMockAppState({ errorPage: error });
+  const settings = mockSettings({ "hide-embed-branding?": !hasEmbedBranding });
+
+  renderWithProviders(
+    <PublicApp>
+      <EmbedFrame {...embedFrameProps}>
+        <h1 data-testid="test-content">Test</h1>
+      </EmbedFrame>
+    </PublicApp>,
+    { mode: "public", storeInitialState: { app, settings }, withRouter: true },
+  );
+}
+
+describe("PublicApp", () => {
+  it("renders children", () => {
+    setup();
+    expect(screen.getByTestId("test-content")).toBeInTheDocument();
+  });
+
+  it("renders name", () => {
+    setup({ name: "My Title", description: "My Description" });
+    expect(screen.getByText("My Title")).toBeInTheDocument();
+    expect(screen.queryByText("My Description")).not.toBeInTheDocument();
+  });
+
+  it("renders description", () => {
+    setup({ name: "My Title", description: "My Description" });
+    userEvent.hover(getIcon("info"));
+    expect(screen.getByText("My Description")).toBeInTheDocument();
+  });
+
+  it("renders action buttons", () => {
+    setup({ actionButtons: [<button key="test">Click Me</button>] });
+    expect(
+      screen.getByRole("button", { name: "Click Me" }),
+    ).toBeInTheDocument();
+  });
+
+  it("renders branding", () => {
+    setup();
+    expect(screen.getByText(/Powered by/i)).toBeInTheDocument();
+    expect(screen.getByText(/Metabase/)).toBeInTheDocument();
+  });
+
+  it("renders not found page on error", () => {
+    setup({ error: { status: 404 } });
+    expect(screen.getByText("Not found")).toBeInTheDocument();
+    expect(screen.queryByTestId("test-content")).not.toBeInTheDocument();
+  });
+
+  it("renders error message", () => {
+    setup({
+      error: {
+        status: 500,
+        data: { error_code: "error", message: "Something went wrong" },
+      },
+    });
+    expect(screen.getByText("Something went wrong")).toBeInTheDocument();
+    expect(screen.queryByTestId("test-content")).not.toBeInTheDocument();
+  });
+
+  it("renders fallback error message", () => {
+    setup({ error: { status: 500 } });
+    expect(screen.getByText(/An error occurred/)).toBeInTheDocument();
+    expect(screen.queryByTestId("test-content")).not.toBeInTheDocument();
+  });
+
+  it("renders branding in error states", () => {
+    setup({ error: { status: 404 } });
+    expect(screen.getByText(/Powered by/i)).toBeInTheDocument();
+    expect(screen.getByText(/Metabase/)).toBeInTheDocument();
+  });
+
+  it("hides branding in error states if it's turned off", () => {
+    setup({ error: { status: 404 }, hasEmbedBranding: false });
+    expect(screen.queryByText(/Powered by/i)).not.toBeInTheDocument();
+    expect(screen.queryByText(/Metabase/)).not.toBeInTheDocument();
+  });
+});
diff --git a/frontend/src/metabase/public/containers/PublicApp/index.ts b/frontend/src/metabase/public/containers/PublicApp/index.ts
new file mode 100644
index 0000000000000000000000000000000000000000..6d79d4f68e1d899eafce06f40ec12f1063e02148
--- /dev/null
+++ b/frontend/src/metabase/public/containers/PublicApp/index.ts
@@ -0,0 +1 @@
+export { default } from "./PublicApp";
diff --git a/frontend/test/__support__/ui.tsx b/frontend/test/__support__/ui.tsx
index 81c77f6c1e3e82ba16bd361cfe5e46551dde7f71..2da9bc423be1c3b47cbd31ab696c4cf2ba21d38f 100644
--- a/frontend/test/__support__/ui.tsx
+++ b/frontend/test/__support__/ui.tsx
@@ -1,6 +1,7 @@
 import React from "react";
 import { render, screen } from "@testing-library/react";
 import { merge } from "icepick";
+import _ from "underscore";
 import { createMemoryHistory } from "history";
 import { Router, Route } from "react-router";
 import { Provider } from "react-redux";
@@ -43,16 +44,19 @@ export function renderWithProviders(
     ...options
   }: RenderWithProvidersOptions = {},
 ) {
-  const initialReduxState = createMockState(
+  let initialState = createMockState(
     withSampleDatabase
       ? merge(sampleDatabaseReduxState, storeInitialState)
       : storeInitialState,
   );
 
-  const store = getStore(
-    mode === "default" ? mainReducers : publicReducers,
-    initialReduxState,
-  );
+  if (mode === "public") {
+    const publicReducerNames = Object.keys(publicReducers);
+    initialState = _.pick(initialState, ...publicReducerNames) as State;
+  }
+
+  const reducers = mode === "default" ? mainReducers : publicReducers;
+  const store = getStore(reducers, initialState);
 
   const wrapper = (props: any) => (
     <Wrapper
@@ -106,10 +110,11 @@ function MaybeRouter({
   if (!hasRouter) {
     return children;
   }
+
   const history = createMemoryHistory({ entries: ["/"] });
 
   function Page(props: any) {
-    return React.cloneElement(children, props);
+    return React.cloneElement(children, _.omit(props, "children"));
   }
 
   return (