decidim-comments 0.0.6 → 0.0.7

Sign up to get free protection for your applications and to get access to all the features.
Files changed (68) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +33 -0
  3. data/app/assets/javascripts/decidim/comments/bundle.js +44 -44
  4. data/app/assets/javascripts/decidim/comments/bundle.js.map +1 -1
  5. data/app/assets/javascripts/decidim/comments/comments.js.erb +2 -0
  6. data/app/frontend/application/{apollo_client.js → apollo_client.ts} +5 -5
  7. data/app/frontend/application/application.component.test.tsx +36 -0
  8. data/app/frontend/application/application.component.tsx +37 -0
  9. data/app/frontend/application/icon.component.test.tsx +49 -0
  10. data/app/frontend/application/icon.component.tsx +35 -0
  11. data/app/frontend/comments/{add_comment_form.component.test.jsx → add_comment_form.component.test.tsx} +98 -92
  12. data/app/frontend/comments/{add_comment_form.component.jsx → add_comment_form.component.tsx} +152 -153
  13. data/app/frontend/comments/{comment.component.test.jsx → comment.component.test.tsx} +59 -71
  14. data/app/frontend/comments/{comment.component.jsx → comment.component.tsx} +114 -116
  15. data/app/frontend/comments/comment_order_selector.component.test.tsx +21 -0
  16. data/app/frontend/comments/comment_order_selector.component.tsx +88 -0
  17. data/app/frontend/comments/comment_thread.component.test.tsx +65 -0
  18. data/app/frontend/comments/comment_thread.component.tsx +70 -0
  19. data/app/frontend/comments/{comments.component.test.jsx → comments.component.test.tsx} +38 -81
  20. data/app/frontend/comments/{comments.component.jsx → comments.component.tsx} +49 -63
  21. data/app/frontend/comments/down_vote_button.component.test.tsx +39 -0
  22. data/app/frontend/comments/down_vote_button.component.tsx +89 -0
  23. data/app/frontend/comments/up_vote_button.component.test.tsx +39 -0
  24. data/app/frontend/comments/up_vote_button.component.tsx +89 -0
  25. data/app/frontend/comments/vote_button.component.tsx +36 -0
  26. data/app/frontend/comments/{vote_button_component.test.jsx → vote_button_component.test.tsx} +16 -20
  27. data/app/frontend/entry.ts +19 -0
  28. data/app/frontend/{comments → fragments}/add_comment_form_commentable.fragment.graphql +1 -1
  29. data/app/frontend/{comments → fragments}/add_comment_form_session.fragment.graphql +1 -1
  30. data/app/frontend/{comments → fragments}/comment.fragment.graphql +3 -1
  31. data/app/frontend/{comments → fragments}/comment_data.fragment.graphql +6 -3
  32. data/app/frontend/{comments → fragments}/comment_thread.fragment.graphql +3 -1
  33. data/app/frontend/{comments/down_vote.fragment.graphql → fragments/down_vote_button.fragment.graphql} +2 -2
  34. data/app/frontend/{comments/up_vote.fragment.graphql → fragments/up_vote_button.fragment.graphql} +2 -2
  35. data/app/frontend/{comments/add_comment_form.mutation.graphql → mutations/add_comment.mutation.graphql} +3 -1
  36. data/app/frontend/{comments → mutations}/down_vote.mutation.graphql +3 -1
  37. data/app/frontend/{comments → mutations}/up_vote.mutation.graphql +3 -1
  38. data/app/frontend/{comments → queries}/comments.query.graphql +4 -1
  39. data/app/frontend/support/{asset_url.js → asset_url.ts} +1 -1
  40. data/app/frontend/support/{generate_comments_data.js → generate_comments_data.ts} +11 -6
  41. data/app/frontend/support/{generate_user_data.js → generate_user_data.ts} +2 -2
  42. data/app/frontend/support/{generate_user_group_data.js → generate_user_group_data.ts} +2 -2
  43. data/app/frontend/support/graphql_transformer.js +32 -0
  44. data/app/frontend/support/load_translations.ts +44 -0
  45. data/app/frontend/support/{require_all.js → require_all.ts} +1 -1
  46. data/app/frontend/support/{resolve_graphql_query.js → resolve_graphql_query.ts} +7 -7
  47. data/app/frontend/support/schema.ts +119 -0
  48. data/config/locales/eu.yml +29 -5
  49. metadata +49 -51
  50. data/app/frontend/application/application.component.jsx +0 -37
  51. data/app/frontend/application/application.component.test.jsx +0 -33
  52. data/app/frontend/application/icon.component.jsx +0 -26
  53. data/app/frontend/application/icon.component.test.jsx +0 -53
  54. data/app/frontend/comments/comment_order_selector.component.jsx +0 -72
  55. data/app/frontend/comments/comment_order_selector.component.test.jsx +0 -20
  56. data/app/frontend/comments/comment_thread.component.jsx +0 -75
  57. data/app/frontend/comments/comment_thread.component.test.jsx +0 -83
  58. data/app/frontend/comments/down_vote_button.component.jsx +0 -98
  59. data/app/frontend/comments/down_vote_button.component.test.jsx +0 -48
  60. data/app/frontend/comments/featured_comment.component.jsx +0 -23
  61. data/app/frontend/comments/featured_comment.component.test.jsx +0 -15
  62. data/app/frontend/comments/up_vote_button.component.jsx +0 -98
  63. data/app/frontend/comments/up_vote_button.component.test.jsx +0 -48
  64. data/app/frontend/comments/vote_button.component.jsx +0 -32
  65. data/app/frontend/entry.js +0 -17
  66. data/app/frontend/entry.test.js +0 -31
  67. data/app/frontend/support/load_translations.js +0 -23
  68. data/app/frontend/support/stub_component.js +0 -29
@@ -1,16 +1,24 @@
1
- import { Component, PropTypes } from 'react';
2
- import { graphql } from 'react-apollo';
3
- import gql from 'graphql-tag';
4
- import { filter } from 'graphql-anywhere';
5
- import { I18n } from 'react-i18nify';
1
+ import * as React from "react";
2
+ import { graphql } from "react-apollo";
6
3
 
7
- import Application from '../application/application.component';
4
+ import Application from "../application/application.component";
8
5
 
9
- import CommentThread from './comment_thread.component';
10
- import AddCommentForm from './add_comment_form.component';
11
- import CommentOrderSelector from './comment_order_selector.component';
6
+ import AddCommentForm from "./add_comment_form.component";
7
+ import CommentOrderSelector from "./comment_order_selector.component";
8
+ import CommentThread from "./comment_thread.component";
12
9
 
13
- import commentsQuery from './comments.query.graphql';
10
+ import {
11
+ GetCommentsQuery,
12
+ GetCommentsQueryVariables,
13
+ } from "../support/schema";
14
+
15
+ const { I18n } = require("react-i18nify");
16
+
17
+ interface CommentsProps extends GetCommentsQuery {
18
+ loading?: boolean;
19
+ orderBy: string;
20
+ reorderComments: (orderBy: string) => void;
21
+ };
14
22
 
15
23
  /**
16
24
  * The core class of the Decidim Comments engine.
@@ -19,14 +27,22 @@ import commentsQuery from './comments.query.graphql';
19
27
  * @class
20
28
  * @augments Component
21
29
  */
22
- export class Comments extends Component {
23
- render() {
30
+ export class Comments extends React.Component<CommentsProps, undefined> {
31
+ public static defaultProps: any = {
32
+ loading: false,
33
+ session: null,
34
+ commentable: {
35
+ comments: [],
36
+ },
37
+ };
38
+
39
+ public render() {
24
40
  const { commentable: { comments }, reorderComments, orderBy, loading } = this.props;
25
41
  let commentClasses = "comments";
26
42
  let commentHeader = I18n.t("components.comments.title", { count: comments.length });
27
43
 
28
44
  if (loading) {
29
- commentClasses += " loading-comments"
45
+ commentClasses += " loading-comments";
30
46
  commentHeader = I18n.t("components.comments.loading");
31
47
  }
32
48
 
@@ -35,7 +51,7 @@ export class Comments extends Component {
35
51
  <section className={commentClasses}>
36
52
  <div className="row collapse order-by">
37
53
  <h2 className="order-by__text section-heading">
38
- { commentHeader }
54
+ {commentHeader}
39
55
  </h2>
40
56
  <CommentOrderSelector
41
57
  reorderComments={reorderComments}
@@ -55,13 +71,13 @@ export class Comments extends Component {
55
71
  * @private
56
72
  * @returns {Void|DOMElement} - A warning message or nothing.
57
73
  */
58
- _renderBlockedCommentsWarning() {
74
+ private _renderBlockedCommentsWarning() {
59
75
  const { commentable: { acceptsNewComments } } = this.props;
60
76
 
61
77
  if (!acceptsNewComments) {
62
78
  return (
63
79
  <div className="callout warning">
64
- <p>{ I18n.t("components.comments.blocked_comments_warning") }</p>
80
+ <p>{I18n.t("components.comments.blocked_comments_warning")}</p>
65
81
  </div>
66
82
  );
67
83
  }
@@ -74,17 +90,17 @@ export class Comments extends Component {
74
90
  * @private
75
91
  * @returns {ReactComponent[]} - A collection of CommentThread components
76
92
  */
77
- _renderCommentThreads() {
93
+ private _renderCommentThreads() {
78
94
  const { session, commentable: { comments, commentsHaveVotes } } = this.props;
79
95
 
80
96
  return comments.map((comment) => (
81
97
  <CommentThread
82
98
  key={comment.id}
83
- comment={filter(CommentThread.fragments.comment, comment)}
99
+ comment={comment}
84
100
  session={session}
85
101
  votable={commentsHaveVotes}
86
102
  />
87
- ))
103
+ ));
88
104
  }
89
105
 
90
106
  /**
@@ -92,7 +108,7 @@ export class Comments extends Component {
92
108
  * @private
93
109
  * @returns {Void|ReactComponent} - A AddCommentForm component or nothing
94
110
  */
95
- _renderAddCommentForm() {
111
+ private _renderAddCommentForm() {
96
112
  const { session, commentable } = this.props;
97
113
  const { acceptsNewComments, commentsHaveAlignment } = commentable;
98
114
 
@@ -110,31 +126,6 @@ export class Comments extends Component {
110
126
  }
111
127
  }
112
128
 
113
- Comments.propTypes = {
114
- loading: PropTypes.bool,
115
- session: PropTypes.shape({
116
- user: PropTypes.any.isRequired
117
- }),
118
- commentable: PropTypes.shape({
119
- acceptsNewComments: PropTypes.bool,
120
- commentsHaveAlignment: PropTypes.bool,
121
- commentsHaveVotes: PropTypes.bool,
122
- comments: PropTypes.arrayOf(PropTypes.shape({
123
- id: PropTypes.string.isRequired
124
- }))
125
- }),
126
- orderBy: PropTypes.string.isRequired,
127
- reorderComments: PropTypes.func.isRequired
128
- };
129
-
130
- Comments.defaultProps = {
131
- loading: false,
132
- session: null,
133
- commentable: {
134
- comments: []
135
- }
136
- };
137
-
138
129
  /**
139
130
  * Wrap the Comments component with a GraphQL query and children
140
131
  * fragments.
@@ -142,34 +133,35 @@ Comments.defaultProps = {
142
133
 
143
134
  window.Comments = Comments;
144
135
 
145
- const CommentsWithData = graphql(gql`
146
- ${commentsQuery}
147
- ${AddCommentForm.fragments.session}
148
- ${AddCommentForm.fragments.commentable}
149
- ${CommentThread.fragments.comment}
150
- `, {
136
+ export const commentsQuery = require("../queries/comments.query.graphql");
137
+
138
+ const CommentsWithData: any = graphql(commentsQuery, {
151
139
  options: {
152
- pollInterval: 15000
140
+ pollInterval: 15000,
153
141
  },
154
142
  props: ({ ownProps, data: { loading, session, commentable, refetch }}) => ({
155
143
  loading,
156
144
  session,
157
145
  commentable,
158
146
  orderBy: ownProps.orderBy,
159
- reorderComments: (orderBy) => {
147
+ reorderComments: (orderBy: string) => {
160
148
  return refetch({
161
- orderBy
149
+ orderBy,
162
150
  });
163
- }
164
- })
151
+ },
152
+ }),
165
153
  })(Comments);
166
154
 
155
+ export interface CommentsApplicationProps extends GetCommentsQueryVariables {
156
+ locale: string;
157
+ }
158
+
167
159
  /**
168
160
  * Wrap the CommentsWithData component within an Application component to
169
161
  * connect it with Apollo client and store.
170
162
  * @returns {ReactComponent} - A component wrapped within an Application component
171
163
  */
172
- const CommentsApplication = ({ locale, commentableId, commentableType }) => (
164
+ const CommentsApplication: React.SFC<CommentsApplicationProps> = ({ locale, commentableId, commentableType }) => (
173
165
  <Application locale={locale}>
174
166
  <CommentsWithData
175
167
  commentableId={commentableId}
@@ -179,10 +171,4 @@ const CommentsApplication = ({ locale, commentableId, commentableType }) => (
179
171
  </Application>
180
172
  );
181
173
 
182
- CommentsApplication.propTypes = {
183
- locale: PropTypes.string.isRequired,
184
- commentableId: PropTypes.string.isRequired,
185
- commentableType: PropTypes.string.isRequired
186
- };
187
-
188
174
  export default CommentsApplication;
@@ -0,0 +1,39 @@
1
+ import { shallow } from "enzyme";
2
+ import * as React from "react";
3
+
4
+ import { DownVoteButton } from "./down_vote_button.component";
5
+ import VoteButton from "./vote_button.component";
6
+
7
+ import generateCommentsData from "../support/generate_comments_data";
8
+
9
+ import { DownVoteFragment } from "../support/schema";
10
+
11
+ describe("<DownVoteButton />", () => {
12
+ let comment: DownVoteFragment;
13
+ const downVote = jasmine.createSpy("downVote");
14
+
15
+ beforeEach(() => {
16
+ let commentsData = generateCommentsData(1);
17
+
18
+ comment = commentsData[0];
19
+ });
20
+
21
+ it("should render a VoteButton component with the correct props", () => {
22
+ const wrapper = shallow(<DownVoteButton comment={comment} downVote={downVote} />);
23
+ expect(wrapper.find(VoteButton).prop("buttonClassName")).toEqual("comment__votes--down");
24
+ expect(wrapper.find(VoteButton).prop("iconName")).toEqual("icon-chevron-bottom");
25
+ expect(wrapper.find(VoteButton).prop("votes")).toEqual(comment.downVotes);
26
+ });
27
+
28
+ it("should pass disabled prop as true if comment downVoted is true", () => {
29
+ comment.downVoted = true;
30
+ const wrapper = shallow(<DownVoteButton comment={comment} downVote={downVote} />);
31
+ expect(wrapper.find(VoteButton).prop("disabled")).toBeTruthy();
32
+ });
33
+
34
+ it("should pass disabled prop as true if comment downVoted is true", () => {
35
+ comment.downVoted = true;
36
+ const wrapper = shallow(<DownVoteButton comment={comment} downVote={downVote} />);
37
+ expect(wrapper.find(VoteButton).prop("disabled")).toBeTruthy();
38
+ });
39
+ });
@@ -0,0 +1,89 @@
1
+ import * as React from "react";
2
+ import { graphql } from "react-apollo";
3
+
4
+ import VoteButton from "./vote_button.component";
5
+
6
+ import {
7
+ CommentFragment,
8
+ DownVoteFragment,
9
+ DownVoteMutation,
10
+ GetCommentsQuery,
11
+ } from "../support/schema";
12
+
13
+ interface DownVoteButtonProps {
14
+ comment: DownVoteFragment;
15
+ downVote?: () => void;
16
+ }
17
+
18
+ export const DownVoteButton: React.SFC<DownVoteButtonProps> = ({
19
+ comment: { downVotes, upVoted, downVoted },
20
+ downVote,
21
+ }) => {
22
+ let selectedClass = "";
23
+
24
+ if (downVoted) {
25
+ selectedClass = "is-vote-selected";
26
+ } else if (upVoted) {
27
+ selectedClass = "is-vote-notselected";
28
+ }
29
+
30
+ return (
31
+ <VoteButton
32
+ buttonClassName="comment__votes--down"
33
+ iconName="icon-chevron-bottom"
34
+ votes={downVotes}
35
+ voteAction={downVote}
36
+ disabled={upVoted || downVoted}
37
+ selectedClass={selectedClass}
38
+ />
39
+ );
40
+ };
41
+
42
+ const downVoteMutation = require("../mutations/down_vote.mutation.graphql");
43
+
44
+ const DownVoteButtonWithMutation = graphql(downVoteMutation, {
45
+ props: ({ ownProps, mutate }) => ({
46
+ downVote: () => mutate({
47
+ variables: {
48
+ id: ownProps.comment.id,
49
+ },
50
+ optimisticResponse: {
51
+ __typename: "Mutation",
52
+ comment: {
53
+ __typename: "CommentMutation",
54
+ downVote: {
55
+ __typename: "Comment",
56
+ ...ownProps.comment,
57
+ downVotes: ownProps.comment.downVotes + 1,
58
+ downVoted: true,
59
+ },
60
+ },
61
+ },
62
+ updateQueries: {
63
+ GetComments: (prev: GetCommentsQuery, { mutationResult: { data } }: { mutationResult: { data: DownVoteMutation }}) => {
64
+ const commentReducer = (comment: CommentFragment): CommentFragment => {
65
+ const replies = comment.comments || [];
66
+
67
+ if (comment.id === ownProps.comment.id && data.comment) {
68
+ return data.comment.downVote;
69
+ }
70
+ return {
71
+ ...comment,
72
+ comments: replies.map(commentReducer),
73
+ };
74
+ };
75
+
76
+ return {
77
+ ...prev,
78
+ commentable: {
79
+ ...prev.commentable,
80
+ comments: prev.commentable.comments.map(commentReducer),
81
+ },
82
+ };
83
+ },
84
+ },
85
+ }),
86
+ }),
87
+ })(DownVoteButton);
88
+
89
+ export default DownVoteButtonWithMutation;
@@ -0,0 +1,39 @@
1
+ import { shallow } from "enzyme";
2
+ import * as React from "react";
3
+
4
+ import { UpVoteButton } from "./up_vote_button.component";
5
+ import VoteButton from "./vote_button.component";
6
+
7
+ import generateCommentsData from "../support/generate_comments_data";
8
+
9
+ import { UpVoteFragment } from "../support/schema";
10
+
11
+ describe("<UpVoteButton />", () => {
12
+ let comment: UpVoteFragment;
13
+ const upVote = jasmine.createSpy("upVote");
14
+
15
+ beforeEach(() => {
16
+ let commentsData = generateCommentsData(1);
17
+
18
+ comment = commentsData[0];
19
+ });
20
+
21
+ it("should render a VoteButton component with the correct props", () => {
22
+ const wrapper = shallow(<UpVoteButton comment={comment} upVote={upVote} />);
23
+ expect(wrapper.find(VoteButton).prop("buttonClassName")).toEqual("comment__votes--up");
24
+ expect(wrapper.find(VoteButton).prop("iconName")).toEqual("icon-chevron-top");
25
+ expect(wrapper.find(VoteButton).prop("votes")).toEqual(comment.upVotes);
26
+ });
27
+
28
+ it("should pass disabled prop as true if comment upVoted is true", () => {
29
+ comment.upVoted = true;
30
+ const wrapper = shallow(<UpVoteButton comment={comment} upVote={upVote} />);
31
+ expect(wrapper.find(VoteButton).prop("disabled")).toBeTruthy();
32
+ });
33
+
34
+ it("should pass disabled prop as true if comment downVoted is true", () => {
35
+ comment.downVoted = true;
36
+ const wrapper = shallow(<UpVoteButton comment={comment} upVote={upVote} />);
37
+ expect(wrapper.find(VoteButton).prop("disabled")).toBeTruthy();
38
+ });
39
+ });
@@ -0,0 +1,89 @@
1
+ import * as React from "react";
2
+ import { graphql } from "react-apollo";
3
+
4
+ import VoteButton from "./vote_button.component";
5
+
6
+ import {
7
+ CommentFragment,
8
+ GetCommentsQuery,
9
+ UpVoteFragment,
10
+ UpVoteMutation,
11
+ } from "../support/schema";
12
+
13
+ interface UpVoteButtonProps {
14
+ comment: UpVoteFragment;
15
+ upVote?: () => void;
16
+ }
17
+
18
+ export const UpVoteButton: React.SFC<UpVoteButtonProps> = ({
19
+ comment: { upVotes, upVoted, downVoted },
20
+ upVote,
21
+ }) => {
22
+ let selectedClass = "";
23
+
24
+ if (upVoted) {
25
+ selectedClass = "is-vote-selected";
26
+ } else if (downVoted) {
27
+ selectedClass = "is-vote-notselected";
28
+ }
29
+
30
+ return (
31
+ <VoteButton
32
+ buttonClassName="comment__votes--up"
33
+ iconName="icon-chevron-top"
34
+ votes={upVotes}
35
+ voteAction={upVote}
36
+ disabled={upVoted || downVoted}
37
+ selectedClass={selectedClass}
38
+ />
39
+ );
40
+ };
41
+
42
+ const upVoteMutation = require("../mutations/up_vote.mutation.graphql");
43
+
44
+ const UpVoteButtonWithMutation = graphql(upVoteMutation, {
45
+ props: ({ ownProps, mutate }) => ({
46
+ upVote: () => mutate({
47
+ variables: {
48
+ id: ownProps.comment.id,
49
+ },
50
+ optimisticResponse: {
51
+ __typename: "Mutation",
52
+ comment: {
53
+ __typename: "CommentMutation",
54
+ upVote: {
55
+ __typename: "Comment",
56
+ ...ownProps.comment,
57
+ upVotes: ownProps.comment.upVotes + 1,
58
+ upVoted: true,
59
+ },
60
+ },
61
+ },
62
+ updateQueries: {
63
+ GetComments: (prev: GetCommentsQuery, { mutationResult: { data } }: { mutationResult: { data: UpVoteMutation}}) => {
64
+ const commentReducer = (comment: CommentFragment): CommentFragment => {
65
+ const replies = comment.comments || [];
66
+
67
+ if (comment.id === ownProps.comment.id && data.comment) {
68
+ return data.comment.upVote;
69
+ }
70
+ return {
71
+ ...comment,
72
+ comments: replies.map(commentReducer),
73
+ };
74
+ };
75
+
76
+ return {
77
+ ...prev,
78
+ commentable: {
79
+ ...prev.commentable,
80
+ comments: prev.commentable.comments.map(commentReducer),
81
+ },
82
+ };
83
+ },
84
+ },
85
+ }),
86
+ }),
87
+ })(UpVoteButton);
88
+
89
+ export default UpVoteButtonWithMutation;