mirror of
https://github.com/block/buzz.git
synced 2026-10-27 04:30:52 +00:00
fix(mobile): style inline code with the app mono face (#6631)
## Summary Inline code on mobile renders as **bold body text on a faint background wash** — no monospace face, no chip, and it cannot wrap. #5257 diagnosed this as a missing `highlightBuilder`. That is no longer the right fix. `gpt_markdown` 1.2.0 deprecates `highlightBuilder` (removal in 2.0.0), renders inline code as a real chip, and adds `InlineCodeStyle` for restyling it. The package author confirmed this on the issue. So this PR is an upgrade — 1.1.6 → 1.2.1 — plus one theme declaration, rather than the builder the issue originally asked for. **Where the style is declared.** `GptMarkdownThemeData` goes in `AppTheme._buildTheme`, which both `light()` and `dark()` call. That reaches all four `GptMarkdown` call sites — `message_content`, `transcript_item_widget`, `token_pill`, `custom_emoji_render` — so the style is stated once instead of per widget. A widget-level `inlineCodeStyle` would have covered channel messages only, leaving the other three on the package's defaults. **What is declared.** Face, size, ink, chip fill and outline — not the face alone. A face name on its own leaves the rest on the package's defaults, which put inline code at 14.1sp beside a fenced block's 13, on a neutral `onSurface` tint rather than the app's code surface. In dark that tint is *lighter* than the surface, while every other code surface in the app is recessed, so the chip read as a different kind of object. All of it now comes from one `CodeStyle` declaration that the fenced block reads from too, so the two cannot be edited apart. **Three adaptations the upgrade requires.** Each was found by running the gate, not by reading the changelog: 1. **`imageBuilder` widened** to `(context, url, width, height)`. This is a hard compile error, and it is **not listed in the package's migration guide**, which states "nothing here stops code compiling". Worth reporting upstream. 2. **`autolink` now defaults to `true`.** `normalizeBareLinks()` already rewrites bare URLs into Markdown links before rendering, so both would run. `message_content` opts out with `autolink: false` to keep current behaviour exactly. The migration guide argues for dropping the pre-processor instead — a better fix, but a behavioural change that belongs in its own PR. 3. **`gpt_markdown.dart` now re-exports `markdown_config.dart`**, making two direct imports redundant. `flutter analyze` reports `No issues found!` on 1.1.6 and flags both on 1.2.1, so these warnings are new, not pre-existing. **Deliberately out of scope.** The three non-message call sites now autolink bare URLs, since only `message_content` has a pre-processor to collide with. Custom inline components (`_MentionMd`, `CustomEmojiMd`, `_ChannelLinkMd`) could additionally declare `allScopesExceptLinkLabel` — 1.2.0 offers it as the fix for a `WidgetSpan` chip going blank inside a link label on iOS — but current behaviour is unchanged without it, so that stays a separate change. ### Related issue Fixes #5257 Duplicate scan: searched `gpt_markdown`, `inline code mobile`, `highlightBuilder` and `InlineCodeStyle` across both PRs and issues. No open PR touches inline code styling. #6135 (link labels) and #6166 (text selection) also touch mobile Markdown but address different defects. ### Testing Full gate, `just ci` — exit 0: | Stage | Result | |---|---| | Rust (33 suites) | 4768 passed, 0 failed | | Desktop | 5799 passed, 0 failed | | Mobile | **2011 passed**, 0 failed | | `flutter analyze` | `No issues found!` | | Desktop + web build | ok | Run on the branch with `main` merged in, so these numbers match what CI builds. **New regression test** — `renders inline code in the app code style`. It resolves the `CodeTextSpan` the package tags inline code with, which carries both the resolved `TextStyle` and the colours the chip behind it is painted with, so face, size, ink, fill and outline are all asserted rather than a widget's presence. It is negative-controlled: reverting only the theme declaration fails it with ```text Expected: a numeric value within <0.001> of <13.0> Actual: <14.1> ``` and dropping the declaration entirely falls back to `packages/gpt_markdown/JetBrainsMono` — so the test measures the real thing, and it would catch a future regression that silently drops the theme extension. The test passes `baseStyle: messageBodyTextStyle`, the style the message surfaces actually use; the widget's own fallback is the smaller `bodyMedium`, which would move the expected size. The test finds paragraphs with `find.byWidgetPredicate((widget) => widget is RichText)`, not `find.byType(RichText)`: inline code renders through `BidiRichText`, a `RichText` subclass, and `byType` matches exact runtime types. That is a hazard for any test that reads text back out of a paragraph, and one landed after this branch was cut: `message_content_custom_emoji_test.dart` arrived with #6996 and its `code keeps literal emoji while adjacent known tokens render` case reads a code span through `find.byType(RichText)`. It passes on `main` and fails on the merge result, which is what CI builds, so it went red only once CI was authorized. It now uses the same predicate. The two other `byType(RichText)` call sites — the rest of that file and `message_author_meta_test.dart` — were re-run and pass: their content carries no code span, so the exact type still matches. They were left alone. ### Screenshots Rendered through the real `MessageContent` widget with the app's own fonts loaded, at 390pt wide, 3x DPR. Sample text: ``Set `BUZZ_RELAY_URL` before launch, then run `just mobile-test` to verify.`` | | Before (1.1.6) | After (1.2.1) | |---|---|---| | Light |  |  | | Dark |  |  | Before: bold Inter on a flat wash, no chip edge, and `just mobile-test` breaks across the line with the wash simply ending. After: Geist Mono in a bordered, rounded chip, and the wrapped fragment gets its own chip on each line. --------- Signed-off-by: Tolga Cinisli <tolgacinisli@gmail.com> Co-authored-by: Tolga Cinisli <tolgacinisli@gmail.com>
This commit is contained in:
@@ -374,9 +374,11 @@ class App extends HookConsumerWidget {
|
||||
// Above the navigator, so a burst keeps playing over a pushed thread page
|
||||
// or a modal sheet — the same reason desktop pins its canvas to the
|
||||
// viewport rather than to the message row.
|
||||
builder: (context, child) => MobileHuddleShell(
|
||||
navigatorKey: _mobileRootNavigatorKey,
|
||||
child: EmojiBurstOverlay(child: child ?? const SizedBox.shrink()),
|
||||
builder: (context, child) => AppMarkdownTheme(
|
||||
child: MobileHuddleShell(
|
||||
navigatorKey: _mobileRootNavigatorKey,
|
||||
child: EmojiBurstOverlay(child: child ?? const SizedBox.shrink()),
|
||||
),
|
||||
),
|
||||
home: authState.when(
|
||||
loading: () => const _SplashScreen(),
|
||||
|
||||
@@ -72,6 +72,10 @@ class _MessageItemWidget extends StatelessWidget {
|
||||
style: context.textTheme.bodyMedium?.copyWith(
|
||||
color: context.colors.onSurface,
|
||||
),
|
||||
// No link handler here, so an autolinked URL would draw as a
|
||||
// link and do nothing when tapped. Transcript URLs stay text,
|
||||
// as they were before gpt_markdown started autolinking.
|
||||
autolink: false,
|
||||
),
|
||||
],
|
||||
),
|
||||
@@ -143,6 +147,8 @@ class _ThoughtItemWidget extends HookWidget {
|
||||
style: context.textTheme.bodySmall?.copyWith(
|
||||
color: context.colors.onSurfaceVariant,
|
||||
),
|
||||
// As above: no link handler on this surface either.
|
||||
autolink: false,
|
||||
),
|
||||
],
|
||||
],
|
||||
|
||||
@@ -7,7 +7,6 @@ import 'package:flutter/services.dart';
|
||||
import 'package:flutter_hooks/flutter_hooks.dart';
|
||||
import 'package:hooks_riverpod/hooks_riverpod.dart';
|
||||
import 'package:gpt_markdown/gpt_markdown.dart';
|
||||
import 'package:gpt_markdown/custom_widgets/markdown_config.dart';
|
||||
import 'package:lucide_icons_flutter/lucide_icons.dart';
|
||||
import 'package:open_filex/open_filex.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
@@ -266,6 +265,9 @@ class MessageContent extends HookConsumerWidget {
|
||||
finalContent,
|
||||
style: style,
|
||||
followLinkColor: false,
|
||||
// normalizeBareLinks() already turns bare URLs into Markdown links;
|
||||
// gpt_markdown 1.2.0 autolinks by default, so both would run.
|
||||
autolink: false,
|
||||
codeBuilder: (context, name, code, closed) =>
|
||||
_MessageCodeBlock(name: name, code: code),
|
||||
linkBuilder: (context, linkText, url, linkStyle) => _buildLink(
|
||||
@@ -279,7 +281,7 @@ class MessageContent extends HookConsumerWidget {
|
||||
resolvedChannelTap,
|
||||
resolvedChannelNames,
|
||||
),
|
||||
imageBuilder: (context, imageUrl) =>
|
||||
imageBuilder: (context, imageUrl, _, _) =>
|
||||
_buildMedia(context, imageUrl, imetaByUrl[imageUrl]),
|
||||
textAlign: textAlign,
|
||||
maxLines: maxLines,
|
||||
@@ -721,9 +723,9 @@ class _MessageCodeBlock extends HookWidget {
|
||||
}
|
||||
|
||||
final codeBaseStyle = TextStyle(
|
||||
fontFamily: 'GeistMono',
|
||||
fontSize: 13,
|
||||
height: 1.5,
|
||||
fontFamily: CodeStyle.fontFamily,
|
||||
fontSize: CodeStyle.fontSize,
|
||||
height: CodeStyle.lineHeight,
|
||||
color: context.colors.onSurface,
|
||||
);
|
||||
final isDark = context.theme.brightness == Brightness.dark;
|
||||
@@ -735,11 +737,9 @@ class _MessageCodeBlock extends HookWidget {
|
||||
return Container(
|
||||
margin: const EdgeInsets.only(top: Grid.half),
|
||||
decoration: BoxDecoration(
|
||||
color: context.colors.surfaceContainerHighest.withValues(alpha: 0.6),
|
||||
color: CodeStyle.background(context.colors),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(
|
||||
color: context.colors.outline.withValues(alpha: 0.7),
|
||||
),
|
||||
border: Border.all(color: CodeStyle.border(context.colors)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gpt_markdown/gpt_markdown.dart';
|
||||
import 'package:gpt_markdown/custom_widgets/markdown_config.dart';
|
||||
|
||||
import '../relay/relay.dart';
|
||||
|
||||
|
||||
46
mobile/lib/shared/theme/app_markdown_theme.dart
Normal file
46
mobile/lib/shared/theme/app_markdown_theme.dart
Normal file
@@ -0,0 +1,46 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:gpt_markdown/gpt_markdown.dart';
|
||||
|
||||
import 'app_theme.dart';
|
||||
import 'code_style.dart';
|
||||
|
||||
/// States the app's code style on every `GptMarkdown` below it and leaves the
|
||||
/// rest of the Markdown theme as it already was.
|
||||
///
|
||||
/// Registering a [GptMarkdownThemeData] as a `ThemeExtension` would be the
|
||||
/// shorter route, but its factory is not a partial override: it builds a fresh
|
||||
/// stock Material theme and fills every heading, rule and highlight field from
|
||||
/// it, so registering one stops `GptMarkdownTheme.of` deriving those from the
|
||||
/// app. Restating them in the extension does not close the gap either — the
|
||||
/// ambient headings carry the localized text geometry, which exists only once
|
||||
/// `Theme.of` has run and a `ThemeData` factory cannot see. Overriding the one
|
||||
/// field on the ambient theme is what leaves non-code Markdown untouched.
|
||||
class AppMarkdownTheme extends StatelessWidget {
|
||||
const AppMarkdownTheme({super.key, required this.child});
|
||||
|
||||
final Widget child;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final scheme = Theme.of(context).colorScheme;
|
||||
return GptMarkdownTheme(
|
||||
gptThemeData: GptMarkdownTheme.of(context).copyWith(
|
||||
// Inline code is otherwise drawn in gpt_markdown's own bundled face, at
|
||||
// its own size, tinted from `onSurface`. Stating the app's code style
|
||||
// gives every Markdown surface the face, size and chip colours a fenced
|
||||
// code block already uses.
|
||||
inlineCode: InlineCodeStyle(
|
||||
fontFamily: CodeStyle.fontFamily,
|
||||
fontSizeFactor: CodeStyle.fontSizeFactor,
|
||||
color: scheme.onSurface,
|
||||
backgroundColor: CodeStyle.background(scheme),
|
||||
borderColor: CodeStyle.border(scheme),
|
||||
// A chip is small, so it takes the smallest step of the app's radius
|
||||
// scale rather than the `Radii.card` a block uses.
|
||||
borderRadius: const Radius.circular(Radii.xs),
|
||||
).resolve(scheme),
|
||||
),
|
||||
child: child,
|
||||
);
|
||||
}
|
||||
}
|
||||
34
mobile/lib/shared/theme/code_style.dart
Normal file
34
mobile/lib/shared/theme/code_style.dart
Normal file
@@ -0,0 +1,34 @@
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
import 'message_typography.dart';
|
||||
|
||||
/// The app's own code style — one place stating the mono face, its size and
|
||||
/// the colours a code surface is drawn with.
|
||||
///
|
||||
/// Two surfaces render code and they have to read as one family: the fenced
|
||||
/// block in `MessageContent`, and the inline chip `gpt_markdown` paints from
|
||||
/// the `InlineCodeStyle` declared in `AppTheme`. Both take their values from
|
||||
/// here, so neither can drift from the other by editing one copy.
|
||||
abstract final class CodeStyle {
|
||||
/// The mono face, already used by the composer and by code blocks.
|
||||
static const fontFamily = 'GeistMono';
|
||||
|
||||
/// Code size at message body scale.
|
||||
static const fontSize = 13.0;
|
||||
|
||||
/// Line height of code text.
|
||||
static const lineHeight = 1.5;
|
||||
|
||||
/// [fontSize] stated against the message body size, for APIs that scale code
|
||||
/// relative to the text around it rather than fixing a point size. Keeping
|
||||
/// it a ratio means code follows the body if that size ever changes.
|
||||
static final fontSizeFactor = fontSize / messageBodyTextStyle.fontSize!;
|
||||
|
||||
/// Fill behind a code surface.
|
||||
static Color background(ColorScheme scheme) =>
|
||||
scheme.surfaceContainerHighest.withValues(alpha: 0.6);
|
||||
|
||||
/// Outline around a code surface.
|
||||
static Color border(ColorScheme scheme) =>
|
||||
scheme.outline.withValues(alpha: 0.7);
|
||||
}
|
||||
@@ -1,8 +1,10 @@
|
||||
export 'accent_colors.dart';
|
||||
export 'adaptive_theme.dart';
|
||||
export 'app_colors.dart';
|
||||
export 'app_markdown_theme.dart';
|
||||
export 'app_theme.dart';
|
||||
export 'buzz_theme.dart';
|
||||
export 'code_style.dart';
|
||||
export 'color_scheme.dart';
|
||||
export 'community_theme_preference.dart';
|
||||
export 'community_theme_provider.dart';
|
||||
|
||||
@@ -620,10 +620,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: gpt_markdown
|
||||
sha256: "5c565a438e569b3b546023d0d4eccccf87e260a3289a17c4e241c41d5e7545df"
|
||||
sha256: "42887afaf5c7a71348221048a6c1e34c9836703ee0e6b4865e789253fcaa204a"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.6"
|
||||
version: "1.2.1"
|
||||
gtk:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -27,7 +27,7 @@ dependencies:
|
||||
nostr: ^2.0.0
|
||||
pointycastle: ^4.0.0
|
||||
url_launcher: ^6.3.2
|
||||
gpt_markdown: ^1.1.6
|
||||
gpt_markdown: ^1.2.1
|
||||
highlight: ^0.7.0
|
||||
intl: ^0.20.2
|
||||
uuid: ^4.5.1
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gpt_markdown/custom_widgets/link_button.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/observer_models.dart';
|
||||
import 'package:buzz/features/channels/agent_activity/transcript_item_widget.dart';
|
||||
import 'package:buzz/shared/theme/theme.dart';
|
||||
|
||||
Widget _testable(Widget child) {
|
||||
return MaterialApp(
|
||||
theme: AppTheme.light(),
|
||||
home: Scaffold(body: SingleChildScrollView(child: child)),
|
||||
);
|
||||
}
|
||||
|
||||
void main() {
|
||||
// Neither transcript surface passes a link handler, so an autolinked bare
|
||||
// URL would draw as a link and then do nothing when tapped. They stay text.
|
||||
testWidgets('leaves a bare URL in a message as plain text', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
_testable(
|
||||
TranscriptItemWidget(
|
||||
item: MessageItem(
|
||||
id: 'm1',
|
||||
role: 'assistant',
|
||||
title: 'Assistant',
|
||||
text: 'Report is at https://example.com/report today.',
|
||||
timestamp: '2026-08-29T09:00:00Z',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(LinkButton), findsNothing);
|
||||
});
|
||||
|
||||
testWidgets('leaves a bare URL in a thought as plain text', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
_testable(
|
||||
TranscriptItemWidget(
|
||||
item: ThoughtItem(
|
||||
id: 't1',
|
||||
title: 'Thinking',
|
||||
text: 'Checking https://example.com/report first.',
|
||||
timestamp: '2026-08-29T09:00:00Z',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
expect(find.byType(LinkButton), findsNothing);
|
||||
});
|
||||
}
|
||||
@@ -111,8 +111,12 @@ void main() {
|
||||
) async {
|
||||
await tester.pumpWidget(_testable('`:wave:` :unknown:wave:'));
|
||||
expect(find.byType(CustomEmojiImage), findsOneWidget);
|
||||
// A paragraph holding inline code renders through BidiRichText, a RichText
|
||||
// subclass, and `find.byType` matches the exact runtime type only.
|
||||
final text = tester
|
||||
.widgetList<RichText>(find.byType(RichText))
|
||||
.widgetList<RichText>(
|
||||
find.byWidgetPredicate((widget) => widget is RichText),
|
||||
)
|
||||
.map((widget) => widget.text.toPlainText())
|
||||
.join();
|
||||
expect(text, contains(':wave:'));
|
||||
|
||||
@@ -45,7 +45,8 @@ Widget _testable(
|
||||
data: MediaQuery.of(
|
||||
context,
|
||||
).copyWith(disableAnimations: disableAnimations),
|
||||
child: Scaffold(body: child),
|
||||
// The app states its code style here, above the navigator.
|
||||
child: AppMarkdownTheme(child: Scaffold(body: child)),
|
||||
),
|
||||
),
|
||||
),
|
||||
@@ -2813,6 +2814,50 @@ Photos
|
||||
expect(_allRichText(tester), isNot(contains('**')));
|
||||
});
|
||||
|
||||
testWidgets('renders inline code in the app code style', (tester) async {
|
||||
// The message surfaces pass this style in; the widget's own fallback
|
||||
// is the smaller `bodyMedium`, which would move the expected size.
|
||||
await tester.pumpWidget(
|
||||
_testable(
|
||||
const MessageContent(
|
||||
content: 'Run `just test` now',
|
||||
baseStyle: messageBodyTextStyle,
|
||||
),
|
||||
),
|
||||
);
|
||||
|
||||
// gpt_markdown tags inline code with a CodeTextSpan carrying both the
|
||||
// resolved text style and the colours the chip behind it is painted
|
||||
// with. It renders through BidiRichText, a RichText subclass, so
|
||||
// find.byType(RichText) would miss it.
|
||||
final codeSpans = <CodeTextSpan>[];
|
||||
for (final rich in tester.widgetList<RichText>(
|
||||
find.byWidgetPredicate((widget) => widget is RichText),
|
||||
)) {
|
||||
rich.text.visitChildren((span) {
|
||||
if (span is CodeTextSpan && span.text == 'just test') {
|
||||
codeSpans.add(span);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
}
|
||||
|
||||
expect(codeSpans, hasLength(1));
|
||||
final code = codeSpans.single;
|
||||
const scheme = lightColorScheme;
|
||||
|
||||
// Face, size and ink — the values a fenced code block is drawn with,
|
||||
// not the package's bundled mono at its own 0.94 of the body size.
|
||||
expect(code.style?.fontFamily, CodeStyle.fontFamily);
|
||||
expect(code.style?.fontSize, closeTo(CodeStyle.fontSize, 0.001));
|
||||
expect(code.style?.color, scheme.onSurface);
|
||||
|
||||
// Chip fill and outline come from the app's code surface rather than
|
||||
// the package's `onSurface` tints.
|
||||
expect(code.codeStyle.backgroundColor, CodeStyle.background(scheme));
|
||||
expect(code.codeStyle.borderColor, CodeStyle.border(scheme));
|
||||
});
|
||||
|
||||
testWidgets('renders code block between paragraphs', (tester) async {
|
||||
await tester.pumpWidget(
|
||||
_testable(
|
||||
|
||||
76
mobile/test/shared/theme/app_markdown_theme_test.dart
Normal file
76
mobile/test/shared/theme/app_markdown_theme_test.dart
Normal file
@@ -0,0 +1,76 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:gpt_markdown/gpt_markdown.dart';
|
||||
import 'package:buzz/shared/theme/app_markdown_theme.dart';
|
||||
import 'package:buzz/shared/theme/app_theme.dart';
|
||||
import 'package:buzz/shared/theme/code_style.dart';
|
||||
|
||||
/// The Markdown theme a widget reads under [theme], with [wrapped] deciding
|
||||
/// whether the app states its code style above it.
|
||||
Future<GptMarkdownThemeData> _markdownTheme(
|
||||
WidgetTester tester,
|
||||
ThemeData theme, {
|
||||
required bool wrapped,
|
||||
}) async {
|
||||
late GptMarkdownThemeData read;
|
||||
final reader = Builder(
|
||||
builder: (context) {
|
||||
read = GptMarkdownTheme.of(context);
|
||||
return const SizedBox.shrink();
|
||||
},
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
MaterialApp(
|
||||
theme: theme,
|
||||
home: wrapped ? AppMarkdownTheme(child: reader) : reader,
|
||||
),
|
||||
);
|
||||
// MaterialApp animates a theme change, so the first frame after a pump can
|
||||
// still carry the previous one.
|
||||
await tester.pumpAndSettle();
|
||||
return read;
|
||||
}
|
||||
|
||||
void main() {
|
||||
testWidgets('states inline code and leaves the rest of Markdown alone', (
|
||||
tester,
|
||||
) async {
|
||||
for (final theme in [AppTheme.light(), AppTheme.dark()]) {
|
||||
final ambient = await _markdownTheme(tester, theme, wrapped: false);
|
||||
final stated = await _markdownTheme(tester, theme, wrapped: true);
|
||||
|
||||
// Headings, rules and the rest keep the values gpt_markdown derives from
|
||||
// the app theme: a code-only change must not restyle anything else.
|
||||
expect(stated.h1, ambient.h1);
|
||||
expect(stated.h2, ambient.h2);
|
||||
expect(stated.h3, ambient.h3);
|
||||
expect(stated.h4, ambient.h4);
|
||||
expect(stated.h5, ambient.h5);
|
||||
expect(stated.h6, ambient.h6);
|
||||
expect(stated.hrLineColor, ambient.hrLineColor);
|
||||
expect(stated.hrLineThickness, ambient.hrLineThickness);
|
||||
expect(stated.hrLinePadding, ambient.hrLinePadding);
|
||||
expect(stated.highlightColor, ambient.highlightColor);
|
||||
expect(stated.linkColor, ambient.linkColor);
|
||||
expect(stated.linkHoverColor, ambient.linkHoverColor);
|
||||
expect(
|
||||
stated.autoAddDividerLineAfterH1,
|
||||
ambient.autoAddDividerLineAfterH1,
|
||||
);
|
||||
expect(stated.styleSheet, ambient.styleSheet);
|
||||
|
||||
// Inline code is the whole of the difference.
|
||||
final scheme = theme.colorScheme;
|
||||
expect(stated.inlineCode.fontFamily, CodeStyle.fontFamily);
|
||||
expect(stated.inlineCode.color, scheme.onSurface);
|
||||
expect(stated.inlineCode.backgroundColor, CodeStyle.background(scheme));
|
||||
expect(stated.inlineCode.borderColor, CodeStyle.border(scheme));
|
||||
expect(
|
||||
ambient.inlineCode.fontFamily,
|
||||
isNot(CodeStyle.fontFamily),
|
||||
reason: 'the package default is what this widget exists to replace',
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user