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
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:
@@ -0,0 +1,49 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {Locator} from '@playwright/test';
|
||||
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
function backgroundColor(item: Locator): Promise<string> {
|
||||
return item.evaluate((el) => window.getComputedStyle(el).backgroundColor);
|
||||
}
|
||||
|
||||
/**
|
||||
* @objective Verify that hovering different channel types (public channel and direct message) in the
|
||||
* sidebar highlights them by changing their background.
|
||||
*/
|
||||
test(
|
||||
'MM-T842 highlights public channel and direct message items on hover',
|
||||
{tag: '@channels_sidebar'},
|
||||
async ({pw}) => {
|
||||
const {adminClient, adminUser, team, user} = await pw.initSetup();
|
||||
|
||||
// # Create a public channel the user is a member of but is not currently viewing
|
||||
const publicChannel = await adminClient.createPublicChannel(team.id, `Highlight ${pw.random.id()}`);
|
||||
await adminClient.addToChannel(user.id, publicChannel.id);
|
||||
|
||||
// # Create a direct message and post to it so it appears in the sidebar
|
||||
const dmChannel = await adminClient.createDirectChannel([adminUser.id, user.id]);
|
||||
await adminClient.createPost({channel_id: dmChannel.id, user_id: adminUser.id, message: 'hi there'});
|
||||
|
||||
// # Log in and view Town Square so the other items are not the active channel
|
||||
const {channelsPage} = await pw.testBrowser.login(user);
|
||||
await channelsPage.goto(team.name, 'town-square');
|
||||
await channelsPage.toBeVisible();
|
||||
|
||||
// * Verify the public channel item background changes on hover
|
||||
const publicItem = channelsPage.sidebarLeft.container.getByRole('link', {name: publicChannel.display_name});
|
||||
await expect(publicItem).toBeVisible();
|
||||
const publicBgBefore = await backgroundColor(publicItem);
|
||||
await publicItem.hover();
|
||||
await expect.poll(() => backgroundColor(publicItem)).not.toBe(publicBgBefore);
|
||||
|
||||
// * Verify the direct message item background changes on hover
|
||||
const dmItem = channelsPage.sidebarLeft.container.getByRole('link', {name: adminUser.username});
|
||||
await expect(dmItem).toBeVisible();
|
||||
const dmBgBefore = await backgroundColor(dmItem);
|
||||
await dmItem.hover();
|
||||
await expect.poll(() => backgroundColor(dmItem)).not.toBe(dmBgBefore);
|
||||
},
|
||||
);
|
||||
@@ -0,0 +1,163 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
import type {PlaywrightExtended} from '@mattermost/playwright-lib';
|
||||
import {expect, test} from '@mattermost/playwright-lib';
|
||||
|
||||
// GET /api/v4/users/me/channels and GET /api/v4/users/me/teams/{team_id}/channels, the two requests
|
||||
// that populate the sidebar's channels. Anchored so the sibling /channels/members and
|
||||
// /channels/categories routes are left alone.
|
||||
const CHANNEL_LIST_PATH = /^\/api\/v4\/users\/me\/(channels|teams\/[^/]+\/channels)$/;
|
||||
|
||||
// GET /api/v4/users/{user_id}/channel_members, the request that populates the user's channel
|
||||
// memberships. The DM half of loadProfilesForSidebar reads getMyChannels, which filters the channels
|
||||
// through these, so it needs them as much as it needs the channels themselves.
|
||||
const CHANNEL_MEMBERS_PATH = /^\/api\/v4\/users\/[^/]+\/channel_members$/;
|
||||
|
||||
// GET /api/v4/users, the paged "load some profiles" request.
|
||||
const USER_PAGE_PATH = /^\/api\/v4\/users$/;
|
||||
|
||||
const REQUEST_DELAY_MS = 4000;
|
||||
|
||||
type Scenario = Awaited<ReturnType<typeof setupSidebarProfiles>>;
|
||||
|
||||
/**
|
||||
* Sets up a favorited DM and a GM whose members appear nowhere else in the loaded state, then logs
|
||||
* in without navigating anywhere yet.
|
||||
*/
|
||||
async function setupSidebarProfiles(pw: PlaywrightExtended) {
|
||||
const {adminClient, user, userClient, team} = await pw.initSetup();
|
||||
|
||||
// # Create the teammates for the DM and the GM. They are deliberately left out of the
|
||||
// channel the test lands on, so that nothing else pulls their profiles into the store.
|
||||
const [dmUser, gmUser1, gmUser2] = await adminClient.createUsers(team.id, 3, 'sidebar-profile');
|
||||
|
||||
const dmChannel = await userClient.createDirectChannel([user.id, dmUser.id]);
|
||||
const gmChannel = await userClient.createGroupChannel([user.id, gmUser1.id, gmUser2.id]);
|
||||
|
||||
// # Land on a channel containing only the test user, so the current channel's member
|
||||
// profiles can't mask a missing DM or GM profile.
|
||||
const landingChannel = await adminClient.createPublicChannel(team.id, 'Sidebar Profiles');
|
||||
await adminClient.addToChannel(user.id, landingChannel.id);
|
||||
|
||||
// # Make both conversations visible in the sidebar
|
||||
await userClient.savePreferences(user.id, [
|
||||
{user_id: user.id, category: 'direct_channel_show', name: dmUser.id, value: 'true'},
|
||||
{user_id: user.id, category: 'group_channel_show', name: gmChannel.id, value: 'true'},
|
||||
]);
|
||||
|
||||
// # Move the DM into Favorites. A DM whose teammate profile is missing is filtered out of
|
||||
// the Direct Messages category, so only another category surfaces the blank row.
|
||||
const {categories} = await userClient.getChannelCategories(user.id, team.id);
|
||||
const favorites = categories.find((category) => category.type === 'favorites');
|
||||
const directMessages = categories.find((category) => category.type === 'direct_messages');
|
||||
if (!favorites || !directMessages) {
|
||||
throw new Error('Expected the default Favorites and Direct Messages categories to exist');
|
||||
}
|
||||
await userClient.updateChannelCategories(user.id, team.id, [
|
||||
{...favorites, channel_ids: [dmChannel.id]},
|
||||
{
|
||||
...directMessages,
|
||||
channel_ids: directMessages.channel_ids.filter((channelId) => channelId !== dmChannel.id),
|
||||
},
|
||||
]);
|
||||
|
||||
const {channelsPage, page} = await pw.testBrowser.login(user);
|
||||
|
||||
// # Empty the first page of users, which on a server this small would otherwise return
|
||||
// every teammate and load the DM profile by accident.
|
||||
await page.route(
|
||||
(url) => USER_PAGE_PATH.test(url.pathname),
|
||||
(route) => route.fulfill({status: 200, contentType: 'application/json', body: '[]'}),
|
||||
);
|
||||
|
||||
return {channelsPage, page, team, landingChannel, dmUser, dmChannel, gmChannel};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delays the requests matching the given path so the sidebar categories always win the race.
|
||||
*/
|
||||
async function delayRequests({page}: Scenario, path: RegExp) {
|
||||
await page.route(
|
||||
(url) => path.test(url.pathname),
|
||||
async (route) => {
|
||||
await new Promise((resolve) => setTimeout(resolve, REQUEST_DELAY_MS));
|
||||
await route.continue();
|
||||
},
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Loads the landing channel. Nothing in the sidebar is clicked afterwards, because navigating to a
|
||||
* channel reloads these profiles and would hide the bug.
|
||||
*/
|
||||
async function gotoLandingChannel({channelsPage, team, landingChannel}: Scenario) {
|
||||
await channelsPage.goto(team.name, landingChannel.name);
|
||||
await channelsPage.toBeVisible();
|
||||
}
|
||||
|
||||
/**
|
||||
* @objective Verify that direct and group message channels in the sidebar render the teammate's
|
||||
* name and the member count when the channel list arrives after the sidebar categories.
|
||||
*
|
||||
* @precondition
|
||||
* Two conditions that occur naturally on a busy account are forced here so the test is
|
||||
* deterministic on a small server:
|
||||
*
|
||||
* 1. The channel list requests are delayed so the sidebar categories always resolve first. That
|
||||
* ordering is what leaves the DM and GM profiles unloaded, and it happens on its own once an
|
||||
* account has enough channels for the channel list to be the slower of the two.
|
||||
* 2. The first page of GET /api/v4/users is emptied. On a server with a handful of users that one
|
||||
* request happens to return every teammate and hides the missing DM profile; on a real server
|
||||
* the teammates are nowhere near the first page.
|
||||
*/
|
||||
test(
|
||||
'renders DM and GM sidebar rows when the channel list arrives after the categories',
|
||||
{tag: '@sidebar_left'},
|
||||
async ({pw}) => {
|
||||
const scenario = await setupSidebarProfiles(pw);
|
||||
|
||||
// # Delay the channel list so the sidebar categories always win the race
|
||||
await delayRequests(scenario, CHANNEL_LIST_PATH);
|
||||
|
||||
await gotoLandingChannel(scenario);
|
||||
|
||||
const {sidebarLeft} = scenario.channelsPage;
|
||||
|
||||
// * Verify the group message shows how many other members it has instead of 0
|
||||
await expect(sidebarLeft.memberCountBadge(scenario.gmChannel.name)).toHaveText('2');
|
||||
|
||||
// * Verify the favorited DM shows the teammate's username instead of rendering an empty row
|
||||
await expect(sidebarLeft.item(scenario.dmChannel.name)).toContainText(scenario.dmUser.username);
|
||||
},
|
||||
);
|
||||
|
||||
/**
|
||||
* @objective Verify that direct and group message channels in the sidebar render the teammate's
|
||||
* name and the member count when the channel memberships arrive after the sidebar categories.
|
||||
*
|
||||
* @precondition
|
||||
* Same as the channel list ordering above, except the memberships are the slow request. This is the
|
||||
* separate half of the dependency: the channels can all be present and the DM profiles still go
|
||||
* unloaded, because getMyChannels filters the channels through the memberships.
|
||||
*/
|
||||
test(
|
||||
'renders DM and GM sidebar rows when the channel memberships arrive after the categories',
|
||||
{tag: '@sidebar_left'},
|
||||
async ({pw}) => {
|
||||
const scenario = await setupSidebarProfiles(pw);
|
||||
|
||||
// # Delay the channel memberships so the sidebar categories always win the race
|
||||
await delayRequests(scenario, CHANNEL_MEMBERS_PATH);
|
||||
|
||||
await gotoLandingChannel(scenario);
|
||||
|
||||
const {sidebarLeft} = scenario.channelsPage;
|
||||
|
||||
// * Verify the group message shows how many other members it has instead of 0
|
||||
await expect(sidebarLeft.memberCountBadge(scenario.gmChannel.name)).toHaveText('2');
|
||||
|
||||
// * Verify the favorited DM shows the teammate's username instead of rendering an empty row
|
||||
await expect(sidebarLeft.item(scenario.dmChannel.name)).toContainText(scenario.dmUser.username);
|
||||
},
|
||||
);
|
||||
Reference in New Issue
Block a user