[15 minute fix] Fixed bug with templates not being inserted for comments. (#13149)

* Fixed bug with templates not being inserted for comments.

* Removed some trailing white space from a heading.

* Added custom Cypress command to create an article.

* Added E2E tests for creating comments on an article.

* Fixed adding a comment issue caused by mention auto-complete.

* Fixed a selector for the E2E tests.

* Removed some unnecessary white space and also making code climate happy.

* Updated E2E test.

* Added a bit more to the E2E tests.

* Now validation for comment doesn't need to loop through textareas.

* Merge remote-tracking branch 'origin/master' into nickytonline/fix-comment-template-insertion-bug

* Removed a similar spec file and added tests to the other file.

* wip

* Fixed bug where a response template could not be submitted.

* Added/updated E2E tests.

* Reverted a small change to condition for showing the combobox popover.

* Undoing some white space changes as not related to PR.

* Trigger Build

* Put back article seed data as it's used in tests outside of comment tests.
This commit is contained in:
Nick Taylor 2021-03-30 07:02:54 -04:00 committed by GitHub
parent 40b989ca62
commit 871650053c
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
5 changed files with 344 additions and 260 deletions

View file

@ -256,9 +256,17 @@ function handleCommentSubmit(event) {
var mainCommentsForm = document.getElementById("new_comment");
mainCommentsForm.classList.remove("submitting");
mainCommentsForm.classList.remove('preview-open');
const textArea = form.getElementsByClassName("comment-textarea")[0];
textArea.closest('.comment-form').classList.remove('comment-form--initiated');
textArea.value = newComment.comment_template || "";
const commentInputs = [...form.getElementsByClassName("comment-textarea")]
commentInputs[0].closest('.comment-form').classList.remove('comment-form--initiated');
// Clearing out all comment textboxes because
// there is an additional one generated by the comment
// mention auto-complete component
commentInputs.forEach(input => {
input.value = newComment.comment_template || "";
});
var preview = document.getElementById("preview-div");
preview.classList.add("preview-toggle");
preview.innerHTML = "";
@ -447,12 +455,12 @@ function handleButtonsActivation(event) {
}
function validateField(event) {
var textarea = event.target.closest('.comment-form').getElementsByClassName('comment-textarea')[0];
if (textarea) {
var commentField = textarea.value;
if (commentField == '') {
event.preventDefault();
}
// We only need to validate the textarea that is not the comment mention auto-complete component
const textArea = event.target.form.querySelector('.comment-textarea:not([role=combobox])');
if (textArea.value === '') {
event.preventDefault();
return;
}
}

View file

@ -124,9 +124,12 @@ function addClickListeners(form) {
);
insertButtons.forEach((button) => {
button.addEventListener('click', (e) => {
const { content } = e.target.dataset;
const textArea = form.getElementsByTagName('textarea')[0];
button.addEventListener('click', (event) => {
const { content } = event.target.dataset;
// We need to grab the textarea that is not the comment mention auto-complete component
const textArea = event.target.form.querySelector(
'.comment-textarea:not([role=combobox])',
);
const textAreaReplaceable =
textArea.value === null ||
textArea.value === '' ||
@ -134,6 +137,7 @@ function addClickListeners(form) {
if (textAreaReplaceable) {
textArea.value = content;
textArea.dispatchEvent(new Event('input', { target: textArea }));
textArea.focus();
responsesContainer.classList.toggle('hidden');
}

View file

@ -2,7 +2,7 @@
<section id="comments" data-updated-at="<%= Time.current %>" class="text-padding mb-4 border-t-1 border-0 border-solid border-base-10">
<% if @article.show_comments %>
<header class="relative flex justify-between items-center mb-6">
<h2 class="crayons-subtitle-1">Discussion <span class="js-comments-count" data-comments-count="<%= @article.comments_count %>">(<%= @article.comments_count %>)</span> </h2>
<h2 class="crayons-subtitle-1">Discussion <span class="js-comments-count" data-comments-count="<%= @article.comments_count %>">(<%= @article.comments_count %>)</span></h2>
<div id="comment-subscription">
<div role="presentation" class="crayons-btn-group">
<span class="crayons-btn crayons-btn--outlined">Subscribe</span>

View file

@ -5,265 +5,338 @@ describe('Comment on articles', () => {
cy.get('@user').then((user) => {
cy.loginUser(user).then(() => {
cy.visit('/');
cy.findAllByText('Test article').last().click();
cy.createArticle({
title: 'Test Article',
tags: ['beginner', 'ruby', 'go'],
content: `This is a test article's contents.`,
published: true,
}).then((response) => {
cy.visit(response.body.current_state_path);
});
});
});
});
it('should comment on an article with user mention autocomplete suggesting max 6 users', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
describe('Comments using mention autocomplete', () => {
it('should comment on an article with user mention autocomplete suggesting max 6 users', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.findByText('Type to search for a user').should('exist');
cy.get('@autocompleteCommentBox').type('earch');
cy.findByText('Type to search for a user').should('exist');
cy.get('@autocompleteCommentBox').type('earch');
const expectedUsernames = [
'@search_user_1',
'@search_user_2',
'@search_user_2',
'@search_user_3',
'@search_user_4',
'@search_user_5',
'@search_user_6',
];
const expectedUsernames = [
'@search_user_1',
'@search_user_2',
'@search_user_2',
'@search_user_3',
'@search_user_4',
'@search_user_5',
'@search_user_6',
];
expectedUsernames.forEach((name) => cy.findByText(name).should('exist'));
cy.findByText('@search_user_7').should('not.exist');
expectedUsernames.forEach((name) => cy.findByText(name).should('exist'));
cy.findByText('@search_user_7').should('not.exist');
cy.findByText('@search_user_3').click();
cy.findByText('@search_user_3').click();
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should(
'have.value',
'Some text @search_user_3 ',
);
});
it('should select a mention autocomplete suggestion by keyboard', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('earch_user{downarrow}{enter}');
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should(
'have.value',
'Some text @search_user_1 ',
);
});
it('should accept entered comment text without user mention if no autocomplete suggestions', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/emptyUsernamesSearch.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @u');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('ser');
cy.findByText('No results found').should('exist');
cy.get('@autocompleteCommentBox').type(' ');
cy.findByText('No results found').should('not.exist');
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should('have.value', 'Some text @user ');
});
it('should stop showing mention autocomplete suggestions on text delete', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('e');
cy.findByText('@search_user_1').should('exist');
cy.get('@autocompleteCommentBox').type('{backspace}{backspace}{backspace}');
cy.findByText('@search_user_1').should('not.exist');
});
it('should resume search suggestions when user types after deleting', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @se');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('{backspace}{backspace}');
cy.findByText('@search_user_1').should('not.exist');
cy.get('@autocompleteCommentBox').type('se');
cy.findByText('@search_user_1').should('exist');
});
it('should close the autocomplete suggestions on Escape press', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').type('earch');
cy.findByText('@search_user_1').should('be.visible');
cy.get('@autocompleteCommentBox').type('{Esc}');
cy.findByText('@search_user_1').should('not.be.visible');
});
xit('should reply to a comment with user mention autocomplete', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('first comment');
cy.findByRole('button', { name: /Submit/ }).click();
cy.findByRole('link', { name: /Reply/ }).click();
cy.findAllByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Reply to a comment/,
})
.last()
.as('replyCommentBox');
cy.get('@replyCommentBox').click();
cy.get('@replyCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Reply to a comment/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').type('earch');
cy.findByText('@search_user_1').click();
cy.get('@replyCommentBox').should(
'have.value',
'Some text @search_user_1 ',
);
});
it('should pre-populate a comment field when editing', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText('Add a comment to the discussion').click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('first comment');
cy.findByRole('button', { name: /Submit/ }).click();
cy.findByRole('link', { name: /Reply/ });
cy.findByTestId('comments-container').within(() => {
cy.findByLabelText('Toggle dropdown menu').click();
// Wait for the menu to be visible
cy.findByText('Edit').should('be.visible');
cy.findByText('Edit').click();
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should(
'have.value',
'Some text @search_user_3 ',
);
});
cy.findByDisplayValue('first comment').should('exist');
it('should select a mention autocomplete suggestion by keyboard', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('earch_user{downarrow}{enter}');
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should(
'have.value',
'Some text @search_user_1 ',
);
});
it('should accept entered comment text without user mention if no autocomplete suggestions', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/emptyUsernamesSearch.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @u');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('ser');
cy.findByText('No results found').should('exist');
cy.get('@autocompleteCommentBox').type(' ');
cy.findByText('No results found').should('not.exist');
cy.get('@plainCommentBox').should('have.focus');
cy.get('@plainCommentBox').should('have.value', 'Some text @user ');
});
it('should stop showing mention autocomplete suggestions on text delete', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('e');
cy.findByText('@search_user_1').should('exist');
cy.get('@autocompleteCommentBox').type(
'{backspace}{backspace}{backspace}',
);
cy.findByText('@search_user_1').should('not.exist');
});
it('should resume search suggestions when user types after deleting', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @se');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').should('have.focus');
cy.get('@autocompleteCommentBox').type('{backspace}{backspace}');
cy.findByText('@search_user_1').should('not.exist');
cy.get('@autocompleteCommentBox').type('se');
cy.findByText('@search_user_1').should('exist');
});
it('should close the autocomplete suggestions on Escape press', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Add a comment to the discussion/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').type('earch');
cy.findByText('@search_user_1').should('be.visible');
cy.get('@autocompleteCommentBox').type('{Esc}');
cy.findByText('@search_user_1').should('not.be.visible');
});
xit('should reply to a comment with user mention autocomplete', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('first comment');
cy.findByRole('button', { name: /Submit/ }).click();
cy.findByRole('link', { name: /Reply/ }).click();
cy.findAllByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Reply to a comment/,
})
.last()
.as('replyCommentBox');
cy.get('@replyCommentBox').click();
cy.get('@replyCommentBox').type('Some text @s');
// Verify the combobox has appeared
cy.findByRole('combobox', { name: /Reply to a comment/ }).as(
'autocompleteCommentBox',
);
cy.get('@autocompleteCommentBox').type('earch');
cy.findByText('@search_user_1').click();
cy.get('@replyCommentBox').should(
'have.value',
'Some text @search_user_1 ',
);
});
it('should pre-populate a comment field when editing', () => {
cy.intercept(
{ method: 'GET', url: '/search/usernames' },
{ fixture: 'search/usernames.json' },
);
cy.findByLabelText(/^Add a comment to the discussion$/i).click();
// Wait for the new autocomplete text areas to be mounted
cy.findByTestId('autocomplete-textarea', {
role: 'textbox',
name: /Add a comment to the discussion/,
}).as('plainCommentBox');
cy.get('@plainCommentBox').type('first comment');
cy.findByRole('button', { name: /Submit/ }).click();
cy.findByRole('link', { name: /Reply/ });
cy.findByTestId('comments-container').within(() => {
cy.findByLabelText('Toggle dropdown menu').click();
// Wait for the menu to be visible
cy.findByText('Edit').should('be.visible');
cy.findByText('Edit').click();
});
cy.findByDisplayValue('first comment').should('exist');
});
});
it('should add a comment', () => {
cy.findByRole('main')
.as('main')
.findByRole('heading', { name: 'Discussion (0)' });
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.focus() // Focus activates the Submit button and mini toolbar below a comment textbox
.type('this is a comment');
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.should('have.value', 'this is a comment');
cy.get('@main')
.findByRole('button', { name: /^Submit$/i })
.click();
// Comment was saved so the new comment textbox should be empty.
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.should('have.value', '');
cy.get('@main').findByText(/^this is a comment$/i);
cy.get('@main').findByRole('heading', { name: 'Discussion (1)' });
});
it('should add a comment from a response template', () => {
cy.createResponseTemplate({
title: 'Test Canned Response',
content: 'This is a test canned response',
}).then((_response) => {
cy.findByRole('main')
.as('main')
.findByRole('heading', { name: 'Discussion (0)' });
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.focus(); // Focus activates the Submit button and mini toolbar below a comment textbox
cy.get('@main')
.findByRole('button', { name: /^Use a response template$/i })
.click();
cy.get('@main')
.findByRole('button', { name: /^Insert$/i })
.click();
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.should('have.value', 'This is a test canned response');
cy.get('@main')
.findByRole('button', { name: /^Submit$/i })
.click();
// Comment was saved so the new comment textbox should be empty.
cy.get('@main')
.findByRole('textbox', { name: /^Add a comment to the discussion$/i })
.should('have.value', '');
cy.get('@main').findByRole('heading', { name: 'Discussion (1)' });
});
});
});

View file

@ -153,7 +153,6 @@ seeder.create_if_doesnt_exist(Article, "title", "Test article") do
published: true
cover_image: #{Faker::Company.logo}
---
#{Faker::Hipster.paragraph(sentence_count: 2)}
#{Faker::Markdown.random}
#{Faker::Hipster.paragraph(sentence_count: 2)}