/* 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 `
`;
};
const generateSingleComment = (commentId, content, replies = "") => {
return `
`;
};
const generateCommentThread = (commentId, content, replies = "") => {
return `
`
}
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 = `
`;
$("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();
})
});
${content}