mattermost-mobile-upstream/app/components/markdown/markdown.test.tsx
Elias Nahum b6fc85385e
Migrate from RNN to Expo router (#9402)
* Expo-Router partial migration

* Full migration to ExpoRouter

* i18n

* add tests

* Merge branch 'main' into expo-router

* align package-lock.json

* i18n

* feedback review

* feedback review

* android 16kb patch

* Refactored emoji picker keyboard using a state machine

* set android launch_screen in drawables instead of layout

* no conditional hooks

* remove duplicate call in test

* address feedback

* fix tests and address more feedback

* small corrections

* small corrections

* small corrections

* fix draft test

* fix draft test 2

* remove navigateToScreen in draff test

* fix nit in draft test

* remove doc file

* Feedback review

* fix snapshots

* improve test coverage

* fix i18n

* Upgrade t o RN 0.83, other dependencies and new architecture (#9737)

* library hardware-keyboard android new arch support fix

* library rnshare android new arch support fix

* library rnutils android new arch support fix

* library secure-pdf-viewer add new arch support

* Enable new arch on iOS and Android

* update emm and network client with new-arch

* update intune to use new-arch

* performance improvements

* fix tests

* fixes post input on new-arch

* fix android emoji picker

* remove doc

* run tests in parallel

* upgrade to React Native 0.83.9, Expo 55, React 19, and New Architecture

Bumps core dependencies to their latest versions:
- React Native 0.77.3 → 0.83.9 (New Architecture enabled)
- React 18.3.1 → 19.2.5
- Expo SDK 52 → 55
- TypeScript 5.8.3 → 6.0.3
- Node.js 22.16.0 → 24.15.0
- react-native-reanimated 3.17.3 → 4.3.0
- Various other dependency upgrades

Migrates iOS AppDelegate from Objective-C to Swift, updates Android
build tooling and Gradle wrapper, refreshes all patches and font
assets, and adapts app code, tests, and CI workflows throughout for
API changes introduced by these upgrades.

* remove unecessary dismissKeyboard in notifications

* feedback review

* update intune to use new-arch

* update @mattermost deps

* missing i18n string

* additional fixes

* update fastlane
2026-05-13 17:44:55 +08:00

255 lines
8.6 KiB
TypeScript

// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
// See LICENSE.txt for license information.
import {screen} from '@testing-library/react-native';
import * as CommonMark from 'commonmark';
const {Node} = CommonMark;
import React from 'react';
import {StyleSheet} from 'react-native';
import {Preferences, Screens} from '@constants';
import {renderWithIntl} from '@test/intl-test-helper';
import {getMarkdownTextStyles} from '@utils/markdown';
import {typography} from '@utils/typography';
import Markdown, {testExports} from './markdown';
import * as Transforms from './transform';
const Parser = jest.requireActual('commonmark').Parser;
jest.mock('@screens/navigation', () => ({
navigateToRoot: jest.fn(),
}));
describe('Markdown', () => {
const baseProps: React.ComponentProps<typeof Markdown> = {
baseTextStyle: {},
enableInlineLatex: true,
enableLatex: true,
location: Screens.CHANNEL,
maxNodes: 2000,
theme: Preferences.THEMES.denim,
};
describe('error handling', () => {
test('should render Markdown normally', () => {
renderWithIntl(
<Markdown
{...baseProps}
value='This is a test'
/>,
);
expect(screen.getByText('This is a test')).toBeVisible();
});
test('should catch errors when parsing Markdown', () => {
jest.spyOn(CommonMark, 'Parser').mockImplementation(() => {
const parser = new Parser();
parser.incorporateLine = () => {
throw new Error('test error');
};
return parser;
});
renderWithIntl(
<Markdown
{...baseProps}
value='This is a test'
/>,
);
expect(screen.queryByText('This is a text')).not.toBeVisible();
expect(screen.getByText('An error occurred while parsing this text')).toBeVisible();
});
test('should catch errors when parsing Markdown', () => {
jest.spyOn(CommonMark, 'Parser').mockImplementation(() => {
const parser = new Parser();
parser.incorporateLine = () => {
throw new Error('test error');
};
return parser;
});
renderWithIntl(
<Markdown
{...baseProps}
value='This is a test'
/>,
);
expect(screen.queryByText('This is a text')).not.toBeVisible();
expect(screen.getByText('An error occurred while parsing this text')).toBeVisible();
});
test('should catch errors when transforming Markdown', () => {
jest.spyOn(Transforms, 'highlightMentions').mockImplementation(() => {
throw new Error('test error');
});
renderWithIntl(
<Markdown
{...baseProps}
value='This is a test'
/>,
);
expect(screen.queryByText('This is a text')).not.toBeVisible();
expect(screen.getByText('An error occurred while parsing this text')).toBeVisible();
});
test('should catch errors when rendering Markdown', () => {
jest.spyOn(CommonMark, 'Parser').mockImplementation(() => {
const parser = new Parser();
parser.parse = () => {
// This replicates what was returned by the parser to cause MM-61148
const document = new Node('document');
const paragraph = new Node('paragraph');
(paragraph as any)._string_content = 'some text';
document.appendChild(new Node('table'));
return document;
};
return parser;
});
renderWithIntl(
<Markdown
{...baseProps}
value='This is a test'
/>,
);
expect(screen.queryByText('This is a text')).not.toBeVisible();
expect(screen.getByText('An error occurred while rendering this text')).toBeVisible();
});
test('should catch errors when with Latex', () => {
const value =
'```latex\n' +
'\\\\\\\\asdfasdfasdf\n' +
'```\n';
renderWithIntl(
<Markdown
{...baseProps}
value={value}
/>,
);
expect(screen.queryByText('This is a text')).not.toBeVisible();
expect(screen.getByText('An error occurred while rendering this text')).toBeVisible();
});
});
describe('renderHashtagWithStyles', () => {
const {renderHashtagWithStyles} = testExports;
test('should render hashtag with correct text and link styling', () => {
const baseTextStyle = {
color: '#000000',
...typography('Body', 200),
};
const textStyles = getMarkdownTextStyles(baseProps.theme);
const context: string[] = [];
renderWithIntl(
renderHashtagWithStyles(context, 'test', textStyles, baseTextStyle),
);
const hashtagElement = screen.getByText('#test');
expect(hashtagElement).toBeVisible();
const flattenedStyle = StyleSheet.flatten(hashtagElement.props.style);
expect(flattenedStyle).toMatchObject({
fontSize: 16,
color: baseProps.theme.linkColor,
});
});
test('should render hashtag in heading with correct heading font size', () => {
const baseTextStyle = {
color: '#000000',
...typography('Body', 200),
};
const textStyles = getMarkdownTextStyles(baseProps.theme);
const context: string[] = ['heading1'];
renderWithIntl(
renderHashtagWithStyles(context, 'hashtag', textStyles, baseTextStyle),
);
const hashtagElement = screen.getByText('#hashtag');
expect(hashtagElement).toBeVisible();
const flattenedStyle = StyleSheet.flatten(hashtagElement.props.style);
expect(flattenedStyle).toMatchObject({
fontSize: 28,
});
});
test('should apply heading2 styles when in heading2 context', () => {
const baseTextStyle = {
color: '#000000',
...typography('Body', 200),
};
const textStyles = getMarkdownTextStyles(baseProps.theme);
const context: string[] = ['heading2'];
renderWithIntl(
renderHashtagWithStyles(context, 'heading2tag', textStyles, baseTextStyle),
);
const hashtagElement = screen.getByText('#heading2tag');
expect(hashtagElement).toBeVisible();
const flattenedStyle = StyleSheet.flatten(hashtagElement.props.style);
expect(flattenedStyle).toMatchObject({
fontSize: 25,
});
});
test('should have clickable onPress handler', () => {
const baseTextStyle = {
color: '#000000',
...typography('Body', 200),
};
const textStyles = getMarkdownTextStyles(baseProps.theme);
const context: string[] = [];
renderWithIntl(
renderHashtagWithStyles(context, 'clickable', textStyles, baseTextStyle),
);
const hashtagElement = screen.getByText('#clickable');
expect(hashtagElement.props.onPress).toBeDefined();
});
test('should include both base text style and link color', () => {
const baseTextStyle = {
color: '#000000',
...typography('Body', 200),
fontFamily: 'OpenSans',
};
const textStyles = getMarkdownTextStyles(baseProps.theme);
const context: string[] = [];
renderWithIntl(
renderHashtagWithStyles(context, 'styled', textStyles, baseTextStyle),
);
const hashtagElement = screen.getByText('#styled');
const flattenedStyle = StyleSheet.flatten(hashtagElement.props.style);
expect(flattenedStyle).toMatchObject({
fontSize: 16,
fontFamily: 'OpenSans',
color: baseProps.theme.linkColor,
});
});
});
});