Files
dcd1d6b9db [MM-61758] Burn on read feature (#34703)
* Add read receipt store for burn on read message types

* update mocks

* fix invalidation target

* have consistent case on index creation

* Add temporary posts table

* add mock

* add transaction support

* reflect review comments

* wip: Add reveal endpoint

* user check error id instead

* wip: Add ws events and cleanup for burn on read posts

* add burn endpoint for explicitly burning messages

* add translations

* Added logic to associate files of BoR post with the post

* Added test

* fixes

* disable pinning posts and review comments

* MM-66594 - Burn on read UI integration (#34647)

* MM-66244 - add BoR visual components to message editor

* MM-66246 - BoR visual indicator for sender and receiver

* MM-66607 - bor - add timer countdown and autodeletion

* add the system console max time to live config

* use the max expire at and create global scheduler to register bor messages

* use seconds for BoR config values in BE

* implement the read by text shown in the tooltip logic

* unestack the posts from same receiver and BoR  and fix styling

* avoid opening reply RHS

* remove unused dispatchers

* persis the BoR label in the drafts

* move expiration value to metadata

* adjust unit tests to metadata insted of props

* code clean up and some performance improvements; add period grace for deletion too

* adjust migration serie number

* hide bor messages when config is off

* performance improvements on post component and code clean up

* keep bor existing post functionality if config is disabled

* Add read receipt store for burn on read message types

* Add temporary posts table

* add transaction support

* reflect review comments

* wip: Add reveal endpoint

* user check error id instead

* wip: Add ws events and cleanup for burn on read posts

* avoid reacting to unrevealed bor messages

* adjust migration number

* Add read receipt store for burn on read message types

* have consistent case on index creation

* Add temporary posts table

* add mock

* add transaction support

* reflect review comments

* wip: Add reveal endpoint

* user check error id instead

* wip: Add ws events and cleanup for burn on read posts

* add burn endpoint for explicitly burning messages

* adjust post reveal and type with backend changes

* use real config values, adjust icon usage and style

* adjust the delete from from sender and receiver

* improve self deleting logic by placing in badge, use burn endpoint

* adjust websocket events handling for the read by sender label information

* adjust styling for concealed and error state

* update burn-on-read post event handling for improved recipient tracking and multi-device sync

* replace burn_on_read with type in database migrations and model

* remove burn_on_read metadata from PostMetadata and related structures

* Added logic to associate files of BoR post with the post

* Added test

* adjust migration name and fix linter

* Add read receipt store for burn on read message types

* update mocks

* have consistent case on index creation

* Add temporary posts table

* add mock

* add transaction support

* reflect review comments

* wip: Add reveal endpoint

* user check error id instead

* wip: Add ws events and cleanup for burn on read posts

* add burn endpoint for explicitly burning messages

* Added logic to associate files of BoR post with the post

* Added test

* disable pinning posts and review comments

* show attachment on bor reveal

* remove unused translation

* Enhance burn-on-read post handling and refine previous post ID retrieval logic

* adjust the returning chunk to work with bor messages

* read temp post from master db

* read from master

* show the copy link button to the sender

* revert unnecessary check

* restore correct json tag

* remove unused error handling  and clarify burn-on-read comment

* improve type safety and use proper selectors

* eliminate code duplication in deletion handler

* optimize performance and add documentation

* delete bor message for sender once all receivers reveal it

* add burn on read to scheduled posts

* add feature enable check

* use master to avoid  all read recipients race condition

---------

Co-authored-by: Mattermost Build <[email protected]>
Co-authored-by: Ibrahim Serdar Acikgoz <[email protected]>
Co-authored-by: Harshil Sharma <[email protected]>

* squash migrations into single file

* add configuration for the scheduler

* don't run messagehasbeenposted hook

* remove parallel tests on burn on read

* add clean up for closing opened modals from previous tests

* simplify delete menu item rendering

* add cleanup step to close open modals after each test to prevent pollution

* streamline delete button visibility logic for Burn on Read posts

* improve reliability of closing post menu and modals by using body ESC key

---------

Co-authored-by: Harshil Sharma <[email protected]>
Co-authored-by: Pablo Vélez <[email protected]>
Co-authored-by: Mattermost Build <[email protected]>
2025-12-11 07:59:50 +01:00

202 lines
7.3 KiB
JavaScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
// ***************************************************************
// - [#] indicates a test step (e.g. # Go to a page)
// - [*] indicates an assertion (e.g. * Check the title)
// - Use element ID when selecting an element. Create one if none.
// ***************************************************************
// Stage: @prod
// Group: @channels @messaging @smoke
import {getAdminAccount} from '../../../support/env';
import * as MESSAGES from '../../../fixtures/messages';
import timeouts from '../../../fixtures/timeouts';
describe('Message', () => {
const admin = getAdminAccount();
let testTeam;
let testChannel;
before(() => {
// # Create new team and new user and visit Off-Topic channel
cy.apiInitSetup({loginAfter: true}).then(({team, channel}) => {
testTeam = team;
testChannel = channel;
cy.visit(`/${testTeam.name}/channels/off-topic`);
});
});
beforeEach(() => {
// # Close any open modals from previous tests (e.g., move-thread-modal)
cy.get('body').then(($body) => {
if ($body.find('.modal.in').length > 0) {
cy.get('body').type('{esc}');
}
});
});
it('MM-T77 Consecutive message does not repeat profile info', () => {
// # Wait for posts to load
cy.get('#postListContent').should('be.visible');
// # Post a message to force next user message to display a message
cy.postMessageAs({sender: admin, message: 'Hello', channelId: testChannel.id});
// # Post message "One"
cy.postMessage('One');
// * Check profile image is visible
shouldHavePostProfileImageVisible(true);
// # Post message "Two"
cy.postMessage('Two');
// * Check profile image is not visible
shouldHavePostProfileImageVisible(false);
// # Post message "Three"
cy.postMessage('Three');
// * Check profile image is not visible
shouldHavePostProfileImageVisible(false);
});
it('MM-T201 Focus move to main input box when a character key is selected', () => {
// # Post message
cy.postMessage('Message');
cy.getLastPostId().then((postId) => {
const divPostId = `#post_${postId}`;
// # Left click on post to move the focus out of the main input box
cy.get(divPostId).click().wait(timeouts.ONE_SEC);
// # Push a character key such as "A"
cy.uiGetPostTextBox().type('A');
// # Open the "..." menu on a post in the main to move the focus out of the main input box
cy.clickPostDotMenu(postId);
cy.get(`#CENTER_dropdown_${postId}`).should('be.visible');
// # Press ESC on body to close the menu (more reliable than typing on dropdown)
cy.get('body').type('{esc}');
// # Wait for menu to close and ensure no modals are open
cy.get(`#CENTER_dropdown_${postId}`).should('not.exist');
cy.get('.modal.in').should('not.exist');
// # Push a character key such as "A"
cy.uiGetPostTextBox().type('A');
// * Focus is moved back to the main input and the keystroke is captured
cy.focused().should('have.id', 'post_textbox');
cy.focused().should('contain', 'AA');
});
});
it('MM-T72 @here. @all. @channel. (containing a period) still highlight', () => {
// # Post message
cy.postMessage('@here. @all. @channel.');
cy.getLastPostId().then((postId) => {
const divPostId = `#postMessageText_${postId}`;
// * Check that the message contains the whole content sent ie. mentions with dots.
cy.get(divPostId).find('p').should('have.text', '@here. @all. @channel.');
// * Check that only the at-mention are inside span.mention--highlight
cy.get(divPostId).find('.mention--highlight').
first().should('have.text', '@here').should('not.have.text', '.').
next().should('have.text', '@all').should('not.have.text', '.').
next().should('have.text', '@channel').should('not.have.text', '.');
});
});
it('MM-2954 /me message should be formatted like a system message', () => {
// # Post message
const message = 'hello there';
cy.postMessage(`/me ${message}`);
cy.uiWaitUntilMessagePostedIncludes(message);
cy.getLastPostId().then((postId) => {
const divPostId = `#post_${postId}`;
// * Check that message has the css class needed for system message styling
cy.get(divPostId).should('have.class', 'post--system');
});
});
it('message with emoji contains hidden shortcode text', () => {
const message = 'This post has a shortcode emoji :raising_hand_man: within it.';
// # Post a message with a shortcode emoji
cy.postMessage(message);
cy.getLastPostId().then((postId) => {
const divPostId = `#post_${postId}`;
// * Check that message matches what was posted with the emoji omitted
cy.get(`#postMessageText_${postId}`).should('have.text', message);
// * Check that the emoji image contains the hidden shortcode text
cy.get(divPostId).find('span.emoticon').should('have.text', ':raising_hand_man:');
});
});
it('message with unicode emoji displays the unicode and not a span with background image', () => {
// # Post a message with a shortcode emoji
cy.postMessage('This post a unicode emoji in a code snippet: `😉`');
cy.getLastPostId().then((postId) => {
const divPostId = `#post_${postId}`;
cy.get(divPostId).find('span.emoticon').should('not.exist');
cy.get(divPostId).find('span.codespan__pre-wrap code').should('have.text', '😉');
});
});
it('MM-T3307 Focus remains in the RHS text box', () => {
cy.postMessage(MESSAGES.MEDIUM);
// # Open reply thread (RHS)
cy.clickPostCommentIcon();
// # Add some text to RHS text box
cy.uiGetReplyTextBox().type(MESSAGES.TINY);
// # Click on Preview
cy.get('#PreviewInputTextButton').click();
// # Click on Reply
cy.uiReply();
// * Focus to remain in the RHS text box
cy.uiGetReplyTextBox().should('be.focused');
});
});
function shouldHavePostProfileImageVisible(isVisible = true) {
cy.getLastPostId().then((postID) => {
const target = `#post_${postID}`;
if (isVisible) {
cy.get(target).invoke('attr', 'class').
should('contain', 'current--user').
and('contain', 'other--root');
cy.get(`${target} > div[data-testid='postContent'] > .post__img`).should('be.visible');
} else {
cy.get(target).invoke('attr', 'class').
should('contain', 'current--user').
and('contain', 'same--user').
and('contain', 'same--root');
cy.get(`${target} > div[data-testid='postContent'] > .post__img`).
should('be.visible').
and('be.empty');
}
});
}