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'); + }); +});