mattermost-mobile-upstream/app/queries/servers/thread.test.ts
yasser khan 6f8f87b081
fix: Product fixes to run e2e tests (#10081)
fix: extract product fixes from Detox PR and stabilize related E2E

Move Saved, thread follow, bookmarks image_url, tables, search persist,
create-channel retry, and sidebar padding onto their own branch so they
can land or revert independently of the E2E harness.

Also includes:
- iOS search clear/Cancel overlap and resting margin fixes
- Saved messages subscription distinctUntilChanged guards (no flicker)
- Table horizontal inset derived from a single padding constant
- Manual time entry commits on parse (not only blur/submit)
- Presence sheet dismiss-before-status-update (Android Fabric crash)
- Bounded keyboard dismiss on Android edge-to-edge only; iOS unbound
- Create-channel retry leaves header button enabled on failure
- E2E flakiness: Maestro attach_logs/subflows, bookmark Edit wait,
  status sheet timing, classification lock transport retries,
  apiCreatePost throws on network errors, flow-header contract in CI
- Unit/integration coverage and CodeRabbit follow-ups for the above
2026-08-26 16:12:37 +05:30

111 lines
3.6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import DatabaseManager from '@database/manager';
import {getThreadById, prepareThreadsFromReceivedPosts} from '@queries/servers/thread';
import TestHelper from '@test/test_helper';
import type ServerDataOperator from '@database/operator/server_data_operator';
import type {Database} from '@nozbe/watermelondb';
const serverUrl = 'baseHandler.test.com';
const teamId = 'team-id-1';
const channelId = 'channel-id-1';
describe('prepareThreadsFromReceivedPosts', () => {
let database: Database;
let operator: ServerDataOperator;
beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
({database, operator} = DatabaseManager.getServerDatabaseAndOperator(serverUrl));
});
afterEach(async () => {
await DatabaseManager.destroyServerDatabase(serverUrl);
});
it('should preserve is_following when the post payload omits the field', async () => {
const rootPost = TestHelper.fakePost({
channel_id: channelId,
id: 'root-post-id',
create_at: 1,
reply_count: 1,
});
await operator.handleThreads({
threads: [{
id: rootPost.id,
reply_count: 1,
last_reply_at: 1,
last_viewed_at: 0,
participants: [],
is_following: true,
unread_replies: 0,
unread_mentions: 0,
delete_at: 0,
lastFetchedAt: 0,
post: rootPost,
} as ThreadWithLastFetchedAt],
prepareRecordsOnly: false,
teamId,
});
const postWithoutFollowFlag = {
...rootPost,
reply_count: 2,
last_reply_at: 2,
};
delete (postWithoutFollowFlag as {is_following?: boolean}).is_following;
const models = await prepareThreadsFromReceivedPosts(operator, [postWithoutFollowFlag], true);
expect(models).toHaveLength(1);
await operator.batchRecords(models, 'prepareThreadsFromReceivedPosts');
const thread = await getThreadById(database, rootPost.id);
expect(thread).toBeDefined();
expect(thread?.isFollowing).toBe(true);
expect(thread?.replyCount).toBe(2);
});
it('should apply an explicit is_following false from the post payload', async () => {
const rootPost = TestHelper.fakePost({
channel_id: channelId,
id: 'root-post-id-2',
create_at: 1,
reply_count: 1,
is_following: true,
});
await operator.handleThreads({
threads: [{
id: rootPost.id,
reply_count: 1,
last_reply_at: 1,
last_viewed_at: 0,
participants: [],
is_following: true,
unread_replies: 0,
unread_mentions: 0,
delete_at: 0,
lastFetchedAt: 0,
post: rootPost,
} as ThreadWithLastFetchedAt],
prepareRecordsOnly: false,
teamId,
});
const models = await prepareThreadsFromReceivedPosts(operator, [{
...rootPost,
reply_count: 2,
is_following: false,
}], true);
expect(models).toHaveLength(1);
await operator.batchRecords(models, 'prepareThreadsFromReceivedPosts');
const thread = await getThreadById(database, rootPost.id);
expect(thread).toBeDefined();
expect(thread?.isFollowing).toBe(false);
});
});