From 2e6237b9d9411dab13eb8ac721e3bb7ea10fa3cb Mon Sep 17 00:00:00 2001 From: Kiwi Date: Fri, 19 Oct 2018 19:54:40 +0200 Subject: [PATCH] [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 --- src/core/client/framework/helpers/getMe.ts | 9 +- .../client/framework/helpers/getMeSourceID.ts | 10 + src/core/client/framework/helpers/index.ts | 1 + .../framework/lib/network/createNetwork.ts | 1 + .../framework/testHelpers/denormalize.ts | 5 +- .../stream/mutations/CreateCommentMutation.ts | 25 +- .../components/CommentHistory.spec.tsx | 47 ++ .../profile/components/CommentHistory.tsx | 50 ++ .../profile/components/CommentsHistory.tsx | 36 -- .../components/HistoryComment.spec.tsx | 25 + .../tabs/profile/components/Profile.spec.tsx | 18 + .../tabs/profile/components/Profile.tsx | 10 +- .../CommentHistory.spec.tsx.snap | 131 +++++ .../HistoryComment.spec.tsx.snap | 75 +++ .../__snapshots__/Profile.spec.tsx.snap | 15 + .../containers/CommentHistoryContainer.tsx | 124 +++++ .../containers/CommentsHistoryContainer.tsx | 70 --- .../containers/HistoryCommentContainer.tsx | 66 +++ .../profile/containers/ProfileContainer.tsx | 4 +- .../tabs/profile/queries/ProfileQuery.tsx | 4 - src/core/client/stream/test/fixtures.ts | 34 ++ .../__snapshots__/loadMore.spec.tsx.snap | 522 ++++++++++++++++++ .../__snapshots__/renderProfile.spec.tsx.snap | 226 ++++++++ src/core/client/stream/test/profile/create.ts | 13 + .../stream/test/profile/loadMore.spec.tsx | 92 +++ .../test/profile/renderProfile.spec.tsx | 39 ++ src/locales/en-US/stream.ftl | 1 + 27 files changed, 1521 insertions(+), 132 deletions(-) create mode 100644 src/core/client/framework/helpers/getMeSourceID.ts create mode 100644 src/core/client/stream/tabs/profile/components/CommentHistory.spec.tsx create mode 100644 src/core/client/stream/tabs/profile/components/CommentHistory.tsx delete mode 100644 src/core/client/stream/tabs/profile/components/CommentsHistory.tsx create mode 100644 src/core/client/stream/tabs/profile/components/HistoryComment.spec.tsx create mode 100644 src/core/client/stream/tabs/profile/components/Profile.spec.tsx create mode 100644 src/core/client/stream/tabs/profile/components/__snapshots__/CommentHistory.spec.tsx.snap create mode 100644 src/core/client/stream/tabs/profile/components/__snapshots__/HistoryComment.spec.tsx.snap create mode 100644 src/core/client/stream/tabs/profile/components/__snapshots__/Profile.spec.tsx.snap create mode 100644 src/core/client/stream/tabs/profile/containers/CommentHistoryContainer.tsx delete mode 100644 src/core/client/stream/tabs/profile/containers/CommentsHistoryContainer.tsx create mode 100644 src/core/client/stream/tabs/profile/containers/HistoryCommentContainer.tsx create mode 100644 src/core/client/stream/test/profile/__snapshots__/loadMore.spec.tsx.snap create mode 100644 src/core/client/stream/test/profile/__snapshots__/renderProfile.spec.tsx.snap create mode 100644 src/core/client/stream/test/profile/create.ts create mode 100644 src/core/client/stream/test/profile/loadMore.spec.tsx create mode 100644 src/core/client/stream/test/profile/renderProfile.spec.tsx diff --git a/src/core/client/framework/helpers/getMe.ts b/src/core/client/framework/helpers/getMe.ts index d989b5be2..f10500db0 100644 --- a/src/core/client/framework/helpers/getMe.ts +++ b/src/core/client/framework/helpers/getMe.ts @@ -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)!; } diff --git a/src/core/client/framework/helpers/getMeSourceID.ts b/src/core/client/framework/helpers/getMeSourceID.ts new file mode 100644 index 000000000..0bb386ae4 --- /dev/null +++ b/src/core/client/framework/helpers/getMeSourceID.ts @@ -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; +} diff --git a/src/core/client/framework/helpers/index.ts b/src/core/client/framework/helpers/index.ts index 99351880d..583e3e3ad 100644 --- a/src/core/client/framework/helpers/index.ts +++ b/src/core/client/framework/helpers/index.ts @@ -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"; diff --git a/src/core/client/framework/lib/network/createNetwork.ts b/src/core/client/framework/lib/network/createNetwork.ts index c37eea89c..effd45b99 100644 --- a/src/core/client/framework/lib/network/createNetwork.ts +++ b/src/core/client/framework/lib/network/createNetwork.ts @@ -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), diff --git a/src/core/client/framework/testHelpers/denormalize.ts b/src/core/client/framework/testHelpers/denormalize.ts index 7b3bbbcdf..9c3567266 100644 --- a/src/core/client/framework/testHelpers/denormalize.ts +++ b/src/core/client/framework/testHelpers/denormalize.ts @@ -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, diff --git a/src/core/client/stream/mutations/CreateCommentMutation.ts b/src/core/client/stream/mutations/CreateCommentMutation.ts index 9ad37a771..7b9134e56 100644 --- a/src/core/client/stream/mutations/CreateCommentMutation.ts +++ b/src/core/client/stream/mutations/CreateCommentMutation.ts @@ -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); }, }); } diff --git a/src/core/client/stream/tabs/profile/components/CommentHistory.spec.tsx b/src/core/client/stream/tabs/profile/components/CommentHistory.spec.tsx new file mode 100644 index 000000000..f9bde3692 --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/CommentHistory.spec.tsx @@ -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 = { + asset: {}, + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onLoadMore: noop, + hasMore: false, + disableLoadMore: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); + +describe("has more", () => { + it("renders correctly", () => { + const props: PropTypesOf = { + asset: {}, + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onLoadMore: noop, + hasMore: true, + disableLoadMore: false, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); + it("disables load more", () => { + const props: PropTypesOf = { + asset: {}, + comments: [{ id: "comment-1" }, { id: "comment-2" }], + onLoadMore: noop, + hasMore: true, + disableLoadMore: true, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); + }); +}); diff --git a/src/core/client/stream/tabs/profile/components/CommentHistory.tsx b/src/core/client/stream/tabs/profile/components/CommentHistory.tsx new file mode 100644 index 000000000..50648f246 --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/CommentHistory.tsx @@ -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["asset"]; + comments: Array< + { id: string } & PropTypesOf["comment"] + >; + onLoadMore?: () => void; + hasMore?: boolean; + disableLoadMore?: boolean; +} + +const CommentHistory: StatelessComponent = props => { + return ( + + + Comment History + + {props.comments.map(comment => ( + + ))} + {props.hasMore && ( + + + + )} + + ); +}; + +export default CommentHistory; diff --git a/src/core/client/stream/tabs/profile/components/CommentsHistory.tsx b/src/core/client/stream/tabs/profile/components/CommentsHistory.tsx deleted file mode 100644 index 654e0daf4..000000000 --- a/src/core/client/stream/tabs/profile/components/CommentsHistory.tsx +++ /dev/null @@ -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 = props => { - return ( - - - Comment History - - {props.comments.map(comment => ( - - ))} - - ); -}; - -export default CommentsHistory; diff --git a/src/core/client/stream/tabs/profile/components/HistoryComment.spec.tsx b/src/core/client/stream/tabs/profile/components/HistoryComment.spec.tsx new file mode 100644 index 000000000..1791765b6 --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/HistoryComment.spec.tsx @@ -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 = { + 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(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/profile/components/Profile.spec.tsx b/src/core/client/stream/tabs/profile/components/Profile.spec.tsx new file mode 100644 index 000000000..c02477631 --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/Profile.spec.tsx @@ -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 = { + asset: {}, + me: {}, + }; + const wrapper = shallow(); + expect(wrapper).toMatchSnapshot(); +}); diff --git a/src/core/client/stream/tabs/profile/components/Profile.tsx b/src/core/client/stream/tabs/profile/components/Profile.tsx index 04e448a23..58df61b21 100644 --- a/src/core/client/stream/tabs/profile/components/Profile.tsx +++ b/src/core/client/stream/tabs/profile/components/Profile.tsx @@ -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["asset"]; + asset: PropTypesOf["asset"]; me: PropTypesOf["me"] & - PropTypesOf["me"]; + PropTypesOf["me"]; } const Profile: StatelessComponent = props => { return ( - {props.me && ( - - )} + ); }; diff --git a/src/core/client/stream/tabs/profile/components/__snapshots__/CommentHistory.spec.tsx.snap b/src/core/client/stream/tabs/profile/components/__snapshots__/CommentHistory.spec.tsx.snap new file mode 100644 index 000000000..ec5b69a83 --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/__snapshots__/CommentHistory.spec.tsx.snap @@ -0,0 +1,131 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`has more disables load more 1`] = ` + + + + Comment History + + + + + + + Load More + + + +`; + +exports[`has more renders correctly 1`] = ` + + + + Comment History + + + + + + + Load More + + + +`; + +exports[`renders correctly 1`] = ` + + + + Comment History + + + + + +`; diff --git a/src/core/client/stream/tabs/profile/components/__snapshots__/HistoryComment.spec.tsx.snap b/src/core/client/stream/tabs/profile/components/__snapshots__/HistoryComment.spec.tsx.snap new file mode 100644 index 000000000..9c4dc16ae --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/__snapshots__/HistoryComment.spec.tsx.snap @@ -0,0 +1,75 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + Story: {$title} + + + + 2018-07-06T18:24:00.000Z + + + + Hello World + + + +
+ + reply + + + + Replies {$replyCount} + + +
+ + + launch + + + + View Conversation + + + +
+ +
+
+
+`; diff --git a/src/core/client/stream/tabs/profile/components/__snapshots__/Profile.spec.tsx.snap b/src/core/client/stream/tabs/profile/components/__snapshots__/Profile.spec.tsx.snap new file mode 100644 index 000000000..ddf5b290a --- /dev/null +++ b/src/core/client/stream/tabs/profile/components/__snapshots__/Profile.spec.tsx.snap @@ -0,0 +1,15 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders correctly 1`] = ` + + + + +`; diff --git a/src/core/client/stream/tabs/profile/containers/CommentHistoryContainer.tsx b/src/core/client/stream/tabs/profile/containers/CommentHistoryContainer.tsx new file mode 100644 index 000000000..8c10ee259 --- /dev/null +++ b/src/core/client/stream/tabs/profile/containers/CommentHistoryContainer.tsx @@ -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 ( + + ); + } + + 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; diff --git a/src/core/client/stream/tabs/profile/containers/CommentsHistoryContainer.tsx b/src/core/client/stream/tabs/profile/containers/CommentsHistoryContainer.tsx deleted file mode 100644 index 3056a488b..000000000 --- a/src/core/client/stream/tabs/profile/containers/CommentsHistoryContainer.tsx +++ /dev/null @@ -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 ; - } -} - -const enhanced = withSetCommentIDMutation( - withFragmentContainer({ - 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; diff --git a/src/core/client/stream/tabs/profile/containers/HistoryCommentContainer.tsx b/src/core/client/stream/tabs/profile/containers/HistoryCommentContainer.tsx new file mode 100644 index 000000000..8e0a21cca --- /dev/null +++ b/src/core/client/stream/tabs/profile/containers/HistoryCommentContainer.tsx @@ -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 ( + + ); + } +} + +const enhanced = withSetCommentIDMutation( + withFragmentContainer({ + 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; diff --git a/src/core/client/stream/tabs/profile/containers/ProfileContainer.tsx b/src/core/client/stream/tabs/profile/containers/ProfileContainer.tsx index 716ce75bc..390110f27 100644 --- a/src/core/client/stream/tabs/profile/containers/ProfileContainer.tsx +++ b/src/core/client/stream/tabs/profile/containers/ProfileContainer.tsx @@ -20,13 +20,13 @@ export class StreamContainer extends React.Component { const enhanced = withFragmentContainer({ 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); diff --git a/src/core/client/stream/tabs/profile/queries/ProfileQuery.tsx b/src/core/client/stream/tabs/profile/queries/ProfileQuery.tsx index 8eef92f6d..e98212f77 100644 --- a/src/core/client/stream/tabs/profile/queries/ProfileQuery.tsx +++ b/src/core/client/stream/tabs/profile/queries/ProfileQuery.tsx @@ -64,10 +64,6 @@ const ProfileQuery: StatelessComponent = ({ assetID, assetURL, }} - cacheConfig={{ - // TODO: enable caching after mutations are adapted. - force: true, - }} render={render} /> ); diff --git a/src/core/client/stream/test/fixtures.ts b/src/core/client/stream/test/fixtures.ts index 04c777810..dbaf708b3 100644 --- a/src/core/client/stream/test/fixtures.ts +++ b/src/core/client/stream/test/fixtures.ts @@ -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, + }, + }, +}; diff --git a/src/core/client/stream/test/profile/__snapshots__/loadMore.spec.tsx.snap b/src/core/client/stream/test/profile/__snapshots__/loadMore.spec.tsx.snap new file mode 100644 index 000000000..59976be4e --- /dev/null +++ b/src/core/client/stream/test/profile/__snapshots__/loadMore.spec.tsx.snap @@ -0,0 +1,522 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`loads more comments 1`] = ` +
+
    + + +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+

+ Comment History +

+
+

+ Story: ⁨title⁩ +

+ + +
+

+ Story: ⁨title⁩ +

+ + +
+

+ Story: ⁨title⁩ +

+ + +
+
+
+
+`; + +exports[`renders comment stream 1`] = ` +
+
    + + +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+

+ Comment History +

+
+

+ Story: ⁨title⁩ +

+ + +
+

+ Story: ⁨title⁩ +

+ + + +
+
+
+
+`; diff --git a/src/core/client/stream/test/profile/__snapshots__/renderProfile.spec.tsx.snap b/src/core/client/stream/test/profile/__snapshots__/renderProfile.spec.tsx.snap new file mode 100644 index 000000000..d406b1c47 --- /dev/null +++ b/src/core/client/stream/test/profile/__snapshots__/renderProfile.spec.tsx.snap @@ -0,0 +1,226 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`renders profile 1`] = ` +
+
    + + +
+
+
+
+
+
+ Signed in as + + Markus + + . +
+
+ + Not you?  + + +
+
+
+
+

+ Comment History +

+
+

+ Story: ⁨title⁩ +

+ + +
+

+ Story: ⁨title⁩ +

+ + +
+
+
+
+`; diff --git a/src/core/client/stream/test/profile/create.ts b/src/core/client/stream/test/profile/create.ts new file mode 100644 index 000000000..377a8caee --- /dev/null +++ b/src/core/client/stream/test/profile/create.ts @@ -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); + } + }, + }); +} diff --git a/src/core/client/stream/test/profile/loadMore.spec.tsx b/src/core/client/stream/test/profile/loadMore.spec.tsx new file mode 100644 index 000000000..9417ed34c --- /dev/null +++ b/src/core/client/stream/test/profile/loadMore.spec.tsx @@ -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(); +}); diff --git a/src/core/client/stream/test/profile/renderProfile.spec.tsx b/src/core/client/stream/test/profile/renderProfile.spec.tsx new file mode 100644 index 000000000..519febb13 --- /dev/null +++ b/src/core/client/stream/test/profile/renderProfile.spec.tsx @@ -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(); +}); diff --git a/src/locales/en-US/stream.ftl b/src/locales/en-US/stream.ftl index ceb0ce01c..89929f3f2 100644 --- a/src/locales/en-US/stream.ftl +++ b/src/locales/en-US/stream.ftl @@ -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