Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
## 1.0.12

* Added golden render regression tests for the Markdown widgets, covering text formatting, emphasis, blockquotes, lists, tables, task lists, and code blocks. They render with real fonts and run in the local `flutter test` suite; like the existing image goldens they are platform-specific, and are skipped on CI (whose Flutter `stable` channel is unpinned) to avoid flaky pixel-exact diffs when the toolchain drifts

## 1.0.11

* Overhauled the README: added a feature list, installation instructions, a widget comparison (`Markdown` / `MarkdownBody` / `MarkdownRaw`), and code examples for link handling (`onTapLink`), `MarkdownStyleSheet` styling, and custom element builders
* Overhauled the README: added a feature list, installation instructions, a widget comparison (`Markdown` / `MarkdownBody` / `MarkdownRaw`), and code examples for link handling (`onTapLink`), `MarkdownStyleSheet` styling, and custom element builders — documenting the previously undocumented `onTapLink` and `styleSheet` APIs (resolves #123)
* Added screenshots to the pub.dev listing showcasing rich-text formatting, GitHub Flavored Markdown tables and task lists, and code blocks
* Pointed the `documentation` link at the generated API docs on pub.dev

Expand Down
2 changes: 1 addition & 1 deletion pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ description: A Markdown renderer for Flutter. Create rich text output,
repository: https://github.com/foresightmobile/flutter_markdown_plus
issue_tracker: https://github.com/foresightmobile/flutter_markdown_plus/issues
documentation: https://pub.dev/documentation/flutter_markdown_plus/latest/
version: 1.0.11
version: 1.0.12

environment:
sdk: ^3.4.0
Expand Down
2 changes: 2 additions & 0 deletions test/all.dart
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import 'image_test.dart' as image_test;
import 'line_break_test.dart' as line_break_test;
import 'link_test.dart' as link_test;
import 'list_test.dart' as list_test;
import 'render_golden_test.dart' as render_golden_test;
import 'scrollable_test.dart' as scrollable_test;
import 'selection_area_compatibility_test.dart' as selection_area_test;
import 'style_sheet_test.dart' as style_sheet_test;
Expand All @@ -36,6 +37,7 @@ void main() {
line_break_test.defineTests();
link_test.defineTests();
list_test.defineTests();
render_golden_test.defineTests();
scrollable_test.defineTests();
selection_area_test.defineTests();
style_sheet_test.defineTests();
Expand Down
Binary file added test/assets/golden/render/code.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/golden/render/formatting.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added test/assets/golden/render/tables.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
191 changes: 191 additions & 0 deletions test/render_golden_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,191 @@
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

// Golden render regression tests for the Markdown widgets. These render a few
// representative documents (formatting, tables/task lists, code) and compare
// them against committed reference images, catching unintended changes to how
// content is laid out and styled.
//
// Real Roboto / MaterialIcons fonts are loaded from the Flutter SDK (located
// via FLUTTER_ROOT, which `flutter test` always sets) so the goldens show
// actual glyphs rather than the Ahem test font.
//
// To regenerate the reference images after an intentional change:
//
// flutter test test/render_golden_test.dart --update-goldens
//
// The comparison is pixel-exact, and font rasterisation varies across machines
// and Flutter versions. Because CI tracks the unpinned `stable` channel, these
// goldens are skipped there (via GITHUB_ACTIONS) to avoid flaky failures when
// CI's Flutter drifts from the version used to generate them; they still run in
// the local `flutter test` suite. Pin the CI Flutter version to also verify
// them in CI.
import 'dart:io' as io;

import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:flutter_markdown_plus/flutter_markdown_plus.dart';
import 'package:flutter_test/flutter_test.dart';

final bool _isLinux = !kIsWeb && io.Platform.isLinux;
final bool _inCi = !kIsWeb && io.Platform.environment['GITHUB_ACTIONS'] == 'true';

/// Directory holding the Roboto and MaterialIcons fonts shipped with the
/// Flutter SDK, or `null` if it cannot be located.
String? _materialFontsDir() {
final String? root = kIsWeb ? null : io.Platform.environment['FLUTTER_ROOT'];
if (root == null) {
return null;
}
final String dir = '$root/bin/cache/artifacts/material_fonts';
return io.Directory(dir).existsSync() ? dir : null;
}

Future<void> _loadFont(String family, List<String> paths) async {
final FontLoader loader = FontLoader(family);
for (final String path in paths) {
loader.addFont(Future<ByteData>.value(io.File(path).readAsBytesSync().buffer.asByteData()));
}
await loader.load();
}

const String _formatting = '''
# Flutter Markdown Plus

A **Markdown renderer** for Flutter with rich text, _emphasis_,
and ~~strikethrough~~ out of the box.

> Blockquotes are theme-aware and adapt to light and dark mode.

## Lists

- Headings, bold, and italics
- Links such as [flutter.dev](https://flutter.dev)
- Ordered and unordered lists

1. Parse GitHub Flavored Markdown
2. Style every element
3. Render native Flutter widgets
''';

const String _tablesAndTasks = '''
## Tables

| Feature | Supported | Notes |
| :--------- | :-------: | :------------------------ |
| Headings | Yes | H1-H6 |
| Tables | Yes | Alignment & column widths |
| Task lists | Yes | Rendered as checkboxes |

## Task lists

- [x] Render tables with custom borders
- [x] Show task-list checkboxes
- [ ] Handle a link tap
''';

const String _code = '''
## Code blocks

Inline code like `MarkdownBody(data: source)` renders in its
own style.

Each fenced block manages its own scroll controller:

```dart
Markdown(
data: markdownSource,
selectable: true,
onTapLink: (text, href, title) {
launchUrl(Uri.parse(href!));
},
);
```
''';

Future<void> _expectGolden(WidgetTester tester, String data, String file) async {
tester.view.devicePixelRatio = 2.0;
tester.view.physicalSize = const Size(390 * 2.0, 844 * 2.0);
addTearDown(tester.view.resetPhysicalSize);
addTearDown(tester.view.resetDevicePixelRatio);

final ThemeData base = ThemeData(
colorSchemeSeed: const Color(0xFF2962FF),
useMaterial3: true,
brightness: Brightness.light,
);
final ThemeData theme = base.copyWith(
textTheme: base.textTheme.apply(fontFamily: 'Roboto'),
);

await tester.pumpWidget(
MaterialApp(
debugShowCheckedModeBanner: false,
theme: theme,
home: Scaffold(
backgroundColor: Colors.white,
body: SafeArea(
child: SingleChildScrollView(
padding: const EdgeInsets.all(24),
child: Builder(
builder: (BuildContext context) {
final MarkdownStyleSheet sheet = MarkdownStyleSheet.fromTheme(Theme.of(context));
// No monospace font is bundled, so render code in the body font
// to exercise the code block's decoration and layout without the
// Ahem fallback.
return MarkdownBody(
data: data,
styleSheet: sheet.copyWith(code: sheet.code?.copyWith(fontFamily: 'Roboto')),
);
},
),
),
),
),
),
);
await tester.pumpAndSettle();
await expectLater(find.byType(MaterialApp), matchesGoldenFile('assets/golden/render/$file'));
}

void main() => defineTests();

void defineTests() {
final String? fontsDir = _materialFontsDir();
final bool skip = kIsWeb || _isLinux || _inCi || fontsDir == null;

group('Golden render', () {
setUpAll(() async {
if (fontsDir == null) {
return;
}
await _loadFont('Roboto', <String>[
'$fontsDir/Roboto-Regular.ttf',
'$fontsDir/Roboto-Bold.ttf',
'$fontsDir/Roboto-Italic.ttf',
'$fontsDir/Roboto-BoldItalic.ttf',
]);
await _loadFont('MaterialIcons', <String>['$fontsDir/MaterialIcons-Regular.otf']);
});

testWidgets(
'formatting, emphasis, blockquotes and lists',
(WidgetTester tester) => _expectGolden(tester, _formatting, 'formatting.png'),
skip: skip,
);

testWidgets(
'tables and task lists',
(WidgetTester tester) => _expectGolden(tester, _tablesAndTasks, 'tables.png'),
skip: skip,
);

testWidgets(
'inline and fenced code blocks',
(WidgetTester tester) => _expectGolden(tester, _code, 'code.png'),
skip: skip,
);
});
}
Loading