MM-63411 Don't focus thread textbox automatically when it has a draft (#31250)

* MM-63411 Don't focus thread textbox automatically when it has a draft

* Add E2E test

* Add test files forgotten in previous commit

---------

Co-authored-by: Mattermost Build <build@mattermost.com>
Этот коммит содержится в:
Harrison Healey
2025-06-04 16:08:33 -04:00
коммит произвёл GitHub
родитель a19eb5b9ef
Коммит 6517fa2fd1
7 изменённых файлов: 175 добавлений и 2 удалений

12
e2e-tests/cypress/tests/support/chai.d.ts поставляемый Обычный файл
Просмотреть файл

@@ -0,0 +1,12 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
/// <reference types="cypress" />
declare namespace Cypress {
interface Chainer<Subject> {
(chainer: 'be.focusVisible', {exactStyles}: {exactStyles?: boolean}): Chainable<Subject>;
(chainer: 'not.be.focusVisible', {exactStyles}: {exactStyles?: boolean}): Chainable<Subject>;
}
}

36
e2e-tests/cypress/tests/support/chai.ts Обычный файл
Просмотреть файл

@@ -0,0 +1,36 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
chai.use((chai: Chai.ChaiStatic) => {
function assertIsFoo({exactStyles = true} = {}) {
// eslint-disable-next-line no-underscore-dangle
const obj = this._obj as JQuery<HTMLElement>;
this.assert(
obj.hasClass('a11y--active'),
'expected #{this} to have a11y--active class',
'expected #{this} to not have a11y--active class',
obj,
);
// These should match the styles set on :focus-visible in sass/utils/_modifiers.scss
this.assert(
exactStyles ? obj.css('box-shadow').includes('0px 0px 1px 3px') : Boolean(obj.css('box-shadow')),
'expected #{this} to have focused element style (box-shadow)',
'expected #{this} to not have focused element style (box-shadow)',
obj.css('box-shadow'),
);
this.assert(
exactStyles ? obj.css('border-radius') === '4px' : Boolean(obj.css('border-radius')),
'expected #{this} to have focused element style (border-radius)',
'expected #{this} to not have focused element style (border-radius)',
obj.css('border-radius'),
);
return this;
}
/* eslint-enable no-underscore-dangle */
chai.Assertion.addMethod('a11yVisible', assertIsFoo);
});

Просмотреть файл

@@ -34,10 +34,11 @@ export class TestBrowser {
const systemConsolePage = new pages.SystemConsolePage(page);
const scheduledPostsPage = new pages.ScheduledPostsPage(page);
const draftsPage = new pages.DraftsPage(page);
const threadsPage = new pages.ThreadsPage(page);
this.context = context;
return {context, page, channelsPage, systemConsolePage, scheduledPostsPage, draftsPage};
return {context, page, channelsPage, systemConsolePage, scheduledPostsPage, draftsPage, threadsPage};
}
async close() {

Просмотреть файл

@@ -9,6 +9,7 @@ import SignupPage from './signup';
import SystemConsolePage from './system_console';
import ScheduledPostsPage from './scheduled_posts';
import DraftsPage from './drafts';
import ThreadsPage from './threads';
const pages = {
ChannelsPage,
@@ -19,6 +20,7 @@ const pages = {
ScheduledPostsPage,
SystemConsolePage,
DraftsPage,
ThreadsPage,
};
export {

Просмотреть файл

@@ -0,0 +1,46 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Page, expect} from '@playwright/test';
import {ChannelsPost} from '@/ui/components';
export default class ThreadsPage {
readonly page: Page;
readonly threadsList;
readonly noThreadSelected;
constructor(page: Page) {
this.page = page;
this.threadsList = page.locator('#threads-list');
this.noThreadSelected = page.locator('.no-results__title', {
hasText: /Looks like youre all caught up|Catch up on your threads/,
});
}
async goto(teamName: string) {
await this.page.goto(`/${teamName}/threads`);
}
async toBeVisible() {
await expect(this.threadsList).toBeVisible();
}
async toHaveThreadSelected() {
await expect(this.noThreadSelected).not.toBeAttached();
}
async toNotHaveThreadSelected() {
await expect(this.noThreadSelected).toBeVisible();
}
async getLastPost() {
const lastPost = this.page.getByTestId('rhsPostView').last();
await lastPost.waitFor();
return new ChannelsPost(lastPost);
}
}

Просмотреть файл

@@ -0,0 +1,76 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {test} from '@mattermost/playwright-lib';
test('Should be able to change threads with arrow keys', async ({pw}, testInfo) => {
test.skip(testInfo.project.name === 'ipad');
const {team, user} = await pw.initSetup();
const {channelsPage, page, threadsPage} = await pw.testBrowser.login(user);
await channelsPage.goto();
await channelsPage.toBeVisible();
// # Start some threads, and leave a draft in one of them
await channelsPage.centerView.postCreate.postMessage('aaa');
await (await channelsPage.getLastPost()).openAThread();
await channelsPage.sidebarRight.postMessage('aaa reply');
await channelsPage.centerView.postCreate.postMessage('bbb');
await (await channelsPage.getLastPost()).openAThread();
await channelsPage.sidebarRight.postMessage('bbb reply');
await channelsPage.sidebarRight.postCreate.writeMessage('bbb second reply');
await channelsPage.centerView.postCreate.postMessage('ccc');
await (await channelsPage.getLastPost()).openAThread();
await channelsPage.sidebarRight.postMessage('ccc reply');
// * Ensure that there's a draft
await channelsPage.sidebarLeft.draftsVisible();
// # Switch to the threads list
await threadsPage.goto(team.name);
await threadsPage.toBeVisible();
// * Ensure no thread starts selected
await threadsPage.toNotHaveThreadSelected();
// # Press the down arrow to select a thread
await page.keyboard.press('ArrowDown');
// * Ensure the latest thread was selected
await threadsPage.toHaveThreadSelected();
(await threadsPage.getLastPost()).toContainText('ccc reply');
// # Press the down arrow again
await page.keyboard.press('ArrowDown');
// * Ensure the latest thread was selected
await threadsPage.toHaveThreadSelected();
(await threadsPage.getLastPost()).toContainText('bbb reply');
await threadsPage.threadsList.focus();
// # Press the down arrow again
await page.keyboard.press('ArrowDown');
// * Ensure the latest thread was selected
await threadsPage.toHaveThreadSelected();
(await threadsPage.getLastPost()).toContainText('aaa reply');
// # Press the up arrow
await page.keyboard.press('ArrowUp');
// * Ensure the latest thread was selected
await threadsPage.toHaveThreadSelected();
(await threadsPage.getLastPost()).toContainText('bbb reply');
// # Press the up arrow
await page.keyboard.press('ArrowUp');
// * Ensure the latest thread was selected
await threadsPage.toHaveThreadSelected();
(await threadsPage.getLastPost()).toContainText('ccc reply');
});

Просмотреть файл

@@ -523,7 +523,7 @@ const AdvancedTextEditor = ({
// Update the caret position in the input box when changed by a side effect
useEffect(() => {
const textbox: HTMLInputElement | HTMLTextAreaElement | undefined = textboxRef.current?.getInputBox();
if (textbox && textbox.selectionStart !== caretPosition) {
if (textbox && textbox === document.activeElement && textbox.selectionStart !== caretPosition) {
Utils.setCaretPosition(textbox, caretPosition);
}
}, [caretPosition]);