From 37f5ccfa5af45710c021c23e394585392f144378 Mon Sep 17 00:00:00 2001
From: ishan kumar <289112142+ishan-one8@users.noreply.github.com>
Date: Sat, 3 Oct 2026 15:18:47 +0530
Subject: [PATCH 1/2] test(react): emit CommonJS under the test env so jest can
run
---
packages/react/babel.config.js | 30 ++++++++++++++++++------------
packages/react/package.json | 3 ++-
2 files changed, 20 insertions(+), 13 deletions(-)
diff --git a/packages/react/babel.config.js b/packages/react/babel.config.js
index 5ccad93f9f..ccf26120dc 100644
--- a/packages/react/babel.config.js
+++ b/packages/react/babel.config.js
@@ -1,14 +1,20 @@
-module.exports = {
- presets: [
- [
- '@babel/preset-env',
- {
- modules: false,
- bugfixes: true,
- targets: { browsers: '> 0.25%, ie 11, not op_mini all, not dead' },
- },
+module.exports = (api) => {
+ // Jest needs CommonJS; the rollup build must keep native ES modules.
+ const isTest = api.env('test');
+ api.cache.using(() => isTest);
+
+ return {
+ presets: [
+ [
+ '@babel/preset-env',
+ {
+ modules: isTest ? 'commonjs' : false,
+ bugfixes: true,
+ targets: { browsers: '> 0.25%, ie 11, not op_mini all, not dead' },
+ },
+ ],
+ '@babel/preset-react',
+ '@emotion/babel-preset-css-prop',
],
- '@babel/preset-react',
- '@emotion/babel-preset-css-prop',
- ],
+ };
};
diff --git a/packages/react/package.json b/packages/react/package.json
index 8b6a05c941..c757ce5f16 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -22,7 +22,8 @@
"lint:fix": "eslint 'src/**/*.js' --fix",
"storybook": "storybook dev -p 6006",
"build-storybook": "storybook build",
- "build-icons": "node tools/icons-generator.js"
+ "build-icons": "node tools/icons-generator.js",
+ "test": "jest --env=jsdom"
},
"author": "",
"license": "ISC",
From 10425a89b1863080b40c427c4c681c2e97fc8b5e Mon Sep 17 00:00:00 2001
From: ishan kumar <289112142+ishan-one8@users.noreply.github.com>
Date: Sat, 3 Oct 2026 15:18:47 +0530
Subject: [PATCH 2/2] fix(react): apply CommonMark flanking rules to composer
emphasis
---
.../react/src/lib/contentEditableComposer.js | 41 ++++++++++++++++++-
.../src/lib/contentEditableComposer.test.js | 24 +++++++++++
2 files changed, 64 insertions(+), 1 deletion(-)
create mode 100644 packages/react/src/lib/contentEditableComposer.test.js
diff --git a/packages/react/src/lib/contentEditableComposer.js b/packages/react/src/lib/contentEditableComposer.js
index ffa3f6f9cd..20610f0ec2 100644
--- a/packages/react/src/lib/contentEditableComposer.js
+++ b/packages/react/src/lib/contentEditableComposer.js
@@ -44,6 +44,41 @@ const findLink = (text, start) => {
};
};
+const EMPHASIS_MARKERS = new Set(['*', '**', '_', '__']);
+
+const isWhitespace = (character) =>
+ character === undefined || /\s/.test(character);
+
+const isWordCharacter = (character) =>
+ character !== undefined && /[\p{L}\p{N}]/u.test(character);
+
+// CommonMark only lets a delimiter run open emphasis when it is left-flanking
+// and close it when it is right-flanking, and never lets `_` do either inside a
+// word. Without these checks `my_file_name.js` and `2 * 3 * 4` become emphasis.
+const canOpenEmphasis = (text, start, marker) => {
+ if (!EMPHASIS_MARKERS.has(marker)) return true;
+ if (isWhitespace(text[start + marker.length])) return false;
+ if (marker[0] === '_' && isWordCharacter(text[start - 1])) return false;
+ return true;
+};
+
+const canCloseEmphasis = (text, end, marker) => {
+ if (!EMPHASIS_MARKERS.has(marker)) return true;
+ if (isWhitespace(text[end - 1])) return false;
+ if (marker[0] === '_' && isWordCharacter(text[end + marker.length])) {
+ return false;
+ }
+ return true;
+};
+
+const findClosingMarker = (text, from, marker) => {
+ let end = text.indexOf(marker, from);
+ while (end !== -1 && !canCloseEmphasis(text, end, marker)) {
+ end = text.indexOf(marker, end + marker.length);
+ }
+ return end;
+};
+
const appendMarkdown = (parent, text) => {
const tokens = [
['**', 'strong'],
@@ -89,9 +124,13 @@ const appendMarkdown = (parent, text) => {
}
}
+ if (token && !canOpenEmphasis(text, index, token[0])) {
+ token = undefined;
+ }
+
if (token) {
const [marker, tagName] = token;
- const end = text.indexOf(marker, index + marker.length);
+ const end = findClosingMarker(text, index + marker.length, marker);
if (end > index + marker.length) {
flush();
const formatted = document.createElement(tagName);
diff --git a/packages/react/src/lib/contentEditableComposer.test.js b/packages/react/src/lib/contentEditableComposer.test.js
new file mode 100644
index 0000000000..0b25bb4d1b
--- /dev/null
+++ b/packages/react/src/lib/contentEditableComposer.test.js
@@ -0,0 +1,24 @@
+import { renderComposerMarkdown } from './contentEditableComposer';
+
+describe('renderComposerMarkdown', () => {
+ const render = (markdown) => {
+ const element = document.createElement('div');
+ renderComposerMarkdown(element, markdown);
+ return element.innerHTML;
+ };
+
+ test('does not treat underscores inside a word as emphasis', () => {
+ expect(render('my_file_name.js')).toBe('my_file_name.js');
+ expect(render('call foo_bar_baz() now')).toBe('call foo_bar_baz() now');
+ });
+
+ test('does not open emphasis on a marker followed by whitespace', () => {
+ expect(render('2 * 3 * 4 = 24')).toBe('2 * 3 * 4 = 24');
+ });
+
+ test('still renders emphasis at word boundaries', () => {
+ expect(render('__init__')).toBe('init');
+ expect(render('**bold**')).toBe('bold');
+ expect(render('*italic*')).toBe('italic');
+ });
+});