diff --git a/packages/markups/babel.config.js b/packages/markups/babel.config.js
index 5ccad93f9..72b293c63 100644
--- a/packages/markups/babel.config.js
+++ b/packages/markups/babel.config.js
@@ -11,4 +11,16 @@ module.exports = {
'@babel/preset-react',
'@emotion/babel-preset-css-prop',
],
+ env: {
+ test: {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ modules: 'commonjs',
+ },
+ ],
+ ],
+ },
+ },
};
diff --git a/packages/markups/jest.config.js b/packages/markups/jest.config.js
new file mode 100644
index 000000000..43defaef1
--- /dev/null
+++ b/packages/markups/jest.config.js
@@ -0,0 +1,6 @@
+module.exports = {
+ testEnvironment: 'node',
+ transform: {
+ '^.+\\.[jt]sx?$': 'babel-jest',
+ },
+};
diff --git a/packages/markups/package.json b/packages/markups/package.json
index ab60f023f..7eee2b6b0 100644
--- a/packages/markups/package.json
+++ b/packages/markups/package.json
@@ -16,6 +16,7 @@
"prebuild": "rimraf dist",
"build": "rollup -c --context=window --environment NODE_ENV:production",
"watch": "rollup -c --watch --context=window",
+ "test": "jest",
"test:lint": "eslint src/**/*.js",
"format": "prettier --write 'src/' ",
"format:check": "prettier --check 'src/' ",
diff --git a/packages/markups/src/elements/InlineElements.js b/packages/markups/src/elements/InlineElements.js
index 2584196e8..a3af83584 100644
--- a/packages/markups/src/elements/InlineElements.js
+++ b/packages/markups/src/elements/InlineElements.js
@@ -12,6 +12,19 @@ import LinkSpan from './LinkSpan';
import UserMention from '../mentions/UserMention';
import TimestampElement from './TimestampElement';
+const isPlainTextToken = (token) => token?.type === 'PLAIN_TEXT';
+
+const isWrappedMention = (items, index) => {
+ const previous = items[index - 1];
+ const next = items[index + 1];
+
+ if (!isPlainTextToken(previous) || !isPlainTextToken(next)) {
+ return false;
+ }
+
+ return /\(\s*$/.test(previous.value) && /^\s*\)/.test(next.value);
+};
+
const InlineElements = ({ contents }) =>
contents.map((content, index) => {
switch (content.type) {
@@ -34,6 +47,9 @@ const InlineElements = ({ contents }) =>
return ;
case 'MENTION_USER':
+ if (isWrappedMention(contents, index)) {
+ return ;
+ }
return ;
case 'EMOJI':
diff --git a/packages/markups/src/elements/InlineElements.test.js b/packages/markups/src/elements/InlineElements.test.js
new file mode 100644
index 000000000..e86d1b904
--- /dev/null
+++ b/packages/markups/src/elements/InlineElements.test.js
@@ -0,0 +1,93 @@
+import React from 'react';
+import { renderToStaticMarkup } from 'react-dom/server';
+import InlineElements from './InlineElements';
+
+jest.mock('./ItalicSpan', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./StrikeSpan', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./BoldSpan', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./CodeElement', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./Emoji', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('../mentions/ChannelMention', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./ColorElement', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./LinkSpan', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+jest.mock('./TimestampElement', () => ({
+ __esModule: true,
+ default: () => ,
+}));
+
+jest.mock('./PlainSpan', () => ({
+ __esModule: true,
+ default: ({ contents }) => {contents},
+}));
+
+jest.mock('../mentions/UserMention', () => ({
+ __esModule: true,
+ default: ({ contents }) => (
+ {contents.value}
+ ),
+}));
+
+describe('InlineElements mention rendering', () => {
+ test('renders wrapped user mentions as plain text', () => {
+ const contents = [
+ { type: 'PLAIN_TEXT', value: '(' },
+ { type: 'MENTION_USER', value: { type: 'PLAIN_TEXT', value: 'test' } },
+ { type: 'PLAIN_TEXT', value: ')' },
+ ];
+
+ const html = renderToStaticMarkup();
+
+ expect(html).toContain('data-kind="plain">@test');
+ expect(html).not.toContain('data-kind="mention-user">test');
+ });
+
+ test('renders wrapped mentions with spaces as plain text', () => {
+ const contents = [
+ { type: 'PLAIN_TEXT', value: '( ' },
+ { type: 'MENTION_USER', value: { type: 'PLAIN_TEXT', value: 'test' } },
+ { type: 'PLAIN_TEXT', value: ' )' },
+ ];
+
+ const html = renderToStaticMarkup();
+
+ expect(html).toContain('data-kind="plain">@test');
+ expect(html).not.toContain('data-kind="mention-user">test');
+ });
+
+ test('keeps normal mentions styled when not wrapped', () => {
+ const contents = [
+ { type: 'PLAIN_TEXT', value: 'hello ' },
+ { type: 'MENTION_USER', value: { type: 'PLAIN_TEXT', value: 'test' } },
+ { type: 'PLAIN_TEXT', value: ' world' },
+ ];
+
+ const html = renderToStaticMarkup();
+
+ expect(html).toContain('data-kind="mention-user">test');
+ expect(html).not.toContain('data-kind="plain">@test');
+ });
+});