mirror of
https://github.com/wassname/talk.git
synced 2026-07-09 10:38:55 +08:00
[CORL-239, CORL-128] Support disabled commenting and closed stories in Stream (#2205)
* feat: closed story + disabled commenting * test: add feature test and fix bugs * fix: snapshot * fix: isClosed can't be null * fix: remove duplicate DeepPartial type * fix: border color
This commit is contained in:
@@ -1,2 +1,3 @@
|
||||
// TODO: (@cvle) Extract useful common types into its own package.
|
||||
export { Omit, Overwrite, PropTypesOf } from "talk-ui/types";
|
||||
export { DeepPartial } from "talk-common/types";
|
||||
|
||||
@@ -0,0 +1,67 @@
|
||||
import React from "react";
|
||||
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
|
||||
import { StoryClosedTimeoutContainer_story as StoryData } from "talk-stream/__generated__/StoryClosedTimeoutContainer_story.graphql";
|
||||
import {
|
||||
SetStoryClosedMutation,
|
||||
withSetStoryClosedMutation,
|
||||
} from "talk-stream/mutations";
|
||||
|
||||
interface Props {
|
||||
story: StoryData;
|
||||
setStoryClosed: SetStoryClosedMutation;
|
||||
}
|
||||
|
||||
function createTimeout(callback: () => void, closedAt: string) {
|
||||
const diff = new Date(closedAt).valueOf() - Date.now();
|
||||
if (diff > 0) {
|
||||
return setTimeout(callback, diff);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
class StoryClosedTimeoutContainer extends React.Component<Props> {
|
||||
private timer: any = null;
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
if (props.story.closedAt) {
|
||||
this.timer = createTimeout(this.handleClose, this.props.story.closedAt);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillReceiveProps(nextProps: Props) {
|
||||
if (nextProps.story.closedAt !== this.props.story.closedAt) {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = createTimeout(this.handleClose, nextProps.story.closedAt);
|
||||
}
|
||||
}
|
||||
|
||||
public componentWillUnmount() {
|
||||
clearTimeout(this.timer);
|
||||
this.timer = null;
|
||||
}
|
||||
|
||||
private handleClose = () => {
|
||||
this.timer = null;
|
||||
this.props.setStoryClosed({ storyID: this.props.story.id, isClosed: true });
|
||||
};
|
||||
|
||||
public render() {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetStoryClosedMutation(
|
||||
withFragmentContainer<Props>({
|
||||
story: graphql`
|
||||
fragment StoryClosedTimeoutContainer_story on Story {
|
||||
id
|
||||
closedAt
|
||||
}
|
||||
`,
|
||||
})(StoryClosedTimeoutContainer)
|
||||
);
|
||||
|
||||
export default enhanced;
|
||||
@@ -4,19 +4,27 @@ import { Environment } from "relay-runtime";
|
||||
import { createFetchContainer, fetchQuery } from "talk-framework/lib/relay";
|
||||
import { RefreshSettingsQuery as QueryTypes } from "talk-stream/__generated__/RefreshSettingsQuery.graphql";
|
||||
|
||||
export type RefreshSettingsVariables = QueryTypes["variables"];
|
||||
|
||||
const query = graphql`
|
||||
query RefreshSettingsQuery {
|
||||
query RefreshSettingsQuery($storyID: ID!) {
|
||||
settings {
|
||||
...StreamContainer_settings
|
||||
}
|
||||
# We also refrech story props that are
|
||||
# dependent on the settings.
|
||||
story(id: $storyID) {
|
||||
closedAt
|
||||
isClosed
|
||||
}
|
||||
}
|
||||
`;
|
||||
|
||||
function fetch(environment: Environment) {
|
||||
function fetch(environment: Environment, variables: RefreshSettingsVariables) {
|
||||
return fetchQuery<QueryTypes["response"]["settings"]>(
|
||||
environment,
|
||||
query,
|
||||
{},
|
||||
variables,
|
||||
{ force: true }
|
||||
);
|
||||
}
|
||||
@@ -26,6 +34,6 @@ export const withRefreshSettingsFetch = createFetchContainer(
|
||||
fetch
|
||||
);
|
||||
|
||||
export type RefreshSettingsFetch = () => Promise<
|
||||
QueryTypes["response"]["settings"]
|
||||
>;
|
||||
export type RefreshSettingsFetch = (
|
||||
variables: RefreshSettingsVariables
|
||||
) => Promise<QueryTypes["response"]["settings"]>;
|
||||
|
||||
@@ -0,0 +1,27 @@
|
||||
import { commitLocalUpdate, Environment } from "relay-runtime";
|
||||
|
||||
import { createMutationContainer } from "talk-framework/lib/relay";
|
||||
|
||||
export interface SetStoryClosedInput {
|
||||
storyID: string;
|
||||
isClosed: boolean;
|
||||
}
|
||||
|
||||
export type SetStoryClosedMutation = (
|
||||
input: SetStoryClosedInput
|
||||
) => Promise<void>;
|
||||
|
||||
export async function commit(
|
||||
environment: Environment,
|
||||
input: SetStoryClosedInput
|
||||
) {
|
||||
return commitLocalUpdate(environment, store => {
|
||||
const record = store.get(input.storyID)!;
|
||||
record.setValue(input.isClosed, "isClosed");
|
||||
});
|
||||
}
|
||||
|
||||
export const withSetStoryClosedMutation = createMutationContainer(
|
||||
"setStoryClosed",
|
||||
commit
|
||||
);
|
||||
@@ -50,3 +50,8 @@ export {
|
||||
withRemoveCommentReactionMutation,
|
||||
RemoveCommentReactionMutation,
|
||||
} from "./RemoveCommentReactionMutation";
|
||||
export {
|
||||
SetStoryClosedInput,
|
||||
withSetStoryClosedMutation,
|
||||
SetStoryClosedMutation,
|
||||
} from "./SetStoryClosedMutation";
|
||||
|
||||
@@ -7,6 +7,7 @@ interface Props {
|
||||
id?: string;
|
||||
onClick?: EventHandler<MouseEvent<HTMLButtonElement>>;
|
||||
active?: boolean;
|
||||
disabled?: boolean;
|
||||
}
|
||||
|
||||
const ReplyButton: StatelessComponent<Props> = props => (
|
||||
@@ -16,6 +17,7 @@ const ReplyButton: StatelessComponent<Props> = props => (
|
||||
variant="ghost"
|
||||
size="small"
|
||||
active={props.active}
|
||||
disabled={props.disabled}
|
||||
>
|
||||
<MatchMedia gtWidth="xs">
|
||||
<ButtonIcon>reply</ButtonIcon>
|
||||
|
||||
@@ -23,6 +23,8 @@ export interface PostCommentFormProps {
|
||||
initialValues?: FormProps;
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
disabled?: boolean;
|
||||
disabledMessage?: React.ReactNode;
|
||||
}
|
||||
|
||||
const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
@@ -64,26 +66,38 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
}
|
||||
value={input.value}
|
||||
placeholder="Post a comment"
|
||||
disabled={submitting}
|
||||
disabled={submitting || props.disabled}
|
||||
/>
|
||||
</Localized>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError && !meta.dirtySinceLastSubmit)) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
/>
|
||||
{props.disabled ? (
|
||||
<>
|
||||
{props.disabledMessage && (
|
||||
<ValidationMessage fullWidth>
|
||||
{props.disabledMessage}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError && !meta.dirtySinceLastSubmit)) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
<Flex direction="column" alignItems="flex-end">
|
||||
@@ -91,7 +105,7 @@ const PostCommentForm: StatelessComponent<PostCommentFormProps> = props => (
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled={submitting || !input.value}
|
||||
disabled={submitting || !input.value || props.disabled}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
.root {
|
||||
}
|
||||
|
||||
.sitewide {
|
||||
background-color: var(--palette-grey-dark);
|
||||
border-color: var(--palette-grey-dark);
|
||||
color: var(--palette-text-light);
|
||||
}
|
||||
@@ -0,0 +1,25 @@
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import PostCommentFormCollapsed from "./PostCommentFormCollapsed";
|
||||
|
||||
it("renders correctly when comments are closed sitewide", () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormCollapsed> = {
|
||||
closedSitewide: true,
|
||||
closedMessage: "closed site-wide",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<PostCommentFormCollapsed {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders correctly when story is closed", () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormCollapsed> = {
|
||||
closedSitewide: false,
|
||||
closedMessage: "closed story",
|
||||
};
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<PostCommentFormCollapsed {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,21 @@
|
||||
import cn from "classnames";
|
||||
import React, { StatelessComponent } from "react";
|
||||
|
||||
import { CallOut } from "talk-ui/components";
|
||||
|
||||
import styles from "./PostCommentFormCollapsed.css";
|
||||
|
||||
interface Props {
|
||||
closedSitewide?: boolean;
|
||||
closedMessage?: React.ReactNode;
|
||||
}
|
||||
const PostCommentFormCollapsed: StatelessComponent<Props> = props => (
|
||||
<CallOut
|
||||
fullWidth
|
||||
className={cn(styles.root, { [styles.sitewide]: props.closedSitewide })}
|
||||
>
|
||||
{props.closedMessage}
|
||||
</CallOut>
|
||||
);
|
||||
|
||||
export default PostCommentFormCollapsed;
|
||||
@@ -39,6 +39,8 @@ export interface ReplyCommentFormProps {
|
||||
parentUsername: string | null;
|
||||
min: number | null;
|
||||
max: number | null;
|
||||
disabled?: boolean;
|
||||
disabledMessage?: React.ReactNode;
|
||||
}
|
||||
|
||||
const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
|
||||
@@ -84,27 +86,40 @@ const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
|
||||
value={input.value}
|
||||
placeholder="Write a reply"
|
||||
forwardRef={props.rteRef}
|
||||
disabled={submitting}
|
||||
disabled={submitting || props.disabled}
|
||||
/>
|
||||
</Localized>
|
||||
</div>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError && !meta.dirtySinceLastSubmit)) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
/>
|
||||
{props.disabled ? (
|
||||
<>
|
||||
{props.disabledMessage && (
|
||||
<ValidationMessage fullWidth>
|
||||
{props.disabledMessage}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
</>
|
||||
) : (
|
||||
<>
|
||||
{meta.touched &&
|
||||
(meta.error ||
|
||||
(meta.submitError &&
|
||||
!meta.dirtySinceLastSubmit)) && (
|
||||
<ValidationMessage fullWidth>
|
||||
{meta.error || meta.submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{submitError && (
|
||||
<ValidationMessage fullWidth>
|
||||
{submitError}
|
||||
</ValidationMessage>
|
||||
)}
|
||||
{props.max && (
|
||||
<RemainingCharactersContainer
|
||||
value={input.value}
|
||||
max={props.max}
|
||||
/>
|
||||
)}
|
||||
</>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
|
||||
@@ -129,7 +144,9 @@ const ReplyCommentForm: StatelessComponent<ReplyCommentFormProps> = props => {
|
||||
<Button
|
||||
color="primary"
|
||||
variant="filled"
|
||||
disabled={submitting || !input.value}
|
||||
disabled={
|
||||
submitting || !input.value || props.disabled
|
||||
}
|
||||
type="submit"
|
||||
fullWidth={matches}
|
||||
>
|
||||
|
||||
@@ -10,7 +10,6 @@ import CommentContainer from "../containers/CommentContainer";
|
||||
import CommunityGuidelinesContainer from "../containers/CommunityGuidelinesContainer";
|
||||
import PostCommentFormContainer from "../containers/PostCommentFormContainer";
|
||||
import ReplyListContainer from "../containers/ReplyListContainer";
|
||||
import PostCommentFormFake from "./PostCommentFormFake";
|
||||
import SortMenu from "./SortMenu";
|
||||
|
||||
import styles from "./Stream.css";
|
||||
@@ -20,7 +19,8 @@ export interface StreamProps {
|
||||
id: string;
|
||||
isClosed?: boolean;
|
||||
} & PropTypesOf<typeof CommentContainer>["story"] &
|
||||
PropTypesOf<typeof ReplyListContainer>["story"];
|
||||
PropTypesOf<typeof ReplyListContainer>["story"] &
|
||||
PropTypesOf<typeof PostCommentFormContainer>["story"];
|
||||
settings: PropTypesOf<typeof CommentContainer>["settings"] &
|
||||
PropTypesOf<typeof ReplyListContainer>["settings"] &
|
||||
PropTypesOf<typeof UserBoxContainer>["settings"] &
|
||||
@@ -46,18 +46,9 @@ export interface StreamProps {
|
||||
const Stream: StatelessComponent<StreamProps> = props => {
|
||||
return (
|
||||
<HorizontalGutter className={styles.root} size="double">
|
||||
<HorizontalGutter>
|
||||
<UserBoxContainer me={props.me} settings={props.settings} />
|
||||
<CommunityGuidelinesContainer settings={props.settings} />
|
||||
{props.me ? (
|
||||
<PostCommentFormContainer
|
||||
storyID={props.story.id}
|
||||
settings={props.settings}
|
||||
/>
|
||||
) : (
|
||||
<PostCommentFormFake />
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
<UserBoxContainer me={props.me} settings={props.settings} />
|
||||
<CommunityGuidelinesContainer settings={props.settings} />
|
||||
<PostCommentFormContainer settings={props.settings} story={props.story} />
|
||||
{props.comments.length > 0 && (
|
||||
<SortMenu orderBy={props.orderBy} onChange={props.onChangeOrderBy} />
|
||||
)}
|
||||
|
||||
+19
@@ -0,0 +1,19 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly when comments are closed sitewide 1`] = `
|
||||
<withPropsOnChange(CallOut)
|
||||
className="PostCommentFormCollapsed-root PostCommentFormCollapsed-sitewide"
|
||||
fullWidth={true}
|
||||
>
|
||||
closed site-wide
|
||||
</withPropsOnChange(CallOut)>
|
||||
`;
|
||||
|
||||
exports[`renders correctly when story is closed 1`] = `
|
||||
<withPropsOnChange(CallOut)
|
||||
className="PostCommentFormCollapsed-root"
|
||||
fullWidth={true}
|
||||
>
|
||||
closed story
|
||||
</withPropsOnChange(CallOut)>
|
||||
`;
|
||||
+139
-97
@@ -5,30 +5,43 @@ exports[`renders correctly 1`] = `
|
||||
className="Stream-root"
|
||||
size="double"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
}
|
||||
/>
|
||||
<withContext(withContext(createMutationContainer(withContext(createFetchContainer(withContext(withLocalStateContainer(Relay(PostCommentFormContainer))))))))
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
@@ -144,30 +157,43 @@ exports[`when there is more disables load more button 1`] = `
|
||||
className="Stream-root"
|
||||
size="double"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
}
|
||||
/>
|
||||
<withContext(withContext(createMutationContainer(withContext(createFetchContainer(withContext(withLocalStateContainer(Relay(PostCommentFormContainer))))))))
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
@@ -297,30 +323,43 @@ exports[`when there is more renders a load more button 1`] = `
|
||||
className="Stream-root"
|
||||
size="double"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<PostCommentFormFake />
|
||||
</ForwardRef(forwardRef)>
|
||||
}
|
||||
/>
|
||||
<withContext(withContext(createMutationContainer(withContext(createFetchContainer(withContext(withLocalStateContainer(Relay(PostCommentFormContainer))))))))
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
@@ -450,40 +489,43 @@ exports[`when use is logged in renders correctly 1`] = `
|
||||
className="Stream-root"
|
||||
size="double"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={Object {}}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={Object {}}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Relay(CommunityGuidelinesContainerProps)
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
/>
|
||||
<withContext(withContext(createMutationContainer(withContext(createFetchContainer(Relay(PostCommentFormContainer))))))
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
<withContext(withContext(createMutationContainer(withContext(createFetchContainer(withContext(withLocalStateContainer(Relay(PostCommentFormContainer))))))))
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
storyID="story-id"
|
||||
/>
|
||||
</ForwardRef(forwardRef)>
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<SortMenu
|
||||
onChange={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
|
||||
@@ -1,209 +1,128 @@
|
||||
import { noop } from "lodash";
|
||||
import { merge, noop } from "lodash";
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { DeepPartial, PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import { CommentContainer } from "./CommentContainer";
|
||||
|
||||
// Remove relay refs so we can stub the props.
|
||||
const CommentContainerN = removeFragmentRefs(CommentContainer);
|
||||
|
||||
it("renders username and body", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: "Marvin",
|
||||
},
|
||||
parent: null,
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
settings: {
|
||||
reaction: {
|
||||
icon: "thumb_up_alt",
|
||||
label: "Respect",
|
||||
},
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
};
|
||||
type Props = PropTypesOf<typeof CommentContainerN>;
|
||||
|
||||
function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
return merge(
|
||||
{},
|
||||
{
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
isClosed: false,
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: "Marvin",
|
||||
},
|
||||
parent: null,
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
},
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
},
|
||||
add
|
||||
);
|
||||
}
|
||||
|
||||
it("renders username and body", () => {
|
||||
const props = createDefaultProps();
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders body only", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
},
|
||||
const props = createDefaultProps({
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: null,
|
||||
},
|
||||
parent: null,
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
settings: {
|
||||
reaction: {
|
||||
icon: "thumb_up_alt",
|
||||
label: "Respect",
|
||||
},
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
};
|
||||
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("hide reply button", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: "Marvin",
|
||||
},
|
||||
parent: null,
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
settings: {
|
||||
reaction: {
|
||||
icon: "thumb_up_alt",
|
||||
label: "Respect",
|
||||
},
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
localReply: false,
|
||||
const props = createDefaultProps({
|
||||
disableReplies: true,
|
||||
};
|
||||
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("shows conversation link", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
},
|
||||
settings: {
|
||||
reaction: {
|
||||
icon: "thumb_up",
|
||||
label: "Respect",
|
||||
labelActive: "Respected",
|
||||
},
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: "Marvin",
|
||||
},
|
||||
parent: null,
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
const props = createDefaultProps({
|
||||
showConversationLink: true,
|
||||
};
|
||||
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders in reply to", () => {
|
||||
const props: PropTypesOf<typeof CommentContainerN> = {
|
||||
me: null,
|
||||
story: {
|
||||
url: "http://localhost/story",
|
||||
},
|
||||
const props = createDefaultProps({
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
id: "author-id",
|
||||
username: "Marvin",
|
||||
},
|
||||
parent: {
|
||||
author: {
|
||||
username: "ParentAuthor",
|
||||
},
|
||||
},
|
||||
body: "Woof",
|
||||
createdAt: "1995-12-17T03:24:00.000Z",
|
||||
editing: {
|
||||
edited: false,
|
||||
editableUntil: "1995-12-17T03:24:30.000Z",
|
||||
},
|
||||
pending: false,
|
||||
},
|
||||
settings: {
|
||||
reaction: {
|
||||
icon: "thumb_up_alt",
|
||||
label: "Respect",
|
||||
},
|
||||
},
|
||||
indentLevel: 1,
|
||||
showAuthPopup: noop as any,
|
||||
setCommentID: noop as any,
|
||||
localReply: false,
|
||||
disableReplies: false,
|
||||
};
|
||||
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders disabled reply when story is closed", () => {
|
||||
const props = createDefaultProps({
|
||||
story: {
|
||||
isClosed: true,
|
||||
},
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders disabled reply when commenting has been disabled", () => {
|
||||
const props = createDefaultProps({
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
},
|
||||
},
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<CommentContainerN {...props} />);
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
|
||||
@@ -153,6 +153,7 @@ export class CommentContainer extends Component<Props, State> {
|
||||
<EditCommentFormContainer
|
||||
settings={settings}
|
||||
comment={comment}
|
||||
story={story}
|
||||
onClose={this.closeEditDialog}
|
||||
/>
|
||||
</div>
|
||||
@@ -199,6 +200,9 @@ export class CommentContainer extends Component<Props, State> {
|
||||
}`}
|
||||
onClick={this.openReplyDialog}
|
||||
active={showReplyDialog}
|
||||
disabled={
|
||||
settings.disableCommenting.enabled || story.isClosed
|
||||
}
|
||||
/>
|
||||
)}
|
||||
<PermalinkButtonContainer
|
||||
@@ -258,8 +262,10 @@ const enhanced = withSetCommentIDMutation(
|
||||
story: graphql`
|
||||
fragment CommentContainer_story on Story {
|
||||
url
|
||||
isClosed
|
||||
...ReplyCommentFormContainer_story
|
||||
...PermalinkButtonContainer_story
|
||||
...EditCommentFormContainer_story
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
@@ -289,6 +295,9 @@ const enhanced = withSetCommentIDMutation(
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment CommentContainer_settings on Settings {
|
||||
disableCommenting {
|
||||
enabled
|
||||
}
|
||||
...ReactionButtonContainer_settings
|
||||
...ReplyCommentFormContainer_settings
|
||||
...EditCommentFormContainer_settings
|
||||
|
||||
@@ -14,6 +14,7 @@ import {
|
||||
|
||||
import { EditCommentFormContainer_comment as CommentData } from "talk-stream/__generated__/EditCommentFormContainer_comment.graphql";
|
||||
import { EditCommentFormContainer_settings as SettingsData } from "talk-stream/__generated__/EditCommentFormContainer_settings.graphql";
|
||||
import { EditCommentFormContainer_story as StoryData } from "talk-stream/__generated__/EditCommentFormContainer_story.graphql";
|
||||
import {
|
||||
EditCommentMutation,
|
||||
withEditCommentMutation,
|
||||
@@ -28,6 +29,7 @@ interface Props {
|
||||
editComment: EditCommentMutation;
|
||||
comment: CommentData;
|
||||
settings: SettingsData;
|
||||
story: StoryData;
|
||||
onClose?: () => void;
|
||||
autofocus: boolean;
|
||||
refreshSettings: RefreshSettingsFetch;
|
||||
@@ -92,7 +94,7 @@ export class EditCommentFormContainer extends Component<Props, State> {
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
if (shouldTriggerSettingsRefresh(error.code)) {
|
||||
await this.props.refreshSettings();
|
||||
await this.props.refreshSettings({ storyID: this.props.story.id });
|
||||
}
|
||||
return error.invalidArgs;
|
||||
}
|
||||
@@ -149,6 +151,11 @@ const enhanced = withContext(({ sessionStorage, browserInfo }) => ({
|
||||
}
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
fragment EditCommentFormContainer_story on Story {
|
||||
id
|
||||
}
|
||||
`,
|
||||
settings: graphql`
|
||||
fragment EditCommentFormContainer_settings on Settings {
|
||||
charCount {
|
||||
|
||||
+116
-28
@@ -1,39 +1,53 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import { merge, noop } from "lodash";
|
||||
import React from "react";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { createPromisifiedStorage } from "talk-framework/lib/storage";
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { DeepPartial, PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import { PostCommentFormContainer } from "./PostCommentFormContainer";
|
||||
|
||||
const contextKey = "postCommentFormBody";
|
||||
const PostCommentFormContainerN = removeFragmentRefs(PostCommentFormContainer);
|
||||
|
||||
function createDefaultProps(): PropTypesOf<typeof PostCommentFormContainerN> {
|
||||
return {
|
||||
createComment: noop as any,
|
||||
refreshSettings: noop as any,
|
||||
storyID: "story-id",
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
settings: {
|
||||
charCount: {
|
||||
enabled: true,
|
||||
min: 3,
|
||||
max: 100,
|
||||
type Props = PropTypesOf<typeof PostCommentFormContainerN>;
|
||||
|
||||
function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
return merge(
|
||||
{},
|
||||
{
|
||||
local: {
|
||||
loggedIn: true,
|
||||
},
|
||||
createComment: noop as any,
|
||||
refreshSettings: noop as any,
|
||||
story: {
|
||||
id: "story-id",
|
||||
isClosed: false,
|
||||
},
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
settings: {
|
||||
charCount: {
|
||||
enabled: true,
|
||||
min: 3,
|
||||
max: 100,
|
||||
},
|
||||
closedMessage: "closed",
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
add
|
||||
);
|
||||
}
|
||||
|
||||
it("renders correctly", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
|
||||
const props = createDefaultProps();
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
@@ -41,9 +55,7 @@ it("renders correctly", async () => {
|
||||
});
|
||||
|
||||
it("renders with initialValues", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
const props = createDefaultProps();
|
||||
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
@@ -54,9 +66,7 @@ it("renders with initialValues", async () => {
|
||||
});
|
||||
|
||||
it("save values", async () => {
|
||||
const props: PropTypesOf<typeof PostCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
const props = createDefaultProps();
|
||||
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
@@ -81,11 +91,13 @@ it("creates a comment", async () => {
|
||||
.withArgs({})
|
||||
.once();
|
||||
|
||||
const props: PropTypesOf<typeof PostCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
const props = createDefaultProps({
|
||||
createComment: createCommentStub,
|
||||
storyID,
|
||||
};
|
||||
story: {
|
||||
id: storyID,
|
||||
isClosed: false,
|
||||
},
|
||||
});
|
||||
|
||||
await props.sessionStorage.setItem(contextKey, "Hello World!");
|
||||
|
||||
@@ -105,3 +117,79 @@ it("creates a comment", async () => {
|
||||
await timeout();
|
||||
formMock.verify();
|
||||
});
|
||||
|
||||
it("renders when story has been closed (collapsing)", async () => {
|
||||
const props = createDefaultProps({
|
||||
story: {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders when commenting has been disabled (collapsing)", async () => {
|
||||
const props = createDefaultProps({
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.update();
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders when story has been closed (non-collapsing)", async () => {
|
||||
const props = createDefaultProps({
|
||||
story: {
|
||||
isClosed: false,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
},
|
||||
});
|
||||
const nextProps = createDefaultProps({
|
||||
story: {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.setProps(nextProps);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders when commenting has been disabled (non-collapsing)", async () => {
|
||||
const props = createDefaultProps({
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
},
|
||||
});
|
||||
const nextProps = createDefaultProps({
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
},
|
||||
});
|
||||
const wrapper = shallow(<PostCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
wrapper.setProps(nextProps);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -2,10 +2,16 @@ import React, { Component } from "react";
|
||||
|
||||
import { withContext } from "talk-framework/lib/bootstrap";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import { graphql, withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import {
|
||||
graphql,
|
||||
withFragmentContainer,
|
||||
withLocalStateContainer,
|
||||
} from "talk-framework/lib/relay";
|
||||
import { PromisifiedStorage } from "talk-framework/lib/storage";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { PostCommentFormContainer_settings as SettingsData } from "talk-stream/__generated__/PostCommentFormContainer_settings.graphql";
|
||||
import { PostCommentFormContainer_story as StoryData } from "talk-stream/__generated__/PostCommentFormContainer_story.graphql";
|
||||
import { PostCommentFormContainerLocal as Local } from "talk-stream/__generated__/PostCommentFormContainerLocal.graphql";
|
||||
import {
|
||||
RefreshSettingsFetch,
|
||||
withRefreshSettingsFetch,
|
||||
@@ -18,25 +24,35 @@ import {
|
||||
import PostCommentForm, {
|
||||
PostCommentFormProps,
|
||||
} from "../components/PostCommentForm";
|
||||
import PostCommentFormCollapsed from "../components/PostCommentFormCollapsed";
|
||||
import PostCommentFormFake from "../components/PostCommentFormFake";
|
||||
import { shouldTriggerSettingsRefresh } from "../helpers";
|
||||
|
||||
interface Props {
|
||||
createComment: CreateCommentMutation;
|
||||
refreshSettings: RefreshSettingsFetch;
|
||||
storyID: string;
|
||||
sessionStorage: PromisifiedStorage;
|
||||
settings: SettingsData;
|
||||
local: Local;
|
||||
story: StoryData;
|
||||
}
|
||||
|
||||
interface State {
|
||||
initialValues?: PostCommentFormProps["initialValues"];
|
||||
initialized: boolean;
|
||||
keepFormWhenClosed: boolean;
|
||||
}
|
||||
|
||||
const contextKey = "postCommentFormBody";
|
||||
|
||||
export class PostCommentFormContainer extends Component<Props, State> {
|
||||
public state: State = { initialized: false };
|
||||
public state: State = {
|
||||
initialized: false,
|
||||
keepFormWhenClosed:
|
||||
this.props.local.loggedIn &&
|
||||
!this.props.story.isClosed &&
|
||||
!this.props.settings.disableCommenting.enabled,
|
||||
};
|
||||
|
||||
constructor(props: Props) {
|
||||
super(props);
|
||||
@@ -63,14 +79,14 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
) => {
|
||||
try {
|
||||
await this.props.createComment({
|
||||
storyID: this.props.storyID,
|
||||
storyID: this.props.story.id,
|
||||
...input,
|
||||
});
|
||||
form.reset({});
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
if (shouldTriggerSettingsRefresh(error.code)) {
|
||||
await this.props.refreshSettings();
|
||||
await this.props.refreshSettings({ storyID: this.props.story.id });
|
||||
}
|
||||
return error.invalidArgs;
|
||||
}
|
||||
@@ -96,6 +112,25 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
if (!this.state.initialized) {
|
||||
return null;
|
||||
}
|
||||
if (
|
||||
!this.state.keepFormWhenClosed &&
|
||||
(this.props.settings.disableCommenting.enabled ||
|
||||
this.props.story.isClosed)
|
||||
) {
|
||||
return (
|
||||
<PostCommentFormCollapsed
|
||||
closedSitewide={this.props.settings.disableCommenting.enabled}
|
||||
closedMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
if (!this.props.local.loggedIn) {
|
||||
return <PostCommentFormFake />;
|
||||
}
|
||||
return (
|
||||
<PostCommentForm
|
||||
onSubmit={this.handleOnSubmit}
|
||||
@@ -111,6 +146,15 @@ export class PostCommentFormContainer extends Component<Props, State> {
|
||||
this.props.settings.charCount.max) ||
|
||||
null
|
||||
}
|
||||
disabled={
|
||||
this.props.settings.disableCommenting.enabled ||
|
||||
this.props.story.isClosed
|
||||
}
|
||||
disabledMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -121,17 +165,36 @@ const enhanced = withContext(({ sessionStorage }) => ({
|
||||
}))(
|
||||
withCreateCommentMutation(
|
||||
withRefreshSettingsFetch(
|
||||
withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment PostCommentFormContainer_settings on Settings {
|
||||
charCount {
|
||||
enabled
|
||||
min
|
||||
max
|
||||
}
|
||||
withLocalStateContainer(
|
||||
graphql`
|
||||
fragment PostCommentFormContainerLocal on Local {
|
||||
loggedIn
|
||||
}
|
||||
`,
|
||||
})(PostCommentFormContainer)
|
||||
`
|
||||
)(
|
||||
withFragmentContainer<Props>({
|
||||
settings: graphql`
|
||||
fragment PostCommentFormContainer_settings on Settings {
|
||||
charCount {
|
||||
enabled
|
||||
min
|
||||
max
|
||||
}
|
||||
disableCommenting {
|
||||
enabled
|
||||
message
|
||||
}
|
||||
closedMessage
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
fragment PostCommentFormContainer_story on Story {
|
||||
id
|
||||
isClosed
|
||||
}
|
||||
`,
|
||||
})(PostCommentFormContainer)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
||||
+78
-43
@@ -1,5 +1,5 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import { merge, noop } from "lodash";
|
||||
import React from "react";
|
||||
import { createRenderer } from "react-test-renderer/shallow";
|
||||
import sinon from "sinon";
|
||||
@@ -7,7 +7,7 @@ import sinon from "sinon";
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { createPromisifiedStorage } from "talk-framework/lib/storage";
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { DeepPartial, PropTypesOf } from "talk-framework/types";
|
||||
import { ReplyCommentFormContainer } from "./ReplyCommentFormContainer";
|
||||
|
||||
const ReplyCommentFormContainerN = removeFragmentRefs(
|
||||
@@ -18,38 +18,48 @@ function getContextKey(commentID: string) {
|
||||
return `replyCommentFormBody-${commentID}`;
|
||||
}
|
||||
|
||||
function createDefaultProps(): PropTypesOf<typeof ReplyCommentFormContainerN> {
|
||||
return {
|
||||
createCommentReply: noop as any,
|
||||
refreshSettings: noop as any,
|
||||
story: {
|
||||
id: "story-id",
|
||||
},
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
username: "Joe",
|
||||
type Props = PropTypesOf<typeof ReplyCommentFormContainerN>;
|
||||
|
||||
function createDefaultProps(add: DeepPartial<Props> = {}): Props {
|
||||
return merge(
|
||||
{},
|
||||
{
|
||||
createCommentReply: noop as any,
|
||||
refreshSettings: noop as any,
|
||||
story: {
|
||||
id: "story-id",
|
||||
isClosed: false,
|
||||
},
|
||||
revision: {
|
||||
id: "revision-id",
|
||||
comment: {
|
||||
id: "comment-id",
|
||||
author: {
|
||||
username: "Joe",
|
||||
},
|
||||
revision: {
|
||||
id: "revision-id",
|
||||
},
|
||||
},
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
autofocus: false,
|
||||
settings: {
|
||||
charCount: {
|
||||
enabled: true,
|
||||
min: 3,
|
||||
max: 100,
|
||||
},
|
||||
closedMessage: "closed",
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "",
|
||||
},
|
||||
},
|
||||
},
|
||||
sessionStorage: createPromisifiedStorage(),
|
||||
autofocus: false,
|
||||
settings: {
|
||||
charCount: {
|
||||
enabled: true,
|
||||
min: 3,
|
||||
max: 100,
|
||||
},
|
||||
},
|
||||
};
|
||||
add
|
||||
);
|
||||
}
|
||||
|
||||
it("renders correctly", async () => {
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
const props = createDefaultProps();
|
||||
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<ReplyCommentFormContainerN {...props} />);
|
||||
@@ -58,9 +68,7 @@ it("renders correctly", async () => {
|
||||
});
|
||||
|
||||
it("renders with initialValues", async () => {
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
const props = createDefaultProps();
|
||||
|
||||
await props.sessionStorage.setItem(
|
||||
getContextKey(props.comment.id),
|
||||
@@ -74,9 +82,7 @@ it("renders with initialValues", async () => {
|
||||
});
|
||||
|
||||
it("save values", async () => {
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
};
|
||||
const props = createDefaultProps();
|
||||
|
||||
await props.sessionStorage.setItem(
|
||||
getContextKey(props.comment.id),
|
||||
@@ -102,11 +108,10 @@ it("creates a comment", async () => {
|
||||
const form = { reset: noop };
|
||||
const onCloseStub = sinon.stub();
|
||||
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
const props = createDefaultProps({
|
||||
onClose: onCloseStub,
|
||||
createCommentReply: createCommentStub,
|
||||
};
|
||||
});
|
||||
|
||||
await props.sessionStorage.setItem(
|
||||
getContextKey(props.comment.id),
|
||||
@@ -134,10 +139,9 @@ it("creates a comment", async () => {
|
||||
|
||||
it("closes on cancel", async () => {
|
||||
const onCloseStub = sinon.stub();
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
const props = createDefaultProps({
|
||||
onClose: onCloseStub,
|
||||
};
|
||||
});
|
||||
|
||||
await props.sessionStorage.setItem(
|
||||
getContextKey(props.comment.id),
|
||||
@@ -161,10 +165,10 @@ it("closes on cancel", async () => {
|
||||
it("autofocuses", async () => {
|
||||
const focusStub = sinon.stub();
|
||||
const rte = { focus: focusStub };
|
||||
const props: PropTypesOf<typeof ReplyCommentFormContainerN> = {
|
||||
...createDefaultProps(),
|
||||
|
||||
const props = createDefaultProps({
|
||||
autofocus: true,
|
||||
};
|
||||
});
|
||||
|
||||
const wrapper = shallow(<ReplyCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
@@ -175,3 +179,34 @@ it("autofocuses", async () => {
|
||||
.rteRef(rte);
|
||||
expect(focusStub.calledOnce).toBe(true);
|
||||
});
|
||||
|
||||
it("renders when story has been closed", async () => {
|
||||
const props = createDefaultProps({
|
||||
story: {
|
||||
isClosed: true,
|
||||
},
|
||||
settings: {
|
||||
closedMessage: "story closed",
|
||||
},
|
||||
});
|
||||
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<ReplyCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("renders when commenting has been disabled", async () => {
|
||||
const props = createDefaultProps({
|
||||
settings: {
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
},
|
||||
});
|
||||
const renderer = createRenderer();
|
||||
renderer.render(<ReplyCommentFormContainerN {...props} />);
|
||||
await timeout();
|
||||
expect(renderer.getRenderOutput()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
@@ -97,7 +97,7 @@ export class ReplyCommentFormContainer extends Component<Props, State> {
|
||||
} catch (error) {
|
||||
if (error instanceof InvalidRequestError) {
|
||||
if (shouldTriggerSettingsRefresh(error.code)) {
|
||||
await this.props.refreshSettings();
|
||||
await this.props.refreshSettings({ storyID: this.props.story.id });
|
||||
}
|
||||
return error.invalidArgs;
|
||||
}
|
||||
@@ -144,6 +144,15 @@ export class ReplyCommentFormContainer extends Component<Props, State> {
|
||||
this.props.settings.charCount.max) ||
|
||||
null
|
||||
}
|
||||
disabled={
|
||||
this.props.settings.disableCommenting.enabled ||
|
||||
this.props.story.isClosed
|
||||
}
|
||||
disabledMessage={
|
||||
(this.props.settings.disableCommenting.enabled &&
|
||||
this.props.settings.disableCommenting.message) ||
|
||||
this.props.settings.closedMessage
|
||||
}
|
||||
/>
|
||||
);
|
||||
}
|
||||
@@ -163,11 +172,17 @@ const enhanced = withContext(({ sessionStorage, browserInfo }) => ({
|
||||
min
|
||||
max
|
||||
}
|
||||
disableCommenting {
|
||||
enabled
|
||||
message
|
||||
}
|
||||
closedMessage
|
||||
}
|
||||
`,
|
||||
story: graphql`
|
||||
fragment ReplyCommentFormContainer_story on Story {
|
||||
id
|
||||
isClosed
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
|
||||
@@ -10,6 +10,7 @@ import {
|
||||
COMMENT_SORT,
|
||||
StreamContainerPaginationQueryVariables,
|
||||
} from "talk-stream/__generated__/StreamContainerPaginationQuery.graphql";
|
||||
import StoryClosedTimeoutContainer from "talk-stream/containers/StoryClosedTimeoutContainer";
|
||||
|
||||
import Stream from "../components/Stream";
|
||||
|
||||
@@ -59,18 +60,21 @@ export class StreamContainer extends React.Component<Props> {
|
||||
public render() {
|
||||
const comments = this.props.story.comments.edges.map(edge => edge.node);
|
||||
return (
|
||||
<Stream
|
||||
story={this.props.story}
|
||||
comments={comments}
|
||||
settings={this.props.settings}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
me={this.props.me}
|
||||
orderBy={this.orderBy}
|
||||
onChangeOrderBy={this.handleOnChangeOrderBy}
|
||||
refetching={this.state.refetching}
|
||||
/>
|
||||
<>
|
||||
<StoryClosedTimeoutContainer story={this.props.story} />
|
||||
<Stream
|
||||
story={this.props.story}
|
||||
comments={comments}
|
||||
settings={this.props.settings}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
me={this.props.me}
|
||||
orderBy={this.orderBy}
|
||||
onChangeOrderBy={this.handleOnChangeOrderBy}
|
||||
refetching={this.state.refetching}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
}
|
||||
|
||||
@@ -122,8 +126,10 @@ const enhanced = withPaginationContainer<
|
||||
}
|
||||
}
|
||||
}
|
||||
...PostCommentFormContainer_story
|
||||
...CommentContainer_story
|
||||
...ReplyListContainer1_story
|
||||
...StoryClosedTimeoutContainer_story
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
|
||||
+203
-16
@@ -19,6 +19,7 @@ exports[`hide reply button 1`] = `
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
@@ -44,9 +45,8 @@ exports[`hide reply button 1`] = `
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -103,6 +103,7 @@ exports[`renders body only 1`] = `
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
@@ -110,6 +111,7 @@ exports[`renders body only 1`] = `
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
@@ -135,9 +137,8 @@ exports[`renders body only 1`] = `
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -177,6 +178,190 @@ exports[`renders body only 1`] = `
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders disabled reply when commenting has been disabled 1`] = `
|
||||
<div
|
||||
data-testid="comment-comment-id"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<IndentedComment
|
||||
blur={false}
|
||||
body="Woof"
|
||||
createdAt="1995-12-17T03:24:00.000Z"
|
||||
footer={
|
||||
<React.Fragment>
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={true}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
|
||||
comment={
|
||||
Object {
|
||||
"author": Object {
|
||||
"id": "author-id",
|
||||
"username": "Marvin",
|
||||
},
|
||||
"body": "Woof",
|
||||
"createdAt": "1995-12-17T03:24:00.000Z",
|
||||
"editing": Object {
|
||||
"editableUntil": "1995-12-17T03:24:30.000Z",
|
||||
"edited": false,
|
||||
},
|
||||
"id": "comment-id",
|
||||
"parent": null,
|
||||
"pending": false,
|
||||
}
|
||||
}
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": true,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ButtonsBar>
|
||||
<ButtonsBar>
|
||||
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
|
||||
comment={
|
||||
Object {
|
||||
"author": Object {
|
||||
"id": "author-id",
|
||||
"username": "Marvin",
|
||||
},
|
||||
"body": "Woof",
|
||||
"createdAt": "1995-12-17T03:24:00.000Z",
|
||||
"editing": Object {
|
||||
"editableUntil": "1995-12-17T03:24:30.000Z",
|
||||
"edited": false,
|
||||
},
|
||||
"id": "comment-id",
|
||||
"parent": null,
|
||||
"pending": false,
|
||||
}
|
||||
}
|
||||
me={null}
|
||||
/>
|
||||
</ButtonsBar>
|
||||
</ForwardRef(forwardRef)>
|
||||
</React.Fragment>
|
||||
}
|
||||
indentLevel={1}
|
||||
parentAuthorName={null}
|
||||
showEditedMarker={false}
|
||||
username="Marvin"
|
||||
/>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders disabled reply when story is closed 1`] = `
|
||||
<div
|
||||
data-testid="comment-comment-id"
|
||||
>
|
||||
<ForwardRef(forwardRef)>
|
||||
<IndentedComment
|
||||
blur={false}
|
||||
body="Woof"
|
||||
createdAt="1995-12-17T03:24:00.000Z"
|
||||
footer={
|
||||
<React.Fragment>
|
||||
<ForwardRef(forwardRef)
|
||||
justifyContent="space-between"
|
||||
>
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={true}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
<Relay(PermalinkButtonContainerProps)
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": true,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(Relay(ReactionButtonContainer)))))))
|
||||
comment={
|
||||
Object {
|
||||
"author": Object {
|
||||
"id": "author-id",
|
||||
"username": "Marvin",
|
||||
},
|
||||
"body": "Woof",
|
||||
"createdAt": "1995-12-17T03:24:00.000Z",
|
||||
"editing": Object {
|
||||
"editableUntil": "1995-12-17T03:24:30.000Z",
|
||||
"edited": false,
|
||||
},
|
||||
"id": "comment-id",
|
||||
"parent": null,
|
||||
"pending": false,
|
||||
}
|
||||
}
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
/>
|
||||
</ButtonsBar>
|
||||
<ButtonsBar>
|
||||
<withContext(createMutationContainer(Relay(ReportButtonContainer)))
|
||||
comment={
|
||||
Object {
|
||||
"author": Object {
|
||||
"id": "author-id",
|
||||
"username": "Marvin",
|
||||
},
|
||||
"body": "Woof",
|
||||
"createdAt": "1995-12-17T03:24:00.000Z",
|
||||
"editing": Object {
|
||||
"editableUntil": "1995-12-17T03:24:30.000Z",
|
||||
"edited": false,
|
||||
},
|
||||
"id": "comment-id",
|
||||
"parent": null,
|
||||
"pending": false,
|
||||
}
|
||||
}
|
||||
me={null}
|
||||
/>
|
||||
</ButtonsBar>
|
||||
</ForwardRef(forwardRef)>
|
||||
</React.Fragment>
|
||||
}
|
||||
indentLevel={1}
|
||||
parentAuthorName={null}
|
||||
showEditedMarker={false}
|
||||
username="Marvin"
|
||||
/>
|
||||
</ForwardRef(forwardRef)>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders in reply to 1`] = `
|
||||
<div
|
||||
data-testid="comment-comment-id"
|
||||
@@ -194,6 +379,7 @@ exports[`renders in reply to 1`] = `
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
@@ -201,6 +387,7 @@ exports[`renders in reply to 1`] = `
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
@@ -230,9 +417,8 @@ exports[`renders in reply to 1`] = `
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -293,6 +479,7 @@ exports[`renders username and body 1`] = `
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
@@ -300,6 +487,7 @@ exports[`renders username and body 1`] = `
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
@@ -325,9 +513,8 @@ exports[`renders username and body 1`] = `
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
@@ -384,6 +571,7 @@ exports[`shows conversation link 1`] = `
|
||||
<ButtonsBar>
|
||||
<ReplyButton
|
||||
active={false}
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-id"
|
||||
onClick={[Function]}
|
||||
/>
|
||||
@@ -391,6 +579,7 @@ exports[`shows conversation link 1`] = `
|
||||
commentID="comment-id"
|
||||
story={
|
||||
Object {
|
||||
"isClosed": false,
|
||||
"url": "http://localhost/story",
|
||||
}
|
||||
}
|
||||
@@ -416,10 +605,8 @@ exports[`shows conversation link 1`] = `
|
||||
me={null}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up",
|
||||
"label": "Respect",
|
||||
"labelActive": "Respected",
|
||||
"disableCommenting": Object {
|
||||
"enabled": false,
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
+40
@@ -2,6 +2,44 @@
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<PostCommentForm
|
||||
disabled={false}
|
||||
disabledMessage="closed"
|
||||
max={100}
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when commenting has been disabled (collapsing) 1`] = `
|
||||
<PostCommentFormCollapsed
|
||||
closedMessage="commenting disabled"
|
||||
closedSitewide={true}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when commenting has been disabled (non-collapsing) 1`] = `
|
||||
<PostCommentForm
|
||||
disabled={true}
|
||||
disabledMessage="commenting disabled"
|
||||
max={100}
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when story has been closed (collapsing) 1`] = `
|
||||
<PostCommentFormCollapsed
|
||||
closedMessage="story closed"
|
||||
closedSitewide={false}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when story has been closed (non-collapsing) 1`] = `
|
||||
<PostCommentForm
|
||||
disabled={true}
|
||||
disabledMessage="story closed"
|
||||
max={100}
|
||||
min={3}
|
||||
onChange={[Function]}
|
||||
@@ -11,6 +49,8 @@ exports[`renders correctly 1`] = `
|
||||
|
||||
exports[`renders with initialValues 1`] = `
|
||||
<PostCommentForm
|
||||
disabled={false}
|
||||
disabledMessage="closed"
|
||||
initialValues={
|
||||
Object {
|
||||
"body": "Hello World!",
|
||||
|
||||
+34
@@ -2,6 +2,38 @@
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<ReplyCommentForm
|
||||
disabled={false}
|
||||
disabledMessage="closed"
|
||||
id="comment-id"
|
||||
max={100}
|
||||
min={3}
|
||||
onCancel={[Function]}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
parentUsername="Joe"
|
||||
rteRef={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when commenting has been disabled 1`] = `
|
||||
<ReplyCommentForm
|
||||
disabled={true}
|
||||
disabledMessage="commenting disabled"
|
||||
id="comment-id"
|
||||
max={100}
|
||||
min={3}
|
||||
onCancel={[Function]}
|
||||
onChange={[Function]}
|
||||
onSubmit={[Function]}
|
||||
parentUsername="Joe"
|
||||
rteRef={[Function]}
|
||||
/>
|
||||
`;
|
||||
|
||||
exports[`renders when story has been closed 1`] = `
|
||||
<ReplyCommentForm
|
||||
disabled={true}
|
||||
disabledMessage="story closed"
|
||||
id="comment-id"
|
||||
max={100}
|
||||
min={3}
|
||||
@@ -15,6 +47,8 @@ exports[`renders correctly 1`] = `
|
||||
|
||||
exports[`renders with initialValues 1`] = `
|
||||
<ReplyCommentForm
|
||||
disabled={false}
|
||||
disabledMessage="closed"
|
||||
id="comment-id"
|
||||
initialValues={
|
||||
Object {
|
||||
|
||||
+259
-163
@@ -1,200 +1,296 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
<Fragment>
|
||||
<withContext(createMutationContainer(Relay(StoryClosedTimeoutContainer)))
|
||||
story={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`when has more comments renders hasMore 1`] = `
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
<Fragment>
|
||||
<withContext(createMutationContainer(Relay(StoryClosedTimeoutContainer)))
|
||||
story={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`when has more comments when loading more disables load more button 1`] = `
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
<Fragment>
|
||||
<withContext(createMutationContainer(Relay(StoryClosedTimeoutContainer)))
|
||||
story={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={true}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={true}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
exports[`when has more comments when loading more enable load more button after loading is done 1`] = `
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
<Fragment>
|
||||
<withContext(createMutationContainer(Relay(StoryClosedTimeoutContainer)))
|
||||
story={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
/>
|
||||
<Stream
|
||||
comments={
|
||||
Array [
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
]
|
||||
}
|
||||
disableLoadMore={false}
|
||||
hasMore={true}
|
||||
me={null}
|
||||
onChangeOrderBy={[Function]}
|
||||
onLoadMore={[Function]}
|
||||
orderBy="CREATED_AT_ASC"
|
||||
refetching={false}
|
||||
settings={
|
||||
Object {
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
"reaction": Object {
|
||||
"icon": "thumb_up_alt",
|
||||
"label": "Respect",
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
story={
|
||||
Object {
|
||||
"comments": Object {
|
||||
"edges": Array [
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-1",
|
||||
},
|
||||
},
|
||||
},
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
Object {
|
||||
"node": Object {
|
||||
"id": "comment-2",
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
],
|
||||
},
|
||||
"id": "story-id",
|
||||
"isClosed": false,
|
||||
}
|
||||
}
|
||||
}
|
||||
/>
|
||||
/>
|
||||
</Fragment>
|
||||
`;
|
||||
|
||||
@@ -3,6 +3,8 @@ import { ERROR_CODES } from "talk-common/errors";
|
||||
const triggers = [
|
||||
ERROR_CODES.COMMENT_BODY_TOO_SHORT,
|
||||
ERROR_CODES.COMMENT_BODY_EXCEEDS_MAX_LENGTH,
|
||||
ERROR_CODES.COMMENTING_DISABLED,
|
||||
ERROR_CODES.STORY_CLOSED,
|
||||
];
|
||||
/**
|
||||
* shouldTriggerSettingsRefresh will indicate whether the settings
|
||||
|
||||
@@ -74,6 +74,7 @@ exports[`cancel edit 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -752,6 +753,7 @@ exports[`edit a comment: render comment with edit button 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -941,6 +943,7 @@ exports[`edit a comment: server response 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -1121,6 +1124,7 @@ exports[`shows expiry message: edit form closed 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -70,6 +70,7 @@ exports[`renders comment stream with load more button 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -219,6 +220,7 @@ exports[`renders comment stream with load more button 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -157,6 +157,7 @@ exports[`renders permalink view 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -323,6 +324,7 @@ exports[`renders permalink view 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -487,6 +489,7 @@ exports[`renders permalink view 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-replies"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -647,6 +650,7 @@ exports[`renders permalink view 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -796,6 +800,7 @@ exports[`renders permalink view 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-4"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
+4
@@ -180,6 +180,7 @@ exports[`renders conversation thread 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -359,6 +360,7 @@ exports[`shows more of this conversation 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -525,6 +527,7 @@ exports[`shows more of this conversation 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -689,6 +692,7 @@ exports[`shows more of this conversation 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -74,6 +74,7 @@ exports[`post a comment: optimistic response 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-uuid-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -60,6 +60,7 @@ exports[`post a reply: open reply form 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost Button-active"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -579,6 +580,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -751,6 +753,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -923,6 +926,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -1095,6 +1099,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -1267,6 +1272,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-4"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -1439,6 +1445,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -1,323 +1,5 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`post a reply and handle server error: open reply form 1`] = `
|
||||
<div
|
||||
data-testid="comment-comment-0"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="Indent-root"
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className="Comment-root"
|
||||
role="article"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween Flex-directionRow"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-alignBaseline Flex-directionColumn"
|
||||
>
|
||||
<span
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary Username-root"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignBaseline Flex-directionRow"
|
||||
>
|
||||
<time
|
||||
className="Timestamp-root RelativeTime-root"
|
||||
dateTime="2018-07-06T18:24:00.000Z"
|
||||
title="2018-07-06T18:24:00.000Z"
|
||||
>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</time>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Joining Too",
|
||||
}
|
||||
}
|
||||
/>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-justifySpaceBetween"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost Button-active"
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Reply
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<button
|
||||
aria-controls="permalink-popover-comment-0"
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Share
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="permalink-popover-comment-0-ariainfo"
|
||||
id="permalink-popover-comment-0"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
id="permalink-popover-comment-0-ariainfo"
|
||||
>
|
||||
A dialog showing a permalink to the comment
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Respect
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-directionRow"
|
||||
>
|
||||
<div
|
||||
className="Popover-root"
|
||||
>
|
||||
<button
|
||||
aria-controls="report-popover-comment-0"
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
<span>
|
||||
Report
|
||||
</span>
|
||||
</button>
|
||||
<div
|
||||
aria-hidden={true}
|
||||
aria-labelledby="report-popover-comment-0-ariainfo"
|
||||
id="report-popover-comment-0"
|
||||
role="popup"
|
||||
>
|
||||
<div
|
||||
className="AriaInfo-root"
|
||||
id="report-popover-comment-0-ariainfo"
|
||||
>
|
||||
A dialog for reporting comments
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<form
|
||||
autoComplete="off"
|
||||
id="comments-replyCommentForm-form-comment-0"
|
||||
onSubmit={[Function]}
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-half"
|
||||
>
|
||||
<div>
|
||||
<label
|
||||
className="AriaInfo-root"
|
||||
htmlFor="comments-replyCommentForm-rte-comment-0"
|
||||
>
|
||||
Write a reply
|
||||
</label>
|
||||
<div
|
||||
className="Flex-root ReplyTo-root Flex-flex Flex-alignCenter"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
reply
|
||||
</span>
|
||||
<span>
|
||||
|
||||
</span>
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary ReplyTo-text"
|
||||
>
|
||||
Replying to:
|
||||
<span
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary ReplyTo-username"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
</span>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder"
|
||||
>
|
||||
Write a reply
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Write a reply"
|
||||
className="RTE-contentEditable RTE-content"
|
||||
contentEditable={true}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
}
|
||||
disabled={false}
|
||||
id="comments-replyCommentForm-rte-comment-0"
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-halfItemGutter Flex-justifyFlexEnd Flex-directionRow"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantOutlined"
|
||||
disabled={false}
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Cancel
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Submit
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</form>
|
||||
</div>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`post a reply: open reply form 1`] = `
|
||||
<div
|
||||
data-testid="comment-comment-0"
|
||||
@@ -378,6 +60,7 @@ exports[`post a reply: open reply form 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost Button-active"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -719,6 +402,7 @@ exports[`post a reply: optimistic response 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-uuid-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
+121
-123
@@ -12,146 +12,142 @@ exports[`renders comment stream with community guidelines 1`] = `
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorPrimary"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="CallOut-root CallOut-colorPrimary"
|
||||
>
|
||||
<div
|
||||
className="Markdown-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<h2 id=\\"community-guidelines\\">Community Guidelines</h2>
|
||||
className="Markdown-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "<h2 id=\\"community-guidelines\\">Community Guidelines</h2>
|
||||
",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
@@ -281,6 +277,7 @@ exports[`renders comment stream with community guidelines 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -430,6 +427,7 @@ exports[`renders comment stream with community guidelines 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -69,6 +69,7 @@ exports[`renders reply list 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-replies"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -223,6 +224,7 @@ exports[`renders reply list 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -372,6 +374,7 @@ exports[`renders reply list 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-4"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -523,6 +526,7 @@ exports[`renders reply list 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders app with comment stream 1`] = `
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="HorizontalGutter-root App-root HorizontalGutter-full"
|
||||
>
|
||||
@@ -43,133 +43,129 @@ exports[`renders app with comment stream 1`] = `
|
||||
className="HorizontalGutter-root Stream-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter"
|
||||
aria-hidden="true"
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign in
|
||||
</button>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantOutlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Register
|
||||
</button>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root PostCommentFormFake-root HorizontalGutter-full"
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
>
|
||||
<div>
|
||||
<div>
|
||||
<div
|
||||
className=""
|
||||
>
|
||||
<div
|
||||
className=""
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
<div
|
||||
aria-hidden="true"
|
||||
className="RTE-placeholder RTE-placeholder "
|
||||
>
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
Post a comment
|
||||
</div>
|
||||
<div
|
||||
aria-placeholder="Post a comment"
|
||||
className="RTE-contentEditable RTE-content RTE-contentEditableDisabled"
|
||||
contentEditable={false}
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "",
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
}
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onChange={[Function]}
|
||||
onCut={[Function]}
|
||||
onFocus={[Function]}
|
||||
onInput={[Function]}
|
||||
onKeyDown={[Function]}
|
||||
onPaste={[Function]}
|
||||
onSelect={[Function]}
|
||||
/>
|
||||
<div
|
||||
className="RTE-toolbar RTE-toolbarDisabled RTE-toolbarBottom Toolbar-toolbar"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Bold"
|
||||
type="button"
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
format_bold
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Italic"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
format_italic
|
||||
</span>
|
||||
</button>
|
||||
<button
|
||||
className="Button-button"
|
||||
disabled={false}
|
||||
onClick={[Function]}
|
||||
title="Blockquote"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-md"
|
||||
>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
format_quote
|
||||
</span>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorPrimary Button-variantFilled Button-fullWidth Button-disabled"
|
||||
disabled={true}
|
||||
onBlur={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="submit"
|
||||
>
|
||||
Sign in and Join the Conversation
|
||||
</button>
|
||||
</div>
|
||||
<div>
|
||||
<div
|
||||
@@ -299,6 +295,7 @@ exports[`renders app with comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -448,6 +445,7 @@ exports[`renders app with comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -69,6 +69,7 @@ exports[`renders comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -60,6 +60,7 @@ exports[`renders deepest comment with link 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-with-deepest-replies-5"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -70,6 +70,7 @@ exports[`renders app with comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
@@ -219,6 +220,7 @@ exports[`renders app with comment stream 1`] = `
|
||||
>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorRegular Button-variantGhost"
|
||||
disabled={false}
|
||||
id="comments-commentContainer-replyButton-comment-3"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
|
||||
@@ -0,0 +1,99 @@
|
||||
import sinon from "sinon";
|
||||
|
||||
import { waitForElement, within } from "talk-framework/testHelpers";
|
||||
|
||||
import { settings, stories } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
afterEach(() => jest.useRealTimers());
|
||||
|
||||
async function createTestRenderer(
|
||||
resolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(stories[0].id, "storyID");
|
||||
},
|
||||
});
|
||||
|
||||
return {
|
||||
testRenderer,
|
||||
context,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders disabled comment stream", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
settings: sinon.stub().callsFake(() => ({
|
||||
...settings,
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
})),
|
||||
},
|
||||
});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByText("commenting disabled")
|
||||
);
|
||||
});
|
||||
|
||||
it("renders closed comment stream", async () => {
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => ({
|
||||
...stories[0],
|
||||
isClosed: true,
|
||||
})),
|
||||
},
|
||||
});
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByText("Story is closed")
|
||||
);
|
||||
});
|
||||
|
||||
it("auto close comment stream when story closed at has been reached", async () => {
|
||||
const closeIn = 360000;
|
||||
const now = new Date();
|
||||
const later = new Date(now.valueOf() + closeIn);
|
||||
jest.useFakeTimers();
|
||||
|
||||
const { testRenderer } = await createTestRenderer({
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => ({
|
||||
...stories[0],
|
||||
closedAt: later.toISOString(),
|
||||
isClosed: false,
|
||||
})),
|
||||
},
|
||||
});
|
||||
expect(within(testRenderer.root).queryByText("Story is closed")).toBeNull();
|
||||
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
|
||||
jest.advanceTimersByTime(closeIn);
|
||||
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByText("Story is closed")
|
||||
);
|
||||
});
|
||||
@@ -5,14 +5,16 @@ import { ERROR_CODES } from "talk-common/errors";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import {
|
||||
createSinonStub,
|
||||
findParentWithType,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import RTE from "@coralproject/rte";
|
||||
import { baseComment, settings, stories, users } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
function createTestRenderer(
|
||||
async function createTestRenderer(
|
||||
resolver: any,
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) {
|
||||
@@ -21,17 +23,15 @@ function createTestRenderer(
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
me: sinon.stub().returns(users[0]),
|
||||
story: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s
|
||||
.withArgs(undefined, { id: stories[0].id, url: null })
|
||||
.returns(stories[0])
|
||||
),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
|
||||
return create({
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
@@ -41,10 +41,34 @@ function createTestRenderer(
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
},
|
||||
});
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
const rte = await waitForElement(
|
||||
() =>
|
||||
findParentWithType(
|
||||
within(tabPane).getByLabelText("Post a comment"),
|
||||
// We'll use the RTE component here as an exception because the
|
||||
// jsdom does not support all of what is needed for rendering the
|
||||
// Rich Text Editor.
|
||||
RTE
|
||||
)!
|
||||
);
|
||||
const form = findParentWithType(rte, "form")!;
|
||||
|
||||
return {
|
||||
testRenderer,
|
||||
context,
|
||||
tabPane,
|
||||
rte,
|
||||
form,
|
||||
};
|
||||
}
|
||||
|
||||
it("post a comment", async () => {
|
||||
const { testRenderer } = createTestRenderer({
|
||||
const { rte, form, tabPane } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toEqual({
|
||||
@@ -70,20 +94,10 @@ it("post a comment", async () => {
|
||||
},
|
||||
});
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({ inputId: "comments-postCommentForm-field" })
|
||||
.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({ id: "comments-postCommentForm-form" })
|
||||
.props.onSubmit();
|
||||
|
||||
form.props.onSubmit();
|
||||
timekeeper.reset();
|
||||
|
||||
// Test optimistic response.
|
||||
@@ -100,7 +114,7 @@ it("post a comment", async () => {
|
||||
});
|
||||
|
||||
it("post a comment and handle server error", async () => {
|
||||
const { testRenderer } = createTestRenderer(
|
||||
const { form, rte, tabPane } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
@@ -111,22 +125,74 @@ it("post a comment and handle server error", async () => {
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
const tabPane = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("current-tab-pane")
|
||||
);
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({ inputId: "comments-postCommentForm-field" })
|
||||
.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
|
||||
testRenderer.root
|
||||
.findByProps({ id: "comments-postCommentForm-form" })
|
||||
.props.onSubmit();
|
||||
|
||||
form.props.onSubmit();
|
||||
timekeeper.reset();
|
||||
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(tabPane).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
|
||||
it("handle disabled commenting error", async () => {
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.COMMENTING_DISABLED,
|
||||
});
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
settings: createSinonStub(
|
||||
s => s.onFirstCall().returns(settings),
|
||||
s =>
|
||||
s.onSecondCall().returns({
|
||||
...settings,
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
})
|
||||
),
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
await waitForElement(() => within(form).getByText("commenting disabled"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("handle story closed", async () => {
|
||||
let returnStory = stories[0];
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createComment: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.STORY_CLOSED,
|
||||
});
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => returnStory),
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Change the story that we return to be closed.
|
||||
returnStory = { ...stories[0], isClosed: true };
|
||||
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
@@ -4,15 +4,16 @@ import timekeeper from "timekeeper";
|
||||
import { ERROR_CODES } from "talk-common/errors";
|
||||
import { InvalidRequestError } from "talk-framework/lib/errors";
|
||||
import {
|
||||
createSinonStub,
|
||||
findParentWithType,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
|
||||
import RTE from "@coralproject/rte";
|
||||
import { baseComment, settings, stories, users } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
function createTestRenderer(
|
||||
async function createTestRenderer(
|
||||
resolver: any,
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) {
|
||||
@@ -21,17 +22,15 @@ function createTestRenderer(
|
||||
Query: {
|
||||
settings: sinon.stub().returns(settings),
|
||||
me: sinon.stub().returns(users[0]),
|
||||
story: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s
|
||||
.withArgs(undefined, { id: stories[0].id, url: null })
|
||||
.returns(stories[0])
|
||||
),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
|
||||
return create({
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
@@ -41,10 +40,39 @@ function createTestRenderer(
|
||||
localRecord.setValue(true, "loggedIn");
|
||||
},
|
||||
});
|
||||
|
||||
const comment = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comment-comment-0")
|
||||
);
|
||||
|
||||
// Open reply form.
|
||||
within(comment)
|
||||
.getByText("Reply", { selector: "button" })
|
||||
.props.onClick();
|
||||
|
||||
const rte = await waitForElement(
|
||||
() =>
|
||||
findParentWithType(
|
||||
within(comment).getByLabelText("Write a reply"),
|
||||
// We'll use the RTE component here as an exception because the
|
||||
// jsdom does not support all of what is needed for rendering the
|
||||
// Rich Text Editor.
|
||||
RTE
|
||||
)!
|
||||
);
|
||||
|
||||
const form = findParentWithType(rte, "form")!;
|
||||
return {
|
||||
testRenderer,
|
||||
context,
|
||||
comment,
|
||||
rte,
|
||||
form,
|
||||
};
|
||||
}
|
||||
|
||||
it("post a reply", async () => {
|
||||
const { testRenderer } = createTestRenderer({
|
||||
const { testRenderer, comment, rte, form } = await createTestRenderer({
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake((_, data) => {
|
||||
expect(data).toEqual({
|
||||
@@ -71,29 +99,16 @@ it("post a reply", async () => {
|
||||
}),
|
||||
},
|
||||
});
|
||||
const streamLog = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
|
||||
const comment = within(streamLog).getByTestID("comment-comment-0");
|
||||
|
||||
// Open reply form.
|
||||
within(comment)
|
||||
.getByText("Reply", { selector: "button" })
|
||||
.props.onClick();
|
||||
|
||||
const form = await waitForElement(() => within(comment).getByType("form"));
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("open reply form");
|
||||
|
||||
// Write reply .
|
||||
testRenderer.root
|
||||
.findByProps({ inputId: "comments-replyCommentForm-rte-comment-0" })
|
||||
.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
|
||||
const commentReplyList = within(streamLog).getByTestID(
|
||||
const commentReplyList = within(testRenderer.root).getByTestID(
|
||||
"commentReplyList-comment-0"
|
||||
);
|
||||
|
||||
@@ -110,7 +125,7 @@ it("post a reply", async () => {
|
||||
});
|
||||
|
||||
it("post a reply and handle server error", async () => {
|
||||
const { testRenderer } = createTestRenderer(
|
||||
const { rte, form, comment } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake(() => {
|
||||
@@ -120,24 +135,9 @@ it("post a reply and handle server error", async () => {
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
const streamLog = await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
|
||||
const comment = within(streamLog).getByTestID("comment-comment-0");
|
||||
|
||||
// Open reply form.
|
||||
within(comment)
|
||||
.getByText("Reply", { selector: "button" })
|
||||
.props.onClick();
|
||||
|
||||
const form = await waitForElement(() => within(comment).getByType("form"));
|
||||
expect(within(comment).toJSON()).toMatchSnapshot("open reply form");
|
||||
|
||||
// Write reply .
|
||||
testRenderer.root
|
||||
.findByProps({ inputId: "comments-replyCommentForm-rte-comment-0" })
|
||||
.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
rte.props.onChange({ html: "<b>Hello world!</b>" });
|
||||
|
||||
timekeeper.freeze(new Date(baseComment.createdAt));
|
||||
form.props.onSubmit();
|
||||
@@ -145,3 +145,67 @@ it("post a reply and handle server error", async () => {
|
||||
// Look for internal error being displayed.
|
||||
await waitForElement(() => within(comment).getByText("INTERNAL_ERROR"));
|
||||
});
|
||||
|
||||
it("handle disabled commenting error", async () => {
|
||||
let returnSettings = settings;
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.COMMENTING_DISABLED,
|
||||
});
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
settings: sinon.stub().callsFake(() => returnSettings),
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Change the settings that we return to be closed.
|
||||
returnSettings = {
|
||||
...settings,
|
||||
disableCommenting: {
|
||||
enabled: true,
|
||||
message: "commenting disabled",
|
||||
},
|
||||
};
|
||||
|
||||
await waitForElement(() => within(form).getByText("commenting disabled"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
it("handle story closed error", async () => {
|
||||
let returnStory = stories[0];
|
||||
const { rte, form } = await createTestRenderer(
|
||||
{
|
||||
Mutation: {
|
||||
createCommentReply: sinon.stub().callsFake(() => {
|
||||
throw new InvalidRequestError({
|
||||
code: ERROR_CODES.STORY_CLOSED,
|
||||
});
|
||||
}),
|
||||
},
|
||||
Query: {
|
||||
story: sinon.stub().callsFake(() => returnStory),
|
||||
},
|
||||
},
|
||||
{ muteNetworkErrors: true }
|
||||
);
|
||||
|
||||
rte.props.onChange({ html: "abc" });
|
||||
form.props.onSubmit();
|
||||
|
||||
// Change the story that we return to be closed.
|
||||
returnStory = { ...stories[0], isClosed: true };
|
||||
|
||||
await waitForElement(() => within(form).getByText("Story is closed"));
|
||||
expect(rte.props.disabled).toBe(true);
|
||||
expect(within(form).getByText("Submit").props.disabled).toBe(true);
|
||||
});
|
||||
|
||||
@@ -1,41 +1,44 @@
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import {
|
||||
createSinonStub,
|
||||
waitForElement,
|
||||
within,
|
||||
} from "talk-framework/testHelpers";
|
||||
import { waitForElement, within } from "talk-framework/testHelpers";
|
||||
|
||||
import { settings, stories } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
beforeEach(() => {
|
||||
async function createTestRenderer(
|
||||
resolver: any = {},
|
||||
options: { muteNetworkErrors?: boolean } = {}
|
||||
) {
|
||||
const resolvers = {
|
||||
...resolver,
|
||||
Query: {
|
||||
story: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s
|
||||
.withArgs(undefined, { id: stories[0].id, url: null })
|
||||
.returns(stories[0])
|
||||
),
|
||||
settings: sinon.stub().returns(settings),
|
||||
story: sinon.stub().callsFake((_: any, variables: any) => {
|
||||
expect(variables.id).toBe(stories[0].id);
|
||||
return stories[0];
|
||||
}),
|
||||
...resolver.Query,
|
||||
},
|
||||
};
|
||||
|
||||
({ testRenderer } = create({
|
||||
const { testRenderer, context } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
muteNetworkErrors: options.muteNetworkErrors,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(stories[0].id, "storyID");
|
||||
},
|
||||
}));
|
||||
});
|
||||
});
|
||||
|
||||
it("renders app with comment stream", async () => {
|
||||
return {
|
||||
testRenderer,
|
||||
context,
|
||||
};
|
||||
}
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
const { testRenderer } = await createTestRenderer();
|
||||
await waitForElement(() =>
|
||||
within(testRenderer.root).getByTestID("comments-stream-log")
|
||||
);
|
||||
|
||||
@@ -11,6 +11,12 @@ export const settings = {
|
||||
enabled: false,
|
||||
content: "",
|
||||
},
|
||||
disableCommenting: {
|
||||
enabled: false,
|
||||
message: "Commenting has been disabled",
|
||||
},
|
||||
closedAt: null,
|
||||
closedMessage: "Story is closed",
|
||||
auth: {
|
||||
integrations: {
|
||||
facebook: {
|
||||
|
||||
@@ -3,6 +3,14 @@ export enum ERROR_TYPES {
|
||||
}
|
||||
|
||||
export enum ERROR_CODES {
|
||||
/**
|
||||
* STORY_CLOSED is used when submitting a comment on a closed story.
|
||||
*/
|
||||
STORY_CLOSED = "STORY_CLOSED",
|
||||
/**
|
||||
* COMMENTING_DISABLED is used when submitting a comment while commenting has been disabled.
|
||||
*/
|
||||
COMMENTING_DISABLED = "COMMENTING_DISABLED",
|
||||
/**
|
||||
* COMMENT_BODY_TOO_SHORT is used when a submitted comment body is too short.
|
||||
*/
|
||||
|
||||
@@ -189,6 +189,22 @@ export class TalkError extends VError {
|
||||
}
|
||||
}
|
||||
|
||||
export class CommentingDisabledError extends TalkError {
|
||||
constructor() {
|
||||
super({
|
||||
code: ERROR_CODES.COMMENTING_DISABLED,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class StoryClosedError extends TalkError {
|
||||
constructor() {
|
||||
super({
|
||||
code: ERROR_CODES.STORY_CLOSED,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
export class CommentBodyTooShortError extends TalkError {
|
||||
constructor(min: number) {
|
||||
super({
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
import { ERROR_CODES } from "talk-common/errors";
|
||||
|
||||
export const ERROR_TRANSLATIONS: Record<ERROR_CODES, string> = {
|
||||
COMMENTING_DISABLED: "error-commentingDisabled",
|
||||
STORY_CLOSED: "error-storyClosed",
|
||||
COMMENT_BODY_TOO_SHORT: "error-commentBodyTooShort",
|
||||
COMMENT_BODY_EXCEEDS_MAX_LENGTH: "error-commentBodyExceedsMaxLength",
|
||||
STORY_URL_NOT_PERMITTED: "error-storyURLNotPermitted",
|
||||
|
||||
@@ -7,7 +7,10 @@ import { storyModerationInputResolver } from "./ModerationQueues";
|
||||
|
||||
export const Story: GQLStoryTypeResolver<story.Story> = {
|
||||
comments: (s, input, ctx) => ctx.loaders.Comments.forStory(s.id, input),
|
||||
isClosed: () => false,
|
||||
isClosed: (s, input, ctx) => {
|
||||
const closedAt = getStoryClosedAt(ctx.tenant, s);
|
||||
return !!closedAt && new Date() >= closedAt;
|
||||
},
|
||||
closedAt: (s, input, ctx) => getStoryClosedAt(ctx.tenant, s),
|
||||
commentActionCounts: s => decodeActionCounts(s.commentCounts.action),
|
||||
commentCounts: s => s.commentCounts.status,
|
||||
|
||||
@@ -1,3 +1,5 @@
|
||||
error-commentingDisabled = Commenting has been disabled tenant wide.
|
||||
error-storyClosed = Story is currently closed for commenting.
|
||||
error-commentBodyTooShort = Comment body must have at least {$min} characters.
|
||||
error-commentBodyExceedsMaxLength =
|
||||
Comment body exceeds maximum length of {$max} characters.
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { CommentingDisabledError } from "talk-server/errors";
|
||||
import { ModerationSettings } from "talk-server/models/settings";
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
@@ -16,7 +17,6 @@ export const commentingDisabled: IntermediateModerationPhase = ({
|
||||
testDisabledCommenting(tenant) ||
|
||||
(story.settings && testDisabledCommenting(story.settings))
|
||||
) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("commenting has been disabled tenant wide");
|
||||
throw new CommentingDisabledError();
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
import { StoryClosedError } from "talk-server/errors";
|
||||
import {
|
||||
IntermediateModerationPhase,
|
||||
IntermediatePhaseResult,
|
||||
@@ -11,7 +12,6 @@ export const storyClosed: IntermediateModerationPhase = ({
|
||||
}): IntermediatePhaseResult | void => {
|
||||
const closedAt = getStoryClosedAt(tenant, story);
|
||||
if (closedAt && closedAt.valueOf() <= Date.now()) {
|
||||
// TODO: (wyattjoh) return better error.
|
||||
throw new Error("story is currently closed for commenting");
|
||||
throw new StoryClosedError();
|
||||
}
|
||||
};
|
||||
|
||||
Reference in New Issue
Block a user