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
@@ -0,0 +1,36 @@
1
+ import * as React from "react";
2
+ import Icon from "../application/icon.component";
3
+
4
+ interface VoteButtonProps {
5
+ buttonClassName: string;
6
+ iconName: string;
7
+ votes: number;
8
+ voteAction?: () => void;
9
+ disabled?: boolean;
10
+ selectedClass?: string;
11
+ }
12
+
13
+ const VoteButton: React.SFC<VoteButtonProps> = ({
14
+ buttonClassName,
15
+ iconName,
16
+ votes,
17
+ voteAction,
18
+ disabled,
19
+ selectedClass,
20
+ }) => (
21
+ <button
22
+ className={`${buttonClassName} ${selectedClass}`}
23
+ onClick={voteAction}
24
+ disabled={disabled}
25
+ >
26
+ <Icon name={iconName} iconExtraClassName="icon--small" />
27
+ {` ${votes}`}
28
+ </button>
29
+ );
30
+
31
+ VoteButton.defaultProps = {
32
+ selectedClass: "selected",
33
+ disabled: false,
34
+ };
35
+
36
+ export default VoteButton;
@@ -1,43 +1,39 @@
1
- /* eslint-disable no-unused-expressions */
2
- import { shallow } from 'enzyme';
3
-
4
- import VoteButton from './vote_button.component';
5
- import Icon from '../application/icon.component';
6
-
7
- import stubComponent from '../support/stub_component';
1
+ import { shallow } from "enzyme";
2
+ import * as React from "react";
3
+ import Icon from "../application/icon.component";
4
+ import VoteButton from "./vote_button.component";
8
5
 
9
6
  describe("<VoteButton />", () => {
10
- const voteAction = sinon.spy();
11
- stubComponent(Icon);
7
+ const voteAction: jasmine.Spy = jasmine.createSpy("voteAction");
12
8
 
13
9
  it("should render the number of votes passed as a prop", () => {
14
10
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
15
- expect(wrapper.find('button').text()).to.match(/10/);
11
+ expect(wrapper.find("button").text()).toMatch(/10/);
16
12
  });
17
13
 
18
14
  it("should render a button with the given buttonClassName", () => {
19
15
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
20
- expect(wrapper.find('button.vote-button')).to.be.present();
16
+ expect(wrapper.find("button.vote-button").exists()).toBeTruthy();
21
17
  });
22
18
 
23
19
  it("should render a Icon component with the correct name prop", () => {
24
20
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
25
- expect(wrapper.find(Icon)).to.have.prop("name").equal('vote-icon');
21
+ expect(wrapper.find(Icon).prop("name")).toEqual("vote-icon");
26
22
  });
27
23
 
28
24
  it("should call the voteAction prop on click", () => {
29
25
  const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} />);
30
- wrapper.find('button').simulate('click');
31
- expect(voteAction).to.have.been.called;
26
+ wrapper.find("button").simulate("click");
27
+ expect(voteAction).toHaveBeenCalled();
32
28
  });
33
29
 
34
30
  it("should disable the button based on the disabled prop", () => {
35
- const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled />);
36
- expect(wrapper.find('button')).to.be.disabled();
37
- })
31
+ const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled={true} />);
32
+ expect(wrapper.find("button").props()).toHaveProperty("disabled");
33
+ });
38
34
 
39
35
  it("should render a button with the given selectedClass", () => {
40
- const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled selectedClass="is-vote-selected" />);
41
- expect(wrapper.find('.is-vote-selected')).to.be.present();
42
- })
36
+ const wrapper = shallow(<VoteButton votes={10} buttonClassName="vote-button" iconName="vote-icon" voteAction={voteAction} disabled={true} selectedClass="is-vote-selected" />);
37
+ expect(wrapper.find(".is-vote-selected").exists()).toBeTruthy();
38
+ });
43
39
  });
@@ -0,0 +1,19 @@
1
+ import * as React from "react";
2
+ import * as ReactDOM from "react-dom";
3
+
4
+ import Comments, { CommentsApplicationProps } from "./comments/comments.component";
5
+ import loadTranslations from "./support/load_translations";
6
+
7
+ window.DecidimComments = window.DecidimComments || {};
8
+
9
+ window.DecidimComments.renderCommentsComponent = (nodeId: string, props: CommentsApplicationProps) => {
10
+ let node = window.$(`#${nodeId}`)[0];
11
+
12
+ ReactDOM.render(
13
+ React.createElement(Comments, props),
14
+ node,
15
+ );
16
+ };
17
+
18
+ // Load component locales from yaml files
19
+ loadTranslations();
@@ -1,4 +1,4 @@
1
1
  fragment AddCommentFormCommentable on Commentable {
2
2
  id
3
3
  type
4
- }
4
+ }
@@ -3,4 +3,4 @@ fragment AddCommentFormSession on Session {
3
3
  id
4
4
  name
5
5
  }
6
- }
6
+ }
@@ -1,3 +1,5 @@
1
+ #import "../fragments/comment_data.fragment.graphql"
2
+
1
3
  fragment Comment on Comment {
2
4
  ...CommentData
3
5
  comments {
@@ -9,4 +11,4 @@ fragment Comment on Comment {
9
11
  }
10
12
  }
11
13
  }
12
- }
14
+ }
@@ -1,3 +1,6 @@
1
+ #import "../fragments/up_vote_button.fragment.graphql"
2
+ #import "../fragments/down_vote_button.fragment.graphql"
3
+
1
4
  fragment CommentData on Comment {
2
5
  id
3
6
  sgid
@@ -12,6 +15,6 @@ fragment CommentData on Comment {
12
15
  acceptsNewComments
13
16
  alignment
14
17
  alreadyReported
15
- ...UpVote
16
- ...DownVote
17
- }
18
+ ...UpVoteButton
19
+ ...DownVoteButton
20
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  fragment CommentThread on Comment {
2
4
  author {
3
5
  name
4
6
  }
5
7
  hasComments
6
8
  ...Comment
7
- }
9
+ }
@@ -1,6 +1,6 @@
1
- fragment DownVote on Comment {
1
+ fragment DownVoteButton on Comment {
2
2
  id
3
3
  downVotes
4
4
  downVoted
5
5
  upVoted
6
- }
6
+ }
@@ -1,6 +1,6 @@
1
- fragment UpVote on Comment {
1
+ fragment UpVoteButton on Comment {
2
2
  id
3
3
  upVotes
4
4
  upVoted
5
5
  downVoted
6
- }
6
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment_thread.fragment.graphql"
2
+
1
3
  mutation addComment($commentableId: String!, $commentableType: String!, $body: String!, $alignment: Int, $userGroupId: ID) {
2
4
  commentable(id: $commentableId, type: $commentableType) {
3
5
  addComment(body: $body, alignment: $alignment, userGroupId: $userGroupId) {
4
6
  ...CommentThread
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  mutation DownVote($id: ID!) {
2
4
  comment(id: $id) {
3
5
  downVote {
4
6
  ...Comment
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,7 +1,9 @@
1
+ #import "../fragments/comment.fragment.graphql"
2
+
1
3
  mutation UpVote($id: ID!) {
2
4
  comment(id: $id) {
3
5
  upVote {
4
6
  ...Comment
5
7
  }
6
8
  }
7
- }
9
+ }
@@ -1,3 +1,6 @@
1
+ #import "../fragments/add_comment_form_session.fragment.graphql"
2
+ #import "../fragments/comment_thread.fragment.graphql"
3
+ #import "../fragments/add_comment_form_commentable.fragment.graphql"
1
4
  query GetComments($commentableId: String!, $commentableType: String!, $orderBy: String) {
2
5
  session {
3
6
  user {
@@ -17,4 +20,4 @@ query GetComments($commentableId: String!, $commentableType: String!, $orderBy:
17
20
  }
18
21
  ...AddCommentFormCommentable
19
22
  }
20
- }
23
+ }
@@ -1,4 +1,4 @@
1
- const assetUrl = (name) => {
1
+ const assetUrl = (name: string): string => {
2
2
  const url = window.DecidimComments.assets[name];
3
3
 
4
4
  if (!url) {
@@ -1,4 +1,6 @@
1
- import { random, name, date, image } from 'faker/locale/en';
1
+ import { date, image, lorem, name, random } from "faker/locale/en";
2
+
3
+ import { CommentFragment } from "../support/schema";
2
4
 
3
5
  /**
4
6
  * Generate random comment data to emulate a database real content
@@ -6,16 +8,17 @@ import { random, name, date, image } from 'faker/locale/en';
6
8
  * @returns {Object[]} - An array of objects representing comments data
7
9
  */
8
10
  const generateCommentsData = (num = 1) => {
9
- let commentsData = [];
11
+ let commentsData: CommentFragment[] = [];
10
12
 
11
13
  for (let idx = 0; idx < num; idx += 1) {
12
14
  commentsData.push({
13
15
  id: random.uuid(),
14
- body: random.words(),
16
+ type: "Decidim::Comments::Comment",
17
+ body: lorem.words(),
15
18
  createdAt: date.past().toISOString(),
16
19
  author: {
17
20
  name: name.findName(),
18
- avatarUrl: image.imageUrl()
21
+ avatarUrl: image.imageUrl(),
19
22
  },
20
23
  hasComments: false,
21
24
  comments: [],
@@ -24,8 +27,10 @@ const generateCommentsData = (num = 1) => {
24
27
  upVotes: random.number(),
25
28
  upVoted: false,
26
29
  downVotes: random.number(),
27
- downVoted: false
28
- })
30
+ downVoted: false,
31
+ sgid: random.uuid(),
32
+ alreadyReported: false,
33
+ });
29
34
  }
30
35
 
31
36
  return commentsData;
@@ -1,4 +1,4 @@
1
- import { name } from 'faker/locale/en';
1
+ import { name } from "faker/locale/en";
2
2
 
3
3
  /**
4
4
  * Generate random user data to emulate a database real content
@@ -6,7 +6,7 @@ import { name } from 'faker/locale/en';
6
6
  */
7
7
  const generateUserData = () => {
8
8
  return {
9
- name: name.findName()
9
+ name: name.findName(),
10
10
  };
11
11
  };
12
12
 
@@ -1,4 +1,4 @@
1
- import { random, company } from 'faker/locale/en';
1
+ import { company, random } from "faker/locale/en";
2
2
 
3
3
  /**
4
4
  * Generate random user group data to emulate a database real content
@@ -7,7 +7,7 @@ import { random, company } from 'faker/locale/en';
7
7
  const generateUserGrouprData = () => {
8
8
  return {
9
9
  id: random.uuid(),
10
- name: company.companyName()
10
+ name: company.companyName(),
11
11
  };
12
12
  };
13
13
 
@@ -0,0 +1,32 @@
1
+ /* eslint-env node */
2
+ const gql = require('graphql-tag');
3
+
4
+ // Takes `source` (the source GraphQL query string)
5
+ // and `doc` (the parsed GraphQL document) and tacks on
6
+ // the imported definitions.
7
+ const expandImports = (source) => {
8
+ const lines = source.split('\n');
9
+ let outputCode = "";
10
+
11
+ lines.some((line) => {
12
+ if (line[0] === '#' && line.slice(1).split(' ')[0] === 'import') {
13
+ const importFile = line.slice(1).split(' ')[1];
14
+ const parseDocument = `require(${importFile})`;
15
+ const appendDef = `doc.definitions = doc.definitions.concat(${parseDocument}.definitions);`;
16
+ outputCode += `${appendDef}\n`;
17
+ }
18
+ return (line.length !== 0 && line[0] !== '#');
19
+ });
20
+
21
+ return outputCode;
22
+ }
23
+
24
+ module.exports = {
25
+ process(src) {
26
+ const doc = gql`${src}`;
27
+ const outputCode = `var doc = ${JSON.stringify(doc)};`;
28
+ const importOutputCode = expandImports(src, doc);
29
+
30
+ return `${outputCode}\n${importOutputCode}\nmodule.exports = doc;`;
31
+ }
32
+ };
@@ -0,0 +1,44 @@
1
+ /* eslint-disable no-param-reassign */
2
+ import requireAll from "./require_all";
3
+
4
+ const { I18n } = require("react-i18nify");
5
+
6
+ /**
7
+ * Load components translations from yaml files and import them into
8
+ * react-i18ify system so they can be used via `I18n.t` method.
9
+ * @returns {Void} - Nothing
10
+ */
11
+ const loadTranslations = () => {
12
+ const translationsContext = (<any> require).context("../../../config/locales/", true, /\.yml$/);
13
+ const translationFiles = requireAll(translationsContext);
14
+
15
+ const translations = translationsContext.keys().reduce((acc: any, key: string, index: number) => {
16
+ const match = key.match(/\.\/(.*)\.yml/);
17
+
18
+ if (match) {
19
+ let locale = match[1];
20
+ acc[locale] = translationFiles[index][locale].decidim;
21
+ }
22
+
23
+ return acc;
24
+ }, {});
25
+
26
+ I18n.setTranslations(translations);
27
+ };
28
+
29
+ /**
30
+ * Load components translations from a locale files and import them into
31
+ * react-i18ify system so they can be used via `I18n.t` method.
32
+ * @returns {Void} - Nothing
33
+ */
34
+ export const loadLocaleTranslations = (locale: string) => {
35
+ const translationFile = require(`./../../../config/locales/${locale}.yml`);
36
+ const translations = Object.keys(translationFile).reduce((acc: any, key: string) => {
37
+ acc[locale] = translationFile[locale].decidim;
38
+ return acc;
39
+ }, {});
40
+
41
+ I18n.setTranslations(translations);
42
+ };
43
+
44
+ export default loadTranslations;
@@ -3,7 +3,7 @@
3
3
  * @param {Object} requireContext - A webpack require context
4
4
  * @returns {Object[]} - An array of webpack modules
5
5
  */
6
- const requireAll = (requireContext) => {
6
+ const requireAll = (requireContext: any) => {
7
7
  return requireContext.keys().map(requireContext);
8
8
  };
9
9
 
@@ -1,5 +1,5 @@
1
- import graphql, { filter } from 'graphql-anywhere';
2
-
1
+ import graphql, { filter } from "graphql-anywhere";
2
+
3
3
  /**
4
4
  * A simple resolver which returns object properties to easily
5
5
  * traverse a graphql response
@@ -7,9 +7,9 @@ import graphql, { filter } from 'graphql-anywhere';
7
7
  * @param {Object} root - An object
8
8
  * @returns {any} - An object's property value
9
9
  */
10
- const resolver = (fieldName, root) => root[fieldName];
10
+ const resolver = (fieldName: string, root: any) => root[fieldName];
11
11
 
12
- /**
12
+ /**
13
13
  * A helper function to mock a graphql api request and return its
14
14
  * result. The result can be filtered by the same query so it just
15
15
  * returns a data subset.
@@ -17,7 +17,7 @@ const resolver = (fieldName, root) => root[fieldName];
17
17
  * @param {options} options - An object with optional options
18
18
  * @returns {Object} - The result of the query filtered or not
19
19
  */
20
- const resolveGraphQLQuery = (document, options = {}) => {
20
+ const resolveGraphQLQuery = (document: any, options: any = {}) => {
21
21
  const { filterResult, rootValue, context, variables } = options;
22
22
 
23
23
  let result = graphql(
@@ -25,13 +25,13 @@ const resolveGraphQLQuery = (document, options = {}) => {
25
25
  document,
26
26
  rootValue,
27
27
  context,
28
- variables
28
+ variables,
29
29
  );
30
30
 
31
31
  if (filterResult) {
32
32
  return filter(document, result);
33
33
  }
34
34
  return result;
35
- }
35
+ };
36
36
 
37
37
  export default resolveGraphQLQuery;