diff --git a/package-lock.json b/package-lock.json
index b3bdf869e..806f7d05e 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -25882,6 +25882,12 @@
"browser-process-hrtime": "^0.1.2"
}
},
+ "wait-for-expect": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/wait-for-expect/-/wait-for-expect-1.1.0.tgz",
+ "integrity": "sha512-vQDokqxyMyknfX3luCDn16bSaRcOyH6gGuUXMIbxBLeTo6nWuEWYqMTT9a+44FmW8c2m6TRWBdNvBBjA1hwEKg==",
+ "dev": true
+ },
"walker": {
"version": "1.0.7",
"resolved": "https://registry.npmjs.org/walker/-/walker-1.0.7.tgz",
diff --git a/package.json b/package.json
index 65156f224..659bf7469 100644
--- a/package.json
+++ b/package.json
@@ -275,6 +275,7 @@
"typescript": "^3.0.3",
"typescript-snapshots-plugin": "^1.2.0",
"uglifyjs-webpack-plugin": "^1.2.5",
+ "wait-for-expect": "^1.1.0",
"webpack": "4.12.0",
"webpack-cli": "^3.0.2",
"webpack-dev-server": "^3.1.10",
diff --git a/src/core/client/admin/components/AppBar.css b/src/core/client/admin/components/AppBar.css
index effb9c43b..8eb154452 100644
--- a/src/core/client/admin/components/AppBar.css
+++ b/src/core/client/admin/components/AppBar.css
@@ -6,7 +6,7 @@
.container {
max-width: 1280px;
margin: 0 auto;
- padding: 0 calc(2 * var(--spacing-unit));
+ padding: 0 var(--spacing-unit) 0 calc(2 * var(--spacing-unit));
height: calc(6 * var(--spacing-unit));
box-sizing: border-box;
}
diff --git a/src/core/client/admin/components/DecisionHistoryButton.css b/src/core/client/admin/components/DecisionHistoryButton.css
new file mode 100644
index 000000000..001a0a329
--- /dev/null
+++ b/src/core/client/admin/components/DecisionHistoryButton.css
@@ -0,0 +1,9 @@
+.popover {
+ z-index: var(--zindex-popover);
+ width: 350px;
+}
+
+.historyIcon {
+ color: var(--palette-text-secondary);
+ margin-right: calc(1.5 * var(--spacing-unit));
+}
diff --git a/src/core/client/admin/components/DecisionHistoryButton.tsx b/src/core/client/admin/components/DecisionHistoryButton.tsx
new file mode 100644
index 000000000..c248cc6c3
--- /dev/null
+++ b/src/core/client/admin/components/DecisionHistoryButton.tsx
@@ -0,0 +1,55 @@
+import React from "react";
+
+import { oncePerFrame } from "talk-common/utils";
+import { BaseButton, ClickOutside, Icon, Popover } from "talk-ui/components";
+
+import DecisionHistoryQuery from "../views/decisionHistory/queries/DecisionHistoryQuery";
+import styles from "./DecisionHistoryButton.css";
+
+class DecisionHistoryButton extends React.Component {
+ // Helper that prevents calling toggleVisibility more then once per frame.
+ // In essence this means we'll process an event only once.
+ // This might happen, when clicking on the button which will
+ // cause its onClick to happen as well as onClickOutside.
+ private toggleVisibilityOncePerFrame = oncePerFrame(
+ (toggleVisibility: () => void) => toggleVisibility()
+ );
+
+ public render() {
+ const popoverID = `decision-history-popover`;
+ return (
+ (
+
+ this.toggleVisibilityOncePerFrame(toggleVisibility)
+ }
+ >
+
+
+
+
+ )}
+ >
+ {({ toggleVisibility, forwardRef, visible }) => (
+ this.toggleVisibilityOncePerFrame(toggleVisibility)}
+ aria-controls={popoverID}
+ forwardRef={forwardRef}
+ className={styles.historyIcon}
+ data-test="decisionHistory-toggle"
+ >
+ history
+
+ )}
+
+ );
+ }
+}
+
+export default DecisionHistoryButton;
diff --git a/src/core/client/admin/components/Navigation.css b/src/core/client/admin/components/Navigation.css
index fd02200e2..661276f43 100644
--- a/src/core/client/admin/components/Navigation.css
+++ b/src/core/client/admin/components/Navigation.css
@@ -26,3 +26,8 @@
text-decoration: none;
color: var(--palette-text-light);
}
+
+.historyIcon {
+ color: var(--palette-text-secondary);
+ padding: var(--spacing-unit) var(--spacing-unit);
+}
diff --git a/src/core/client/admin/components/Navigation.tsx b/src/core/client/admin/components/Navigation.tsx
index 18112c2af..9f90c6a3e 100644
--- a/src/core/client/admin/components/Navigation.tsx
+++ b/src/core/client/admin/components/Navigation.tsx
@@ -4,7 +4,9 @@ import React, { StatelessComponent } from "react";
import { Flex } from "talk-ui/components";
import SignOutButtonContainer from "../containers/SignOutButtonContainer";
+import DecisionHistoryButton from "./DecisionHistoryButton";
import styles from "./Navigation.css";
+import NavigationDivider from "./NavigationDivider";
import NavigationLink from "./NavigationLink";
const Navigation: StatelessComponent = () => (
@@ -24,6 +26,8 @@ const Navigation: StatelessComponent = () => (
+
+
diff --git a/src/core/client/admin/components/NavigationDivider.css b/src/core/client/admin/components/NavigationDivider.css
new file mode 100644
index 000000000..db5d684a2
--- /dev/null
+++ b/src/core/client/admin/components/NavigationDivider.css
@@ -0,0 +1,4 @@
+.root {
+ height: 75%;
+ border-right: 1px solid var(--palette-divider);
+}
diff --git a/src/core/client/admin/components/NavigationDivider.spec.tsx b/src/core/client/admin/components/NavigationDivider.spec.tsx
new file mode 100644
index 000000000..07eb79537
--- /dev/null
+++ b/src/core/client/admin/components/NavigationDivider.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import NavigationDivider from "./NavigationDivider";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/components/NavigationDivider.tsx b/src/core/client/admin/components/NavigationDivider.tsx
new file mode 100644
index 000000000..f85a43c7c
--- /dev/null
+++ b/src/core/client/admin/components/NavigationDivider.tsx
@@ -0,0 +1,9 @@
+import React, { StatelessComponent } from "react";
+
+import styles from "./NavigationDivider.css";
+
+const NavigationDivider: StatelessComponent<{}> = () => (
+
+);
+
+export default NavigationDivider;
diff --git a/src/core/client/admin/components/NavigationLink.css b/src/core/client/admin/components/NavigationLink.css
index 80cd64d7d..9155c5f83 100644
--- a/src/core/client/admin/components/NavigationLink.css
+++ b/src/core/client/admin/components/NavigationLink.css
@@ -1,7 +1,7 @@
.root {
color: var(--palette-text-secondary);
font-family: var(--font-family-sans-serif);
- font-weight: var(--font-weight-bold);
+ font-weight: var(--font-weight-medium);
font-size: calc(18rem / var(--rem-base));
line-height: calc(20em / 18);
letter-spacing: calc(-0.1em / 18);
diff --git a/src/core/client/admin/components/SignOutButton.css b/src/core/client/admin/components/SignOutButton.css
new file mode 100644
index 000000000..8e62c2bc6
--- /dev/null
+++ b/src/core/client/admin/components/SignOutButton.css
@@ -0,0 +1,5 @@
+.root {
+ font-weight: var(--font-weight-bold);
+ font-size: calc(18rem / var(--rem-base));
+ letter-spacing: calc(-0.2em / 18);
+}
diff --git a/src/core/client/admin/components/SignOutButton.tsx b/src/core/client/admin/components/SignOutButton.tsx
index c4f0bd8c9..e50927d72 100644
--- a/src/core/client/admin/components/SignOutButton.tsx
+++ b/src/core/client/admin/components/SignOutButton.tsx
@@ -3,6 +3,8 @@ import React, { StatelessComponent } from "react";
import { Button } from "talk-ui/components";
+import styles from "./SignOutButton.css";
+
interface Props {
id?: string;
onClick: React.EventHandler;
@@ -10,7 +12,7 @@ interface Props {
const SignOutButton: StatelessComponent = props => (
-
diff --git a/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap
index 1d44e5052..2ae96297a 100644
--- a/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap
+++ b/src/core/client/admin/components/__snapshots__/Navigation.spec.tsx.snap
@@ -48,6 +48,8 @@ exports[`renders correctly 1`] = `
+
+
diff --git a/src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap
new file mode 100644
index 000000000..2c3bd27f6
--- /dev/null
+++ b/src/core/client/admin/components/__snapshots__/NavigationDivider.spec.tsx.snap
@@ -0,0 +1,7 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/src/core/client/admin/components/__snapshots__/SignOutButton.spec.tsx.snap b/src/core/client/admin/components/__snapshots__/SignOutButton.spec.tsx.snap
index a44d3d67c..23ffa5fa0 100644
--- a/src/core/client/admin/components/__snapshots__/SignOutButton.spec.tsx.snap
+++ b/src/core/client/admin/components/__snapshots__/SignOutButton.spec.tsx.snap
@@ -5,6 +5,7 @@ exports[`renders correctly 1`] = `
id="navigation-signOutButton"
>
diff --git a/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap b/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap
new file mode 100644
index 000000000..decfa1756
--- /dev/null
+++ b/src/core/client/admin/test/decisionHistory/__snapshots__/decisionHistory.spec.tsx.snap
@@ -0,0 +1,472 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`loads more 1`] = `
+
+
+
+ history
+
+
+
+
+
+ Your Decision History
+
+
+
+
+ -
+
+
+
+ check
+
+
+
+
+ Accepted comment by
+
+ dany
+
+
+
+
+
+
+ -
+
+
+
+ cancel
+
+
+
+
+ Rejected comment by
+
+ dany
+
+
+
+
+
+
+ -
+
+
+
+ check
+
+
+
+
+ Accepted comment by
+
+ dany
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`opens popover when clicked on button showing loading state 1`] = `
+
+
+
+ history
+
+
+
+
+
+ Your Decision History
+
+
+
+
+`;
+
+exports[`render popover content 1`] = `
+
+
+
+ history
+
+
+
+
+
+ Your Decision History
+
+
+
+
+ -
+
+
+
+ check
+
+
+
+
+ Accepted comment by
+
+ addy
+
+
+
+
+
+
+ -
+
+
+
+ cancel
+
+
+
+
+ Rejected comment by
+
+ addy
+
+
+
+
+
+
+
+
+ Show More
+
+
+
+`;
+
+exports[`renders decision history popover button 1`] = `
+
+
+
+ history
+
+
+
+
+ A dialog showing a permalink to the comment
+
+
+
+`;
diff --git a/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx
new file mode 100644
index 000000000..1b40addac
--- /dev/null
+++ b/src/core/client/admin/test/decisionHistory/decisionHistory.spec.tsx
@@ -0,0 +1,136 @@
+import { get, merge } from "lodash";
+import sinon from "sinon";
+
+import {
+ createSinonStub,
+ getByTestID,
+ getByText,
+ limitSnapshotTo,
+ replaceHistoryLocation,
+ wait,
+ waitForElement,
+} from "talk-framework/testHelpers";
+
+import create from "../create";
+import { moderationActions, settings } from "../fixtures";
+
+beforeEach(async () => {
+ replaceHistoryLocation("http://localhost/admin/configure/auth");
+});
+
+const commentModerationActionHistory = createSinonStub(
+ s => s.throws(),
+ s =>
+ s.withArgs({ first: 5 }).returns({
+ edges: [
+ {
+ node: moderationActions[0],
+ cursor: moderationActions[0].createdAt,
+ },
+ {
+ node: moderationActions[1],
+ cursor: moderationActions[1].createdAt,
+ },
+ ],
+ pageInfo: {
+ endCursor: moderationActions[1].createdAt,
+ hasNextPage: true,
+ },
+ }),
+ s =>
+ s
+ .withArgs({
+ first: 10,
+ after: moderationActions[1].createdAt,
+ })
+ .returns({
+ edges: [
+ {
+ node: moderationActions[2],
+ cursor: moderationActions[2].createdAt,
+ },
+ ],
+ pageInfo: {
+ endCursor: moderationActions[2].createdAt,
+ hasNextPage: false,
+ },
+ })
+);
+
+const createTestRenderer = async (resolver: any = {}) => {
+ const resolvers = {
+ ...resolver,
+ Query: {
+ ...resolver.Query,
+ me: sinon.stub().returns({
+ id: "me",
+ commentModerationActionHistory,
+ }),
+ settings: sinon
+ .stub()
+ .returns(merge(settings, get(resolver, "Query.settings"))),
+ },
+ };
+ const { testRenderer } = create({
+ // Set this to true, to see graphql responses.
+ logNetwork: false,
+ resolvers,
+ initLocalState: localRecord => {
+ localRecord.setValue(true, "loggedIn");
+ },
+ });
+ await waitForElement(() =>
+ getByTestID("decisionHistory-toggle", testRenderer.root)
+ );
+ return testRenderer;
+};
+
+async function createTestRendererAndOpenPopover() {
+ const testRenderer = await createTestRenderer();
+ const toggle = testRenderer.root.findByProps({
+ "data-test": "decisionHistory-toggle",
+ })!;
+ toggle.props.onClick();
+ return testRenderer;
+}
+
+it("renders decision history popover button", async () => {
+ const testRenderer = await createTestRenderer();
+ expect(
+ limitSnapshotTo("decisionHistory-popover", testRenderer.toJSON())
+ ).toMatchSnapshot();
+});
+
+it("opens popover when clicked on button showing loading state", async () => {
+ const testRenderer = await createTestRendererAndOpenPopover();
+ expect(
+ limitSnapshotTo("decisionHistory-loading-container", testRenderer.toJSON())
+ ).toMatchSnapshot();
+});
+
+it("render popover content", async () => {
+ const testRenderer = await createTestRendererAndOpenPopover();
+ await waitForElement(() =>
+ getByTestID("decisionHistory-container", testRenderer.root)
+ );
+ expect(
+ limitSnapshotTo("decisionHistory-container", testRenderer.toJSON())
+ ).toMatchSnapshot();
+});
+
+it("loads more", async () => {
+ const testRenderer = await createTestRendererAndOpenPopover();
+ const decisionHistoryContainer = await waitForElement(() =>
+ getByTestID("decisionHistory-container", testRenderer.root)
+ );
+ const ShowMoreButton = getByText("Show More", decisionHistoryContainer)!;
+ expect(ShowMoreButton.props.disabled).toBeFalsy();
+ ShowMoreButton.props.onClick();
+ expect(ShowMoreButton.props.disabled).toBeTruthy();
+ await wait(() => {
+ expect(() => getByText("Show More", decisionHistoryContainer)).toThrow();
+ });
+ expect(
+ limitSnapshotTo("decisionHistory-container", testRenderer.toJSON())
+ ).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/test/fixtures.ts b/src/core/client/admin/test/fixtures.ts
index c33ddd43a..e1669ce41 100644
--- a/src/core/client/admin/test/fixtures.ts
+++ b/src/core/client/admin/test/fixtures.ts
@@ -48,3 +48,86 @@ export const settings = {
},
},
};
+
+export const moderationActions = [
+ {
+ id: "07e8f815-e165-4b5d-b438-7163415c8cf7",
+ revision: {
+ id: "4210dc8b-c212-4f74-9381-913e8c52e51a",
+ comment: {
+ author: {
+ username: "luke2",
+ id: "4383c3d3-bb9b-40b9-847a-240f3cf6c6af",
+ },
+ id: "1b41be9f-510f-41f3-a1df-5a431dc98bf3",
+ },
+ },
+ createdAt: "2018-11-29T16:01:51.897Z",
+ status: "ACCEPTED",
+ __typename: "CommentModerationAction",
+ },
+ {
+ id: "6869314b-47ef-4cf9-b8ce-42b12bca8231",
+ revision: {
+ id: "4210dc8b-c212-4f74-9381-913e8c52e51a",
+ comment: {
+ author: {
+ username: "addy",
+ id: "4383c3d3-bb9b-40b9-847a-240f3cf6c6af",
+ },
+ id: "1b41be9f-510f-41f3-a1df-5a431dc98bf3",
+ },
+ },
+ createdAt: "2018-11-29T16:01:45.644Z",
+ status: "REJECTED",
+ __typename: "CommentModerationAction",
+ },
+ {
+ id: "caebbf7f-4813-42c0-ac3c-46b1be8199e0",
+ revision: {
+ id: "4210dc8b-c212-4f74-9381-913e8c52e51a",
+ comment: {
+ author: {
+ username: "dany",
+ id: "4383c3d3-bb9b-40b9-847a-240f3cf6c6af",
+ },
+ id: "1b41be9f-510f-41f3-a1df-5a431dc98bf3",
+ },
+ },
+ createdAt: "2018-11-29T16:01:42.060Z",
+ status: "ACCEPTED",
+ __typename: "CommentModerationAction",
+ },
+ {
+ id: "b2f92717-e4a8-4075-a543-95f7c5eaefb2",
+ revision: {
+ id: "4210dc8b-c212-4f74-9381-913e8c52e51a",
+ comment: {
+ author: {
+ username: "admin",
+ id: "4383c3d3-bb9b-40b9-847a-240f3cf6c6af",
+ },
+ id: "1b41be9f-510f-41f3-a1df-5a431dc98bf3",
+ },
+ },
+ createdAt: "2018-11-29T16:01:34.539Z",
+ status: "REJECTED",
+ __typename: "CommentModerationAction",
+ },
+ {
+ id: "9fb2ff3c-7105-4357-99e1-36cdeea49c75",
+ revision: {
+ id: "4210dc8b-c212-4f74-9381-913e8c52e51a",
+ comment: {
+ author: {
+ username: "mod245",
+ id: "4383c3d3-bb9b-40b9-847a-240f3cf6c6af",
+ },
+ id: "1b41be9f-510f-41f3-a1df-5a431dc98bf3",
+ },
+ },
+ createdAt: "2018-11-29T16:01:30.648Z",
+ status: "ACCEPTED",
+ __typename: "CommentModerationAction",
+ },
+];
diff --git a/src/core/client/admin/views/decisionHistory/components/AcceptedComment.spec.tsx b/src/core/client/admin/views/decisionHistory/components/AcceptedComment.spec.tsx
new file mode 100644
index 000000000..f28b46015
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/AcceptedComment.spec.tsx
@@ -0,0 +1,18 @@
+import { shallow } from "enzyme";
+import { noop } from "lodash";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import AcceptedComment from "./AcceptedComment";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ href: "#",
+ username: "InTheExpensiveSeats",
+ date: "2018-07-06T18:24:00.000Z",
+ onGotoComment: noop,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/AcceptedComment.tsx b/src/core/client/admin/views/decisionHistory/components/AcceptedComment.tsx
new file mode 100644
index 000000000..ad30b4767
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/AcceptedComment.tsx
@@ -0,0 +1,43 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import AcceptedIcon from "./AcceptedIcon";
+import DecisionItem from "./DecisionItem";
+import DotDivider from "./DotDivider";
+import Footer from "./Footer";
+import GoToCommentLink from "./GoToCommentLink";
+import Info from "./Info";
+import Timestamp from "./Timestamp";
+
+import { Typography } from "talk-ui/components";
+
+interface Props {
+ href: string;
+ username: string;
+ date: string;
+ onGotoComment?: React.EventHandler;
+}
+
+const Username: StatelessComponent<{ username: string }> = ({ username }) => (
+ {username}
+);
+
+const ApprovedComment: StatelessComponent = props => (
+ }>
+ }
+ >
+ {"Accepted comment by "}
+
+
+
+);
+
+export default ApprovedComment;
diff --git a/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.css b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.css
new file mode 100644
index 000000000..50efdc1dc
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.css
@@ -0,0 +1,3 @@
+.root {
+ color: var(--palette-success-main);
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.spec.tsx b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.spec.tsx
new file mode 100644
index 000000000..c1dec6d58
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import AcceptedIcon from "./AcceptedIcon";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.tsx b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.tsx
new file mode 100644
index 000000000..076e6467c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/AcceptedIcon.tsx
@@ -0,0 +1,11 @@
+import React, { StatelessComponent } from "react";
+
+import { Icon } from "talk-ui/components";
+
+import styles from "./AcceptedIcon.css";
+
+const AcceptedIcon: StatelessComponent = () => (
+ check
+);
+
+export default AcceptedIcon;
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx
new file mode 100644
index 000000000..bbef181a3
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.spec.tsx
@@ -0,0 +1,54 @@
+import { shallow } from "enzyme";
+import { noop } from "lodash";
+import React from "react";
+
+import { removeFragmentRefs } from "talk-framework/testHelpers";
+import { PropTypesOf } from "talk-framework/types";
+
+import DecisionHistory from "./DecisionHistory";
+
+const DecisionHistoryN = removeFragmentRefs(DecisionHistory);
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
+ onLoadMore: noop,
+ hasMore: false,
+ disableLoadMore: false,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it("renders empty state", () => {
+ const props: PropTypesOf = {
+ actions: [],
+ onLoadMore: noop,
+ hasMore: false,
+ disableLoadMore: false,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it("renders hasMore", () => {
+ const props: PropTypesOf = {
+ actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
+ onLoadMore: noop,
+ hasMore: true,
+ disableLoadMore: false,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
+
+it("renders disable load more", () => {
+ const props: PropTypesOf = {
+ actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
+ onLoadMore: noop,
+ hasMore: true,
+ disableLoadMore: true,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx
new file mode 100644
index 000000000..ea090ae32
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistory.tsx
@@ -0,0 +1,41 @@
+import React, { StatelessComponent } from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import DecisionHistoryItemContainer from "../containers/DecisionHistoryItemContainer";
+import DecisionList from "./DecisionList";
+import Empty from "./Empty";
+import Main from "./Main";
+import ShowMoreButton from "./ShowMoreButton";
+import Title from "./Title";
+
+interface Props {
+ actions: Array<
+ { id: string } & PropTypesOf["action"]
+ >;
+ onLoadMore?: () => void;
+ hasMore?: boolean;
+ disableLoadMore?: boolean;
+}
+
+const DecisionHistory: StatelessComponent = props => (
+
+
+
+
+ {props.actions.length === 0 && }
+ {props.actions.map(action => (
+
+ ))}
+
+ {props.hasMore && (
+
+ )}
+
+
+);
+
+export default DecisionHistory;
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.css b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.css
new file mode 100644
index 000000000..7b77839a1
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.css
@@ -0,0 +1,4 @@
+.container {
+ padding: var(--spacing-unit);
+ min-height: 30px;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.spec.tsx
new file mode 100644
index 000000000..d8047d34e
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import DecisionHistoryLoading from "./DecisionHistoryLoading";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx
new file mode 100644
index 000000000..987f7e8ce
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionHistoryLoading.tsx
@@ -0,0 +1,26 @@
+import React, { StatelessComponent } from "react";
+
+import { Delay, Flex, Spinner } from "talk-ui/components";
+
+import styles from "./DecisionHistoryLoading.css";
+import Main from "./Main";
+import Title from "./Title";
+
+const DecisionHistoryLoading: StatelessComponent = () => (
+
+
+
+
+
+
+
+
+
+
+);
+
+export default DecisionHistoryLoading;
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionItem.css b/src/core/client/admin/views/decisionHistory/components/DecisionItem.css
new file mode 100644
index 000000000..5216bdf50
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionItem.css
@@ -0,0 +1,13 @@
+.root {
+ padding: calc(0.5 * var(--spacing-unit)) calc(0.5 * var(--spacing-unit))
+ calc(0.5 * var(--spacing-unit)) 0;
+}
+
+.leftCol {
+ display: flex;
+ justify-content: center;
+ flex-grow: 0;
+ flex-shrink: 0;
+ width: calc(3 * var(--spacing-unit));
+ padding-top: 3px;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionItem.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionItem.spec.tsx
new file mode 100644
index 000000000..5c8063609
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionItem.spec.tsx
@@ -0,0 +1,15 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import DecisionItem from "./DecisionItem";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ icon: "icon",
+ children: "children",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionItem.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionItem.tsx
new file mode 100644
index 000000000..422e7fd75
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionItem.tsx
@@ -0,0 +1,21 @@
+import React, { StatelessComponent } from "react";
+
+import { Flex } from "talk-ui/components";
+
+import styles from "./DecisionItem.css";
+
+interface Props {
+ icon: React.ReactNode;
+ children: React.ReactNode;
+}
+
+const DecisionItem: StatelessComponent = props => (
+
+
+ {props.icon}
+ {props.children}
+
+
+);
+
+export default DecisionItem;
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionList.css b/src/core/client/admin/views/decisionHistory/components/DecisionList.css
new file mode 100644
index 000000000..92219280f
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionList.css
@@ -0,0 +1,12 @@
+.root {
+ margin: 0;
+ padding: 0;
+ list-style-type: none;
+
+ & > * {
+ border-bottom: 1px solid var(--palette-divider);
+ }
+ & > *:last-child {
+ border-bottom: 0;
+ }
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionList.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionList.spec.tsx
new file mode 100644
index 000000000..2a1103b47
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionList.spec.tsx
@@ -0,0 +1,14 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import DecisionList from "./DecisionList";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ children: "children",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/DecisionList.tsx b/src/core/client/admin/views/decisionHistory/components/DecisionList.tsx
new file mode 100644
index 000000000..a06dac070
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DecisionList.tsx
@@ -0,0 +1,9 @@
+import React, { StatelessComponent } from "react";
+
+import styles from "./DecisionList.css";
+
+const DecisionList: StatelessComponent = props => (
+
+);
+
+export default DecisionList;
diff --git a/src/core/client/admin/views/decisionHistory/components/DotDivider.css b/src/core/client/admin/views/decisionHistory/components/DotDivider.css
new file mode 100644
index 000000000..2c8a678ff
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DotDivider.css
@@ -0,0 +1,7 @@
+.root {
+ height: 100%;
+ font-size: calc(12rem / var(--rem-base));
+ line-height: calc(12em / 12);
+ color: var(--palette-divider);
+ padding: 0 calc(0.5 * var(--spacing-unit));
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/DotDivider.spec.tsx b/src/core/client/admin/views/decisionHistory/components/DotDivider.spec.tsx
new file mode 100644
index 000000000..26e75f123
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DotDivider.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import DotDivider from "./DotDivider";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/DotDivider.tsx b/src/core/client/admin/views/decisionHistory/components/DotDivider.tsx
new file mode 100644
index 000000000..b433d3e00
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/DotDivider.tsx
@@ -0,0 +1,9 @@
+import React, { StatelessComponent } from "react";
+
+import styles from "./DotDivider.css";
+
+const Footer: StatelessComponent<{}> = () => (
+ •
+);
+
+export default Footer;
diff --git a/src/core/client/admin/views/decisionHistory/components/Empty.css b/src/core/client/admin/views/decisionHistory/components/Empty.css
new file mode 100644
index 000000000..e9b985a5f
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Empty.css
@@ -0,0 +1,3 @@
+.root {
+ padding: calc(1.5 * var(--spacing-unit)) calc(2 * var(--spacing-unit));
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Empty.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Empty.spec.tsx
new file mode 100644
index 000000000..3cf25937c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Empty.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import Empty from "./Empty";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Empty.tsx b/src/core/client/admin/views/decisionHistory/components/Empty.tsx
new file mode 100644
index 000000000..71d313a13
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Empty.tsx
@@ -0,0 +1,18 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import { Flex, Typography } from "talk-ui/components";
+
+import styles from "./Empty.css";
+
+const Empty: StatelessComponent<{}> = () => (
+
+
+
+ You will see a list of your post moderation actions here.
+
+
+
+);
+
+export default Empty;
diff --git a/src/core/client/admin/views/decisionHistory/components/Footer.css b/src/core/client/admin/views/decisionHistory/components/Footer.css
new file mode 100644
index 000000000..c3a2af639
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Footer.css
@@ -0,0 +1,2 @@
+.root {
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Footer.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Footer.spec.tsx
new file mode 100644
index 000000000..eea6261c3
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Footer.spec.tsx
@@ -0,0 +1,14 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import Footer from "./Footer";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ children: "children",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Footer.tsx b/src/core/client/admin/views/decisionHistory/components/Footer.tsx
new file mode 100644
index 000000000..7f4fff81c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Footer.tsx
@@ -0,0 +1,17 @@
+import React, { StatelessComponent } from "react";
+
+import { Flex } from "talk-ui/components";
+
+import styles from "./Footer.css";
+
+interface Props {
+ children: React.ReactNode;
+}
+
+const Footer: StatelessComponent = props => (
+
+ {props.children}
+
+);
+
+export default Footer;
diff --git a/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.css b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.css
new file mode 100644
index 000000000..519ed50fb
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.css
@@ -0,0 +1,9 @@
+.root {
+ font-size: calc(12rem / var(--rem-base));
+ font-weight: var(--font-weight-regular);
+ font-family: var(--font-family-sans-serif);
+ line-height: calc(14em / 12);
+ letter-spacing: calc(0.2em / 12);
+ color: var(--palette-primary-main);
+ text-decoration: none;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.spec.tsx b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.spec.tsx
new file mode 100644
index 000000000..31984f27f
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.spec.tsx
@@ -0,0 +1,16 @@
+import { shallow } from "enzyme";
+import { noop } from "lodash";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import GoToCommentLink from "./GoToCommentLink";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ href: "#",
+ onClick: noop,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx
new file mode 100644
index 000000000..96e3ec809
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/GoToCommentLink.tsx
@@ -0,0 +1,22 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import { Icon, TextLink } from "talk-ui/components";
+
+import styles from "./GoToCommentLink.css";
+
+interface Props {
+ href?: string;
+ onClick?: React.EventHandler;
+}
+
+const GoToCommentLink: StatelessComponent = props => (
+
+ {" "}
+ chevron_right
+
+);
+
+export default GoToCommentLink;
diff --git a/src/core/client/admin/views/decisionHistory/components/Info.css b/src/core/client/admin/views/decisionHistory/components/Info.css
new file mode 100644
index 000000000..daa3c0e8f
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Info.css
@@ -0,0 +1,13 @@
+.root {
+ font-size: calc(14rem / var(--rem-base));
+ line-height: calc(16em / 12);
+ /*
+ Fallback begin
+
+ Unfortunately Firefox / IE does not support
+ `word-break: break-word` yet.
+ */
+ word-break: break-all;
+ /* Fallback end */
+ word-break: break-word;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Info.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Info.spec.tsx
new file mode 100644
index 000000000..005ca6fa9
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Info.spec.tsx
@@ -0,0 +1,14 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import Info from "./Info";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ children: "children",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Info.tsx b/src/core/client/admin/views/decisionHistory/components/Info.tsx
new file mode 100644
index 000000000..aee38accf
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Info.tsx
@@ -0,0 +1,15 @@
+import React, { StatelessComponent } from "react";
+
+import { Typography } from "talk-ui/components";
+
+import styles from "./Info.css";
+
+interface Props {
+ children: React.ReactNode;
+}
+
+const Info: StatelessComponent = props => (
+ {props.children}
+);
+
+export default Info;
diff --git a/src/core/client/admin/views/decisionHistory/components/Main.css b/src/core/client/admin/views/decisionHistory/components/Main.css
new file mode 100644
index 000000000..062a053aa
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Main.css
@@ -0,0 +1,5 @@
+.root {
+ max-height: calc(26 * var(--spacing-unit));
+ overflow: hidden;
+ overflow-y: scroll;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Main.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Main.spec.tsx
new file mode 100644
index 000000000..edfbee57b
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Main.spec.tsx
@@ -0,0 +1,14 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import Main from "./Main";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ children: "children",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Main.tsx b/src/core/client/admin/views/decisionHistory/components/Main.tsx
new file mode 100644
index 000000000..568b32798
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Main.tsx
@@ -0,0 +1,9 @@
+import React, { StatelessComponent } from "react";
+
+import styles from "./Main.css";
+
+const Main: StatelessComponent = props => (
+ {props.children}
+);
+
+export default Main;
diff --git a/src/core/client/admin/views/decisionHistory/components/RejectedComment.spec.tsx b/src/core/client/admin/views/decisionHistory/components/RejectedComment.spec.tsx
new file mode 100644
index 000000000..2319c383d
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/RejectedComment.spec.tsx
@@ -0,0 +1,18 @@
+import { shallow } from "enzyme";
+import { noop } from "lodash";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import RejectedComment from "./RejectedComment";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ href: "#",
+ username: "InTheExpensiveSeats",
+ date: "2018-07-06T18:24:00.000Z",
+ onGotoComment: noop,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/RejectedComment.tsx b/src/core/client/admin/views/decisionHistory/components/RejectedComment.tsx
new file mode 100644
index 000000000..bbaddcd38
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/RejectedComment.tsx
@@ -0,0 +1,43 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import DecisionItem from "./DecisionItem";
+import DotDivider from "./DotDivider";
+import Footer from "./Footer";
+import GoToCommentLink from "./GoToCommentLink";
+import Info from "./Info";
+import RejectedIcon from "./RejectedIcon";
+import Timestamp from "./Timestamp";
+
+import { Typography } from "talk-ui/components";
+
+interface Props {
+ href: string;
+ username: string;
+ date: string;
+ onGotoComment?: React.EventHandler;
+}
+
+const Username: StatelessComponent<{ username: string }> = ({ username }) => (
+ {username}
+);
+
+const RejectedComment: StatelessComponent = props => (
+ }>
+ }
+ >
+ {"Rejected comment by "}
+
+
+
+);
+
+export default RejectedComment;
diff --git a/src/core/client/admin/views/decisionHistory/components/RejectedIcon.css b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.css
new file mode 100644
index 000000000..488befa65
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.css
@@ -0,0 +1,3 @@
+.root {
+ color: var(--palette-error-main);
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/RejectedIcon.spec.tsx b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.spec.tsx
new file mode 100644
index 000000000..238ee1ce0
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import RejectedIcon from "./RejectedIcon";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/RejectedIcon.tsx b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.tsx
new file mode 100644
index 000000000..3d0453986
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/RejectedIcon.tsx
@@ -0,0 +1,11 @@
+import React, { StatelessComponent } from "react";
+
+import { Icon } from "talk-ui/components";
+
+import styles from "./RejectedIcon.css";
+
+const RejectedIcon: StatelessComponent = () => (
+ cancel
+);
+
+export default RejectedIcon;
diff --git a/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.css b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.css
new file mode 100644
index 000000000..3ce579659
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.css
@@ -0,0 +1,16 @@
+.root {
+ text-transform: uppercase;
+ font-size: calc(12rem / var(--rem-base));
+ font-weight: var(--font-weight-medium);
+ font-family: var(--font-family-sans-serif);
+ line-height: calc(2 * var(--spacing-unit));
+ letter-spacing: calc(0.2em / 12);
+ color: var(--palette-grey-main);
+ width: 100%;
+ border-top: 1px solid var(--palette-divider);
+ text-align: center;
+
+ &:disabled {
+ color: var(--palette-grey-lighter);
+ }
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.spec.tsx b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.spec.tsx
new file mode 100644
index 000000000..e51b0fbb5
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.spec.tsx
@@ -0,0 +1,16 @@
+import { shallow } from "enzyme";
+import { noop } from "lodash";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import ShowMoreButton from "./ShowMoreButton";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ disabled: false,
+ onClick: noop,
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.tsx b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.tsx
new file mode 100644
index 000000000..ac587b235
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/ShowMoreButton.tsx
@@ -0,0 +1,25 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import { BaseButton } from "talk-ui/components";
+
+import styles from "./ShowMoreButton.css";
+
+interface Props {
+ disabled?: boolean;
+ onClick?: () => void;
+}
+
+const ShowMoreButton: StatelessComponent = props => (
+
+
+ Show More
+
+
+);
+
+export default ShowMoreButton;
diff --git a/src/core/client/admin/views/decisionHistory/components/Timestamp.css b/src/core/client/admin/views/decisionHistory/components/Timestamp.css
new file mode 100644
index 000000000..45c88b4cf
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Timestamp.css
@@ -0,0 +1,8 @@
+.root {
+ color: var(--palette-text-secondary);
+ font-family: var(--font-family-sans-serif);
+ font-weight: var(--font-weight-medium);
+ font-size: calc(12rem / var(--rem-base));
+ line-height: calc(14em / 12);
+ letter-spacing: 0;
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Timestamp.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Timestamp.spec.tsx
new file mode 100644
index 000000000..bc9c50d1f
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Timestamp.spec.tsx
@@ -0,0 +1,14 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import { PropTypesOf } from "talk-framework/types";
+
+import Timestamp from "./Timestamp";
+
+it("renders correctly", () => {
+ const props: PropTypesOf = {
+ children: "2018-07-06T18:24:00.000Z",
+ };
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Timestamp.tsx b/src/core/client/admin/views/decisionHistory/components/Timestamp.tsx
new file mode 100644
index 000000000..8e001775e
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Timestamp.tsx
@@ -0,0 +1,15 @@
+import React, { StatelessComponent } from "react";
+
+import { RelativeTime } from "talk-ui/components";
+
+import styles from "./Timestamp.css";
+
+interface Props {
+ children: string;
+}
+
+const DecisionHistory: StatelessComponent = props => (
+
+);
+
+export default DecisionHistory;
diff --git a/src/core/client/admin/views/decisionHistory/components/Title.css b/src/core/client/admin/views/decisionHistory/components/Title.css
new file mode 100644
index 000000000..60df8148b
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Title.css
@@ -0,0 +1,16 @@
+.root {
+ padding: 0 calc(0.5 * var(--spacing-unit));
+ background-color: #f2f2f2;
+ color: var(--palette-text-primary);
+ border-bottom: 1px solid var(--palette-grey-lighter);
+}
+
+.text {
+ text-transform: uppercase;
+ font-size: calc(12rem / var(--rem-base));
+ font-weight: var(--font-weight-medium);
+ font-family: var(--font-family-sans-serif);
+ line-height: calc(12em / 12);
+ letter-spacing: calc(0.2em / 12);
+ color: var(--palette-text-primary);
+}
diff --git a/src/core/client/admin/views/decisionHistory/components/Title.spec.tsx b/src/core/client/admin/views/decisionHistory/components/Title.spec.tsx
new file mode 100644
index 000000000..13a8a3350
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Title.spec.tsx
@@ -0,0 +1,9 @@
+import { shallow } from "enzyme";
+import React from "react";
+
+import Title from "./Title";
+
+it("renders correctly", () => {
+ const wrapper = shallow();
+ expect(wrapper).toMatchSnapshot();
+});
diff --git a/src/core/client/admin/views/decisionHistory/components/Title.tsx b/src/core/client/admin/views/decisionHistory/components/Title.tsx
new file mode 100644
index 000000000..9d2e252a3
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/Title.tsx
@@ -0,0 +1,19 @@
+import { Localized } from "fluent-react/compat";
+import React, { StatelessComponent } from "react";
+
+import { Flex, Icon, Typography } from "talk-ui/components";
+
+import styles from "./Title.css";
+
+const Title: StatelessComponent = () => (
+
+ history{" "}
+
+
+ Your Decision History
+
+
+
+);
+
+export default Title;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedComment.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedComment.spec.tsx.snap
new file mode 100644
index 000000000..73da65ade
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedComment.spec.tsx.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+}
+>
+
+ }
+ >
+
+ Accepted comment by <username></username>
+
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedIcon.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedIcon.spec.tsx.snap
new file mode 100644
index 000000000..2b5623b90
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/AcceptedIcon.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ check
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap
new file mode 100644
index 000000000..412f7ff66
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistory.spec.tsx.snap
@@ -0,0 +1,130 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders disable load more 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders empty state 1`] = `
+
+
+
+
+
+
+
+
+`;
+
+exports[`renders hasMore 1`] = `
+
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistoryLoading.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistoryLoading.spec.tsx.snap
new file mode 100644
index 000000000..4bc7efda0
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionHistoryLoading.spec.tsx.snap
@@ -0,0 +1,24 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+
+
+
+
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionItem.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionItem.spec.tsx.snap
new file mode 100644
index 000000000..20dd8d17d
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionItem.spec.tsx.snap
@@ -0,0 +1,18 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+ icon
+
+
+ children
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionList.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionList.spec.tsx.snap
new file mode 100644
index 000000000..57e7f4d20
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DecisionList.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/DotDivider.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DotDivider.spec.tsx.snap
new file mode 100644
index 000000000..8178ec67b
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/DotDivider.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ •
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Empty.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Empty.spec.tsx.snap
new file mode 100644
index 000000000..e15533cea
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Empty.spec.tsx.snap
@@ -0,0 +1,17 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+ You will see a list of your post moderation actions here.
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Footer.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Footer.spec.tsx.snap
new file mode 100644
index 000000000..5b74b302c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Footer.spec.tsx.snap
@@ -0,0 +1,10 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ children
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap
new file mode 100644
index 000000000..f91cab384
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/GoToCommentLink.spec.tsx.snap
@@ -0,0 +1,21 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+
+
+ chevron_right
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Info.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Info.spec.tsx.snap
new file mode 100644
index 000000000..339d7e695
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Info.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ children
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Main.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Main.spec.tsx.snap
new file mode 100644
index 000000000..c4fce236d
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Main.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ children
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedComment.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedComment.spec.tsx.snap
new file mode 100644
index 000000000..726c384a5
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedComment.spec.tsx.snap
@@ -0,0 +1,34 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+}
+>
+
+ }
+ >
+
+ Rejected comment by <username></username>
+
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedIcon.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedIcon.spec.tsx.snap
new file mode 100644
index 000000000..288505090
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/RejectedIcon.spec.tsx.snap
@@ -0,0 +1,9 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+ cancel
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/ShowMoreButton.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/ShowMoreButton.spec.tsx.snap
new file mode 100644
index 000000000..9e9480e48
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/ShowMoreButton.spec.tsx.snap
@@ -0,0 +1,15 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+ Show More
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Timestamp.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Timestamp.spec.tsx.snap
new file mode 100644
index 000000000..f0cdefdae
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Timestamp.spec.tsx.snap
@@ -0,0 +1,8 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/components/__snapshots__/Title.spec.tsx.snap b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Title.spec.tsx.snap
new file mode 100644
index 000000000..693dd705c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/components/__snapshots__/Title.spec.tsx.snap
@@ -0,0 +1,23 @@
+// Jest Snapshot v1, https://goo.gl/fbAQLP
+
+exports[`renders correctly 1`] = `
+
+
+ history
+
+
+
+
+ Your Decision History
+
+
+
+`;
diff --git a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx
new file mode 100644
index 000000000..f75d2db0c
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryContainer.tsx
@@ -0,0 +1,118 @@
+import React from "react";
+import { graphql, RelayPaginationProp } from "react-relay";
+
+import { DecisionHistoryContainer_me as MeData } from "talk-admin/__generated__/DecisionHistoryContainer_me.graphql";
+import { DecisionHistoryContainerPaginationQueryVariables } from "talk-admin/__generated__/DecisionHistoryContainerPaginationQuery.graphql";
+import { withPaginationContainer } from "talk-framework/lib/relay";
+
+import DecisionHistory from "../components/DecisionHistory";
+
+interface DecisionHistoryContainerProps {
+ me: MeData;
+ relay: RelayPaginationProp;
+}
+
+export class DecisionHistoryContainer extends React.Component<
+ DecisionHistoryContainerProps
+> {
+ public state = {
+ disableLoadMore: false,
+ };
+
+ public render() {
+ const actions = this.props.me.commentModerationActionHistory.edges.map(
+ edge => edge.node
+ );
+ return (
+
+ );
+ }
+
+ private loadMore = () => {
+ if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
+ return;
+ }
+ this.setState({ disableLoadMore: true });
+ this.props.relay.loadMore(
+ 10, // Fetch the next 10 feed items
+ error => {
+ this.setState({ disableLoadMore: false });
+ if (error) {
+ // tslint:disable-next-line:no-console
+ console.error(error);
+ }
+ }
+ );
+ };
+}
+
+// TODO: (cvle) This should be autogenerated.
+interface FragmentVariables {
+ count: number;
+ cursor?: string;
+}
+
+const enhanced = withPaginationContainer<
+ DecisionHistoryContainerProps,
+ DecisionHistoryContainerPaginationQueryVariables,
+ FragmentVariables
+>(
+ {
+ me: graphql`
+ fragment DecisionHistoryContainer_me on User
+ @argumentDefinitions(
+ count: { type: "Int!", defaultValue: 5 }
+ cursor: { type: "Cursor" }
+ ) {
+ commentModerationActionHistory(first: $count, after: $cursor)
+ @connection(key: "DecisionHistory_commentModerationActionHistory") {
+ edges {
+ node {
+ id
+ ...DecisionHistoryItemContainer_action
+ }
+ }
+ }
+ }
+ `,
+ },
+ {
+ direction: "forward",
+ getConnectionFromProps(props) {
+ return props.me && props.me.commentModerationActionHistory;
+ },
+ // This is also the default implementation of `getFragmentVariables` if it isn't provided.
+ getFragmentVariables(prevVars, totalCount) {
+ return {
+ ...prevVars,
+ count: totalCount,
+ };
+ },
+ getVariables(props, { count, cursor }, fragmentVariables) {
+ return {
+ count,
+ cursor,
+ };
+ },
+ query: graphql`
+ # Pagination query to be fetched upon calling 'loadMore'.
+ # Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
+ query DecisionHistoryContainerPaginationQuery(
+ $count: Int!
+ $cursor: Cursor
+ ) {
+ me {
+ ...DecisionHistoryContainer_me
+ @arguments(count: $count, cursor: $cursor)
+ }
+ }
+ `,
+ }
+)(DecisionHistoryContainer);
+
+export default enhanced;
diff --git a/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx
new file mode 100644
index 000000000..50d202efe
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/containers/DecisionHistoryItemContainer.tsx
@@ -0,0 +1,66 @@
+import React from "react";
+import { graphql } from "react-relay";
+
+import { DecisionHistoryItemContainer_action as ActionData } from "talk-admin/__generated__/DecisionHistoryItemContainer_action.graphql";
+import { withFragmentContainer } from "talk-framework/lib/relay";
+
+import AcceptedComment from "../components/AcceptedComment";
+import RejectedComment from "../components/RejectedComment";
+
+interface DecisionHistoryItemContainerProps {
+ action: ActionData;
+}
+
+class DecisionHistoryItemContainer extends React.Component<
+ DecisionHistoryItemContainerProps
+> {
+ private handleGoToComment = (e: React.MouseEvent) => {
+ return;
+ };
+ public render() {
+ const username =
+ (this.props.action.revision.comment.author &&
+ this.props.action.revision.comment.author.username) ||
+ "Unknown"; // TODO: (cvle) Figure out what to display, when username is not available.
+ if (this.props.action.status === "ACCEPTED") {
+ return (
+
+ );
+ } else if (this.props.action.status === "REJECTED") {
+ return (
+
+ );
+ }
+ return null;
+ }
+}
+
+const enhanced = withFragmentContainer({
+ action: graphql`
+ fragment DecisionHistoryItemContainer_action on CommentModerationAction {
+ id
+ revision {
+ id
+ comment {
+ author {
+ username
+ }
+ }
+ }
+ createdAt
+ status
+ }
+ `,
+})(DecisionHistoryItemContainer);
+
+export default enhanced;
diff --git a/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx b/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx
new file mode 100644
index 000000000..3826902a3
--- /dev/null
+++ b/src/core/client/admin/views/decisionHistory/queries/DecisionHistoryQuery.tsx
@@ -0,0 +1,36 @@
+import React, { Component } from "react";
+import { graphql, QueryRenderer } from "talk-framework/lib/relay";
+
+import { DecisionHistoryQuery as QueryTypes } from "talk-admin/__generated__/DecisionHistoryQuery.graphql";
+
+import DecisionHistoryLoading from "../components/DecisionHistoryLoading";
+import DecisionHistoryContainer from "../containers/DecisionHistoryContainer";
+
+class DecisionHistoryQuery extends Component {
+ public render() {
+ return (
+
+ query={graphql`
+ query DecisionHistoryQuery {
+ me {
+ ...DecisionHistoryContainer_me
+ }
+ }
+ `}
+ variables={{}}
+ render={({ error, props }) => {
+ if (error) {
+ return {error.message}
;
+ }
+ if (!props || !props.me) {
+ return ;
+ }
+
+ return ;
+ }}
+ />
+ );
+ }
+}
+
+export default DecisionHistoryQuery;
diff --git a/src/core/client/framework/testHelpers/getByTestID.ts b/src/core/client/framework/testHelpers/getByTestID.ts
new file mode 100644
index 000000000..653d0899f
--- /dev/null
+++ b/src/core/client/framework/testHelpers/getByTestID.ts
@@ -0,0 +1,5 @@
+import { ReactTestInstance } from "react-test-renderer";
+
+export default function getByTestID(id: string, instance: ReactTestInstance) {
+ return instance.findByProps({ "data-test": id });
+}
diff --git a/src/core/client/framework/testHelpers/getByText.ts b/src/core/client/framework/testHelpers/getByText.ts
new file mode 100644
index 000000000..60132154c
--- /dev/null
+++ b/src/core/client/framework/testHelpers/getByText.ts
@@ -0,0 +1,20 @@
+import React from "react";
+import { ReactTestInstance } from "react-test-renderer";
+
+export default function getByText(text: string, instance: ReactTestInstance) {
+ return instance.find(i => {
+ if (!i.props.children) {
+ return false;
+ }
+ const children = React.Children.toArray(i.props.children);
+ for (const c of children) {
+ if (
+ typeof c === "string" &&
+ c.toLowerCase().includes(text.toLowerCase())
+ ) {
+ return true;
+ }
+ }
+ return false;
+ });
+}
diff --git a/src/core/client/framework/testHelpers/index.ts b/src/core/client/framework/testHelpers/index.ts
index c9b807f90..0593be40f 100644
--- a/src/core/client/framework/testHelpers/index.ts
+++ b/src/core/client/framework/testHelpers/index.ts
@@ -13,3 +13,7 @@ export * from "./denormalize";
export { default as replaceHistoryLocation } from "./replaceHistoryLocation";
export { default as limitSnapshotTo } from "./limitSnapshotTo";
export { default as inputPredicate } from "./inputPredicate";
+export { default as getByTestID } from "./getByTestID";
+export { default as getByText } from "./getByText";
+export { default as wait } from "./wait";
+export { default as waitForElement } from "./waitForElement";
diff --git a/src/core/client/framework/testHelpers/wait.ts b/src/core/client/framework/testHelpers/wait.ts
new file mode 100644
index 000000000..29454b281
--- /dev/null
+++ b/src/core/client/framework/testHelpers/wait.ts
@@ -0,0 +1,13 @@
+import waitForExpect from "wait-for-expect";
+
+interface Options {
+ timeout?: number;
+ interval?: number;
+}
+
+export default async function wait(
+ callback: () => void,
+ { timeout = 4500, interval = 50 }: Options = {}
+) {
+ return waitForExpect(callback, timeout, interval);
+}
diff --git a/src/core/client/framework/testHelpers/waitForElement.ts b/src/core/client/framework/testHelpers/waitForElement.ts
new file mode 100644
index 000000000..38640be11
--- /dev/null
+++ b/src/core/client/framework/testHelpers/waitForElement.ts
@@ -0,0 +1,22 @@
+import { ReactTestInstance } from "react-test-renderer";
+
+import wait from "./wait";
+
+interface Options {
+ timeout?: number;
+ interval?: number;
+}
+
+export default async function waitForElement(
+ callback: () => ReactTestInstance | null,
+ options?: Options
+): Promise {
+ let result: ReactTestInstance | null = null;
+ await wait(() => {
+ result = callback();
+ if (!result) {
+ throw new Error();
+ }
+ }, options);
+ return result!;
+}
diff --git a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkPopover.css b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkPopover.css
index 3fad6d3be..85c883743 100644
--- a/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkPopover.css
+++ b/src/core/client/stream/tabs/comments/components/PermalinkButton/PermalinkPopover.css
@@ -1,4 +1,5 @@
.root {
+ padding: calc(0.5 * var(--spacing-unit));
}
.textField {
diff --git a/src/core/client/ui/components/Popover/Popover.css b/src/core/client/ui/components/Popover/Popover.css
index 3c7488ce6..ed8fe6c1a 100644
--- a/src/core/client/ui/components/Popover/Popover.css
+++ b/src/core/client/ui/components/Popover/Popover.css
@@ -7,7 +7,6 @@
box-sizing: border-box;
box-shadow: var(--elevation-main);
border-radius: var(--round-corners);
- padding: calc(0.5 * var(--spacing-unit));
}
.top {
diff --git a/src/core/client/ui/components/Popover/Popover.tsx b/src/core/client/ui/components/Popover/Popover.tsx
index 30b2f6a3b..111bfcf61 100644
--- a/src/core/client/ui/components/Popover/Popover.tsx
+++ b/src/core/client/ui/components/Popover/Popover.tsx
@@ -43,7 +43,6 @@ interface PopoverProps {
children: (props: ChildrenRenderProps) => React.ReactNode;
description: string;
id: string;
- onClose?: () => void;
className?: string;
placement?: Placement;
classes: typeof styles;
@@ -97,6 +96,7 @@ class Popover extends React.Component {
className,
placement,
classes,
+ ...rest
} = this.props;
const { visible } = this.state;
@@ -108,7 +108,7 @@ class Popover extends React.Component {
});
return (
-
+
{(props: PopperArrowProps) =>
diff --git a/src/core/client/ui/components/Spinner/Spinner.tsx b/src/core/client/ui/components/Spinner/Spinner.tsx
index 090980260..c9fbbd99b 100644
--- a/src/core/client/ui/components/Spinner/Spinner.tsx
+++ b/src/core/client/ui/components/Spinner/Spinner.tsx
@@ -3,6 +3,8 @@ import React, { StatelessComponent } from "react";
import { withStyles } from "talk-ui/hocs";
import styles from "./Spinner.css";
+type Size = "sm" | "md";
+
export interface SpinnerProps {
/**
* Convenient prop to override the root styling.
@@ -12,18 +14,32 @@ export interface SpinnerProps {
* Override or extend the styles applied to the component.
*/
classes: typeof styles;
+
+ size?: Size;
+}
+
+function calculateSize(size: Size): number {
+ switch (size) {
+ case "sm":
+ return 30;
+ case "md":
+ return 40;
+ default:
+ throw new Error(`Unknown ${size}`);
+ }
}
const Spinner: StatelessComponent = props => {
const { className, classes } = props;
const rootClassName = cn(classes.spinner, className);
+ const s = calculateSize(props.size!);
return (