mattermost-mobile/app/utils/permalink_sync.test.ts
Rajat Dabade d732b5a33e
Ability to show attachment if the permalink post contains attachments. (#9101)
* typescript and view component for permalink with user and message

* Old post edited handling in permalink

* Added test and update flag value to EnablePermalinkPreview

* Added test for permalink_preview component

* Added test for content/index.tsx for permalink

* Addressed review comments

* Unit test for missing file and review comments

* Added test to check handlePostEdited permalink sync only calls one time only

* Change TouchableOpacity to Pressable

* When user not found fetch the user from the server

* Removed the redundant test in the test for permalink_preview/index?

* ts to tsx

* Removed the circular dependency

* Address review comments

* displayname fallback

* remove permalink when permalink post is deleted

* UX review comments

* Linter fixes

* Test fixes

* File attachment in permalink preview component

* Fix the width and height of the image in permalink

* Added gredient when exceeds height of permalink container

* Minor

* Updated tests

* Minor

* Review comments

* Minor review comments

* type fixes

* Review comments

* Minor

* Address review comments

* Minor

* Merge fixes

* Addressed review comments

* Some more review comments

* linter fixes

* UX review and remove show more height not require

* Fix tests

* Review fixes, dev and ux

---------

Co-authored-by: yasserfaraazkhan <attitude3cena.yf@gmail.com>
2025-09-12 13:44:54 +05:30

358 lines
13 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {Database} from '@nozbe/watermelondb';
import {ActionType} from '@constants';
import DatabaseManager from '@database/manager';
import ServerDataOperator from '@database/operator/server_data_operator';
import TestHelper from '@test/test_helper';
import * as logModule from '@utils/log';
import {updatePermalinkMetadata} from './permalink_sync';
import type PostModel from '@typings/database/models/servers/post';
describe('Permalink Sync Utils', () => {
const serverUrl = 'permalinksync.test.com';
let database: Database;
let operator: ServerDataOperator;
beforeEach(async () => {
await DatabaseManager.init([serverUrl]);
const serverDatabaseAndOperator = DatabaseManager.getServerDatabaseAndOperator(serverUrl);
database = serverDatabaseAndOperator.database;
operator = serverDatabaseAndOperator.operator;
jest.clearAllMocks();
});
afterEach(async () => {
await DatabaseManager.destroyServerDatabase(serverUrl);
});
describe('updatePermalinkMetadata', () => {
it('should return null when post has no metadata', async () => {
const referencedPostId = 'referenced_post_456';
const freshPostData = TestHelper.fakePost({id: referencedPostId});
const postWithoutMetadata = TestHelper.fakePost({
id: 'post_without_metadata',
channel_id: 'channel1',
metadata: undefined,
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [postWithoutMetadata.id],
posts: [postWithoutMetadata],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(postWithoutMetadata.id) as PostModel;
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).toBeNull();
});
it('should return null when post has no embeds', async () => {
const referencedPostId = 'referenced_post_456';
const freshPostData = TestHelper.fakePost({id: referencedPostId});
const postWithEmptyEmbeds = TestHelper.fakePost({
id: 'post_with_empty_embeds',
channel_id: 'channel1',
metadata: {
embeds: [],
},
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [postWithEmptyEmbeds.id],
posts: [postWithEmptyEmbeds],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(postWithEmptyEmbeds.id) as PostModel;
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).toBeNull();
});
it('should update permalink metadata when fresh post is newer', async () => {
const referencedPostId = 'referenced_post_456';
const referencingPost = TestHelper.fakePost({
id: 'referencing_post_123',
channel_id: 'channel1',
metadata: {
embeds: [
{
type: 'permalink',
url: '',
data: {
post_id: referencedPostId,
post: {
id: referencedPostId,
message: 'Old message',
edit_at: 1000,
update_at: 1000,
user_id: 'user_123',
create_at: 500,
},
},
},
],
},
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [referencingPost.id],
posts: [referencingPost],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(referencingPost.id) as PostModel;
const freshPostData = TestHelper.fakePost({
id: referencedPostId,
message: 'Updated message',
edit_at: 2000,
update_at: 2000,
user_id: 'user_123',
create_at: 500,
});
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).not.toBeNull();
expect(result!.id).toBe(referencingPost.id);
expect(result!._preparedState).toBe('update');
await operator.batchRecords([result!], 'test update');
});
it('should not update when fresh post is older or same age', async () => {
const referencedPostId = 'referenced_post_456';
const referencingPost = TestHelper.fakePost({
id: 'referencing_post_123',
channel_id: 'channel1',
metadata: {
embeds: [
{
type: 'permalink',
url: '',
data: {
post_id: referencedPostId,
post: {
id: referencedPostId,
message: 'Current message',
edit_at: 2000,
update_at: 2000,
user_id: 'user_123',
create_at: 500,
},
},
},
],
},
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [referencingPost.id],
posts: [referencingPost],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(referencingPost.id) as PostModel;
const freshPostData = TestHelper.fakePost({
id: referencedPostId,
message: 'Older message',
edit_at: 1000,
update_at: 1000,
});
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).toBeNull();
});
it('should update permalink metadata including file metadata when fresh post is newer', async () => {
const referencedPostId = 'referenced_post_with_files';
const oldFileMetadata: FileInfo[] = [
{
id: 'file1',
name: 'old_file.txt',
extension: 'txt',
size: 100,
mime_type: 'text/plain',
width: 0,
height: 0,
has_preview_image: false,
user_id: 'user_123',
},
];
const newFileMetadata: FileInfo[] = [
{
id: 'file1',
name: 'old_file.txt',
extension: 'txt',
size: 100,
mime_type: 'text/plain',
width: 0,
height: 0,
has_preview_image: false,
user_id: 'user_123',
},
{
id: 'file2',
name: 'new_file.jpg',
extension: 'jpg',
size: 2000,
mime_type: 'image/jpeg',
width: 1920,
height: 1080,
has_preview_image: true,
user_id: 'user_123',
},
];
const referencingPost = TestHelper.fakePost({
id: 'referencing_post_files',
channel_id: 'channel1',
metadata: {
embeds: [
{
type: 'permalink',
url: '',
data: {
post_id: referencedPostId,
post: {
id: referencedPostId,
message: 'Post with old files',
edit_at: 1000,
update_at: 1000,
user_id: 'user_123',
create_at: 500,
metadata: {
files: oldFileMetadata,
},
},
},
},
],
},
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [referencingPost.id],
posts: [referencingPost],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(referencingPost.id) as PostModel;
const freshPostData = TestHelper.fakePost({
id: referencedPostId,
message: 'Post with updated files',
edit_at: 2000,
update_at: 2000,
user_id: 'user_123',
create_at: 500,
metadata: {
files: newFileMetadata,
},
});
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).not.toBeNull();
expect(result!.id).toBe(referencingPost.id);
expect(result!._preparedState).toBe('update');
// Verify the metadata changes
expect(result!.metadata!.embeds![0].data.post.message).toBe('Post with updated files');
expect(result!.metadata!.embeds![0].data.post.edit_at).toBe(2000);
expect(result!.metadata!.embeds![0].data.post.metadata).toEqual({
files: newFileMetadata,
});
expect(result!.metadata!.embeds![0].data.post.metadata!.files).toHaveLength(2);
expect(result!.metadata!.embeds![0].data.post.metadata!.files![1].name).toBe('new_file.jpg');
await operator.batchRecords([result!], 'test file metadata update');
});
it('should handle errors during post update gracefully', async () => {
const referencedPostId = 'referenced_post_456';
const referencingPost = TestHelper.fakePost({
id: 'referencing_post_123',
channel_id: 'channel1',
metadata: {
embeds: [
{
type: 'permalink',
url: '',
data: {
post_id: referencedPostId,
post: {
id: referencedPostId,
message: 'Old message',
edit_at: 1000,
update_at: 1000,
user_id: 'user_123',
create_at: 500,
},
},
},
],
},
});
const models = await operator.handlePosts({
actionType: ActionType.POSTS.RECEIVED_NEW,
order: [referencingPost.id],
posts: [referencingPost],
prepareRecordsOnly: true,
});
await operator.batchRecords(models, 'test');
const postModel = await database.get('Post').find(referencingPost.id) as PostModel;
const testError = new Error('Database update failed');
jest.spyOn(postModel, 'prepareUpdate').mockImplementationOnce(() => {
throw testError;
});
const logWarningSpy = jest.spyOn(logModule, 'logWarning').mockImplementationOnce(() => {});
const freshPostData = TestHelper.fakePost({
id: referencedPostId,
message: 'Updated message',
edit_at: 2000,
update_at: 2000,
user_id: 'user_123',
create_at: 500,
});
const result = updatePermalinkMetadata(postModel, referencedPostId, freshPostData);
expect(result).toBeNull();
expect(logWarningSpy).toHaveBeenCalledWith('Error updating permalink metadata:', testError);
});
});
});