Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 9 additions & 0 deletions static/css/comment.css
Original file line number Diff line number Diff line change
Expand Up @@ -221,4 +221,13 @@ input.error, textarea.error {
/* OTHER */
.hidden {
display: none;
}

/* Disabled state for the Add Comment toolbar button */
.addComment.disabled {
opacity: 0.4;
cursor: not-allowed;
}
.addComment.disabled > a {
pointer-events: none;
}
32 changes: 31 additions & 1 deletion static/js/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -143,9 +143,15 @@ EpComments.prototype.init = async function () {
// `fromNow()` without needing to track the cached string anywhere.
setInterval(() => this.refreshRelativeDates(), 60 * 1000);

// Cache the toolbar button element so it can be toggled efficiently in
// aceEditEvent (which fires on every keystroke / selection change).
this.$addCommentBtn = $('.addComment');
this.$addCommentBtnLink = this.$addCommentBtn.find('a');

// On click comment icon toolbar
$('.addComment').on('click', (e) => {
this.$addCommentBtn.on('click', (e) => {
e.preventDefault(); // stops focus from being lost
if (this.$addCommentBtn.hasClass('disabled')) return;
this.displayNewCommentForm();
});

Expand Down Expand Up @@ -1036,6 +1042,20 @@ EpComments.prototype.checkNoTextSelected = function (rep) {
return noTextSelected;
};

// Enable or disable the "Add Comment" toolbar button based on whether text is selected.
EpComments.prototype.updateAddCommentButtonState = function (hasSelection) {
const $btn = this.$addCommentBtn;
const $a = this.$addCommentBtnLink;
if (!$btn) return;
if (hasSelection) {
$btn.removeClass('disabled');
$a.removeAttr('aria-disabled');
} else {
$btn.addClass('disabled');
$a.attr('aria-disabled', 'true');
}
};

// Create form to add comment
EpComments.prototype.createNewCommentFormIfDontExist = function (rep) {
const data = this.getCommentData();
Expand Down Expand Up @@ -1295,6 +1315,9 @@ const hooks = {
await Comments.initDone;
pad.plugins.ep_comments_page = Comments;

// Start with the button disabled — no text is selected on load.
Comments.updateAddCommentButtonState(false);

if (!$('#editorcontainerbox').hasClass('flex-layout')) {
$.gritter.add({
title: 'Error',
Expand Down Expand Up @@ -1343,6 +1366,13 @@ const hooks = {
pad.plugins.ep_comments_page.shouldCollectComment = false;
});
}

// Update toolbar button enabled/disabled state based on whether text is selected.
const rep = context.rep;
if (rep) {
const ep = pad.plugins.ep_comments_page;
ep.updateAddCommentButtonState(!ep.checkNoTextSelected(rep));
}
}
return;
},
Expand Down
6 changes: 6 additions & 0 deletions static/tests/frontend-new/helper/comments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,13 @@ export const selectLine = async (page: Page, lineIndex: number): Promise<void> =
};

// Clicks the toolbar Add Comment button (lives in the chrome page).
// Waits for the button to be enabled (i.e., text is currently selected in the editor).
export const clickAddCommentButton = async (page: Page): Promise<void> => {
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => !el.classList.contains('disabled')
)
).toBe(true);
await page.locator('.addComment').first().click();
};

Expand Down
82 changes: 81 additions & 1 deletion static/tests/frontend-new/specs/newComment.spec.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import {expect, test} from '@playwright/test';
import {getPadBody} from 'ep_etherpad-lite/tests/frontend-new/helper/padHelper';
import {aNewCommentsPad} from '../helper/comments';
import {aNewCommentsPad, clickAddCommentButton} from '../helper/comments';

test.describe('ep_comments_page - New comment', () => {
test('new comment button focuses on comment textarea', async ({page}) => {
Expand All @@ -23,3 +23,83 @@ test.describe('ep_comments_page - New comment', () => {
expect(isFocused).toBe(true);
});
});

test.describe('ep_comments_page - Add Comment button disabled state', () => {
test.beforeEach(async ({page}) => {
// aNewCommentsPad waits for the plugin to initialise, which can take up to 60s.
test.setTimeout(60_000);
});

test('button is disabled on load (no text selected)', async ({page}) => {
await aNewCommentsPad(page);
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => el.classList.contains('disabled')
)
).toBe(true);
const ariaDisabled = await page.locator('.addComment a').first().getAttribute('aria-disabled');
expect(ariaDisabled).toBe('true');
});

test('button is enabled when text is selected', async ({page}) => {
await aNewCommentsPad(page);
const inner = await getPadBody(page);
await inner.click();
await page.keyboard.type('some text');
await inner.locator('div').first().click({clickCount: 3});
// Button should become enabled after selection.
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => el.classList.contains('disabled')
)
).toBe(false);
const ariaDisabled = await page.locator('.addComment a').first().getAttribute('aria-disabled');
expect(ariaDisabled).toBeNull();
});

test('button becomes disabled again after selection is cleared', async ({page}) => {
await aNewCommentsPad(page);
const inner = await getPadBody(page);
await inner.click();
await page.keyboard.type('some text');
// Select text.
await inner.locator('div').first().click({clickCount: 3});
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => !el.classList.contains('disabled')
)
).toBe(true);
// Click elsewhere to deselect.
await inner.click();
// Button should become disabled again.
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => el.classList.contains('disabled')
)
).toBe(true);
});

test('clicking disabled button does not open the comment form', async ({page}) => {
await aNewCommentsPad(page);
// Ensure no text is selected (button is disabled).
await expect.poll(async () =>
page.locator('.addComment').first().evaluate(
(el: Element) => el.classList.contains('disabled')
)
).toBe(true);
// Click the <li> directly (bypassing pointer-events: none on the <a>).
await page.locator('.addComment').first().click();
// The comment form must not appear.
await expect(page.locator('#newComment.popup-show')).toHaveCount(0);
});

test('clicking enabled button opens the comment form', async ({page}) => {
await aNewCommentsPad(page);
const inner = await getPadBody(page);
await inner.click();
await page.keyboard.type('some text');
await inner.locator('div').first().click({clickCount: 3});
await clickAddCommentButton(page);
await expect(page.locator('#newComment.popup-show')).toHaveCount(1);
});
});
Loading