fix(markdown): preserve inline code formatting and heading sizes (#7889)

Scale inline code with the enclosing block using theme tokens, and preserve its size and font through bold and links. Use contextual package patches so clean installs reproduce the verified native source.
This commit is contained in:
Mohamed Boudra
2026-10-26 11:22:07 +07:00
committed by GitHub
parent 6f07b0de48
commit 6521739947
6 changed files with 557 additions and 47 deletions
@@ -0,0 +1,25 @@
# Inline code formatting
This fixture renders the production `MarkdownRenderer` with plain, bold, italic,
combined emphasis, strikethrough and linked code, plus bold code in headings,
lists and quotes. Toggle regular/compact text. Inline code uses the theme’s
code-to-body size ratio at every heading level; fenced code keeps the code-size
token. Bold and links preserve the resulting size. Check the glyphs, not the background height: native backgrounds use
line metrics.
Use a development client rebuilt from this checkout. Follow the temporary entry
point and Metro workflow in [status-ring](../status-ring/README.md), importing
`MarkdownFormattingRegression` from `./e2e/mobile/markdown-formatting/native`.
Open the same Metro URL in a browser to compare web. Restore `index.ts` afterward.
The package patch contains `InlineCodeFormattingTest`, which exercises actual
Android spans in Robolectric native graphics mode, covering draw and measurement,
span order, code inside paragraph/heading sizes, bold/italic composition, and
links preserving code and emphasized prose fonts.
It runs in the existing `test:android` CI job. To run only this regression locally,
from `packages/app/android` after Expo prebuild:
```sh
./gradlew :react-native-enriched-markdown:testDebugUnitTest \
--tests '*InlineCodeFormattingTest' --no-daemon --max-workers=2
```
@@ -0,0 +1,52 @@
import { QueryClient, QueryClientProvider } from "@tanstack/react-query";
import { useCallback, useState } from "react";
import { Button, ScrollView, Text } from "react-native";
import { StyleSheet } from "react-native-unistyles";
import { GestureHandlerRootView } from "react-native-gesture-handler";
import { MarkdownRenderer } from "@/components/markdown/renderer";
const styles = StyleSheet.create((theme) => ({
root: { flex: 1 },
scroll: { backgroundColor: theme.colors.surface0 },
content: { padding: 32, paddingTop: 60 },
title: { color: theme.colors.foreground },
}));
const queryClient = new QueryClient();
const source = [
"Plain: `MMMMiiii`",
"Bold: **`MMMMiiii`**",
"Italic: *`MMMMiiii`*",
"Both: ***`MMMMiiii`***",
"Strike: ~~`MMMMiiii`~~",
"Link: [`MMMMiiii`](https://example.com)",
"# Heading **`MMMMiiii`**",
"## Heading *`MMMMiiii`*",
"### Heading [`MMMMiiii`](https://example.com)",
"#### Heading ~~`MMMMiiii`~~",
"##### Heading ***`MMMMiiii`***",
"###### Heading `MMMMiiii`",
"- List **`MMMMiiii`**",
"> Quote **`MMMMiiii`**",
"Normal **bold** and *italic* prose.",
].join("\n\n");
export default function MarkdownFormattingRegression() {
const [compact, setCompact] = useState(false);
const toggleCompact = useCallback(() => setCompact((value) => !value), []);
return (
<QueryClientProvider client={queryClient}>
<GestureHandlerRootView style={styles.root}>
<ScrollView style={styles.scroll} contentContainerStyle={styles.content}>
<Text style={styles.title}>Inline code formatting regression</Text>
<Button
title={compact ? "Use regular text" : "Use compact text"}
onPress={toggleCompact}
/>
<MarkdownRenderer text={source} compact={compact} />
</ScrollView>
</GestureHandlerRootView>
</QueryClientProvider>
);
}
+1 -1
View File
@@ -40,7 +40,7 @@
"dev:ios": "npm --prefix ../.. run build:client && cross-env APP_VARIANT=development expo run:ios",
"build:apk": "npm --prefix ../.. run build:apk",
"build:apk:arm64": "npm --prefix ../.. run build:apk:arm64",
"test:android": "expo prebuild --platform android --no-install && cd android && ./gradlew :paseo-native-trace:testReleaseUnitTest --no-daemon --max-workers=2 -Dorg.gradle.parallel=false"
"test:android": "expo prebuild --platform android --no-install && cd android && ./gradlew :paseo-native-trace:testReleaseUnitTest :react-native-enriched-markdown:testReleaseUnitTest --tests '*InlineCodeFormattingTest' --no-daemon --max-workers=2 -Dorg.gradle.parallel=false"
},
"dependencies": {
"@codemirror/commands": "6.10.4",
@@ -804,3 +804,18 @@ it("makes the web Markdown container selectable in Safari as well as unprefixed
WebkitUserSelect: "text",
});
});
it.each([false, true])(
"scales inline code from theme tokens with its surrounding block (compact: %s)",
(compact) => {
const theme = {
...darkTheme,
fontSize: { ...darkTheme.fontSize, code: 13, content: 17, base: 16 },
};
const { markdownStyle } = createMarkdownStyles(theme, { compact, blockGap: 16 });
const bodySize = compact ? theme.fontSize.base : theme.fontSize.content;
expect(markdownStyle.code).not.toHaveProperty("fontSize");
expect(markdownStyle.code).toHaveProperty("fontScale", theme.fontSize.code / bodySize);
expect(markdownStyle.codeBlock?.fontSize).toBe(theme.fontSize.code);
},
);
@@ -72,7 +72,7 @@ export function createMarkdownStyles(
},
code: {
fontFamily: theme.fontFamily.mono === "ui-monospace" && !isWeb ? "" : theme.fontFamily.mono,
fontSize: theme.fontSize.code,
fontScale: theme.fontSize.code / fontSize,
color: theme.colors.foreground,
backgroundColor: theme.colors.surface2,
borderColor: "transparent",
File diff suppressed because one or more lines are too long