decidim-comments 0.0.6 → 0.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
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;