From 7788d94a514f93d7ee5d5fa8702b64f72cf1818b Mon Sep 17 00:00:00 2001
From: Leonard Techel
Date: Fri, 16 Jul 2021 20:59:45 +0200
Subject: [PATCH] feat(messages): Initial implementation of the messages
feature
Closes #13
---
app/jest.config.js | 2 +-
app/lang/de.json | 16 ++
app/src/app/AppRouter.tsx | 14 +-
app/src/app/MainNav.test.tsx | 29 ++
app/src/app/MainNav.tsx | 21 +-
.../app/__snapshots__/MainNav.test.tsx.snap | 86 ++++++
app/src/app/pages/MessagesPage.test.tsx | 41 +++
app/src/app/pages/MessagesPage.tsx | 80 ++++++
.../__snapshots__/IndexPage.test.tsx.snap | 102 ++++++-
.../__snapshots__/MessagesPage.test.tsx.snap | 267 ++++++++++++++++++
.../components/ChooseCharacterHeroMessage.tsx | 4 +-
.../common/components/MessageCard.test.tsx | 20 ++
app/src/common/components/MessageCard.tsx | 45 +++
.../ChooseCharacterHeroMessage.test.tsx.snap | 2 +-
.../__snapshots__/MessageCard.test.tsx.snap | 22 ++
app/src/common/hooks/api/useMessages.test.ts | 12 +
app/src/common/hooks/api/useMessages.ts | 32 +++
app/src/common/types/Message.ts | 26 ++
app/src/config.ts | 9 +
app/src/mocks/data/messages.json | 20 ++
app/src/mocks/handlers.ts | 4 +
app/src/{setupTests.ts => setupTests.tsx} | 5 +
22 files changed, 851 insertions(+), 8 deletions(-)
create mode 100644 app/src/app/MainNav.test.tsx
create mode 100644 app/src/app/__snapshots__/MainNav.test.tsx.snap
create mode 100644 app/src/app/pages/MessagesPage.test.tsx
create mode 100644 app/src/app/pages/MessagesPage.tsx
create mode 100644 app/src/app/pages/__snapshots__/MessagesPage.test.tsx.snap
create mode 100644 app/src/common/components/MessageCard.test.tsx
create mode 100644 app/src/common/components/MessageCard.tsx
create mode 100644 app/src/common/components/__snapshots__/MessageCard.test.tsx.snap
create mode 100644 app/src/common/hooks/api/useMessages.test.ts
create mode 100644 app/src/common/hooks/api/useMessages.ts
create mode 100644 app/src/common/types/Message.ts
create mode 100644 app/src/mocks/data/messages.json
rename app/src/{setupTests.ts => setupTests.tsx} (73%)
diff --git a/app/jest.config.js b/app/jest.config.js
index 8d76dd2..b34295c 100644
--- a/app/jest.config.js
+++ b/app/jest.config.js
@@ -27,5 +27,5 @@ module.exports = {
},
},
- setupFilesAfterEnv: ["/src/setupTests.ts"],
+ setupFilesAfterEnv: ["/src/setupTests.tsx"],
};
diff --git a/app/lang/de.json b/app/lang/de.json
index fd3c122..76d348d 100644
--- a/app/lang/de.json
+++ b/app/lang/de.json
@@ -27,6 +27,10 @@
"defaultMessage": "Hygiene",
"description": "parameter label"
},
+ "DZHDmU": {
+ "defaultMessage": "Nachrichten",
+ "description": "main nav messages page item label"
+ },
"Ejhsxh": {
"defaultMessage": "Ein Fehler ist aufgetreten!",
"description": "unknown error snack"
@@ -79,6 +83,10 @@
"defaultMessage": "Crew-Moral",
"description": "parameter label"
},
+ "Vdokz/": {
+ "defaultMessage": "Es gibt noch keine Nachrichten",
+ "description": "messages page no messages yet title"
+ },
"WHOfrR": {
"defaultMessage": "Setze Charakter",
"description": "qr code action label"
@@ -107,6 +115,10 @@
"defaultMessage": "Diversity Puzzle Trails",
"description": "main page title"
},
+ "eYC0U9": {
+ "defaultMessage": "Nachrichten",
+ "description": "messages page title"
+ },
"f0maUH": {
"defaultMessage": "Um an einem Spiel teilzunehmen ist ein Spiel-Code erforderlich. Bitte tippe diesen hier ein:",
"description": "start code page description"
@@ -135,6 +147,10 @@
"defaultMessage": "An einem Spiel teilnehmen",
"description": "start code page title"
},
+ "nwAgUn": {
+ "defaultMessage": "Schau doch später nochmal vorbei.",
+ "description": "messages page no messages yet description"
+ },
"qPHCvM": {
"defaultMessage": "Spielstand",
"description": "global parameter card title"
diff --git a/app/src/app/AppRouter.tsx b/app/src/app/AppRouter.tsx
index 8c450ba..62b3d96 100644
--- a/app/src/app/AppRouter.tsx
+++ b/app/src/app/AppRouter.tsx
@@ -4,6 +4,7 @@ import ProgressBar from "@badrap/bar-of-progress";
import useLocation, { BaseLocationHook } from "wouter/use-location";
import useGameId from "../common/hooks/useGameId";
+import config from "../config";
/**
* Wrap `React.lazy()` and return the factory as well for preloading the component
@@ -17,6 +18,7 @@ const lazyWithPreload = >(
const CodePage = lazyWithPreload(() => import("./pages/CodePage"));
const IndexPage = lazyWithPreload(() => import("./pages/IndexPage"));
+const MessagesPage = lazyWithPreload(() => import("./pages/MessagesPage"));
const ScannerPage = lazyWithPreload(() => import("./pages/ScannerPage"));
const StartPage = lazyWithPreload(() => import("./pages/StartPage"));
@@ -37,6 +39,10 @@ const routeFactories = [
path: "/start/:gameId?",
factory: StartPage.factory,
},
+ {
+ path: "/messages",
+ factory: MessagesPage.factory,
+ },
];
/**
@@ -84,7 +90,13 @@ const AppRouter = () => {
{({ gameId }) => }
-
+ {config.featureMessages ? (
+
+ {gameId ? : }
+
+ ) : (
+ <>>
+ )}
{/* Redirect to index page on 404 */}
diff --git a/app/src/app/MainNav.test.tsx b/app/src/app/MainNav.test.tsx
new file mode 100644
index 0000000..fdb936d
--- /dev/null
+++ b/app/src/app/MainNav.test.tsx
@@ -0,0 +1,29 @@
+import { screen } from "@testing-library/react";
+import * as React from "react";
+import userEvent from "@testing-library/user-event";
+
+import config from "../config";
+import MainNav from "./MainNav";
+import render from "../common/testing/render";
+
+it("renders correctly", () => {
+ const { container } = render();
+ expect(container.firstChild).toMatchSnapshot();
+});
+
+it("sets the location on click", () => {
+ render();
+
+ expect(window.location.pathname).toEqual("/");
+
+ if (config.featureMessages) {
+ userEvent.click(screen.getByText(/Nachrichten/));
+ expect(window.location.pathname).toEqual("/messages");
+ }
+
+ userEvent.click(screen.getByText(/Scanner/));
+ expect(window.location.pathname).toEqual("/scan");
+
+ userEvent.click(screen.getByText(/Status/));
+ expect(window.location.pathname).toEqual("/");
+});
diff --git a/app/src/app/MainNav.tsx b/app/src/app/MainNav.tsx
index b224644..32a7942 100644
--- a/app/src/app/MainNav.tsx
+++ b/app/src/app/MainNav.tsx
@@ -1,10 +1,13 @@
-import { Paper, BottomNavigation, BottomNavigationAction } from "@mui/material";
-import { Home } from "@mui/icons-material";
-import * as React from "react";
+import { ChatBubble, Home } from "@mui/icons-material";
import { FormattedMessage } from "react-intl";
+import { Paper, BottomNavigation, BottomNavigationAction } from "@mui/material";
import { useLocation } from "wouter";
+import * as React from "react";
+
import QrCodeScanner from "../common/icons/QrCodeScanner";
+import config from "../config";
+
type MainNavProps = {};
const MainNav = (props: MainNavProps) => {
@@ -47,6 +50,18 @@ const MainNav = (props: MainNavProps) => {
}
icon={}
/>
+ {config.featureMessages && (
+
+ }
+ icon={}
+ />
+ )}
);
diff --git a/app/src/app/__snapshots__/MainNav.test.tsx.snap b/app/src/app/__snapshots__/MainNav.test.tsx.snap
new file mode 100644
index 0000000..563dc42
--- /dev/null
+++ b/app/src/app/__snapshots__/MainNav.test.tsx.snap
@@ -0,0 +1,86 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/app/src/app/pages/MessagesPage.test.tsx b/app/src/app/pages/MessagesPage.test.tsx
new file mode 100644
index 0000000..0b7014f
--- /dev/null
+++ b/app/src/app/pages/MessagesPage.test.tsx
@@ -0,0 +1,41 @@
+import { rest } from "msw";
+import { screen, waitFor } from "@testing-library/react";
+import * as React from "react";
+
+import { server } from "../../mocks/server";
+import MessagesPage from "./MessagesPage";
+import render from "../../common/testing/render";
+
+it("renders a loading screen", () => {
+ render();
+
+ expect(screen.getByRole("progressbar")).toBeInTheDocument();
+});
+
+it("renders messages", async () => {
+ const { container } = render();
+
+ await waitFor(() => expect(screen.getByText(/Test 123/)).toBeInTheDocument());
+ expect(container.firstChild).toMatchSnapshot();
+});
+
+it("renders no messages yet hero message", async () => {
+ server.use(
+ rest.get("/games/:gameId/messages", (req, res, ctx) => {
+ return res(
+ ctx.json({
+ data: [],
+ })
+ );
+ })
+ );
+
+ const { container } = render();
+
+ await waitFor(() =>
+ expect(
+ screen.getByText(/Es gibt noch keine Nachrichten/)
+ ).toBeInTheDocument()
+ );
+ expect(container.firstChild).toMatchSnapshot();
+});
diff --git a/app/src/app/pages/MessagesPage.tsx b/app/src/app/pages/MessagesPage.tsx
new file mode 100644
index 0000000..507e814
--- /dev/null
+++ b/app/src/app/pages/MessagesPage.tsx
@@ -0,0 +1,80 @@
+import {
+ AppBar,
+ Toolbar,
+ Typography,
+ Box,
+ Container,
+ LinearProgress,
+} from "@mui/material";
+import { FormattedMessage } from "react-intl";
+import * as React from "react";
+
+import MainNav from "../MainNav";
+import MessageCard from "../../common/components/MessageCard";
+import useMessages from "../../common/hooks/api/useMessages";
+import { Message } from "../../common/types/Message";
+import HeroMessage from "../../common/components/HeroMessage";
+
+const MessagesPage = () => {
+ const { data: messages } = useMessages();
+
+ // Scroll to the bottom after (new) messages are rendered
+ React.useEffect(() => {
+ const $html = document.documentElement;
+ $html.scrollTop = $html.scrollHeight;
+ }, [messages]);
+
+ /**
+ * Whether the API has returned zero messages
+ */
+ const hasNoMessages = messages && messages.data.length === 0;
+
+ return (
+
+
+
+
+
+
+
+
+
+
+ {hasNoMessages ? (
+
+ }
+ description={
+
+ }
+ />
+ ) : messages ? (
+
+ {messages.data.map((msg: Message) => (
+
+ ))}
+
+ ) : (
+
+ )}
+
+
+ );
+};
+
+export default MessagesPage;
diff --git a/app/src/app/pages/__snapshots__/IndexPage.test.tsx.snap b/app/src/app/pages/__snapshots__/IndexPage.test.tsx.snap
index 2cdb0aa..9950943 100644
--- a/app/src/app/pages/__snapshots__/IndexPage.test.tsx.snap
+++ b/app/src/app/pages/__snapshots__/IndexPage.test.tsx.snap
@@ -93,6 +93,31 @@ exports[`can show the game is over 1`] = `
class="MuiTouchRipple-root css-8je8zh-MuiTouchRipple-root"
/>
+
+
+
+
+
+
+
+
+
+
+
+ Irgendeine Nachricht :)
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders no messages yet hero message 1`] = `
+
+
+
+
+
+
+
+ Es gibt noch keine Nachrichten
+
+
+ Schau doch später nochmal vorbei.
+
+
+
+
+
+`;
diff --git a/app/src/common/components/ChooseCharacterHeroMessage.tsx b/app/src/common/components/ChooseCharacterHeroMessage.tsx
index 3b92a93..12b16ff 100644
--- a/app/src/common/components/ChooseCharacterHeroMessage.tsx
+++ b/app/src/common/components/ChooseCharacterHeroMessage.tsx
@@ -4,6 +4,8 @@ import * as React from "react";
import HeroMessage from "./HeroMessage";
+import config from "../../config";
+
type ChooseCharacterHeroMessageProps = {};
const ChooseCharacterHeroMessage = (props: ChooseCharacterHeroMessageProps) => {
@@ -26,7 +28,7 @@ const ChooseCharacterHeroMessage = (props: ChooseCharacterHeroMessageProps) => {
sx={{
marginBottom: 3,
marginTop: 3,
- transform: "translateX(84px)", // TODO remove when the messages nav entry is available
+ transform: config.featureMessages ? "" : "translateX(84px)",
}}
/>
}
diff --git a/app/src/common/components/MessageCard.test.tsx b/app/src/common/components/MessageCard.test.tsx
new file mode 100644
index 0000000..bef92f9
--- /dev/null
+++ b/app/src/common/components/MessageCard.test.tsx
@@ -0,0 +1,20 @@
+import * as React from "react";
+import render from "../testing/render";
+
+import MessageCard from "./MessageCard";
+
+it("renders correctly", () => {
+ const { container } = render(
+
+ );
+ expect(container.firstChild).toMatchSnapshot();
+});
diff --git a/app/src/common/components/MessageCard.tsx b/app/src/common/components/MessageCard.tsx
new file mode 100644
index 0000000..f7990c9
--- /dev/null
+++ b/app/src/common/components/MessageCard.tsx
@@ -0,0 +1,45 @@
+import { Card, CardContent, Typography } from "@mui/material";
+import { FormattedRelativeTime } from "react-intl";
+import * as React from "react";
+
+import { Message } from "../types/Message";
+
+type MessageCardProps = {
+ message: Message;
+};
+
+const MessageCard = ({ message }: MessageCardProps) => {
+ return (
+
+
+ {message.attributes.message}
+ theme.spacing(1),
+ }}
+ variant="caption"
+ >
+
+
+
+
+ );
+};
+
+export default MessageCard;
diff --git a/app/src/common/components/__snapshots__/ChooseCharacterHeroMessage.test.tsx.snap b/app/src/common/components/__snapshots__/ChooseCharacterHeroMessage.test.tsx.snap
index fbe9284..b162328 100644
--- a/app/src/common/components/__snapshots__/ChooseCharacterHeroMessage.test.tsx.snap
+++ b/app/src/common/components/__snapshots__/ChooseCharacterHeroMessage.test.tsx.snap
@@ -19,7 +19,7 @@ exports[`renders correctly 1`] = `