/* eslint-disable id-length, max-lines */ /* global spyOn, jest */ const $ = require("jquery"); // Ability to spy on the jQuery methods inside the component in order to test // the sub-elements correctly. Needs to be defined before the modules are loaded // in order for them to define the $ variable correctly. window.$ = jest.fn().mockImplementation((...args) => $(...args)); window.$.ajax = jest.fn().mockImplementation((...args) => $.ajax(...args)); // Quill is expected by the input character counter window.Quill = require("../../../../../../decidim-core/vendor/assets/javascripts/quill.min.js"); // Fake timers for testing polling jest.useFakeTimers(); require("../../../../../../decidim-core/app/assets/javascripts/decidim/input_character_counter.js.es6"); require("./comments.component.js.es6"); const { Decidim: { CommentsComponent, createCharacterCounter } } = window; // Create a dummy foundation jQuery method for the comments component to call $.fn.foundation = () => {}; describe("CommentsComponent", () => { const selector = "#comments-for-Dummy-123"; let subject = null; let $doc = null; let $container = null; let addComment = null; let orderLinks = null; let allToggles = null; let allTextareas = null; let allForms = null; const spyOnAddComment = (methodToSpy) => { addComment.each((i) => { addComment[i].$ = $(addComment[i]); addComment[i].opinionToggles = $(".opinion-toggle .button", addComment[i].$); addComment[i].commentForm = $("form", addComment[i].$); addComment[i].commentTextarea = $("textarea", addComment[i].commentForm); if (methodToSpy) { spyOn(addComment[i].opinionToggles, methodToSpy); spyOn(addComment[i].commentForm, methodToSpy); spyOn(addComment[i].commentTextarea, methodToSpy); } }); spyOn(window, "$").mockImplementation((...args) => { const jqSelector = args[0]; const parent = args[1]; if (jqSelector === document) { return $doc; } else if (jqSelector === ".order-by__dropdown .is-submenu-item a" && parent.is(subject.$element)) { return orderLinks; } else if (jqSelector === ".add-comment" && parent.is(subject.$element)) { return addComment; } else if (jqSelector === ".add-comment .opinion-toggle .button" && parent.is(subject.$element)) { return allToggles; } else if (jqSelector === ".add-comment textarea" && parent.is(subject.$element)) { return allTextareas; } else if (jqSelector === ".add-comment form" && parent.is(subject.$element)) { return allForms; } const addCommentsArray = addComment.toArray(); for (let i = 0; i < addCommentsArray.length; i += 1) { if (jqSelector === ".opinion-toggle .button" && parent.is(addCommentsArray[i].$)) { return addCommentsArray[i].opinionToggles; } else if (jqSelector === "form" && parent.is(addCommentsArray[i].$)) { return addCommentsArray[i].commentForm; } else if (jqSelector === "textarea" && parent.is(addCommentsArray[i].commentForm)) { return addCommentsArray[i].commentTextarea; } } return $(...args); }); } const generateCommentForm = (modelName, modelId) => { return `
1000 characters left
`; }; const generateSingleComment = (commentId, content, replies = "") => { return `
Get link to single comment

${content}

${replies}
${generateCommentForm("Comment", commentId)}
`; }; const generateCommentThread = (commentId, content, replies = "") => { return `
${generateSingleComment(commentId, content, replies)}
` } beforeEach(() => { let orderSelector = ` `; let reply = generateSingleComment(451, "This is a reply."); let firstThread = generateCommentThread(450, "This is the first comment thread.", reply); let secondThread = generateCommentThread(452, "This is another comment thread."); let comments = `

3 comments

Order by: ${orderSelector}
${firstThread} ${secondThread}

Add your comment

${generateCommentForm("Dummy", 123)}

Loading comments ...

`; $("body").append(comments); $container = $(document).find(selector); subject = new CommentsComponent($container, { commentableGid: "commentable-gid", commentsUrl: "/comments", rootDepth: 0, order: "older", lastCommentId: 456, pollingInterval: 1000 }); $("textarea[maxlength]", $container).each((_i, elem) => { createCharacterCounter($(elem)); }); $doc = $(document); addComment = $(".add-comment", subject.$element); orderLinks = $(".order-by__dropdown .is-submenu-item a", subject.$element); allToggles = $(".add-comment .opinion-toggle .button", subject.$element); allTextareas = $(".add-comment textarea", subject.$element); allForms = $(".add-comment form", subject.$element); }); it("exists", () => { expect(CommentsComponent).toBeDefined(); }); it("initializes unmounted", () => { expect(subject.mounted).toBeFalsy(); }); it("initializes the comments element with the given selector", () => { expect(subject.$element).toEqual($(selector)); }); it("starts polling for new comments", () => { subject.mountComponent(); expect(window.setTimeout).toHaveBeenLastCalledWith(expect.any(Function), 1000); jest.advanceTimersByTime(1000); expect(window.$.ajax).toHaveBeenCalledWith({ url: "/comments", method: "GET", contentType: "application/javascript", data: { "commentable_gid": "commentable-gid", "root_depth": 0, order: "older", after: 456 } }); }); describe("when mounted", () => { beforeEach(() => { spyOnAddComment("on"); spyOn(orderLinks, "on"); spyOn($doc, "trigger"); subject.mountComponent(); }); afterEach(() => { subject.unmountComponent(); }); it("mounts the component", () => { expect(subject.mounted).toBeTruthy(); }); it("binds the order selector events", () => { expect(orderLinks.on).toHaveBeenCalledWith( "click.decidim-comments", expect.any(Function) ); }); it("binds the add comment element events", () => { addComment.each((i) => { expect(addComment[i].opinionToggles.on).toHaveBeenCalledWith( "click.decidim-comments", subject._onToggleOpinion ); expect(addComment[i].commentTextarea.on).toHaveBeenCalledWith( "input.decidim-comments", subject._onTextInput ); expect(addComment[i].commentForm.on).toHaveBeenCalledWith( "submit.decidim-comments", expect.any(Function) ); }); }); it("attaches the mentions elements to the text fields", () => { addComment.each((i) => { expect($doc.trigger).toHaveBeenCalledWith( "attach-mentions-element", [addComment[i].commentTextarea[0]] ); }); }); }); describe("when interacting", () => { beforeEach(() => { spyOnAddComment(); subject.mountComponent(); }); afterEach(() => { subject.unmountComponent(); }); describe("form and input events", () => { let commentSection = null; let commentForm = null; let commentText = null; beforeEach(() => { commentSection = addComment[addComment.length - 1]; commentForm = commentSection.commentForm; commentText = commentSection.commentTextarea; // Avoid not implemented error commentForm[0].submit = jest.fn(); }); it("enables the submit button when comment is entered", () => { commentText.html("This is a test comment") commentText.trigger("input"); expect( $("button[type='submit']", commentSection.commentForm).is(":enabled") ).toBeTruthy(); }); it("disables the submit button on submit and stops polling", () => { spyOn(window, "clearTimeout"); commentText.html("This is a test comment") commentText.trigger("input"); commentForm.trigger("submit"); expect( $("button[type='submit']", commentSection.commentForm).is(":disabled") ).toBeTruthy(); expect(window.clearTimeout).toHaveBeenCalledWith(subject.pollTimeout); }); }); describe("opinion toggles", () => { let commentSection = null; let toggles = null; beforeEach(() => { commentSection = addComment[addComment.length - 1]; toggles = commentSection.opinionToggles; }); it("adds the correct alignment on positive toggle", () => { $(toggles[0]).trigger("click"); expect($(".alignment-input", commentSection).val()).toEqual("1"); }); it("adds the correct alignment on neutral toggle", () => { $(toggles[0]).trigger("click"); $(toggles[1]).trigger("click"); expect($(".alignment-input", commentSection).val()).toEqual("0"); }); it("adds the correct alignment on negative toggle", () => { $(toggles[2]).trigger("click"); expect($(".alignment-input", commentSection).val()).toEqual("-1"); }); }); }); describe("when adding comments", () => { beforeEach(() => { subject.mountComponent(); }); afterEach(() => { subject.unmountComponent(); }); describe("addThread", () => { it("adds a new comment thread", () => { const newThread = generateCommentThread(999, "This is a dynamically added comment"); subject.addThread(newThread); expect(subject.$element.html()).toEqual(expect.stringContaining( "This is a dynamically added comment" )); }); }); describe("addReply", () => { it("adds a new reply to an existing thread", () => { const newThread = generateSingleComment(999, "This is a dynamically added reply"); subject.addReply(450, newThread); expect(subject.$element.html()).toEqual(expect.stringContaining( "This is a dynamically added reply" )); }); }); }); describe("when unmounted", () => { beforeEach(() => { spyOn(orderLinks, "off"); spyOn(allToggles, "off"); spyOn(allTextareas, "off"); spyOn(allForms, "off"); subject.mountComponent(); subject.unmountComponent(); }); it("mounts the component", () => { expect(subject.mounted).toBeFalsy(); }); it("unbinds the order selector events", () => { expect(orderLinks.off).toHaveBeenCalledWith( "click.decidim-comments" ); }); it("unbinds the add comment element events", () => { expect(allToggles.off).toHaveBeenCalledWith("click.decidim-comments"); expect(allTextareas.off).toHaveBeenCalledWith("input.decidim-comments"); expect(allForms.off).toHaveBeenCalledWith("submit.decidim-comments"); }); }); afterEach(() => { $(selector).remove(); }) });