diff --git a/demo_app/test/background/position/bottom.png b/demo_app/test/background/position/bottom.png new file mode 100644 index 000000000..7f5a8e0ac Binary files /dev/null and b/demo_app/test/background/position/bottom.png differ diff --git a/demo_app/test/background/position/bottom_left.png b/demo_app/test/background/position/bottom_left.png new file mode 100644 index 000000000..03499969d Binary files /dev/null and b/demo_app/test/background/position/bottom_left.png differ diff --git a/demo_app/test/background/position/bottom_right.png b/demo_app/test/background/position/bottom_right.png new file mode 100644 index 000000000..6e4198a87 Binary files /dev/null and b/demo_app/test/background/position/bottom_right.png differ diff --git a/demo_app/test/background/position/center.png b/demo_app/test/background/position/center.png new file mode 100644 index 000000000..1300262ae Binary files /dev/null and b/demo_app/test/background/position/center.png differ diff --git a/demo_app/test/background/position/left.png b/demo_app/test/background/position/left.png new file mode 100644 index 000000000..cb7ce5609 Binary files /dev/null and b/demo_app/test/background/position/left.png differ diff --git a/demo_app/test/background/position/right.png b/demo_app/test/background/position/right.png new file mode 100644 index 000000000..23b435386 Binary files /dev/null and b/demo_app/test/background/position/right.png differ diff --git a/demo_app/test/background/position/top.png b/demo_app/test/background/position/top.png new file mode 100644 index 000000000..84c48936f Binary files /dev/null and b/demo_app/test/background/position/top.png differ diff --git a/demo_app/test/background/position/top_left.png b/demo_app/test/background/position/top_left.png new file mode 100644 index 000000000..60a872848 Binary files /dev/null and b/demo_app/test/background/position/top_left.png differ diff --git a/demo_app/test/background/position/top_right.png b/demo_app/test/background/position/top_right.png new file mode 100644 index 000000000..c7ae82300 Binary files /dev/null and b/demo_app/test/background/position/top_right.png differ diff --git a/demo_app/test/background/repeat/no-repeat.png b/demo_app/test/background/repeat/no-repeat.png new file mode 100644 index 000000000..60a872848 Binary files /dev/null and b/demo_app/test/background/repeat/no-repeat.png differ diff --git a/demo_app/test/background/repeat/repeat-x.png b/demo_app/test/background/repeat/repeat-x.png new file mode 100644 index 000000000..6882f600c Binary files /dev/null and b/demo_app/test/background/repeat/repeat-x.png differ diff --git a/demo_app/test/background/repeat/repeat-y.png b/demo_app/test/background/repeat/repeat-y.png new file mode 100644 index 000000000..10352f066 Binary files /dev/null and b/demo_app/test/background/repeat/repeat-y.png differ diff --git a/demo_app/test/background/repeat/repeat.png b/demo_app/test/background/repeat/repeat.png new file mode 100644 index 000000000..f1dbcbbcb Binary files /dev/null and b/demo_app/test/background/repeat/repeat.png differ diff --git a/demo_app/test/background/size/auto.png b/demo_app/test/background/size/auto.png new file mode 100644 index 000000000..94b3aa78f Binary files /dev/null and b/demo_app/test/background/size/auto.png differ diff --git a/demo_app/test/background/size/contain.png b/demo_app/test/background/size/contain.png new file mode 100644 index 000000000..9e8a0ca34 Binary files /dev/null and b/demo_app/test/background/size/contain.png differ diff --git a/demo_app/test/background/size/cover.png b/demo_app/test/background/size/cover.png new file mode 100644 index 000000000..4191950de Binary files /dev/null and b/demo_app/test/background/size/cover.png differ diff --git a/demo_app/test/fwfh_text_style/Text.png b/demo_app/test/fwfh_text_style/Text.png deleted file mode 100644 index 6345fe2ab..000000000 Binary files a/demo_app/test/fwfh_text_style/Text.png and /dev/null differ diff --git a/demo_app/test/fwfh_text_style/Text.rich.png b/demo_app/test/fwfh_text_style/Text.rich.png deleted file mode 100644 index cbcc95981..000000000 Binary files a/demo_app/test/fwfh_text_style/Text.rich.png and /dev/null differ diff --git a/demo_app/test/fwfh_text_style/TextSpan.png b/demo_app/test/fwfh_text_style/TextSpan.png deleted file mode 100644 index 621525062..000000000 Binary files a/demo_app/test/fwfh_text_style/TextSpan.png and /dev/null differ diff --git a/demo_app/test/li/computeDryLayout.png b/demo_app/test/li/computeDryLayout.png deleted file mode 100644 index e67420d8c..000000000 Binary files a/demo_app/test/li/computeDryLayout.png and /dev/null differ diff --git a/demo_app/test/li/img_block.png b/demo_app/test/li/img_block.png index 09a17db5f..208ccb28c 100644 Binary files a/demo_app/test/li/img_block.png and b/demo_app/test/li/img_block.png differ diff --git a/demo_app/test/li/img_block_between_text.png b/demo_app/test/li/img_block_between_text.png index 7990a40d1..ab4fe684b 100644 Binary files a/demo_app/test/li/img_block_between_text.png and b/demo_app/test/li/img_block_between_text.png differ diff --git a/demo_app/test/li/img_block_then_text.png b/demo_app/test/li/img_block_then_text.png index 6ca9ddb25..1e08cd96d 100644 Binary files a/demo_app/test/li/img_block_then_text.png and b/demo_app/test/li/img_block_then_text.png differ diff --git a/demo_app/test/li/img_inline.png b/demo_app/test/li/img_inline.png index 09a17db5f..208ccb28c 100644 Binary files a/demo_app/test/li/img_inline.png and b/demo_app/test/li/img_inline.png differ diff --git a/demo_app/test/li/img_inline_between_text.png b/demo_app/test/li/img_inline_between_text.png index be0c10cd8..dccfce79c 100644 Binary files a/demo_app/test/li/img_inline_between_text.png and b/demo_app/test/li/img_inline_between_text.png differ diff --git a/demo_app/test/li/img_inline_then_text.png b/demo_app/test/li/img_inline_then_text.png index ec6c89b5e..6e323ece6 100644 Binary files a/demo_app/test/li/img_inline_then_text.png and b/demo_app/test/li/img_inline_then_text.png differ diff --git a/demo_app/test/li/issue460/control_group.png b/demo_app/test/li/issue460/control_group.png deleted file mode 100644 index b409935a7..000000000 Binary files a/demo_app/test/li/issue460/control_group.png and /dev/null differ diff --git a/demo_app/test/li/issue460/default_text_style.png b/demo_app/test/li/issue460/default_text_style.png deleted file mode 100644 index ba0f3cc91..000000000 Binary files a/demo_app/test/li/issue460/default_text_style.png and /dev/null differ diff --git a/demo_app/test/sizing/_guessChildSize/child_height_gt_max_height.png b/demo_app/test/sizing/_guessChildSize/child_height_gt_max_height.png index 57d0161b9..313d31dbe 100644 Binary files a/demo_app/test/sizing/_guessChildSize/child_height_gt_max_height.png and b/demo_app/test/sizing/_guessChildSize/child_height_gt_max_height.png differ diff --git a/demo_app/test/sizing/_guessChildSize/child_width_gt_max_width.png b/demo_app/test/sizing/_guessChildSize/child_width_gt_max_width.png index f5598e48e..0ab33922d 100644 Binary files a/demo_app/test/sizing/_guessChildSize/child_width_gt_max_width.png and b/demo_app/test/sizing/_guessChildSize/child_width_gt_max_width.png differ diff --git a/demo_app/test/sizing/_guessChildSize/native_192x192.png b/demo_app/test/sizing/_guessChildSize/native_192x192.png index f5598e48e..0ab33922d 100644 Binary files a/demo_app/test/sizing/_guessChildSize/native_192x192.png and b/demo_app/test/sizing/_guessChildSize/native_192x192.png differ diff --git a/demo_app/test/table/aspect_ratio_img.png b/demo_app/test/table/aspect_ratio_img.png index 02a651ccd..bddecc70d 100644 Binary files a/demo_app/test/table/aspect_ratio_img.png and b/demo_app/test/table/aspect_ratio_img.png differ diff --git a/packages/core/README.md b/packages/core/README.md index 22c42b5b6..968a58c94 100644 --- a/packages/core/README.md +++ b/packages/core/README.md @@ -126,17 +126,21 @@ These tags and their contents will be ignored: ### Inline stylings - background: 1 value (color) - - background-color + - background-color: hex values, `rgb()`, `hsl()` or named colors + - background-image: `url()` with support for asset (`asset://`), data uri, local file (`file://`) and network image + - background-repeat: no-repeat/repeat/repeat-x/repeat-y + - background-position: single or double instances of bottom/center/left/right/top (e.g. `top left`) + - background-size: auto/contain/cover - border: 3 values (width style color), 2 values (width style) or 1 value (width) - border-top, border-right, border-bottom, border-left - border-block-start, border-block-end - border-inline-start, border-inline-end -- border-radius: 4, 3, 2 or 1 values with slash support (e.g. `10px / 20px`) +- border-radius: 4, 3, 2 or 1 value with slash support (e.g. `10px / 20px`) - border-top-left-radius: 2 values or 1 value in `em`, `pt` and `px` - border-top-right-radius: 2 values or 1 value in `em`, `pt` and `px` - border-bottom-right-radius: 2 values or 1 value in `em`, `pt` and `px` - border-bottom-left-radius: 2 values or 1 value in `em`, `pt` and `px` -- color: hex values, `rgb()`, `hsl()` or named colors +- color (similar to `background-color` inline styling) - direction (similar to `dir` attribute) - display: block/flex/inline/inline-block/none - In `flex` mode: @@ -171,7 +175,7 @@ These tags and their contents will be ignored: ## Extensibility -This package implements widget building logic with high testing coverage to ensure correctness. It tries to render an optimal tree by using `RichText` with specific `TextStyle`, merging text spans together, showing images in sized box, etc. The idea is to build a solid foundation for apps to customize. +This package implements widget building logic with high testing coverage to ensure correctness. It tries to render an optimal tree by using `RichText` with specific `TextStyle`, collapsing margins, proper whitespace handling, etc. The idea is to build a solid foundation for apps to customize. The [enhanced](https://pub.dev/packages/flutter_widget_from_html) package uses a custom `WidgetFactory` with pre-built mixins for easy usage: diff --git a/packages/core/lib/src/core_widget_factory.dart b/packages/core/lib/src/core_widget_factory.dart index 56c42bd57..687f73e58 100644 --- a/packages/core/lib/src/core_widget_factory.dart +++ b/packages/core/lib/src/core_widget_factory.dart @@ -147,12 +147,12 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { BoxBorder? border, BorderRadius? borderRadius, Color? color, - String? bgImageUrl, + DecorationImage? image, }) { if (border == null && borderRadius == null && color == null && - bgImageUrl == null) { + image == null) { return child; } @@ -164,7 +164,7 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { var decoration = baseDeco.copyWith( border: border, color: color, - image: buildDecorationImage(bgImageUrl), + image: image, ); var clipBehavior = Clip.none; @@ -187,7 +187,13 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { } /// Builds decoration image from [url] - DecorationImage? buildDecorationImage(String? url) { + DecorationImage? buildDecorationImage( + BuildTree tree, + String? url, { + AlignmentGeometry alignment = Alignment.topLeft, + BoxFit fit = BoxFit.scaleDown, + ImageRepeat repeat = ImageRepeat.noRepeat, + }) { if (url == null) { return null; } @@ -208,7 +214,12 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { return null; } - return DecorationImage(image: provider); + return DecorationImage( + alignment: alignment, + fit: fit, + image: provider, + repeat: repeat, + ); } /// Builds [Flex]. @@ -1026,12 +1037,6 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { void parseStyle(BuildTree tree, css.Declaration style) { final key = style.property; switch (key) { - case kCssBackground: - case kCssBackgroundColor: - case kCssBackgroundImage: - tree.register(_styleBackground ??= StyleBackground(this).buildOp); - break; - case kCssColor: final color = tryParseColor(style.value); if (color != null) { @@ -1134,6 +1139,10 @@ class WidgetFactory extends WidgetFactoryResetter with AnchorWidgetFactory { break; } + if (key.startsWith(kCssBackground)) { + tree.register(_styleBackground ??= StyleBackground(this).buildOp); + } + if (key.startsWith(kCssBorder)) { tree.register(_styleBorder ??= StyleBorder(this).buildOp); } diff --git a/packages/core/lib/src/internal/ops/priorities.dart b/packages/core/lib/src/internal/ops/priorities.dart index d15655a87..77952a45e 100644 --- a/packages/core/lib/src/internal/ops/priorities.dart +++ b/packages/core/lib/src/internal/ops/priorities.dart @@ -8,12 +8,16 @@ part of '../core_ops.dart'; /// User's build op has a default priority of 10 so they should run /// after our early ops and before our normal ops. class Priority { + static const max = 9007199254740991; + + @visibleForTesting + static const step = 1000000000; + static const _baseEarly00 = -3000000000000000; static const _baseNormal00 = 1000000000000000; static const _baseBoxModel = 5000000000000000; static const _baseLate0000 = 9000000000000000; - static const _step = 1000000000; - static const max = 9007199254740991; + static const _step = step; static const first = _baseNormal00; static const tagA = first + _step; diff --git a/packages/core/lib/src/internal/ops/style_background.dart b/packages/core/lib/src/internal/ops/style_background.dart index a825edd75..31a9fab55 100644 --- a/packages/core/lib/src/internal/ops/style_background.dart +++ b/packages/core/lib/src/internal/ops/style_background.dart @@ -4,6 +4,24 @@ const kCssBackground = 'background'; const kCssBackgroundColor = 'background-color'; const kCssBackgroundImage = 'background-image'; +const kCssBackgroundPosition = 'background-position'; +const kCssBackgroundPositionBottom = 'bottom'; +const kCssBackgroundPositionCenter = 'center'; +const kCssBackgroundPositionLeft = 'left'; +const kCssBackgroundPositionRight = 'right'; +const kCssBackgroundPositionTop = 'top'; + +const kCssBackgroundRepeat = 'background-repeat'; +const kCssBackgroundRepeatNo = 'no-repeat'; +const kCssBackgroundRepeatX = 'repeat-x'; +const kCssBackgroundRepeatY = 'repeat-y'; +const kCssBackgroundRepeatYes = 'repeat'; + +const kCssBackgroundSize = 'background-size'; +const kCssBackgroundSizeAuto = 'auto'; +const kCssBackgroundSizeContain = 'contain'; +const kCssBackgroundSizeCover = 'cover'; + class StyleBackground { final WidgetFactory wf; @@ -13,24 +31,33 @@ class StyleBackground { alwaysRenderBlock: false, debugLabel: kCssBackground, onRenderBlock: (tree, placeholder) { - final color = _parseColor(tree); - final bgImageUrl = _parseBackgroundImageUrl(wf, tree); + final data = tree.backgroundData; + final color = data.color; + final imageUrl = data.imageUrl; - if (color == null && bgImageUrl == null) { + if (color == null && imageUrl == null) { return placeholder; } + final image = wf.buildDecorationImage( + tree, + imageUrl, + alignment: data.alignment, + fit: data.size, + repeat: data.repeat, + ); + return placeholder.wrapWith( (_, child) => wf.buildDecoration( tree, child, color: color, - bgImageUrl: bgImageUrl, + image: image, ), ); }, onRenderInline: (tree) { - final color = _parseColor(tree); + final color = tree.backgroundData.color; if (color == null) { return; } @@ -50,45 +77,283 @@ class StyleBackground { debugLabel: 'fwfh: $kCssBackgroundColor', ), ); +} + +extension on BuildTree { + _StyleBackgroundData get backgroundData => + getNonInherited<_StyleBackgroundData>() ?? + setNonInherited<_StyleBackgroundData>(_parse()); - static Color? _parseColor(BuildTree tree) { - Color? color; - for (final style in tree.styles) { + _StyleBackgroundData _parse() { + var data = const _StyleBackgroundData(); + for (final style_ in styles) { + final style = _StyleBackgroundDeclaration(style_); switch (style.property) { case kCssBackground: - for (final expression in style.values) { - color = tryParseColor(expression) ?? color; + while (style.hasValue) { + final prev = data; + data = data.copyWithColor(style); + if (style.hasValue) { + data = data.copyWithImageUrl(style); + } + if (style.hasValue) { + data = data.copyWithPosition(style); + } + if (style.hasValue) { + data = data.copyWithRepeatOrSize(style); + } + if (identical(data, prev)) { + // unrecognized value, ignore it + style.increaseIndex(); + } } break; case kCssBackgroundColor: - color = tryParseColor(style.value) ?? color; + data = data.copyWithColor(style); + break; + case kCssBackgroundImage: + data = data.copyWithImageUrl(style); + break; + case kCssBackgroundPosition: + while (style.hasValue) { + final prev = data; + data = data.copyWithPosition(style); + if (identical(data, prev)) { + // unrecognized value, ignore it + style.increaseIndex(); + } + } + break; + case kCssBackgroundRepeat: + case kCssBackgroundSize: + data = data.copyWithRepeatOrSize(style); break; } } - return color; + return data; } +} - /// Attempts to parse the background image URL from the [tree] styles. - String? _parseBackgroundImageUrl(WidgetFactory wf, BuildTree tree) { - for (final style in tree.styles) { - final styleValue = style.value; - if (styleValue == null) { - continue; - } +extension on css.Expression { + _StyleBackgroundPosition? get position { + final self = this; + final term = self is css.LiteralTerm ? self.valueAsString : null; + switch (term) { + case kCssBackgroundPositionBottom: + return _StyleBackgroundPosition.bottom; + case kCssBackgroundPositionCenter: + return _StyleBackgroundPosition.center; + case kCssBackgroundPositionLeft: + return _StyleBackgroundPosition.left; + case kCssBackgroundPositionRight: + return _StyleBackgroundPosition.right; + case kCssBackgroundPositionTop: + return _StyleBackgroundPosition.top; + } - switch (style.property) { - case kCssBackground: - case kCssBackgroundImage: - for (final expression in style.values) { - if (expression is css.UriTerm) { - return expression.text; - } + return null; + } +} + +@immutable +class _StyleBackgroundData { + final AlignmentGeometry alignment; + final Color? color; + final String? imageUrl; + final ImageRepeat repeat; + final BoxFit size; + const _StyleBackgroundData({ + this.alignment = Alignment.topLeft, + this.color, + this.imageUrl, + this.repeat = ImageRepeat.noRepeat, + this.size = BoxFit.scaleDown, + }); + + _StyleBackgroundData copyWith({ + AlignmentGeometry? alignment, + Color? color, + String? imageUrl, + ImageRepeat? repeat, + BoxFit? size, + }) => + _StyleBackgroundData( + alignment: alignment ?? this.alignment, + color: color ?? this.color, + imageUrl: imageUrl ?? this.imageUrl, + repeat: repeat ?? this.repeat, + size: size ?? this.size, + ); + + _StyleBackgroundData copyWithColor(_StyleBackgroundDeclaration style) { + final color = tryParseColor(style.value); + if (color == null) { + return this; + } + + style.increaseIndex(); + return copyWith(color: color); + } + + _StyleBackgroundData copyWithImageUrl(_StyleBackgroundDeclaration style) { + final value = style.value; + final imageUrl = value is css.UriTerm ? value.text : null; + if (imageUrl == null) { + return this; + } + + style.increaseIndex(); + return copyWith(imageUrl: imageUrl); + } + + _StyleBackgroundData copyWithPosition(_StyleBackgroundDeclaration style) { + final value1 = style.value; + final position1 = value1?.position; + if (position1 == null) { + return this; + } + + final value2 = style.valuePlus1; + final position2 = value2?.position; + if (position2 == null) { + // single keyword position + style.increaseIndex(); + switch (position1) { + case _StyleBackgroundPosition.bottom: + return copyWith(alignment: Alignment.bottomCenter); + case _StyleBackgroundPosition.center: + return copyWith(alignment: Alignment.center); + case _StyleBackgroundPosition.left: + return copyWith(alignment: Alignment.centerLeft); + case _StyleBackgroundPosition.right: + return copyWith(alignment: Alignment.centerRight); + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.topCenter); + } + } else { + // double keywords position + style.increaseIndex(2); + switch (position1) { + case _StyleBackgroundPosition.bottom: + switch (position2) { + case _StyleBackgroundPosition.left: + return copyWith(alignment: Alignment.bottomLeft); + case _StyleBackgroundPosition.right: + return copyWith(alignment: Alignment.bottomRight); + case _StyleBackgroundPosition.bottom: + case _StyleBackgroundPosition.center: + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.bottomCenter); + } + case _StyleBackgroundPosition.center: + switch (position2) { + case _StyleBackgroundPosition.bottom: + return copyWith(alignment: Alignment.bottomCenter); + case _StyleBackgroundPosition.center: + return copyWith(alignment: Alignment.center); + case _StyleBackgroundPosition.left: + return copyWith(alignment: Alignment.centerLeft); + case _StyleBackgroundPosition.right: + return copyWith(alignment: Alignment.centerRight); + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.topCenter); + } + case _StyleBackgroundPosition.left: + switch (position2) { + case _StyleBackgroundPosition.bottom: + return copyWith(alignment: Alignment.bottomLeft); + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.topLeft); + case _StyleBackgroundPosition.center: + case _StyleBackgroundPosition.left: + case _StyleBackgroundPosition.right: + return copyWith(alignment: Alignment.centerLeft); + } + case _StyleBackgroundPosition.right: + switch (position2) { + case _StyleBackgroundPosition.bottom: + return copyWith(alignment: Alignment.bottomRight); + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.topRight); + case _StyleBackgroundPosition.left: + case _StyleBackgroundPosition.right: + case _StyleBackgroundPosition.center: + return copyWith(alignment: Alignment.centerRight); + } + case _StyleBackgroundPosition.top: + switch (position2) { + case _StyleBackgroundPosition.left: + return copyWith(alignment: Alignment.topLeft); + case _StyleBackgroundPosition.right: + return copyWith(alignment: Alignment.topRight); + case _StyleBackgroundPosition.bottom: + case _StyleBackgroundPosition.center: + case _StyleBackgroundPosition.top: + return copyWith(alignment: Alignment.topCenter); } - break; } } + } - return null; + _StyleBackgroundData copyWithRepeatOrSize(_StyleBackgroundDeclaration style) { + final value = style.value; + final term = value is css.LiteralTerm ? value.valueAsString : null; + final copied = copyWithTerm(term); + if (identical(copied, this)) { + return this; + } + + style.increaseIndex(); + return copied; + } + + _StyleBackgroundData copyWithTerm(String? term) { + switch (term) { + case kCssBackgroundRepeatNo: + return copyWith(repeat: ImageRepeat.noRepeat); + case kCssBackgroundRepeatX: + return copyWith(repeat: ImageRepeat.repeatX); + case kCssBackgroundRepeatY: + return copyWith(repeat: ImageRepeat.repeatY); + case kCssBackgroundRepeatYes: + return copyWith(repeat: ImageRepeat.repeat); + case kCssBackgroundSizeAuto: + return copyWith(size: BoxFit.scaleDown); + case kCssBackgroundSizeContain: + return copyWith(size: BoxFit.contain); + case kCssBackgroundSizeCover: + return copyWith(size: BoxFit.cover); + } + + return this; } } + +class _StyleBackgroundDeclaration { + final String property; + final List values; + + var _i = 0; + + _StyleBackgroundDeclaration(css.Declaration style) + : property = style.property, + values = style.values; + + bool get hasValue => _i < values.length; + + css.Expression? get value => hasValue ? values[_i] : null; + + css.Expression? get valuePlus1 => + _i + 1 < values.length ? values[_i + 1] : null; + + void increaseIndex([int delta = 1]) => _i += delta; +} + +enum _StyleBackgroundPosition { + bottom, + center, + left, + right, + top, +} diff --git a/packages/core/test/_.dart b/packages/core/test/_.dart index 5f817f4a2..5d3b6a314 100644 --- a/packages/core/test/_.dart +++ b/packages/core/test/_.dart @@ -155,10 +155,13 @@ Future explainWithoutPumping({ return 'null'; } - return Explainer( + var str = Explainer( key.currentContext!, explainer: explainer, ).explain(built); + + str = str.replaceAll(RegExp('String#[^,]+,'), 'String,'); + return str.replaceAll(RegExp('Uint8List#[0-9a-f]+,'), 'bytes,'); } final _explainMarginRegExp = RegExp( @@ -269,24 +272,33 @@ class Explainer { final attr = []; if (d is BoxDecoration) { - final color = d.color; - if (color != null) { - attr.add('bg=${_color(color)}'); - } - final border = d.border; if (border != null) { attr.add('border=${_boxBorder(border)}'); } - final borderRadius = d.borderRadius; - if (borderRadius != null && borderRadius is BorderRadius) { - attr.add('radius=${_borderRadius(borderRadius)}'); + final color = d.color; + if (color != null) { + attr.add('color=${_color(color)}'); } final image = d.image; if (image != null) { - attr.add("bgimage=$image"); + attr.add("image=${image.image}"); + if (image.alignment != Alignment.topLeft) { + attr.add(_alignment(image.alignment)); + } + if (image.fit != BoxFit.scaleDown) { + attr.add('fit=${image.fit?.name}'); + } + if (image.repeat != ImageRepeat.noRepeat) { + attr.add('repeat=${image.repeat.name}'); + } + } + + final borderRadius = d.borderRadius; + if (borderRadius != null && borderRadius is BorderRadius) { + attr.add('radius=${_borderRadius(borderRadius)}'); } } @@ -465,7 +477,7 @@ class Explainer { final bg = style.background; if (bg != null) { - s += 'bg=${_color(bg.color)}'; + s += 'color=${_color(bg.color)}'; } final color = style.color; diff --git a/packages/core/test/core_test.dart b/packages/core/test/core_test.dart index a1b8136c4..c9173378a 100644 --- a/packages/core/test/core_test.dart +++ b/packages/core/test/core_test.dart @@ -598,153 +598,6 @@ Future main() async { }); }); - group('background-color', () { - testWidgets('renders MARK tag', (WidgetTester tester) async { - const html = 'Foo'; - final explained = await explain(tester, html); - expect(explained, equals('[RichText:(bg=#FFFFFF00#FF000000:Foo)]')); - }); - - testWidgets('renders block', (WidgetTester tester) async { - const html = '
Foo
'; - final explained = await explain(tester, html); - expect( - explained, - equals( - '[Container:bg=#FFFF0000,child=' - '[CssBlock:child=' - '[RichText:(:Foo)]]]', - ), - ); - }); - - testWidgets('renders with margins and paddings', (tester) async { - const html = '
Foo
'; - final explained = await explainMargin(tester, html); - expect( - explained, - equals( - '[SizedBox:0.0x1.0],' - '[HorizontalMargin:left=1,right=1,child=' - '[Container:bg=#FFFF0000,child=' - '[Padding:(2,2,2,2),child=' - '[CssBlock:child=' - '[RichText:(:Foo)]]]' - ']],[SizedBox:0.0x1.0]', - ), - ); - }); - - testWidgets('renders blocks', (WidgetTester tester) async { - const h = '

A

B

'; - final explained = await explain(tester, h); - expect( - explained, - equals( - '[Container:bg=#FFFF0000,child=' - '[CssBlock:child=' - '[Column:children=' - '[CssBlock:child=[RichText:(:A)]],' - '[SizedBox:0.0x10.0],' - '[CssBlock:child=[RichText:(:B)]]' - ']]]', - ), - ); - }); - - testWidgets('renders inline', (WidgetTester tester) async { - const html = 'Foo bar'; - final explained = await explain(tester, html); - expect(explained, equals('[RichText:(:Foo (bg=#FFFF0000:bar))]')); - }); - - testWidgets('renders background', (WidgetTester tester) async { - const html = 'Foo bar'; - final explained = await explain(tester, html); - expect(explained, equals('[RichText:(:Foo (bg=#FFFF0000:bar))]')); - }); - - group('renders without erroneous white spaces', () { - testWidgets('before', (WidgetTester tester) async { - const html = 'Foo bar'; - final explained = await explain(tester, html); - expect(explained, equals('[RichText:(:Foo (bg=#FFFF0000:bar))]')); - }); - - testWidgets('after', (WidgetTester tester) async { - const html = 'Foo bar '; - final explained = await explain(tester, html); - expect(explained, equals('[RichText:(:Foo (bg=#FFFF0000:bar))]')); - }); - }); - - testWidgets('resets in continuous SPANs (#155)', (tester) async { - const html = - 'Foo' - 'bar'; - final explained = await explain(tester, html); - expect( - explained, - equals( - '[RichText:(:(bg=#FF0000FF#FFFFFF00:Foo)(#FFFF0000:bar))]', - ), - ); - }); - }); - - group('background-image', () { - testWidgets('asset', (WidgetTester tester) async { - const assetName = 'test/images/logo.png'; - const html = - '
Foo
'; - final explained = await explain(tester, html); - - expect( - explained, - equals( - '[Container:bgimage=' - 'DecorationImage(' - 'AssetImage(bundle: null, name: "test/images/logo.png"), Alignment.center, scale 1.0, opacity 1.0, FilterQuality.low),child=' - '[CssBlock:child=' - '[RichText:(:Foo)]]]', - ), - ); - }); - - testWidgets('data uri', (WidgetTester tester) async { - const html = '
Foo
'; - final explained = await explain(tester, html); - - expect( - explained, - matches( - r'^\[Container:bgimage=DecorationImage\(MemoryImage\(Uint8List#[0-9a-fA-F]+, scale: 1\.0\), ' - r'Alignment\.center, scale 1\.0, opacity 1\.0, FilterQuality\.low\),child=' - r'\[CssBlock:child=' - r'\[RichText:\(:Foo\)\]\]\]$'), - ); - }); - - testWidgets('file', (WidgetTester tester) async { - const fileName = 'test/images/logo.png'; - const html = - '
Foo
'; - final explained = await explain(tester, html); - - expect( - explained, - equals( - '[Container:bgimage=' - 'DecorationImage(' - 'FileImage("/test/images/logo.png", scale: 1.0), Alignment.center, scale 1.0, opacity 1.0, FilterQuality.low),child=' - '[CssBlock:child=' - '[RichText:(:Foo)]]]', - ), - ); - }); - }); - group('color (inline style)', () { testWidgets('renders hex values', (WidgetTester tester) async { const html = 'red' @@ -1053,7 +906,7 @@ Future main() async { const html = 'Foo'; final e = await explain(tester, html); - expect(e, equals('[Container:bg=#FFFF6600,child=[RichText:(:Foo)]]')); + expect(e, equals('[Container:color=#FFFF6600,child=[RichText:(:Foo)]]')); }); testWidgets('renders display: none', (WidgetTester tester) async { diff --git a/packages/core/test/images/44px.png b/packages/core/test/images/44px.png new file mode 100644 index 000000000..dcd138121 Binary files /dev/null and b/packages/core/test/images/44px.png differ diff --git a/packages/core/test/images/logo.png b/packages/core/test/images/logo.png index 4d6372eeb..33414d1fd 100644 Binary files a/packages/core/test/images/logo.png and b/packages/core/test/images/logo.png differ diff --git a/packages/core/test/src/internal/ops/priorities_test.dart b/packages/core/test/src/internal/ops/priorities_test.dart index 6e2e564f7..32f401a97 100644 --- a/packages/core/test/src/internal/ops/priorities_test.dart +++ b/packages/core/test/src/internal/ops/priorities_test.dart @@ -12,6 +12,11 @@ void main() { expect(BoxModel.first, greaterThan(kPriorityDefault)); }); + test('background and border should be next to each other', () { + // they both uses `WidgetFactory.buildDecoration` + expect(BoxModel.background - BoxModel.border, equals(Priority.step)); + }); + test('Late.displayInlineBlock is before inline block default', () { expect(Late.displayInlineBlock, lessThan(kPriorityInlineBlockDefault)); }); diff --git a/packages/core/test/style_background_test.dart b/packages/core/test/style_background_test.dart new file mode 100644 index 000000000..cb42f851e --- /dev/null +++ b/packages/core/test/style_background_test.dart @@ -0,0 +1,481 @@ +import 'dart:io'; + +import 'package:flutter/material.dart'; +import 'package:flutter_test/flutter_test.dart'; +import 'package:flutter_widget_from_html_core/flutter_widget_from_html_core.dart'; +import 'package:flutter_widget_from_html_core/src/internal/core_ops.dart'; +import 'package:golden_toolkit/golden_toolkit.dart'; +import 'package:mocktail_image_network/mocktail_image_network.dart'; + +import '_.dart'; + +Future main() async { + await loadAppFonts(); + + group('background-color', () { + testWidgets('renders MARK tag', (WidgetTester tester) async { + const html = 'Foo'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(color=#FFFFFF00#FF000000:Foo)]')); + }); + + testWidgets('renders block', (WidgetTester tester) async { + const html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:color=#FFFF0000,child=' + '[CssBlock:child=' + '[RichText:(:Foo)]]]', + ), + ); + }); + + testWidgets('renders with margins and paddings', (tester) async { + const html = '
Foo
'; + final explained = await explainMargin(tester, html); + expect( + explained, + equals( + '[SizedBox:0.0x1.0],' + '[HorizontalMargin:left=1,right=1,child=' + '[Container:color=#FFFF0000,child=' + '[Padding:(2,2,2,2),child=' + '[CssBlock:child=' + '[RichText:(:Foo)]]]' + ']],[SizedBox:0.0x1.0]', + ), + ); + }); + + testWidgets('renders blocks', (WidgetTester tester) async { + const h = '

A

B

'; + final explained = await explain(tester, h); + expect( + explained, + equals( + '[Container:color=#FFFF0000,child=' + '[CssBlock:child=' + '[Column:children=' + '[CssBlock:child=[RichText:(:A)]],' + '[SizedBox:0.0x10.0],' + '[CssBlock:child=[RichText:(:B)]]' + ']]]', + ), + ); + }); + + testWidgets('renders inline', (WidgetTester tester) async { + const html = 'Foo bar'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(:Foo (color=#FFFF0000:bar))]')); + }); + + group('renders without erroneous white spaces', () { + testWidgets('before', (WidgetTester tester) async { + const html = 'Foo bar'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(:Foo (color=#FFFF0000:bar))]')); + }); + + testWidgets('after', (WidgetTester tester) async { + const html = 'Foo bar '; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(:Foo (color=#FFFF0000:bar))]')); + }); + }); + + testWidgets('resets in continuous SPANs', (tester) async { + // https://github.com/daohoangson/flutter_widget_from_html/issues/155 + const html = + 'Foo' + 'bar'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[RichText:(:(color=#FF0000FF#FFFFFF00:Foo)(#FFFF0000:bar))]', + ), + ); + }); + }); + + group('background-image', () { + testWidgets('renders network', (WidgetTester tester) async { + const src = 'http://domain.com/image.png'; + const html = '
Foo
'; + final explained = await mockNetworkImages(() => explain(tester, html)); + expect( + explained, + equals( + '[Container:image=NetworkImage("$src", scale: 1.0),child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + group('asset', () { + const assetName = 'test/images/logo.png'; + + testWidgets('renders asset', (WidgetTester tester) async { + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=AssetImage(bundle: null, name: "$assetName"),child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders asset (specified package)', (tester) async { + const package = 'flutter_widget_from_html_core'; + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=AssetImage(bundle: null, name: "packages/$package/$assetName"),child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + }); + + testWidgets('data uri', (WidgetTester tester) async { + const html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=MemoryImage(bytes, scale: 1.0),child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('file uri', (WidgetTester tester) async { + final filePath = '${Directory.current.path}/test/images/logo.png'; + final fileUri = 'file://$filePath'; + final html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=FileImage("$filePath", scale: 1.0),child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + group('background-position', () { + const backgroundAssetImage = + 'background-image: url(asset:test/images/logo.png)'; + + final positionTestCases = ValueVariant<_PositionTestCase>( + { + // bottom + 'bottom'.position(Alignment.bottomCenter), + 'bottom bottom'.position(Alignment.bottomCenter), + 'bottom center'.position(Alignment.bottomCenter), + 'bottom left'.position(Alignment.bottomLeft), + 'bottom right'.position(Alignment.bottomRight), + 'bottom top'.position(Alignment.bottomCenter), + + // center + 'center'.position(Alignment.center), + 'center bottom'.position(Alignment.bottomCenter), + 'center center'.position(Alignment.center), + 'center left'.position(Alignment.centerLeft), + 'center right'.position(Alignment.centerRight), + 'center top'.position(Alignment.topCenter), + + // left + 'left'.position(Alignment.centerLeft), + 'left bottom'.position(Alignment.bottomLeft), + 'left center'.position(Alignment.centerLeft), + 'left left'.position(Alignment.centerLeft), + 'left right'.position(Alignment.centerLeft), + 'left top'.position(Alignment.topLeft), + + // right + 'right'.position(Alignment.centerRight), + 'right bottom'.position(Alignment.bottomRight), + 'right center'.position(Alignment.centerRight), + 'right left'.position(Alignment.centerRight), + 'right right'.position(Alignment.centerRight), + 'right top'.position(Alignment.topRight), + + // top + 'top'.position(Alignment.topCenter), + 'top bottom'.position(Alignment.topCenter), + 'top center'.position(Alignment.topCenter), + 'top left'.position(Alignment.topLeft), + 'top right'.position(Alignment.topRight), + 'top top'.position(Alignment.topCenter), + + // default + 'foo'.position(Alignment.topLeft), + }, + ); + + testWidgets( + 'renders alignment', + (WidgetTester tester) async { + final testCase = positionTestCases.currentValue!; + final html = '
Foo
'; + + await explain(tester, html); + final container = tester.widget(find.byType(Container)); + expect( + container.decoration, + isA().having( + (deco) => deco.image?.alignment, + 'image.alignment', + equals(testCase.alignment), + ), + ); + }, + variant: positionTestCases, + ); + }); + }); + + group('shorthand', () { + const assetName = 'test/images/logo.png'; + const assetImage = 'AssetImage(bundle: null, name: "$assetName")'; + + testWidgets('renders color', (WidgetTester tester) async { + const html = 'Foo bar'; + final explained = await explain(tester, html); + expect(explained, equals('[RichText:(:Foo (color=#FFFF0000:bar))]')); + }); + + testWidgets('renders image', (WidgetTester tester) async { + const html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=$assetImage,child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders position (single)', (WidgetTester tester) async { + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=$assetImage,alignment=centerRight,child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders position (double)', (WidgetTester tester) async { + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=$assetImage,alignment=bottomRight,child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders repeat', (WidgetTester tester) async { + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=$assetImage,repeat=repeat,child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders size', (WidgetTester tester) async { + const html = + '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:image=$assetImage,fit=cover,child=' + '[CssBlock:child=[RichText:(:Foo)]]' + ']', + ), + ); + }); + + testWidgets('renders everything', (WidgetTester tester) async { + const html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:color=#FFFF0000,' + 'image=$assetImage,' + 'alignment=bottomRight,' + 'fit=cover,' + 'repeat=repeat,' + 'child=[CssBlock:child=[RichText:(:Foo)]]]', + ), + ); + }); + + testWidgets('renders unrecognized values', (WidgetTester tester) async { + const html = '
Foo
'; + final explained = await explain(tester, html); + expect( + explained, + equals( + '[Container:color=#FFFF0000,' + 'image=$assetImage,' + 'alignment=bottomRight,' + 'fit=cover,' + 'repeat=repeat,' + 'child=[CssBlock:child=[RichText:(:Foo)]]]', + ), + ); + }); + }); + + final goldenSkipEnvVar = Platform.environment['GOLDEN_SKIP']; + final goldenSkip = goldenSkipEnvVar == null + ? Platform.isLinux + ? null + : 'Linux only' + : 'GOLDEN_SKIP=$goldenSkipEnvVar'; + + GoldenToolkit.runWithConfiguration( + () { + group( + 'background-image', + () { + const image44 = 'background-image: url(asset:test/images/44px.png)'; + const size100x75 = 'width: 100px; height: 75px'; + const size100x100 = 'width: 100px; height: 100px'; + const testCases = { + 'position/center': + '
Foo
', + 'position/top': + '
Foo
', + 'position/top_right': + '
Foo
', + 'position/right': + '
Foo
', + 'position/bottom_right': + '
Foo
', + 'position/bottom': + '
Foo
', + 'position/bottom_left': + '
Foo
', + 'position/left': + '
Foo
', + 'position/top_left': + '
Foo
', + 'repeat/no-repeat': + '
Foo
', + 'repeat/repeat-x': + '
Foo
', + 'repeat/repeat-y': + '
Foo
', + 'repeat/repeat': + '
Foo
', + 'size/auto': + '
Foo
', + 'size/contain': + '
Foo
', + 'size/cover': + '
Foo
', + }; + + for (final testCase in testCases.entries) { + testGoldens( + testCase.key, + (tester) async { + await tester.pumpWidgetBuilder( + _Golden(testCase.value), + wrapper: materialAppWrapper(theme: ThemeData.light()), + surfaceSize: const Size(116, 116), + ); + + await screenMatchesGolden(tester, testCase.key); + }, + skip: goldenSkip != null, + ); + } + }, + skip: goldenSkip, + ); + }, + config: GoldenToolkitConfiguration( + fileNameFactory: (name) => '$kGoldenFilePrefix/background/$name.png', + ), + ); +} + +extension on String { + _PositionTestCase position(AlignmentGeometry alignment) => + _PositionTestCase(this, alignment); +} + +class _Golden extends StatelessWidget { + final String html; + + const _Golden(this.html); + + @override + Widget build(BuildContext _) => Scaffold( + body: Padding( + padding: const EdgeInsets.all(8.0), + child: HtmlWidget( + html, + customStylesBuilder: (element) { + if (element.localName == 'div') { + return const {kCssBackgroundColor: 'lightgray'}; + } + + return null; + }, + ), + ), + ); +} + +@immutable +class _PositionTestCase { + final String value; + final AlignmentGeometry alignment; + const _PositionTestCase(this.value, this.alignment); + + @override + String toString() => value; +} diff --git a/packages/core/test/style_border_test.dart b/packages/core/test/style_border_test.dart index dab605e5b..a11061782 100644 --- a/packages/core/test/style_border_test.dart +++ b/packages/core/test/style_border_test.dart @@ -1163,7 +1163,7 @@ void main() { expect( explained, equals( - '[Container:bg=#FFFF0000,border=$_border1,child=' + '[Container:border=$_border1,color=#FFFF0000,child=' '[Column:children=' '[SizedBox:0.0x12.4],' '[CssBlock:child=[RichText:(@15.0+b:Foo)]],' diff --git a/packages/core/test/style_padding_test.dart b/packages/core/test/style_padding_test.dart index b6a0d62e9..b363b0ad5 100644 --- a/packages/core/test/style_padding_test.dart +++ b/packages/core/test/style_padding_test.dart @@ -611,7 +611,7 @@ void main() { expect( explained, equals( - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(5,5,5,5),child=' '[Column:children=' '[SizedBox:0.0x12.4],' diff --git a/packages/core/test/style_sizing_test.dart b/packages/core/test/style_sizing_test.dart index e5ef0c688..ca83033f8 100644 --- a/packages/core/test/style_sizing_test.dart +++ b/packages/core/test/style_sizing_test.dart @@ -426,14 +426,14 @@ Future main() async { expect( explained, equals( - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(20,20,20,20),child=' '[Column:children=[SizedBox:0.0x15.0],' '[CssBlock:child=' - '[Container:bg=#FF008000,child=' + '[Container:color=#FF008000,child=' '[CssBlock:child=' '[HorizontalMargin:left=15,right=15,child=' - '[Container:bg=#FF0000FF,child=' + '[Container:color=#FF0000FF,child=' '[Padding:(5,5,5,5),child=' '[CssSizing:height=100.0,width=100.0,child=' '[RichText:(#FFFFFFFF:Foo)]' diff --git a/packages/core/test/tag_img_test.dart b/packages/core/test/tag_img_test.dart index 6c69c3afb..257e2c3a5 100644 --- a/packages/core/test/tag_img_test.dart +++ b/packages/core/test/tag_img_test.dart @@ -215,8 +215,7 @@ void main() { testWidgets('renders data uri', (WidgetTester tester) async { const html = ''; - final explained = (await explain(tester, html)) - .replaceAll(RegExp('Uint8List#[0-9a-f]+,'), 'bytes,'); + final explained = await explain(tester, html); expect( explained, equals( @@ -253,8 +252,7 @@ void main() { testWidgets('renders file uri', (WidgetTester tester) async { final html = ''; - final explained = (await explain(tester, html)) - .replaceAll(RegExp('Uint8List#[0-9a-f]+,'), 'bytes,'); + final explained = await explain(tester, html); expect( explained, equals( diff --git a/packages/core/test/tag_table_test.dart b/packages/core/test/tag_table_test.dart index c9bbafa48..4b8318f74 100644 --- a/packages/core/test/tag_table_test.dart +++ b/packages/core/test/tag_table_test.dart @@ -654,7 +654,7 @@ Future main() async { equals( '[SingleChildScrollView:child=[HtmlTable:children=' '[HtmlTableCell:child=' - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(1,1,1,1),child=' '[Align:alignment=centerLeft,widthFactor=1.0,child=' '[RichText:(:Foo)]' @@ -674,13 +674,13 @@ Future main() async { equals( '[SingleChildScrollView:child=[HtmlTable:children=' '[HtmlTableCell:child=' - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(1,1,1,1),child=' '[Align:alignment=centerLeft,widthFactor=1.0,child=' '[RichText:(:Foo)]' ']]]],' '[HtmlTableCell:child=' - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(1,1,1,1),child=' '[Align:alignment=centerLeft,widthFactor=1.0,child=' '[RichText:(:Bar)]' @@ -700,13 +700,13 @@ Future main() async { equals( '[SingleChildScrollView:child=[HtmlTable:children=' '[HtmlTableCell:child=' - '[Container:bg=#FFFF0000,child=' + '[Container:color=#FFFF0000,child=' '[Padding:(1,1,1,1),child=' '[Align:alignment=centerLeft,widthFactor=1.0,child=' '[RichText:(:Foo)]' ']]]],' '[HtmlTableCell:child=' - '[Container:bg=#FF00FF00,child=' + '[Container:color=#FF00FF00,child=' '[Padding:(1,1,1,1),child=' '[Align:alignment=centerLeft,widthFactor=1.0,child=' '[RichText:(:Bar)]' diff --git a/packages/enhanced/README.md b/packages/enhanced/README.md index c00594f51..07c123101 100644 --- a/packages/enhanced/README.md +++ b/packages/enhanced/README.md @@ -146,17 +146,21 @@ These tags and their contents will be ignored: ### Inline stylings - background: 1 value (color) - - background-color + - background-color: hex values, `rgb()`, `hsl()` or named colors + - background-image: `url()` with support for asset (`asset://`), data uri, local file (`file://`) and network image + - background-repeat: no-repeat/repeat/repeat-x/repeat-y + - background-position: single or double instances of bottom/center/left/right/top (e.g. `top left`) + - background-size: auto/contain/cover - border: 3 values (width style color), 2 values (width style) or 1 value (width) - border-top, border-right, border-bottom, border-left - border-block-start, border-block-end - border-inline-start, border-inline-end -- border-radius: 4, 3, 2 or 1 values with slash support (e.g. `10px / 20px`) +- border-radius: 4, 3, 2 or 1 value with slash support (e.g. `10px / 20px`) - border-top-left-radius: 2 values or 1 value in `em`, `pt` and `px` - border-top-right-radius: 2 values or 1 value in `em`, `pt` and `px` - border-bottom-right-radius: 2 values or 1 value in `em`, `pt` and `px` - border-bottom-left-radius: 2 values or 1 value in `em`, `pt` and `px` -- color: hex values, `rgb()`, `hsl()` or named colors +- color (similar to `background-color` inline styling) - direction (similar to `dir` attribute) - display: block/flex/inline/inline-block/none - In `flex` mode: diff --git a/packages/fwfh_cached_network_image/test/cached_network_image_factory_test.dart b/packages/fwfh_cached_network_image/test/cached_network_image_factory_test.dart index 4f2e86b83..ba22dc64d 100644 --- a/packages/fwfh_cached_network_image/test/cached_network_image_factory_test.dart +++ b/packages/fwfh_cached_network_image/test/cached_network_image_factory_test.dart @@ -21,8 +21,7 @@ void main() { testWidgets('renders Image for data uri', (WidgetTester tester) async { const html = ''; - final explained = (await explain(tester, html)) - .replaceAll(RegExp('Uint8List#[0-9a-f]+,'), 'bytes,'); + final explained = await explain(tester, html); expect( explained, equals( diff --git a/packages/fwfh_chewie/test/chewie_factory_test.dart b/packages/fwfh_chewie/test/chewie_factory_test.dart index 7115e8653..1f990f9ad 100644 --- a/packages/fwfh_chewie/test/chewie_factory_test.dart +++ b/packages/fwfh_chewie/test/chewie_factory_test.dart @@ -160,8 +160,7 @@ void main() { testWidgets('renders video player with data uri', (tester) async { const h = ''; - final e = await explain(tester, h); - final explained = e.replaceAll(RegExp('Uint8List#[0-9a-f]+,'), 'bytes,'); + final explained = await explain(tester, h); expect( explained, equals( diff --git a/packages/fwfh_svg/test/svg_factory_test.dart b/packages/fwfh_svg/test/svg_factory_test.dart index 649caf2d8..46fbcd35c 100644 --- a/packages/fwfh_svg/test/svg_factory_test.dart +++ b/packages/fwfh_svg/test/svg_factory_test.dart @@ -12,7 +12,7 @@ import 'package:golden_toolkit/golden_toolkit.dart'; import 'package:mocktail/mocktail.dart'; import '../../core/test/_.dart' as core; -import '_.dart' as helper; +import '_.dart'; const redTriangle = ''' @@ -35,18 +35,15 @@ Future main() async { SVG support is not enabled. '''; - final explained = await helper.explain(tester, html); - expect( - explained.replaceAll(RegExp('String#[^,]+,'), 'String,'), - equals('[SvgPicture:bytesLoader=SvgStringLoader]'), - ); + final explained = await explain(tester, html); + expect(explained, equals('[SvgPicture:bytesLoader=SvgStringLoader]')); }); group('IMG', () { testWidgets('renders asset picture', (WidgetTester tester) async { const assetName = 'test/images/icon.svg'; const html = ''; - final explained = await helper.explain(tester, html); + final explained = await explain(tester, html); expect( explained, equals( @@ -61,7 +58,7 @@ Future main() async { testWidgets('renders file picture', (WidgetTester tester) async { final filePath = '${Directory.current.path}/test/images/icon.svg'; final html = ''; - final explained = await helper.explain(tester, html); + final explained = await explain(tester, html); expect( explained, equals( @@ -74,10 +71,6 @@ Future main() async { }); group('MemoryPicture', () { - Future explain(WidgetTester tester, String html) => helper - .explain(tester, html) - .then((e) => e.replaceAll(RegExp(r'\(Uint8List#.+\)'), '(bytes)')); - testWidgets('renders bad data uri', (WidgetTester tester) async { const html = ''; final explained = await explain(tester, html); @@ -121,7 +114,7 @@ Future main() async { const html = ''; const expectedLoading = 'â””CircularProgressIndicator'; final loading = await HttpOverrides.runZoned( - () => helper.explain(tester, html, useExplainer: false), + () => explain(tester, html, useExplainer: false), createHttpClient: (_) => _createMockSvgImageHttpClient(), ); expect(loading, contains('â””SvgPicture'));