snapshot of https://github.com/mattermost/mattermost.git (master, 2026-09-16; blobs > 131072B omitted)
Some checks failed
Server CI / Postgres (shard 0) (push) Has been skipped
Server CI / Postgres (shard 3) (push) Has been skipped
BuildEnv Docker Image / build-image (push) Failing after 4s
BuildEnv Docker Image / build-image-fips (push) Failing after 4s
Migration Review & Release Notes / Generate migration review & release notes (push) Failing after 3s
Server CI / Build Environment (push) Failing after 3s
Server CI / Postgres (shard 1) (push) Has been skipped
Tools CI / check-style (mattermost-govet) (push) Failing after 4s
Server CI / Postgres (shard 2) (push) Has been skipped
Tools CI / check-style (mmgotool) (push) Failing after 3s
Server CI / Elasticsearch v8 Compatibility (push) Has been skipped
Server CI / OpenSearch v2 Compatibility (push) Has been skipped
Server CI / Postgres FIPS (shard 0) (push) Has been skipped
Tools CI / Test (mattermost-govet) (push) Failing after 3s
Server CI / Postgres FIPS (shard 1) (push) Has been skipped
Server CI / Postgres FIPS (shard 2) (push) Has been skipped
Tools CI / Test (mmgotool) (push) Failing after 3s
Server CI / Postgres FIPS (shard 3) (push) Has been skipped
Server CI / Run mmctl tests (push) Has been skipped
Server CI / Run mmctl tests (FIPS) (push) Has been skipped
Server CI / Merge Postgres Test Results (push) Has been skipped
Server CI / Merge Postgres FIPS Test Results (push) Has been skipped
Server CI / Check backport migrations (push) Has been skipped
Server CI / Check generated files (push) Has been skipped
Server CI / check-style (push) Has been skipped
Server CI / Vet API (push) Has been skipped
Server CI / Build mattermost server app (push) Has been skipped
Server CI / Server CI Complete (push) Failing after 2s
Server CI / Server CI Artifacts (push) Has been skipped
Server CI / Sentry Upload (push) Has been skipped
Server CI / Server CI Report (push) Failing after 5m1s
Server CI Nightly Race / Race Detector (push) Has been skipped
CodeQL / Analyze (go) (push) Has been cancelled
CodeQL / Analyze (javascript) (push) Has been cancelled
Webapp CI Cache Warm / Warm Webapp node_modules Cache (push) Has been cancelled
E2E CI Cache Warm / Warm E2E npm Registry Cache (push) Has been cancelled
API / build (push) Has been cancelled
Web App CI / check-types (push) Has been cancelled
Web App CI / check-lint (push) Has been cancelled
Web App CI / check-i18n (push) Has been cancelled
Web App CI / check-external-links (push) Has been cancelled
Web App CI / test (platform) (push) Has been cancelled
Web App CI / test (mattermost-redux) (push) Has been cancelled
Web App CI / test (channels shard 1/4) (push) Has been cancelled
Web App CI / test (channels shard 2/4) (push) Has been cancelled
Web App CI / test (channels shard 3/4) (push) Has been cancelled
Web App CI / test (channels shard 4/4) (push) Has been cancelled
Web App CI / upload-coverage (push) Has been cancelled
Web App CI / build (push) Has been cancelled
YAML Lint / yamllint (push) Has been cancelled
Push mirrored docker images / cd/Push mirrored docker images (push) Has been cancelled
docs-cd / Build and deploy docs (push) Has been cancelled
Docs CI / build (push) Has been cancelled
E2E Cache Warm: Cypress deps / Warm Cypress deps cache (push) Has been cancelled
Scorecards supply-chain security / Scorecard analysis (push) Has been cancelled
Server CI Weekly / Postgres FIPS (push) Has been skipped
Server CI Weekly / Run mmctl tests (FIPS) (push) Has been skipped
Server CI Weekly / Postgres with binary parameters (push) Has been skipped
Server CI Weekly / Build Environment (push) Failing after 1s
Server CI Cache Warm / Warm Go Module and Build Cache (push) Failing after 2s
Server CI Nightly Race / Build Environment (push) Failing after 1s

This commit is contained in:
upstream-mirror
2026-09-16 00:29:55 +05:00
commit 240db4e2a7
15309 changed files with 2173406 additions and 0 deletions

View File

@@ -0,0 +1,203 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import type {Locator, Page} from '@playwright/test';
import {expect, test} from '@mattermost/playwright-lib';
const AUTOCOMPLETE_ROUTE = /\/api\/v4\/teams\/[^/]+\/channels\/autocomplete/;
function searchedName(url: string) {
return new URL(url).searchParams.get('name') ?? '';
}
async function typeMentionAndWaitForRequests(input: Locator, page: Page, message: string) {
let name = '';
for (const [index, character] of [...message].entries()) {
let request: Promise<unknown> | undefined;
if (index > 0) {
name += character;
request = page.waitForRequest(
(request) => AUTOCOMPLETE_ROUTE.test(request.url()) && searchedName(request.url()) === name,
);
}
await input.pressSequentially(character);
await request;
}
}
/**
* Holds every channel search until the test releases it, so that the autocomplete can be observed while a
* search is still in flight.
*/
async function holdChannelSearch(page: Page) {
let release!: () => void;
const released = new Promise<void>((resolve) => {
release = resolve;
});
await page.route(AUTOCOMPLETE_ROUTE, async (route) => {
await released;
await route.continue();
});
return release;
}
/**
* @objective Verify that the ~channel autocomplete renders the channels it already knows about while a
* search for more channels is still in flight, and preserves those results when the response adds a member.
*/
test(
'channel mention autocomplete shows local channels while searching for more',
{tag: ['@mentions']},
async ({pw}) => {
// # Initialize setup
const {team, user, adminClient} = await pw.initSetup();
// # Create a public channel that the user is a member of, so it comes from the local store
const localChannel = await adminClient.createChannel({
team_id: team.id,
name: 'ac-local-' + Date.now(),
display_name: 'AC Z Local',
type: 'O',
});
await adminClient.addToChannel(user.id, localChannel.id);
// # Create a private channel that the user is a member of. Private channels are not included in the
// # initial local public-channel results, but the server response adds them to the same channel list.
const privateChannel = await adminClient.createPrivateChannel(
team.id,
'AC A Private',
'ac-private-' + Date.now(),
);
await adminClient.addToChannel(user.id, privateChannel.id);
// # Create a channel that the user is not a member of, so it can only come from the search
await adminClient.createChannel({
team_id: team.id,
name: 'ac-remote-' + Date.now(),
display_name: 'AC Remote',
type: 'O',
});
// # Log in as regular user
const {channelsPage, page} = await pw.testBrowser.login(user);
// # Hold the channel search open so the initial local results can be observed independently of the response
const releaseSearch = await holdChannelSearch(page);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Type a channel mention matching both channels
const postCreate = channelsPage.centerView.postCreate;
await postCreate.typeMessage('~ac-');
// * Verify the channels the user is a member of are shown without waiting for the search
const suggestionList = postCreate.suggestionList;
const myChannels = suggestionList.getByRole('group', {name: 'My Channels'});
await expect(myChannels.getByRole('option')).toContainText(['AC Z Local']);
// * Verify the group of channels being searched for shows that it is still loading
const otherChannels = suggestionList.getByRole('group', {name: 'Other Channels'});
await expect(otherChannels.getByTestId('loadingSpinner')).toBeVisible();
// # Release the response and wait for it to be delivered
const searchResponse = page.waitForResponse(
(response) => AUTOCOMPLETE_ROUTE.test(response.url()) && searchedName(response.url()) === 'ac-',
);
releaseSearch();
await searchResponse;
// * Verify the searched channel is shown once the search finishes
await expect(otherChannels.getByRole('option')).toContainText(['AC Remote']);
// * Verify the member channel added by the response is paired with its own term, rather than mutating the
// * already-rendered local result. The order the two are rendered in is not part of what's under test.
const myChannelOptions = myChannels.getByRole('option');
await expect(myChannelOptions).toHaveCount(2);
await expect(myChannelOptions.filter({hasText: 'AC A Private'})).toHaveCount(1);
await expect(myChannelOptions.filter({hasText: 'AC Z Local'})).toHaveCount(1);
await expect(otherChannels.getByTestId('loadingSpinner')).not.toBeVisible();
},
);
/**
* @objective Verify that the ~channel autocomplete keeps showing results for what has been typed when an
* earlier, slower search finishes after a later one.
*/
test('channel mention autocomplete ignores a search that finishes out of order', {tag: ['@mentions']}, async ({pw}) => {
// # Initialize setup
const {team, user, adminClient} = await pw.initSetup();
// # Create channels that the user is not a member of, so they can only come from the search
await adminClient.createChannel({
team_id: team.id,
name: 'ac-alpha-' + Date.now(),
display_name: 'AC Alpha',
type: 'O',
});
await adminClient.createChannel({
team_id: team.id,
name: 'ac-beta-' + Date.now(),
display_name: 'AC Beta',
type: 'O',
});
// # Log in as regular user
const {channelsPage, page} = await pw.testBrowser.login(user);
// # Hold the shortest search while allowing the fully typed search to finish first
const staleSearchName = 'ac-';
let releaseStaleSearch!: () => void;
const staleSearchReleased = new Promise<void>((resolve) => {
releaseStaleSearch = resolve;
});
await page.route(AUTOCOMPLETE_ROUTE, async (route) => {
if (searchedName(route.request().url()) === staleSearchName) {
await staleSearchReleased;
}
await route.continue();
});
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Note when the delayed search for the shortest mention finishes
const staleSearch = page.waitForResponse(
(response) => AUTOCOMPLETE_ROUTE.test(response.url()) && searchedName(response.url()) === staleSearchName,
);
const currentSearch = page.waitForResponse(
(response) => AUTOCOMPLETE_ROUTE.test(response.url()) && searchedName(response.url()) === 'ac-alpha',
);
// # Type a channel mention, waiting for each request so the stale response can be held deterministically
const postCreate = channelsPage.centerView.postCreate;
await typeMentionAndWaitForRequests(postCreate.input, page, '~ac-alpha');
// # Ensure the current search response is the one that populated the list before releasing the stale response
await currentSearch;
// * Verify only the channel matching what was typed is shown
const otherChannels = postCreate.suggestionList.getByRole('group', {name: 'Other Channels'});
await expect(otherChannels.getByRole('option')).toContainText(['AC Alpha']);
await expect(otherChannels.getByRole('option')).toHaveCount(1);
// # Release the stale response and wait for it to finish last
releaseStaleSearch();
await staleSearch;
// * Verify its results are ignored, since they no longer match what was typed
await expect(otherChannels.getByRole('option')).toHaveCount(1);
await expect(otherChannels.getByRole('option')).toContainText(['AC Alpha']);
// * Verify the mention can still be completed, so the suggestion list is still interactive
await otherChannels.getByRole('option').first().click();
await expect(postCreate.input).toHaveValue(/~ac-alpha/);
});

View File

@@ -0,0 +1,78 @@
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {expect, test} from '@mattermost/playwright-lib';
/**
* @objective Verify that the "Other Channels" group in the ~channel autocomplete in the message input prioritizes
* channels whose DisplayName matches the search term.
*/
test(
'MM-67953 channel mention autocomplete prioritizes DisplayName matches in Other Channels',
{tag: ['@mentions']},
async ({pw}) => {
// # Initialize setup
const {team, user, adminClient} = await pw.initSetup();
// # Create channels whose DisplayName matches the search term (user is NOT a member)
await adminClient.createChannel({
team_id: team.id,
name: 'ac-gamma-conversation-' + Date.now(),
display_name: 'Gamma: Conversation',
type: 'O',
});
await adminClient.createChannel({
team_id: team.id,
name: 'ac-gamma-logs-' + Date.now(),
display_name: 'Gamma: Logs',
type: 'O',
});
// # Create channels whose Purpose matches but DisplayName does NOT (user is NOT a member)
await adminClient.createChannel({
team_id: team.id,
name: 'ac-alpha-channel-' + Date.now(),
display_name: 'Alpha Channel',
type: 'O',
purpose: 'alpha release of gamma',
});
await adminClient.createChannel({
team_id: team.id,
name: 'ac-beta-channel-' + Date.now(),
display_name: 'Beta Channel',
type: 'O',
purpose: 'beta release of gamma',
});
// # Log in as regular user
const {channelsPage} = await pw.testBrowser.login(user);
// # Visit town-square channel
await channelsPage.goto(team.name, 'town-square');
await channelsPage.toBeVisible();
// # Type a channel mention in the message input to trigger autocomplete
await channelsPage.centerView.postCreate.typeMessage('~gamma');
// # Wait for the suggestion list to appear
const suggestionList = channelsPage.centerView.postCreate.suggestionList;
await expect(suggestionList).toBeVisible();
// # Get all suggestion items within the "Other Channels" group
const otherChannelsGroup = suggestionList.getByRole('group', {name: 'Other Channels'});
await expect(otherChannelsGroup).toBeVisible();
const suggestions = otherChannelsGroup.getByRole('option');
// * Verify at least 4 suggestions are shown in "Other Channels"
await expect(suggestions).toHaveCount(4);
// * Verify DisplayName-matching channels appear first, sorted alphabetically
await expect(suggestions.nth(0)).toContainText('Gamma: Conversation');
await expect(suggestions.nth(1)).toContainText('Gamma: Logs');
// * Verify Purpose-only-matching channels appear after, sorted alphabetically by DisplayName
await expect(suggestions.nth(2)).toContainText('Alpha Channel');
await expect(suggestions.nth(3)).toContainText('Beta Channel');
},
);