mirror of
https://github.com/wassname/talk.git
synced 2026-07-02 03:34:45 +08:00
[next] Implement Comment History Pagination (#2008)
* refactor: profile * feat: add pagination to comment history * feat: add getMeSourceID helper * feat: update profile in CreateCommentMutation * fix: clear query response cache on mutation * test: add integration tests for profile * test: add unit tests
This commit is contained in:
@@ -1,11 +1,12 @@
|
||||
import { Environment, ROOT_ID } from "relay-runtime";
|
||||
import { Environment } from "relay-runtime";
|
||||
|
||||
import getMeSourceID from "./getMeSourceID";
|
||||
|
||||
export default function getMe(environment: Environment) {
|
||||
const source = environment.getStore().getSource();
|
||||
const root = source.get(ROOT_ID)!;
|
||||
if (!root.me) {
|
||||
const meID = getMeSourceID(environment);
|
||||
if (!meID) {
|
||||
return null;
|
||||
}
|
||||
const meID = root.me.__ref;
|
||||
return source.get(meID)!;
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
import { Environment, ROOT_ID } from "relay-runtime";
|
||||
|
||||
export default function getMeSourceID(environment: Environment): string | null {
|
||||
const source = environment.getStore().getSource();
|
||||
const root = source.get(ROOT_ID)!;
|
||||
if (!root.me) {
|
||||
return null;
|
||||
}
|
||||
return root.me.__ref;
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
export { default as getMe } from "./getMe";
|
||||
export { default as getMeSourceID } from "./getMeSourceID";
|
||||
export { default as getURLWithCommentID } from "./getURLWithCommentID";
|
||||
export { default as urls } from "./urls";
|
||||
|
||||
@@ -19,6 +19,7 @@ export default function createNetwork(tokenGetter: TokenGetter) {
|
||||
cacheMiddleware({
|
||||
size: 100, // max 100 requests
|
||||
ttl: 900000, // 15 minutes
|
||||
clearOnMutation: true,
|
||||
}),
|
||||
urlMiddleware({
|
||||
url: req => Promise.resolve(graphqlURL),
|
||||
|
||||
@@ -28,7 +28,10 @@ export function denormalizeComments(commentList: any[]) {
|
||||
export function denormalizeAsset(asset: any) {
|
||||
const commentNodes =
|
||||
(asset.comments &&
|
||||
asset.comments.edges.map((edge: any) => denormalizeComment(edge))) ||
|
||||
asset.comments.edges.map((edge: any) => ({
|
||||
...edge,
|
||||
node: denormalizeComment(edge.node),
|
||||
}))) ||
|
||||
[];
|
||||
const commentsPageInfo = (asset.comments && asset.comments.pageInfo) || {
|
||||
endCursor: null,
|
||||
|
||||
@@ -5,7 +5,7 @@ import {
|
||||
RecordSourceSelectorProxy,
|
||||
} from "relay-runtime";
|
||||
|
||||
import { getMe } from "talk-framework/helpers";
|
||||
import { getMe, getMeSourceID } from "talk-framework/helpers";
|
||||
import { TalkContext } from "talk-framework/lib/bootstrap";
|
||||
import {
|
||||
commitMutationPromiseNormalized,
|
||||
@@ -21,6 +21,7 @@ export type CreateCommentInput = Omit<
|
||||
> & { local?: boolean };
|
||||
|
||||
function sharedUpdater(
|
||||
environment: Environment,
|
||||
store: RecordSourceSelectorProxy,
|
||||
input: CreateCommentInput
|
||||
) {
|
||||
@@ -30,7 +31,7 @@ function sharedUpdater(
|
||||
} else {
|
||||
update(store, input);
|
||||
}
|
||||
updateProfile(store, input);
|
||||
updateProfile(environment, store, input);
|
||||
}
|
||||
|
||||
function updateAsset(
|
||||
@@ -120,6 +121,7 @@ function localUpdate(
|
||||
* updateProfile integrates new comment into the profile.
|
||||
*/
|
||||
function updateProfile(
|
||||
environment: Environment,
|
||||
store: RecordSourceSelectorProxy,
|
||||
input: CreateCommentInput
|
||||
) {
|
||||
@@ -128,12 +130,17 @@ function updateProfile(
|
||||
|
||||
// Get the edge of the newly created comment.
|
||||
const newEdge = payload.getLinkedRecord("edge")!;
|
||||
const newComment = newEdge.getLinkedRecord("node");
|
||||
|
||||
// TODO: update profile comments connection after we
|
||||
// integrated pagination.
|
||||
// tslint:disable-next-line:no-unused-expression
|
||||
newComment;
|
||||
const meProxy = store.get(getMeSourceID(environment)!);
|
||||
const con = ConnectionHandler.getConnection(
|
||||
meProxy,
|
||||
"CommentHistory_comments"
|
||||
);
|
||||
// Note: Currently this is always null, until Relay comes
|
||||
// with better data retaintion and data from store support.
|
||||
if (con) {
|
||||
ConnectionHandler.insertEdgeBefore(con, newEdge);
|
||||
}
|
||||
}
|
||||
|
||||
const mutation = graphql`
|
||||
@@ -196,11 +203,11 @@ function commit(
|
||||
},
|
||||
} as any, // TODO: (cvle) generated types should contain one for the optimistic response.
|
||||
optimisticUpdater: store => {
|
||||
sharedUpdater(store, input);
|
||||
sharedUpdater(environment, store, input);
|
||||
store.get(id)!.setValue(true, "pending");
|
||||
},
|
||||
updater: store => {
|
||||
sharedUpdater(store, input);
|
||||
sharedUpdater(environment, store, input);
|
||||
},
|
||||
});
|
||||
}
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import CommentHistory from "./CommentHistory";
|
||||
|
||||
const CommentHistoryN = removeFragmentRefs(CommentHistory);
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof CommentHistoryN> = {
|
||||
asset: {},
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: false,
|
||||
disableLoadMore: false,
|
||||
};
|
||||
const wrapper = shallow(<CommentHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
|
||||
describe("has more", () => {
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof CommentHistoryN> = {
|
||||
asset: {},
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: true,
|
||||
disableLoadMore: false,
|
||||
};
|
||||
const wrapper = shallow(<CommentHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
it("disables load more", () => {
|
||||
const props: PropTypesOf<typeof CommentHistoryN> = {
|
||||
asset: {},
|
||||
comments: [{ id: "comment-1" }, { id: "comment-2" }],
|
||||
onLoadMore: noop,
|
||||
hasMore: true,
|
||||
disableLoadMore: true,
|
||||
};
|
||||
const wrapper = shallow(<CommentHistoryN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
});
|
||||
@@ -0,0 +1,50 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import { Button, HorizontalGutter, Typography } from "talk-ui/components";
|
||||
|
||||
import HistoryCommentContainer from "../containers/HistoryCommentContainer";
|
||||
|
||||
interface CommentHistoryProps {
|
||||
asset: PropTypesOf<typeof HistoryCommentContainer>["asset"];
|
||||
comments: Array<
|
||||
{ id: string } & PropTypesOf<typeof HistoryCommentContainer>["comment"]
|
||||
>;
|
||||
onLoadMore?: () => void;
|
||||
hasMore?: boolean;
|
||||
disableLoadMore?: boolean;
|
||||
}
|
||||
|
||||
const CommentHistory: StatelessComponent<CommentHistoryProps> = props => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Localized id="profile-historyComment-commentHistory">
|
||||
<Typography variant="heading3">Comment History</Typography>
|
||||
</Localized>
|
||||
{props.comments.map(comment => (
|
||||
<HistoryCommentContainer
|
||||
key={comment.id}
|
||||
asset={props.asset}
|
||||
comment={comment}
|
||||
/>
|
||||
))}
|
||||
{props.hasMore && (
|
||||
<Localized id="profile-commentHistory-loadMore">
|
||||
<Button
|
||||
id={"talk-profile-commentHistory-loadMore"}
|
||||
onClick={props.onLoadMore}
|
||||
variant="outlined"
|
||||
fullWidth
|
||||
disabled={props.disableLoadMore}
|
||||
aria-controls="talk-profile-commentHistory-log"
|
||||
>
|
||||
Load More
|
||||
</Button>
|
||||
</Localized>
|
||||
)}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentHistory;
|
||||
@@ -1,36 +0,0 @@
|
||||
import { Localized } from "fluent-react/compat";
|
||||
import * as React from "react";
|
||||
import { StatelessComponent } from "react";
|
||||
import { HorizontalGutter, Typography } from "talk-ui/components";
|
||||
import HistoryComment from "./HistoryComment";
|
||||
|
||||
interface Comment {
|
||||
id: string;
|
||||
body: string | null;
|
||||
createdAt: any;
|
||||
replyCount: number | null;
|
||||
asset: {
|
||||
title: string | null;
|
||||
};
|
||||
conversationURL: string;
|
||||
onGotoConversation: (e: React.MouseEvent) => void;
|
||||
}
|
||||
|
||||
interface CommentsHistoryProps {
|
||||
comments: Comment[];
|
||||
}
|
||||
|
||||
const CommentsHistory: StatelessComponent<CommentsHistoryProps> = props => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<Localized id="profile-historyComment-commentHistory">
|
||||
<Typography variant="heading3">Comment History</Typography>
|
||||
</Localized>
|
||||
{props.comments.map(comment => (
|
||||
<HistoryComment key={comment.id} {...comment} />
|
||||
))}
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
export default CommentsHistory;
|
||||
@@ -0,0 +1,25 @@
|
||||
import { shallow } from "enzyme";
|
||||
import { noop } from "lodash";
|
||||
import React from "react";
|
||||
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import HistoryComment from "./HistoryComment";
|
||||
|
||||
const HistoryCommentN = removeFragmentRefs(HistoryComment);
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof HistoryCommentN> = {
|
||||
body: "Hello World",
|
||||
createdAt: "2018-07-06T18:24:00.000Z",
|
||||
replyCount: 4,
|
||||
asset: {
|
||||
title: "Asset Title",
|
||||
},
|
||||
conversationURL: "http://localhost/conversation",
|
||||
onGotoConversation: noop,
|
||||
};
|
||||
const wrapper = shallow(<HistoryCommentN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,18 @@
|
||||
import { shallow } from "enzyme";
|
||||
import React from "react";
|
||||
|
||||
import { removeFragmentRefs } from "talk-framework/testHelpers";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
|
||||
import Profile from "./Profile";
|
||||
|
||||
const ProfileN = removeFragmentRefs(Profile);
|
||||
|
||||
it("renders correctly", () => {
|
||||
const props: PropTypesOf<typeof ProfileN> = {
|
||||
asset: {},
|
||||
me: {},
|
||||
};
|
||||
const wrapper = shallow(<ProfileN {...props} />);
|
||||
expect(wrapper).toMatchSnapshot();
|
||||
});
|
||||
@@ -3,21 +3,19 @@ import { StatelessComponent } from "react";
|
||||
import { PropTypesOf } from "talk-framework/types";
|
||||
import UserBoxContainer from "talk-stream/containers/UserBoxContainer";
|
||||
import { HorizontalGutter } from "talk-ui/components";
|
||||
import CommentsHistoryContainer from "../containers/CommentsHistoryContainer";
|
||||
import CommentHistoryContainer from "../containers/CommentHistoryContainer";
|
||||
|
||||
export interface ProfileProps {
|
||||
asset: PropTypesOf<typeof CommentsHistoryContainer>["asset"];
|
||||
asset: PropTypesOf<typeof CommentHistoryContainer>["asset"];
|
||||
me: PropTypesOf<typeof UserBoxContainer>["me"] &
|
||||
PropTypesOf<typeof CommentsHistoryContainer>["me"];
|
||||
PropTypesOf<typeof CommentHistoryContainer>["me"];
|
||||
}
|
||||
|
||||
const Profile: StatelessComponent<ProfileProps> = props => {
|
||||
return (
|
||||
<HorizontalGutter size="double">
|
||||
<UserBoxContainer me={props.me} />
|
||||
{props.me && (
|
||||
<CommentsHistoryContainer me={props.me} asset={props.asset} />
|
||||
)}
|
||||
<CommentHistoryContainer me={props.me} asset={props.asset} />
|
||||
</HorizontalGutter>
|
||||
);
|
||||
};
|
||||
|
||||
+131
@@ -0,0 +1,131 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`has more disables load more 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
size="double"
|
||||
>
|
||||
<Localized
|
||||
id="profile-historyComment-commentHistory"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="heading3"
|
||||
>
|
||||
Comment History
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
key="comment-1"
|
||||
/>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
key="comment-2"
|
||||
/>
|
||||
<Localized
|
||||
id="profile-commentHistory-loadMore"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
aria-controls="talk-profile-commentHistory-log"
|
||||
disabled={true}
|
||||
fullWidth={true}
|
||||
id="talk-profile-commentHistory-loadMore"
|
||||
onClick={[Function]}
|
||||
variant="outlined"
|
||||
>
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
`;
|
||||
|
||||
exports[`has more renders correctly 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
size="double"
|
||||
>
|
||||
<Localized
|
||||
id="profile-historyComment-commentHistory"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="heading3"
|
||||
>
|
||||
Comment History
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
key="comment-1"
|
||||
/>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
key="comment-2"
|
||||
/>
|
||||
<Localized
|
||||
id="profile-commentHistory-loadMore"
|
||||
>
|
||||
<withPropsOnChange(Button)
|
||||
aria-controls="talk-profile-commentHistory-log"
|
||||
disabled={false}
|
||||
fullWidth={true}
|
||||
id="talk-profile-commentHistory-loadMore"
|
||||
onClick={[Function]}
|
||||
variant="outlined"
|
||||
>
|
||||
Load More
|
||||
</withPropsOnChange(Button)>
|
||||
</Localized>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
`;
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
size="double"
|
||||
>
|
||||
<Localized
|
||||
id="profile-historyComment-commentHistory"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="heading3"
|
||||
>
|
||||
Comment History
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-1",
|
||||
}
|
||||
}
|
||||
key="comment-1"
|
||||
/>
|
||||
<withContext(createMutationContainer(Relay(HistoryCommentContainer)))
|
||||
asset={Object {}}
|
||||
comment={
|
||||
Object {
|
||||
"id": "comment-2",
|
||||
}
|
||||
}
|
||||
key="comment-2"
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
`;
|
||||
+75
@@ -0,0 +1,75 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)>
|
||||
<Localized
|
||||
$title="Asset Title"
|
||||
id="profile-historyComment-story"
|
||||
>
|
||||
<withPropsOnChange(Typography)
|
||||
variant="heading4"
|
||||
>
|
||||
Story: {$title}
|
||||
</withPropsOnChange(Typography)>
|
||||
</Localized>
|
||||
<Timestamp>
|
||||
2018-07-06T18:24:00.000Z
|
||||
</Timestamp>
|
||||
<withPropsOnChange(Typography)
|
||||
container="div"
|
||||
variant="bodyCopy"
|
||||
>
|
||||
<HTMLContent>
|
||||
Hello World
|
||||
</HTMLContent>
|
||||
</withPropsOnChange(Typography)>
|
||||
<withPropsOnChange(Flex)
|
||||
alignItems="center"
|
||||
direction="row"
|
||||
itemGutter={true}
|
||||
>
|
||||
<div
|
||||
className="HistoryComment-replies"
|
||||
>
|
||||
<withPropsOnChange(Icon)
|
||||
className="HistoryComment-icon"
|
||||
>
|
||||
reply
|
||||
</withPropsOnChange(Icon)>
|
||||
<Localized
|
||||
$replyCount={4}
|
||||
id="profile-historyComment-replies"
|
||||
>
|
||||
<span>
|
||||
Replies {$replyCount}
|
||||
</span>
|
||||
</Localized>
|
||||
</div>
|
||||
<withPropsOnChange(Button)
|
||||
anchor={true}
|
||||
href="http://localhost/conversation"
|
||||
onClick={[Function]}
|
||||
target="_parent"
|
||||
variant="underlined"
|
||||
>
|
||||
<withPropsOnChange(Icon)>
|
||||
launch
|
||||
</withPropsOnChange(Icon)>
|
||||
<Localized
|
||||
id="profile-historyComment-viewConversation"
|
||||
>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</Localized>
|
||||
</withPropsOnChange(Button)>
|
||||
</withPropsOnChange(Flex)>
|
||||
<MatchMediaWithContext
|
||||
lteWidth="xs"
|
||||
>
|
||||
<hr
|
||||
className="HistoryComment-divider"
|
||||
/>
|
||||
</MatchMediaWithContext>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
`;
|
||||
@@ -0,0 +1,15 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders correctly 1`] = `
|
||||
<withPropsOnChange(HorizontalGutter)
|
||||
size="double"
|
||||
>
|
||||
<withContext(createMutationContainer(withContext(createMutationContainer(withContext(createMutationContainer(withContext(withLocalStateContainer(Relay(UserBoxContainer)))))))))
|
||||
me={Object {}}
|
||||
/>
|
||||
<Relay(CommentHistoryContainer)
|
||||
asset={Object {}}
|
||||
me={Object {}}
|
||||
/>
|
||||
</withPropsOnChange(HorizontalGutter)>
|
||||
`;
|
||||
@@ -0,0 +1,124 @@
|
||||
import React from "react";
|
||||
import { graphql, RelayPaginationProp } from "react-relay";
|
||||
|
||||
import { withPaginationContainer } from "talk-framework/lib/relay";
|
||||
import { CommentHistoryContainer_asset as AssetData } from "talk-stream/__generated__/CommentHistoryContainer_asset.graphql";
|
||||
import { CommentHistoryContainer_me as MeData } from "talk-stream/__generated__/CommentHistoryContainer_me.graphql";
|
||||
import { CommentHistoryContainerPaginationQueryVariables } from "talk-stream/__generated__/CommentHistoryContainerPaginationQuery.graphql";
|
||||
|
||||
import CommentHistory from "../components/CommentHistory";
|
||||
|
||||
interface CommentHistoryContainerProps {
|
||||
me: MeData;
|
||||
asset: AssetData;
|
||||
relay: RelayPaginationProp;
|
||||
}
|
||||
|
||||
export class CommentHistoryContainer extends React.Component<
|
||||
CommentHistoryContainerProps
|
||||
> {
|
||||
public state = {
|
||||
disableLoadMore: false,
|
||||
};
|
||||
|
||||
public render() {
|
||||
const comments = this.props.me.comments.edges.map(edge => edge.node);
|
||||
return (
|
||||
<CommentHistory
|
||||
asset={this.props.asset}
|
||||
comments={comments}
|
||||
onLoadMore={this.loadMore}
|
||||
hasMore={this.props.relay.hasMore()}
|
||||
disableLoadMore={this.state.disableLoadMore}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
private loadMore = () => {
|
||||
if (!this.props.relay.hasMore() || this.props.relay.isLoading()) {
|
||||
return;
|
||||
}
|
||||
this.setState({ disableLoadMore: true });
|
||||
this.props.relay.loadMore(
|
||||
10, // Fetch the next 10 feed items
|
||||
error => {
|
||||
this.setState({ disableLoadMore: false });
|
||||
if (error) {
|
||||
// tslint:disable-next-line:no-console
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
// TODO: (cvle) This should be autogenerated.
|
||||
interface FragmentVariables {
|
||||
count: number;
|
||||
cursor?: string;
|
||||
}
|
||||
|
||||
const enhanced = withPaginationContainer<
|
||||
CommentHistoryContainerProps,
|
||||
CommentHistoryContainerPaginationQueryVariables,
|
||||
FragmentVariables
|
||||
>(
|
||||
{
|
||||
asset: graphql`
|
||||
fragment CommentHistoryContainer_asset on Asset {
|
||||
...HistoryCommentContainer_asset
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
fragment CommentHistoryContainer_me on User
|
||||
@argumentDefinitions(
|
||||
count: { type: "Int!", defaultValue: 5 }
|
||||
cursor: { type: "Cursor" }
|
||||
) {
|
||||
comments(first: $count, after: $cursor)
|
||||
@connection(key: "CommentHistory_comments") {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
...HistoryCommentContainer_comment
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
},
|
||||
{
|
||||
direction: "forward",
|
||||
getConnectionFromProps(props) {
|
||||
return props.me && props.me.comments;
|
||||
},
|
||||
// This is also the default implementation of `getFragmentVariables` if it isn't provided.
|
||||
getFragmentVariables(prevVars, totalCount) {
|
||||
return {
|
||||
...prevVars,
|
||||
count: totalCount,
|
||||
};
|
||||
},
|
||||
getVariables(props, { count, cursor }, fragmentVariables) {
|
||||
return {
|
||||
count,
|
||||
cursor,
|
||||
};
|
||||
},
|
||||
query: graphql`
|
||||
# Pagination query to be fetched upon calling 'loadMore'.
|
||||
# Notice that we re-use our fragment, and the shape of this query matches our fragment spec.
|
||||
query CommentHistoryContainerPaginationQuery(
|
||||
$count: Int!
|
||||
$cursor: Cursor
|
||||
) {
|
||||
me {
|
||||
...CommentHistoryContainer_me
|
||||
@arguments(count: $count, cursor: $cursor)
|
||||
}
|
||||
}
|
||||
`,
|
||||
}
|
||||
)(CommentHistoryContainer);
|
||||
|
||||
export default enhanced;
|
||||
@@ -1,70 +0,0 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { getURLWithCommentID } from "talk-framework/helpers";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { CommentsHistoryContainer_asset as AssetData } from "talk-stream/__generated__/CommentsHistoryContainer_asset.graphql";
|
||||
import { CommentsHistoryContainer_me as CommentsData } from "talk-stream/__generated__/CommentsHistoryContainer_me.graphql";
|
||||
import {
|
||||
SetCommentIDMutation,
|
||||
withSetCommentIDMutation,
|
||||
} from "talk-stream/mutations";
|
||||
import CommentsHistory from "../components/CommentsHistory";
|
||||
|
||||
interface CommentsHistoryContainerProps {
|
||||
setCommentID: SetCommentIDMutation;
|
||||
me: CommentsData;
|
||||
asset: AssetData;
|
||||
}
|
||||
|
||||
export class CommentsHistoryContainer extends React.Component<
|
||||
CommentsHistoryContainerProps
|
||||
> {
|
||||
private onGoToConversation = (id: string) => {
|
||||
this.props.setCommentID({ id });
|
||||
};
|
||||
public render() {
|
||||
const comments = this.props.me.comments.edges.map(edge => ({
|
||||
...edge.node,
|
||||
conversationURL: getURLWithCommentID(edge.node.asset.url, edge.node.id),
|
||||
onGotoConversation: (e: React.MouseEvent) => {
|
||||
if (this.props.asset.id === edge.node.asset.id) {
|
||||
this.onGoToConversation(edge.node.id);
|
||||
e.preventDefault();
|
||||
}
|
||||
},
|
||||
}));
|
||||
return <CommentsHistory comments={comments} />;
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetCommentIDMutation(
|
||||
withFragmentContainer<CommentsHistoryContainerProps>({
|
||||
asset: graphql`
|
||||
fragment CommentsHistoryContainer_asset on Asset {
|
||||
id
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
fragment CommentsHistoryContainer_me on User {
|
||||
comments {
|
||||
edges {
|
||||
node {
|
||||
id
|
||||
body
|
||||
createdAt
|
||||
replyCount
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(CommentsHistoryContainer)
|
||||
);
|
||||
|
||||
export default enhanced;
|
||||
@@ -0,0 +1,66 @@
|
||||
import React from "react";
|
||||
import { graphql } from "react-relay";
|
||||
|
||||
import { getURLWithCommentID } from "talk-framework/helpers";
|
||||
import { withFragmentContainer } from "talk-framework/lib/relay";
|
||||
import { HistoryCommentContainer_asset as AssetData } from "talk-stream/__generated__/HistoryCommentContainer_asset.graphql";
|
||||
import { HistoryCommentContainer_comment as CommentData } from "talk-stream/__generated__/HistoryCommentContainer_comment.graphql";
|
||||
import {
|
||||
SetCommentIDMutation,
|
||||
withSetCommentIDMutation,
|
||||
} from "talk-stream/mutations";
|
||||
import HistoryComment from "../components/HistoryComment";
|
||||
|
||||
interface HistoryCommentContainerProps {
|
||||
setCommentID: SetCommentIDMutation;
|
||||
asset: AssetData;
|
||||
comment: CommentData;
|
||||
}
|
||||
|
||||
export class HistoryCommentContainer extends React.Component<
|
||||
HistoryCommentContainerProps
|
||||
> {
|
||||
private handleGoToConversation = (e: React.MouseEvent) => {
|
||||
if (this.props.asset.id === this.props.comment.asset.id) {
|
||||
this.props.setCommentID({ id: this.props.comment.id });
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
public render() {
|
||||
return (
|
||||
<HistoryComment
|
||||
{...this.props.comment}
|
||||
conversationURL={getURLWithCommentID(
|
||||
this.props.comment.asset.url,
|
||||
this.props.comment.id
|
||||
)}
|
||||
onGotoConversation={this.handleGoToConversation}
|
||||
/>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const enhanced = withSetCommentIDMutation(
|
||||
withFragmentContainer<HistoryCommentContainerProps>({
|
||||
asset: graphql`
|
||||
fragment HistoryCommentContainer_asset on Asset {
|
||||
id
|
||||
}
|
||||
`,
|
||||
comment: graphql`
|
||||
fragment HistoryCommentContainer_comment on Comment {
|
||||
id
|
||||
body
|
||||
createdAt
|
||||
replyCount
|
||||
asset {
|
||||
id
|
||||
title
|
||||
url
|
||||
}
|
||||
}
|
||||
`,
|
||||
})(HistoryCommentContainer)
|
||||
);
|
||||
|
||||
export default enhanced;
|
||||
@@ -20,13 +20,13 @@ export class StreamContainer extends React.Component<ProfileContainerProps> {
|
||||
const enhanced = withFragmentContainer<ProfileContainerProps>({
|
||||
asset: graphql`
|
||||
fragment ProfileContainer_asset on Asset {
|
||||
...CommentsHistoryContainer_asset
|
||||
...CommentHistoryContainer_asset
|
||||
}
|
||||
`,
|
||||
me: graphql`
|
||||
fragment ProfileContainer_me on User {
|
||||
...UserBoxContainer_me
|
||||
...CommentsHistoryContainer_me
|
||||
...CommentHistoryContainer_me
|
||||
}
|
||||
`,
|
||||
})(StreamContainer);
|
||||
|
||||
@@ -64,10 +64,6 @@ const ProfileQuery: StatelessComponent<InnerProps> = ({
|
||||
assetID,
|
||||
assetURL,
|
||||
}}
|
||||
cacheConfig={{
|
||||
// TODO: enable caching after mutations are adapted.
|
||||
force: true,
|
||||
}}
|
||||
render={render}
|
||||
/>
|
||||
);
|
||||
|
||||
@@ -242,6 +242,20 @@ export const assets = denormalizeAssets([
|
||||
},
|
||||
},
|
||||
},
|
||||
{
|
||||
...baseAsset,
|
||||
id: "asset-2",
|
||||
url: "http://localhost/assets/asset-2",
|
||||
comments: {
|
||||
edges: [
|
||||
{ node: comments[2], cursor: comments[2].createdAt },
|
||||
{ node: comments[3], cursor: comments[3].createdAt },
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
},
|
||||
]);
|
||||
|
||||
export const assetWithReplies = denormalizeAsset({
|
||||
@@ -293,3 +307,23 @@ export const assetWithDeepestReplies = denormalizeAsset({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
export const meWithComments = {
|
||||
id: "me-with-comments",
|
||||
username: "Markus",
|
||||
comments: {
|
||||
edges: [
|
||||
{
|
||||
node: { ...assets[0].comments.edges[0].node, asset: assets[0] },
|
||||
cursor: comments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: { ...assets[1].comments.edges[0].node, asset: assets[1] },
|
||||
cursor: comments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
hasNextPage: false,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
@@ -0,0 +1,522 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`loads more comments 1`] = `
|
||||
<div
|
||||
className="HorizontalGutter-root App-root HorizontalGutter-full"
|
||||
>
|
||||
<ul
|
||||
className="TabBar-root TabBar-primary"
|
||||
role="tablist"
|
||||
>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-COMMENTS"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-COMMENTS"
|
||||
aria-selected={false}
|
||||
className="BaseButton-root Tab-button Tab-primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
2 Comments
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-PROFILE"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-PROFILE"
|
||||
aria-selected={true}
|
||||
className="BaseButton-root Tab-button Tab-primary Tab-active"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
My Profile
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section
|
||||
aria-labelledby="tab-PROFILE"
|
||||
className="App-tabContent"
|
||||
id="tabPane-PROFILE"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Comment History
|
||||
</h1>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Joining Too",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "What's up?",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Hey!",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
|
||||
exports[`renders comment stream 1`] = `
|
||||
<div
|
||||
className="HorizontalGutter-root App-root HorizontalGutter-full"
|
||||
>
|
||||
<ul
|
||||
className="TabBar-root TabBar-primary"
|
||||
role="tablist"
|
||||
>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-COMMENTS"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-COMMENTS"
|
||||
aria-selected={false}
|
||||
className="BaseButton-root Tab-button Tab-primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
2 Comments
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-PROFILE"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-PROFILE"
|
||||
aria-selected={true}
|
||||
className="BaseButton-root Tab-button Tab-primary Tab-active"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
My Profile
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section
|
||||
aria-labelledby="tab-PROFILE"
|
||||
className="App-tabContent"
|
||||
id="tabPane-PROFILE"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Comment History
|
||||
</h1>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Joining Too",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "What's up?",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-1"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<button
|
||||
aria-controls="talk-profile-commentHistory-log"
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantOutlined Button-fullWidth"
|
||||
disabled={false}
|
||||
id="talk-profile-commentHistory-loadMore"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Load More
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,226 @@
|
||||
// Jest Snapshot v1, https://goo.gl/fbAQLP
|
||||
|
||||
exports[`renders profile 1`] = `
|
||||
<div
|
||||
className="HorizontalGutter-root App-root HorizontalGutter-full"
|
||||
>
|
||||
<ul
|
||||
className="TabBar-root TabBar-primary"
|
||||
role="tablist"
|
||||
>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-COMMENTS"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-COMMENTS"
|
||||
aria-selected={false}
|
||||
className="BaseButton-root Tab-button Tab-primary"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
2 Comments
|
||||
</button>
|
||||
</li>
|
||||
<li
|
||||
className="Tab-root"
|
||||
id="tab-PROFILE"
|
||||
role="presentation"
|
||||
>
|
||||
<button
|
||||
aria-controls="tabPane-PROFILE"
|
||||
aria-selected={true}
|
||||
className="BaseButton-root Tab-button Tab-primary Tab-active"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
role="tab"
|
||||
type="button"
|
||||
>
|
||||
My Profile
|
||||
</button>
|
||||
</li>
|
||||
</ul>
|
||||
<section
|
||||
aria-labelledby="tab-PROFILE"
|
||||
className="App-tabContent"
|
||||
id="tabPane-PROFILE"
|
||||
role="tabpanel"
|
||||
>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<div
|
||||
className="Flex-root"
|
||||
>
|
||||
<div
|
||||
className="Flex-flex Flex-halfItemGutter Flex-wrap"
|
||||
>
|
||||
<div
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
Signed in as
|
||||
<span
|
||||
className="Typography-root Typography-bodyCopyBold Typography-colorTextPrimary"
|
||||
>
|
||||
Markus
|
||||
</span>
|
||||
.
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Typography-root Typography-bodyCopy Typography-colorTextPrimary Flex-flex"
|
||||
>
|
||||
<span>
|
||||
Not you?
|
||||
</span>
|
||||
<button
|
||||
className="BaseButton-root Button-root Button-sizeSmall Button-colorPrimary Button-variantUnderlined"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
type="button"
|
||||
>
|
||||
Sign Out
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-double"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading3 Typography-colorTextPrimary"
|
||||
>
|
||||
Comment History
|
||||
</h1>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Joining Too",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-1?commentID=comment-0"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
<div
|
||||
className="HorizontalGutter-root HorizontalGutter-full"
|
||||
>
|
||||
<h1
|
||||
className="Typography-root Typography-heading4 Typography-colorTextPrimary"
|
||||
>
|
||||
Story: title
|
||||
</h1>
|
||||
<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
|
||||
className="Typography-root Typography-bodyCopy Typography-colorTextPrimary"
|
||||
>
|
||||
<div
|
||||
className="HTMLContent-root"
|
||||
dangerouslySetInnerHTML={
|
||||
Object {
|
||||
"__html": "Hey!",
|
||||
}
|
||||
}
|
||||
/>
|
||||
</div>
|
||||
<div
|
||||
className="Flex-root Flex-flex Flex-itemGutter Flex-alignCenter Flex-directionRow"
|
||||
>
|
||||
<a
|
||||
className="BaseButton-root Button-root Button-sizeRegular Button-colorRegular Button-variantUnderlined"
|
||||
href="http://localhost/assets/asset-2?commentID=comment-2"
|
||||
onBlur={[Function]}
|
||||
onClick={[Function]}
|
||||
onFocus={[Function]}
|
||||
onMouseDown={[Function]}
|
||||
onMouseOut={[Function]}
|
||||
onMouseOver={[Function]}
|
||||
onTouchEnd={[Function]}
|
||||
target="_parent"
|
||||
type="button"
|
||||
>
|
||||
<span
|
||||
aria-hidden="true"
|
||||
className="Icon-root Icon-sm"
|
||||
>
|
||||
launch
|
||||
</span>
|
||||
<span>
|
||||
View Conversation
|
||||
</span>
|
||||
</a>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
</section>
|
||||
</div>
|
||||
`;
|
||||
@@ -0,0 +1,13 @@
|
||||
import createTopLevel, { CreateParams } from "../create";
|
||||
|
||||
export default function create(params: CreateParams) {
|
||||
return createTopLevel({
|
||||
...params,
|
||||
initLocalState: (localRecord, source, environment) => {
|
||||
if (params.initLocalState) {
|
||||
localRecord.setValue("PROFILE", "activeTab");
|
||||
params.initLocalState(localRecord, source, environment);
|
||||
}
|
||||
},
|
||||
});
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { createSinonStub } from "talk-framework/testHelpers";
|
||||
|
||||
import { assets, comments, meWithComments } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
beforeEach(() => {
|
||||
const meStub = {
|
||||
...meWithComments,
|
||||
comments: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s.withArgs({ first: 5, orderBy: "CREATED_AT_DESC" }).returns({
|
||||
edges: [
|
||||
{
|
||||
node: { ...comments[0], asset: assets[0] },
|
||||
cursor: comments[0].createdAt,
|
||||
},
|
||||
{
|
||||
node: { ...comments[1], asset: assets[0] },
|
||||
cursor: comments[1].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[1].createdAt,
|
||||
hasNextPage: true,
|
||||
},
|
||||
}),
|
||||
s =>
|
||||
s
|
||||
.withArgs({
|
||||
first: 10,
|
||||
orderBy: "CREATED_AT_DESC",
|
||||
after: comments[1].createdAt,
|
||||
})
|
||||
.returns({
|
||||
edges: [
|
||||
{
|
||||
node: { ...comments[2], asset: assets[0] },
|
||||
cursor: comments[2].createdAt,
|
||||
},
|
||||
],
|
||||
pageInfo: {
|
||||
endCursor: comments[2].createdAt,
|
||||
hasNextPage: false,
|
||||
},
|
||||
})
|
||||
),
|
||||
};
|
||||
|
||||
const resolvers = {
|
||||
Query: {
|
||||
asset: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s
|
||||
.withArgs(undefined, { id: assets[0].id, url: null })
|
||||
.returns(assets[0])
|
||||
),
|
||||
me: sinon.stub().returns(meStub),
|
||||
},
|
||||
};
|
||||
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assets[0].id, "assetID");
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders comment stream", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
|
||||
it("loads more comments", async () => {
|
||||
testRenderer.root
|
||||
.findByProps({ id: "talk-profile-commentHistory-loadMore" })
|
||||
.props.onClick();
|
||||
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
import { ReactTestRenderer } from "react-test-renderer";
|
||||
import sinon from "sinon";
|
||||
|
||||
import { timeout } from "talk-common/utils";
|
||||
import { createSinonStub } from "talk-framework/testHelpers";
|
||||
|
||||
import { assets, meWithComments } from "../fixtures";
|
||||
import create from "./create";
|
||||
|
||||
let testRenderer: ReactTestRenderer;
|
||||
beforeEach(() => {
|
||||
const resolvers = {
|
||||
Query: {
|
||||
asset: createSinonStub(
|
||||
s => s.throws(),
|
||||
s =>
|
||||
s
|
||||
.withArgs(undefined, { id: assets[0].id, url: null })
|
||||
.returns(assets[0])
|
||||
),
|
||||
me: sinon.stub().returns(meWithComments),
|
||||
},
|
||||
};
|
||||
|
||||
({ testRenderer } = create({
|
||||
// Set this to true, to see graphql responses.
|
||||
logNetwork: false,
|
||||
resolvers,
|
||||
initLocalState: localRecord => {
|
||||
localRecord.setValue(assets[0].id, "assetID");
|
||||
},
|
||||
}));
|
||||
});
|
||||
|
||||
it("renders profile", async () => {
|
||||
// Wait for loading.
|
||||
await timeout();
|
||||
expect(testRenderer.toJSON()).toMatchSnapshot();
|
||||
});
|
||||
@@ -92,3 +92,4 @@ profile-historyComment-commentHistory = Comment History
|
||||
profile-historyComment-story = Story: {$title}
|
||||
profile-profileQuery-errorLoadingProfile = Error loading profile
|
||||
profile-profileQuery-assetNotFound = Asset not found
|
||||
profile-commentHistory-loadMore = Load More
|
||||
|
||||
Reference in New Issue
Block a user