mirror of
https://github.com/wassname/talk.git
synced 2026-08-04 13:23:35 +08:00
[next] Decision History (#2103)
* feat: decision history components * feat: implement decision history container * test: add unit tests * test: integration test * test: fix remaining snapshots * test: add more concepts from react-testing-library * fix: wrap long username into the next line * fix: fallback to word-break: break-all;
This commit is contained in:
Generated
+6
@@ -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",
|
||||
|
||||
@@ -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",
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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));
|
||||
}
|
||||
@@ -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 (
|
||||
<Popover
|
||||
data-test="decisionHistory-popover"
|
||||
id={popoverID}
|
||||
placement="bottom-end"
|
||||
description="A dialog showing a permalink to the comment"
|
||||
classes={{ popover: styles.popover }}
|
||||
body={({ toggleVisibility }) => (
|
||||
<ClickOutside
|
||||
onClickOutside={() =>
|
||||
this.toggleVisibilityOncePerFrame(toggleVisibility)
|
||||
}
|
||||
>
|
||||
<div>
|
||||
<DecisionHistoryQuery />
|
||||
</div>
|
||||
</ClickOutside>
|
||||
)}
|
||||
>
|
||||
{({ toggleVisibility, forwardRef, visible }) => (
|
||||
<BaseButton
|
||||
onClick={() => this.toggleVisibilityOncePerFrame(toggleVisibility)}
|
||||
aria-controls={popoverID}
|
||||
forwardRef={forwardRef}
|
||||
className={styles.historyIcon}
|
||||
data-test="decisionHistory-toggle"
|
||||
>
|
||||
<Icon size="lg">history</Icon>
|
||||
</BaseButton>
|
||||
)}
|
||||
</Popover>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DecisionHistoryButton;
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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 = () => (
|
||||
</Localized>
|
||||
</Flex>
|
||||
<Flex alignItems="center">
|
||||
<DecisionHistoryButton />
|
||||
<NavigationDivider />
|
||||
<SignOutButtonContainer id="navigation-signOutButton" />
|
||||
</Flex>
|
||||
</Flex>
|
||||
|
||||
@@ -0,0 +1,4 @@
|
||||
.root {
|
||||
height: 75%;
|
||||
border-right: 1px solid var(--palette-divider);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import NavigationDivider from "./NavigationDivider";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<NavigationDivider />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./NavigationDivider.css";
|
||||
|
||||
const NavigationDivider: StatelessComponent<{}> = () => (
|
||||
<div className={styles.root} />
|
||||
);
|
||||
|
||||
export default NavigationDivider;
|
||||
@@ -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);
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
.root {
|
||||
font-weight: var(--font-weight-bold);
|
||||
font-size: calc(18rem / var(--rem-base));
|
||||
letter-spacing: calc(-0.2em / 18);
|
||||
}
|
||||
@@ -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<React.MouseEvent>;
|
||||
@@ -10,7 +12,7 @@ interface Props {
|
||||
|
||||
const SignOutButton: StatelessComponent<Props> = props => (
|
||||
<Localized id="navigation-signOutButton">
|
||||
<Button id={props.id} onClick={props.onClick}>
|
||||
<Button id={props.id} onClick={props.onClick} className={styles.root}>
|
||||
Sign Out
|
||||
</Button>
|
||||
</Localized>
|
||||
|
||||
@@ -48,6 +48,8 @@ exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="center"
|
||||
>
|
||||
<DecisionHistoryButton />
|
||||
<NavigationDivider />
|
||||
<withContext(createMutationContainer(RedirectAppContainer))
|
||||
id="navigation-signOutButton"
|
||||
/>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="NavigationDivider-root"
|
||||
/>
|
||||
`;
|
||||
@@ -5,6 +5,7 @@ exports[`renders correctly 1`] = `
|
||||
id="navigation-signOutButton"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
className="SignOutButton-root"
|
||||
id="id"
|
||||
onClick={[Function]}
|
||||
>
|
||||
|
||||
+472
@@ -0,0 +1,472 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loads more 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Title-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
history
|
||||
</span>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Title-text"
|
||||
>
|
||||
Your Decision History
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="Main-root"
|
||||
>
|
||||
<ul
|
||||
className="DecisionList-root"
|
||||
>
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root AcceptedIcon-root Icon-sm"
|
||||
>
|
||||
check
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Info-root"
|
||||
>
|
||||
Accepted comment by
|
||||
<strong>
|
||||
dany
|
||||
</strong>
|
||||
</p>
|
||||
<div
|
||||
className="Flex-root Footer-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-timestamp Typography-colorTextPrimary"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-11-29T16:01:51.897Z"
|
||||
title="2018-11-29T16:01:51.897Z"
|
||||
>
|
||||
2018-11-29T16:01:51.897Z
|
||||
</time>
|
||||
</span>
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
<a
|
||||
className="TextLink-root GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root RejectedIcon-root Icon-sm"
|
||||
>
|
||||
cancel
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Info-root"
|
||||
>
|
||||
Rejected comment by
|
||||
<strong>
|
||||
dany
|
||||
</strong>
|
||||
</p>
|
||||
<div
|
||||
className="Flex-root Footer-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-timestamp Typography-colorTextPrimary"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-11-29T16:01:45.644Z"
|
||||
title="2018-11-29T16:01:45.644Z"
|
||||
>
|
||||
2018-11-29T16:01:45.644Z
|
||||
</time>
|
||||
</span>
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
<a
|
||||
className="TextLink-root GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root AcceptedIcon-root Icon-sm"
|
||||
>
|
||||
check
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Info-root"
|
||||
>
|
||||
Accepted comment by
|
||||
<strong>
|
||||
dany
|
||||
</strong>
|
||||
</p>
|
||||
<div
|
||||
className="Flex-root Footer-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-timestamp Typography-colorTextPrimary"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-11-29T16:01:42.060Z"
|
||||
title="2018-11-29T16:01:42.060Z"
|
||||
>
|
||||
2018-11-29T16:01:42.060Z
|
||||
</time>
|
||||
</span>
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
<a
|
||||
className="TextLink-root GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`opens popover when clicked on button showing loading state 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-loading-container"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Title-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
history
|
||||
</span>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Title-text"
|
||||
>
|
||||
Your Decision History
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="Main-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root DecisionHistoryLoading-container Flex-flex Flex-justifyCenter Flex-alignCenter"
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`render popover content 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Title-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
history
|
||||
</span>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Title-text"
|
||||
>
|
||||
Your Decision History
|
||||
</span>
|
||||
</div>
|
||||
<div
|
||||
className="Main-root"
|
||||
>
|
||||
<ul
|
||||
className="DecisionList-root"
|
||||
>
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root AcceptedIcon-root Icon-sm"
|
||||
>
|
||||
check
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Info-root"
|
||||
>
|
||||
Accepted comment by
|
||||
<strong>
|
||||
addy
|
||||
</strong>
|
||||
</p>
|
||||
<div
|
||||
className="Flex-root Footer-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-timestamp Typography-colorTextPrimary"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-11-29T16:01:51.897Z"
|
||||
title="2018-11-29T16:01:51.897Z"
|
||||
>
|
||||
2018-11-29T16:01:51.897Z
|
||||
</time>
|
||||
</span>
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
<a
|
||||
className="TextLink-root GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex"
|
||||
>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root RejectedIcon-root Icon-sm"
|
||||
>
|
||||
cancel
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<p
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary Info-root"
|
||||
>
|
||||
Rejected comment by
|
||||
<strong>
|
||||
addy
|
||||
</strong>
|
||||
</p>
|
||||
<div
|
||||
className="Flex-root Footer-root Flex-flex Flex-alignBaseline"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-timestamp Typography-colorTextPrimary"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-11-29T16:01:45.644Z"
|
||||
title="2018-11-29T16:01:45.644Z"
|
||||
>
|
||||
2018-11-29T16:01:45.644Z
|
||||
</time>
|
||||
</span>
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
<a
|
||||
className="TextLink-root GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
chevron_right
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</li>
|
||||
</ul>
|
||||
<button
|
||||
className="BaseButton-root ShowMoreButton-root"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Show More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders decision history popover button 1`] = `
|
||||
<div
|
||||
className="Popover-root"
|
||||
data-test="decisionHistory-popover"
|
||||
>
|
||||
<button
|
||||
aria-controls="decision-history-popover"
|
||||
className="BaseButton-root DecisionHistoryButton-historyIcon"
|
||||
data-test="decisionHistory-toggle"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-lg"
|
||||
>
|
||||
history
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="decision-history-popover-ariainfo"
|
||||
id="decision-history-popover"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
id="decision-history-popover-ariainfo"
|
||||
>
|
||||
A dialog showing a permalink to the comment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
@@ -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();
|
||||
});
|
||||
@@ -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",
|
||||
},
|
||||
];
|
||||
|
||||
@@ -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<typeof AcceptedComment> = {
|
||||
href: "#",
|
||||
username: "InTheExpensiveSeats",
|
||||
date: "2018-07-06T18:24:00.000Z",
|
||||
onGotoComment: noop,
|
||||
};
|
||||
const wrapper = shallow(<AcceptedComment {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const Username: StatelessComponent<{ username: string }> = ({ username }) => (
|
||||
<strong>{username}</strong>
|
||||
);
|
||||
|
||||
const ApprovedComment: StatelessComponent<Props> = props => (
|
||||
<DecisionItem icon={<AcceptedIcon />}>
|
||||
<Localized
|
||||
id="decisionHistory-acceptedCommentBy"
|
||||
username={<Username username={props.username} />}
|
||||
>
|
||||
<Info>{"Accepted comment by <username></username>"}</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<Typography variant="timestamp">
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
</Typography>
|
||||
<DotDivider />
|
||||
<GoToCommentLink href={props.href} onClick={props.onGotoComment} />
|
||||
</Footer>
|
||||
</DecisionItem>
|
||||
);
|
||||
|
||||
export default ApprovedComment;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
color: var(--palette-success-main);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import AcceptedIcon from "./AcceptedIcon";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<AcceptedIcon />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Icon } from "talk-ui/components";
|
||||
|
||||
import styles from "./AcceptedIcon.css";
|
||||
|
||||
const AcceptedIcon: StatelessComponent = () => (
|
||||
<Icon className={styles.root}>check</Icon>
|
||||
);
|
||||
|
||||
export default AcceptedIcon;
|
||||
@@ -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<typeof DecisionHistoryN> = {
|
||||
actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: false,
|
||||
disableLoadMore: false,
|
||||
};
|
||||
const wrapper = shallow(<DecisionHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders empty state", () => {
|
||||
const props: PropTypesOf<typeof DecisionHistoryN> = {
|
||||
actions: [],
|
||||
onLoadMore: noop,
|
||||
hasMore: false,
|
||||
disableLoadMore: false,
|
||||
};
|
||||
const wrapper = shallow(<DecisionHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders hasMore", () => {
|
||||
const props: PropTypesOf<typeof DecisionHistoryN> = {
|
||||
actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: true,
|
||||
disableLoadMore: false,
|
||||
};
|
||||
const wrapper = shallow(<DecisionHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders disable load more", () => {
|
||||
const props: PropTypesOf<typeof DecisionHistoryN> = {
|
||||
actions: [{ id: "1" }, { id: "2" }, { id: "3" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: true,
|
||||
disableLoadMore: true,
|
||||
};
|
||||
const wrapper = shallow(<DecisionHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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<typeof DecisionHistoryItemContainer>["action"]
|
||||
>;
|
||||
onLoadMore?: () => void;
|
||||
hasMore?: boolean;
|
||||
disableLoadMore?: boolean;
|
||||
}
|
||||
|
||||
const DecisionHistory: StatelessComponent<Props> = props => (
|
||||
<div data-test="decisionHistory-container">
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
{props.actions.length === 0 && <Empty />}
|
||||
{props.actions.map(action => (
|
||||
<DecisionHistoryItemContainer key={action.id} action={action} />
|
||||
))}
|
||||
</DecisionList>
|
||||
{props.hasMore && (
|
||||
<ShowMoreButton
|
||||
onClick={props.onLoadMore}
|
||||
disabled={props.disableLoadMore}
|
||||
/>
|
||||
)}
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default DecisionHistory;
|
||||
@@ -0,0 +1,4 @@
|
||||
.container {
|
||||
padding: var(--spacing-unit);
|
||||
min-height: 30px;
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import DecisionHistoryLoading from "./DecisionHistoryLoading";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<DecisionHistoryLoading />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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 = () => (
|
||||
<div data-test="decisionHistory-loading-container">
|
||||
<Title />
|
||||
<Main>
|
||||
<Flex
|
||||
justifyContent="center"
|
||||
alignItems="center"
|
||||
className={styles.container}
|
||||
>
|
||||
<Delay>
|
||||
<Spinner size="sm" />
|
||||
</Delay>
|
||||
</Flex>
|
||||
</Main>
|
||||
</div>
|
||||
);
|
||||
|
||||
export default DecisionHistoryLoading;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<typeof DecisionItem> = {
|
||||
icon: "icon",
|
||||
children: "children",
|
||||
};
|
||||
const wrapper = shallow(<DecisionItem {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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 => (
|
||||
<li className={styles.root}>
|
||||
<Flex>
|
||||
<div className={styles.leftCol}>{props.icon}</div>
|
||||
<div>{props.children}</div>
|
||||
</Flex>
|
||||
</li>
|
||||
);
|
||||
|
||||
export default DecisionItem;
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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<typeof DecisionList> = {
|
||||
children: "children",
|
||||
};
|
||||
const wrapper = shallow(<DecisionList {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./DecisionList.css";
|
||||
|
||||
const DecisionList: StatelessComponent = props => (
|
||||
<ul className={styles.root}>{props.children}</ul>
|
||||
);
|
||||
|
||||
export default DecisionList;
|
||||
@@ -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));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import DotDivider from "./DotDivider";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<DotDivider />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./DotDivider.css";
|
||||
|
||||
const Footer: StatelessComponent<{}> = () => (
|
||||
<div className={styles.root}>•</div>
|
||||
);
|
||||
|
||||
export default Footer;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
padding: calc(1.5 * var(--spacing-unit)) calc(2 * var(--spacing-unit));
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import Empty from "./Empty";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<Empty />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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<{}> = () => (
|
||||
<Flex justifyContent="center" alignItems="center" className={styles.root}>
|
||||
<Localized id="decisionHistory-youWillSeeAList">
|
||||
<Typography>
|
||||
You will see a list of your post moderation actions here.
|
||||
</Typography>
|
||||
</Localized>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default Empty;
|
||||
@@ -0,0 +1,2 @@
|
||||
.root {
|
||||
}
|
||||
@@ -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<typeof Footer> = {
|
||||
children: "children",
|
||||
};
|
||||
const wrapper = shallow(<Footer {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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 => (
|
||||
<Flex className={styles.root} alignItems="baseline">
|
||||
{props.children}
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default Footer;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<typeof GoToCommentLink> = {
|
||||
href: "#",
|
||||
onClick: noop,
|
||||
};
|
||||
const wrapper = shallow(<GoToCommentLink {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const GoToCommentLink: StatelessComponent<Props> = props => (
|
||||
<TextLink className={styles.root} onClick={props.onClick} href={props.href}>
|
||||
<Localized id="decisionHistory-goToComment">
|
||||
<span>Go to comment</span>
|
||||
</Localized>{" "}
|
||||
<Icon>chevron_right</Icon>
|
||||
</TextLink>
|
||||
);
|
||||
|
||||
export default GoToCommentLink;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<typeof Info> = {
|
||||
children: "children",
|
||||
};
|
||||
const wrapper = shallow(<Info {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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 => (
|
||||
<Typography className={styles.root}>{props.children}</Typography>
|
||||
);
|
||||
|
||||
export default Info;
|
||||
@@ -0,0 +1,5 @@
|
||||
.root {
|
||||
max-height: calc(26 * var(--spacing-unit));
|
||||
overflow: hidden;
|
||||
overflow-y: scroll;
|
||||
}
|
||||
@@ -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<typeof Main> = {
|
||||
children: "children",
|
||||
};
|
||||
const wrapper = shallow(<Main {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import styles from "./Main.css";
|
||||
|
||||
const Main: StatelessComponent = props => (
|
||||
<div className={styles.root}>{props.children}</div>
|
||||
);
|
||||
|
||||
export default Main;
|
||||
@@ -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<typeof RejectedComment> = {
|
||||
href: "#",
|
||||
username: "InTheExpensiveSeats",
|
||||
date: "2018-07-06T18:24:00.000Z",
|
||||
onGotoComment: noop,
|
||||
};
|
||||
const wrapper = shallow(<RejectedComment {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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<React.MouseEvent>;
|
||||
}
|
||||
|
||||
const Username: StatelessComponent<{ username: string }> = ({ username }) => (
|
||||
<strong>{username}</strong>
|
||||
);
|
||||
|
||||
const RejectedComment: StatelessComponent<Props> = props => (
|
||||
<DecisionItem icon={<RejectedIcon />}>
|
||||
<Localized
|
||||
id="decisionHistory-rejectedCommentBy"
|
||||
username={<Username username={props.username} />}
|
||||
>
|
||||
<Info>{"Rejected comment by <username></username>"}</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<Typography variant="timestamp">
|
||||
<Timestamp>{props.date}</Timestamp>
|
||||
</Typography>
|
||||
<DotDivider />
|
||||
<GoToCommentLink href={props.href} onClick={props.onGotoComment} />
|
||||
</Footer>
|
||||
</DecisionItem>
|
||||
);
|
||||
|
||||
export default RejectedComment;
|
||||
@@ -0,0 +1,3 @@
|
||||
.root {
|
||||
color: var(--palette-error-main);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import RejectedIcon from "./RejectedIcon";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<RejectedIcon />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,11 @@
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { Icon } from "talk-ui/components";
|
||||
|
||||
import styles from "./RejectedIcon.css";
|
||||
|
||||
const RejectedIcon: StatelessComponent = () => (
|
||||
<Icon className={styles.root}>cancel</Icon>
|
||||
);
|
||||
|
||||
export default RejectedIcon;
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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<typeof ShowMoreButton> = {
|
||||
disabled: false,
|
||||
onClick: noop,
|
||||
};
|
||||
const wrapper = shallow(<ShowMoreButton {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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> = props => (
|
||||
<Localized id="decisionHistory-showMoreButton">
|
||||
<BaseButton
|
||||
className={styles.root}
|
||||
onClick={props.onClick}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
Show More
|
||||
</BaseButton>
|
||||
</Localized>
|
||||
);
|
||||
|
||||
export default ShowMoreButton;
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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<typeof Timestamp> = {
|
||||
children: "2018-07-06T18:24:00.000Z",
|
||||
};
|
||||
const wrapper = shallow(<Timestamp {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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> = props => (
|
||||
<RelativeTime className={styles.root} date={props.children} />
|
||||
);
|
||||
|
||||
export default DecisionHistory;
|
||||
@@ -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);
|
||||
}
|
||||
@@ -0,0 +1,9 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import Title from "./Title";
|
||||
|
||||
it("renders correctly", () => {
|
||||
const wrapper = shallow(<Title />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -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 = () => (
|
||||
<Flex className={styles.root} alignItems="center">
|
||||
<Icon>history</Icon>{" "}
|
||||
<Localized id="decisionHistory-yourDecisionHistory">
|
||||
<Typography container="span" className={styles.text}>
|
||||
Your Decision History
|
||||
</Typography>
|
||||
</Localized>
|
||||
</Flex>
|
||||
);
|
||||
|
||||
export default Title;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<DecisionItem
|
||||
icon={<AcceptedIcon />}
|
||||
>
|
||||
<Localized
|
||||
id="decisionHistory-acceptedCommentBy"
|
||||
username={
|
||||
<Username
|
||||
username="InTheExpensiveSeats"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Info>
|
||||
Accepted comment by <username></username>
|
||||
</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="timestamp"
|
||||
>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
</withPropsOnChange(Typography)>
|
||||
<Footer />
|
||||
<GoToCommentLink
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Footer>
|
||||
</DecisionItem>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Icon)
|
||||
className="AcceptedIcon-root"
|
||||
>
|
||||
check
|
||||
</withPropsOnChange(Icon)>
|
||||
`;
|
||||
+130
@@ -0,0 +1,130 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "1",
|
||||
}
|
||||
}
|
||||
key="1"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "2",
|
||||
}
|
||||
}
|
||||
key="2"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "3",
|
||||
}
|
||||
}
|
||||
key="3"
|
||||
/>
|
||||
</DecisionList>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders disable load more 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "1",
|
||||
}
|
||||
}
|
||||
key="1"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "2",
|
||||
}
|
||||
}
|
||||
key="2"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "3",
|
||||
}
|
||||
}
|
||||
key="3"
|
||||
/>
|
||||
</DecisionList>
|
||||
<ShowMoreButton
|
||||
disabled={true}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders empty state 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
<Empty />
|
||||
</DecisionList>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders hasMore 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-container"
|
||||
>
|
||||
<Title />
|
||||
<Main>
|
||||
<DecisionList>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "1",
|
||||
}
|
||||
}
|
||||
key="1"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "2",
|
||||
}
|
||||
}
|
||||
key="2"
|
||||
/>
|
||||
<Relay(DecisionHistoryItemContainer)
|
||||
action={
|
||||
Object {
|
||||
"id": "3",
|
||||
}
|
||||
}
|
||||
key="3"
|
||||
/>
|
||||
</DecisionList>
|
||||
<ShowMoreButton
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
+24
@@ -0,0 +1,24 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
data-test="decisionHistory-loading-container"
|
||||
>
|
||||
<Title />
|
||||
<Main>
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="center"
|
||||
className="DecisionHistoryLoading-container"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Delay
|
||||
ms={500}
|
||||
>
|
||||
<withPropsOnChange(Spinner)
|
||||
size="sm"
|
||||
/>
|
||||
</Delay>
|
||||
</withPropsOnChange(Flex)>
|
||||
</Main>
|
||||
</div>
|
||||
`;
|
||||
+18
@@ -0,0 +1,18 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<li
|
||||
className="DecisionItem-root"
|
||||
>
|
||||
<withPropsOnChange(Flex)>
|
||||
<div
|
||||
className="DecisionItem-leftCol"
|
||||
>
|
||||
icon
|
||||
</div>
|
||||
<div>
|
||||
children
|
||||
</div>
|
||||
</withPropsOnChange(Flex)>
|
||||
</li>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ul
|
||||
className="DecisionList-root"
|
||||
>
|
||||
children
|
||||
</ul>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="DotDivider-root"
|
||||
>
|
||||
•
|
||||
</div>
|
||||
`;
|
||||
+17
@@ -0,0 +1,17 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="center"
|
||||
className="Empty-root"
|
||||
justifyContent="center"
|
||||
>
|
||||
<Localized
|
||||
id="decisionHistory-youWillSeeAList"
|
||||
>
|
||||
<withPropsOnChange(Typography)>
|
||||
You will see a list of your post moderation actions here.
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
</withPropsOnChange(Flex)>
|
||||
`;
|
||||
+10
@@ -0,0 +1,10 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="baseline"
|
||||
className="Footer-root"
|
||||
>
|
||||
children
|
||||
</withPropsOnChange(Flex)>
|
||||
`;
|
||||
+21
@@ -0,0 +1,21 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(TextLinkProps)
|
||||
className="GoToCommentLink-root"
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
>
|
||||
<Localized
|
||||
id="decisionHistory-goToComment"
|
||||
>
|
||||
<span>
|
||||
Go to comment
|
||||
</span>
|
||||
</Localized>
|
||||
|
||||
<withPropsOnChange(Icon)>
|
||||
chevron_right
|
||||
</withPropsOnChange(Icon)>
|
||||
</withPropsOnChange(TextLinkProps)>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Typography)
|
||||
className="Info-root"
|
||||
>
|
||||
children
|
||||
</withPropsOnChange(Typography)>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<div
|
||||
className="Main-root"
|
||||
>
|
||||
children
|
||||
</div>
|
||||
`;
|
||||
+34
@@ -0,0 +1,34 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<DecisionItem
|
||||
icon={<RejectedIcon />}
|
||||
>
|
||||
<Localized
|
||||
id="decisionHistory-rejectedCommentBy"
|
||||
username={
|
||||
<Username
|
||||
username="InTheExpensiveSeats"
|
||||
/>
|
||||
}
|
||||
>
|
||||
<Info>
|
||||
Rejected comment by <username></username>
|
||||
</Info>
|
||||
</Localized>
|
||||
<Footer>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="timestamp"
|
||||
>
|
||||
<DecisionHistory>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</DecisionHistory>
|
||||
</withPropsOnChange(Typography)>
|
||||
<Footer />
|
||||
<GoToCommentLink
|
||||
href="#"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
</Footer>
|
||||
</DecisionItem>
|
||||
`;
|
||||
+9
@@ -0,0 +1,9 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Icon)
|
||||
className="RejectedIcon-root"
|
||||
>
|
||||
cancel
|
||||
</withPropsOnChange(Icon)>
|
||||
`;
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Localized
|
||||
id="decisionHistory-showMoreButton"
|
||||
>
|
||||
<withPropsOnChange(WithMouseHover)
|
||||
className="ShowMoreButton-root"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
>
|
||||
Show More
|
||||
</withPropsOnChange(WithMouseHover)>
|
||||
</Localized>
|
||||
`;
|
||||
+8
@@ -0,0 +1,8 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(RelativeTime)
|
||||
className="Timestamp-root"
|
||||
date="2018-07-06T18:24:00.000Z"
|
||||
/>
|
||||
`;
|
||||
+23
@@ -0,0 +1,23 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="center"
|
||||
className="Title-root"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
history
|
||||
</withPropsOnChange(Icon)>
|
||||
|
||||
<Localized
|
||||
id="decisionHistory-yourDecisionHistory"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
className="Title-text"
|
||||
container="span"
|
||||
>
|
||||
Your Decision History
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
</withPropsOnChange(Flex)>
|
||||
`;
|
||||
@@ -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 (
|
||||
<DecisionHistory
|
||||
actions={actions}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
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;
|
||||
+66
@@ -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 (
|
||||
<AcceptedComment
|
||||
href="#"
|
||||
username={username}
|
||||
date={this.props.action.createdAt}
|
||||
onGotoComment={this.handleGoToComment}
|
||||
/>
|
||||
);
|
||||
} else if (this.props.action.status === "REJECTED") {
|
||||
return (
|
||||
<RejectedComment
|
||||
href="#"
|
||||
username={username}
|
||||
date={this.props.action.createdAt}
|
||||
onGotoComment={this.handleGoToComment}
|
||||
/>
|
||||
);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withFragmentContainer<DecisionHistoryItemContainerProps>({
|
||||
action: graphql`
|
||||
fragment DecisionHistoryItemContainer_action on CommentModerationAction {
|
||||
id
|
||||
revision {
|
||||
id
|
||||
comment {
|
||||
author {
|
||||
username
|
||||
}
|
||||
}
|
||||
}
|
||||
createdAt
|
||||
status
|
||||
}
|
||||
`,
|
||||
})(DecisionHistoryItemContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -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 (
|
||||
<QueryRenderer<QueryTypes>
|
||||
query={graphql`
|
||||
query DecisionHistoryQuery {
|
||||
me {
|
||||
...DecisionHistoryContainer_me
|
||||
}
|
||||
}
|
||||
`}
|
||||
variables={{}}
|
||||
render={({ error, props }) => {
|
||||
if (error) {
|
||||
return <div>{error.message}</div>;
|
||||
}
|
||||
if (!props || !props.me) {
|
||||
return <DecisionHistoryLoading />;
|
||||
}
|
||||
|
||||
return <DecisionHistoryContainer me={props.me} />;
|
||||
}}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export default DecisionHistoryQuery;
|
||||
@@ -0,0 +1,5 @@
|
||||
import { ReactTestInstance } from "react-test-renderer";
|
||||
|
||||
export default function getByTestID(id: string, instance: ReactTestInstance) {
|
||||
return instance.findByProps({ "data-test": id });
|
||||
}
|
||||
@@ -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;
|
||||
});
|
||||
}
|
||||
@@ -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";
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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<ReactTestInstance> {
|
||||
let result: ReactTestInstance | null = null;
|
||||
await wait(() => {
|
||||
result = callback();
|
||||
if (!result) {
|
||||
throw new Error();
|
||||
}
|
||||
}, options);
|
||||
return result!;
|
||||
}
|
||||
@@ -1,4 +1,5 @@
|
||||
.root {
|
||||
padding: calc(0.5 * var(--spacing-unit));
|
||||
}
|
||||
|
||||
.textField {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<PopoverProps> {
|
||||
className,
|
||||
placement,
|
||||
classes,
|
||||
...rest
|
||||
} = this.props;
|
||||
|
||||
const { visible } = this.state;
|
||||
@@ -108,7 +108,7 @@ class Popover extends React.Component<PopoverProps> {
|
||||
});
|
||||
|
||||
return (
|
||||
<div className={cn(classes.root, className)}>
|
||||
<div className={cn(classes.root, className)} {...rest}>
|
||||
<Manager>
|
||||
<Reference>
|
||||
{(props: PopperArrowProps) =>
|
||||
|
||||
@@ -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<SpinnerProps> = props => {
|
||||
const { className, classes } = props;
|
||||
|
||||
const rootClassName = cn(classes.spinner, className);
|
||||
const s = calculateSize(props.size!);
|
||||
|
||||
return (
|
||||
<svg
|
||||
className={rootClassName}
|
||||
width="40px"
|
||||
height="40px"
|
||||
width={`${s}px`}
|
||||
height={`${s}px`}
|
||||
viewBox="0 0 66 66"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
@@ -40,5 +56,9 @@ const Spinner: StatelessComponent<SpinnerProps> = props => {
|
||||
);
|
||||
};
|
||||
|
||||
Spinner.defaultProps = {
|
||||
size: "md",
|
||||
};
|
||||
|
||||
const enhanced = withStyles(styles)(Spinner);
|
||||
export default enhanced;
|
||||
|
||||
@@ -84,6 +84,9 @@ const variables = {
|
||||
lg: 1400,
|
||||
xl: 1600,
|
||||
},
|
||||
zindex: {
|
||||
popover: 300,
|
||||
},
|
||||
};
|
||||
|
||||
export default variables;
|
||||
|
||||
@@ -109,3 +109,13 @@ configure-auth-oidc-authorizationURL = Authorization URL
|
||||
configure-auth-oidc-tokenURL = Token URL
|
||||
configure-auth-oidc-jwksURI = JWKS URI
|
||||
configure-auth-oidc-useLoginOn = Use OpenID Connect login on
|
||||
|
||||
## Decision History
|
||||
decisionHistory-youWillSeeAList =
|
||||
You will see a list of your post moderation actions here.
|
||||
decisionHistory-showMoreButton =
|
||||
Show More
|
||||
decisionHistory-yourDecisionHistory = Your Decision History
|
||||
decisionHistory-rejectedCommentBy = Rejected comment by <username></username>
|
||||
decisionHistory-acceptedCommentBy = Accepted comment by <username></username>
|
||||
decisionHistory-goToComment = Go to comment
|
||||
|
||||
Reference in New Issue
Block a user