diff --git a/.github/workflows/main.yaml b/.github/workflows/main.yaml index f4ac6d4..e79f8aa 100644 --- a/.github/workflows/main.yaml +++ b/.github/workflows/main.yaml @@ -1,4 +1,4 @@ -name: ci +name: CI concurrency: group: ${{ github.workflow }}-${{ github.ref }} @@ -45,4 +45,27 @@ jobs: with: generate_badges: pr upper_threshold: 30 + + check_generation: + name: Check Code Generation + timeout-minutes: 10 + runs-on: ubuntu-latest + steps: + - name: 📚 Checkout + uses: actions/checkout@v4 + + - name: 🐦 Setup Flutter + uses: subosito/flutter-action@v2 + with: + channel: 'stable' + cache: true + + - name: Ⓜ️ Set up Melos + uses: bluefireteam/melos-action@v3 + + - name: 🔨 Generate + run: melos run generate + + - name: 🔎 Check there are no uncommitted changes + run: git add . && git diff --cached --exit-code \ No newline at end of file diff --git a/.github/workflows/tag-release.yaml b/.github/workflows/tag-release.yaml new file mode 100644 index 0000000..6b548ef --- /dev/null +++ b/.github/workflows/tag-release.yaml @@ -0,0 +1,25 @@ +name: Tag release +on: + push: + branches: [main] + +jobs: + publish-packages: + name: Create tag for a release + permissions: + contents: write + runs-on: [ ubuntu-latest ] + if: contains(github.event.head_commit.message, 'chore(release)') + steps: + - name: 📚 Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🐦 Setup Flutter + uses: subosito/flutter-action@v2 + + - name: Ⓜ️ Set up Melos + uses: bluefireteam/melos-action@v3 + with: + tag: true diff --git a/.github/workflows/version.yaml b/.github/workflows/version.yaml new file mode 100644 index 0000000..b63fe4d --- /dev/null +++ b/.github/workflows/version.yaml @@ -0,0 +1,47 @@ +name: Version + +on: + workflow_dispatch: + inputs: + prerelease: + description: 'Version as prerelease' + required: false + default: false + type: boolean + graduate: + description: 'Graduate prereleases' + required: false + default: false + type: boolean + +jobs: + prepare-release: + name: Prepare release + permissions: + contents: write + pull-requests: write + runs-on: ubuntu-latest + steps: + - name: 📚 Checkout + uses: actions/checkout@v4 + with: + fetch-depth: 0 + + - name: 🐦 Setup Flutter + uses: subosito/flutter-action@v2 + + - name: Ⓜ️ Set up Melos + uses: bluefireteam/melos-action@v3 + with: + run-versioning: ${{ inputs.prerelease == false }} + run-versioning-prerelease: ${{ inputs.prerelease == true }} + run-versioning-graduate: ${{ inputs.graduate == true }} + publish-dry-run: true + + - name: 🎋 Create Pull Request + uses: peter-evans/create-pull-request@v7 + with: + title: "chore(release): Publish packages" + body: "Prepared all packages to be released to pub.dev" + branch: chore/release + delete-branch: true \ No newline at end of file diff --git a/example/lib/main.dart b/example/lib/main.dart index aa8414e..6305a15 100644 --- a/example/lib/main.dart +++ b/example/lib/main.dart @@ -339,8 +339,8 @@ class _HomePageState extends State { required Color color, }) { return ValueListenableBuilder( - valueListenable: notifier.select( - (value) => value is Drawing && value.selectedColor == color.value), + valueListenable: notifier.select((value) => + value is Drawing && value.selectedColor == color.toARGB32()), builder: (context, value, child) => Padding( padding: const EdgeInsets.symmetric(horizontal: 4), child: ColorButton( diff --git a/lib/src/domain/model/point/point.freezed.dart b/lib/src/domain/model/point/point.freezed.dart index bc354ce..7b25d0b 100644 --- a/lib/src/domain/model/point/point.freezed.dart +++ b/lib/src/domain/model/point/point.freezed.dart @@ -24,8 +24,12 @@ mixin _$Point { double get y => throw _privateConstructorUsedError; double get pressure => throw _privateConstructorUsedError; + /// Serializes this Point to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of Point + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $PointCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -47,6 +51,8 @@ class _$PointCopyWithImpl<$Res, $Val extends Point> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of Point + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -89,6 +95,8 @@ class __$$PointImplCopyWithImpl<$Res> _$PointImpl _value, $Res Function(_$PointImpl) _then) : super(_value, _then); + /// Create a copy of Point + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -145,11 +153,13 @@ class _$PointImpl extends _Point { other.pressure == pressure)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, x, y, pressure); - @JsonKey(ignore: true) + /// Create a copy of Point + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$PointImplCopyWith<_$PointImpl> get copyWith => @@ -176,8 +186,11 @@ abstract class _Point extends Point { double get y; @override double get pressure; + + /// Create a copy of Point + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$PointImplCopyWith<_$PointImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/domain/model/sketch/sketch.freezed.dart b/lib/src/domain/model/sketch/sketch.freezed.dart index 286a8a4..787e53b 100644 --- a/lib/src/domain/model/sketch/sketch.freezed.dart +++ b/lib/src/domain/model/sketch/sketch.freezed.dart @@ -22,8 +22,12 @@ Sketch _$SketchFromJson(Map json) { mixin _$Sketch { List get lines => throw _privateConstructorUsedError; + /// Serializes this Sketch to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of Sketch + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SketchCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -45,6 +49,8 @@ class _$SketchCopyWithImpl<$Res, $Val extends Sketch> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of Sketch + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -77,6 +83,8 @@ class __$$SketchImplCopyWithImpl<$Res> _$SketchImpl _value, $Res Function(_$SketchImpl) _then) : super(_value, _then); + /// Create a copy of Sketch + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -120,12 +128,14 @@ class _$SketchImpl implements _Sketch { const DeepCollectionEquality().equals(other._lines, _lines)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash(runtimeType, const DeepCollectionEquality().hash(_lines)); - @JsonKey(ignore: true) + /// Create a copy of Sketch + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SketchImplCopyWith<_$SketchImpl> get copyWith => @@ -146,8 +156,11 @@ abstract class _Sketch implements Sketch { @override List get lines; + + /// Create a copy of Sketch + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SketchImplCopyWith<_$SketchImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/domain/model/sketch_line/sketch_line.freezed.dart b/lib/src/domain/model/sketch_line/sketch_line.freezed.dart index 0bfa22f..1d8315b 100644 --- a/lib/src/domain/model/sketch_line/sketch_line.freezed.dart +++ b/lib/src/domain/model/sketch_line/sketch_line.freezed.dart @@ -29,8 +29,12 @@ mixin _$SketchLine { /// The width of the line double get width => throw _privateConstructorUsedError; + /// Serializes this SketchLine to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of SketchLine + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $SketchLineCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -54,6 +58,8 @@ class _$SketchLineCopyWithImpl<$Res, $Val extends SketchLine> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of SketchLine + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -97,6 +103,8 @@ class __$$SketchLineImplCopyWithImpl<$Res> _$SketchLineImpl _value, $Res Function(_$SketchLineImpl) _then) : super(_value, _then); + /// Create a copy of SketchLine + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -167,12 +175,14 @@ class _$SketchLineImpl implements _SketchLine { (identical(other.width, width) || other.width == width)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, const DeepCollectionEquality().hash(_points), color, width); - @JsonKey(ignore: true) + /// Create a copy of SketchLine + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$SketchLineImplCopyWith<_$SketchLineImpl> get copyWith => @@ -195,20 +205,22 @@ abstract class _SketchLine implements SketchLine { factory _SketchLine.fromJson(Map json) = _$SketchLineImpl.fromJson; - @override - /// The points that make up the line - List get points; @override + List get points; /// The color of the line in hexadecimal format (ARGB) - int get color; @override + int get color; /// The width of the line + @override double get width; + + /// Create a copy of SketchLine + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$SketchLineImplCopyWith<_$SketchLineImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/domain/model/sketch_line/sketch_line.g.dart b/lib/src/domain/model/sketch_line/sketch_line.g.dart index 2e5dbb5..f7c00ba 100644 --- a/lib/src/domain/model/sketch_line/sketch_line.g.dart +++ b/lib/src/domain/model/sketch_line/sketch_line.g.dart @@ -11,7 +11,7 @@ _$SketchLineImpl _$$SketchLineImplFromJson(Map json) => points: (json['points'] as List) .map((e) => Point.fromJson(e as Map)) .toList(), - color: json['color'] as int, + color: (json['color'] as num).toInt(), width: (json['width'] as num).toDouble(), ); diff --git a/lib/src/view/notifier/scribble_notifier.dart b/lib/src/view/notifier/scribble_notifier.dart index f9b21a8..10e616e 100644 --- a/lib/src/view/notifier/scribble_notifier.dart +++ b/lib/src/view/notifier/scribble_notifier.dart @@ -230,13 +230,13 @@ class ScribbleNotifier extends ScribbleNotifierBase temporaryValue = value.map( drawing: (s) => ScribbleState.drawing( sketch: s.sketch, - selectedColor: color.value, + selectedColor: color.toARGB32(), selectedWidth: s.selectedWidth, allowedPointersMode: s.allowedPointersMode, ), erasing: (s) => ScribbleState.drawing( sketch: s.sketch, - selectedColor: color.value, + selectedColor: color.toARGB32(), selectedWidth: s.selectedWidth, allowedPointersMode: s.allowedPointersMode, scaleFactor: value.scaleFactor, diff --git a/lib/src/view/state/scribble.state.freezed.dart b/lib/src/view/state/scribble.state.freezed.dart index 3c5ac07..ea1774c 100644 --- a/lib/src/view/state/scribble.state.freezed.dart +++ b/lib/src/view/state/scribble.state.freezed.dart @@ -55,10 +55,12 @@ mixin _$ScribbleState { /// {@endtemplate} double get scaleFactor => throw _privateConstructorUsedError; + /// {@template view.state.scribble_state.simplification_tolerance} /// The current tolerance of simplification, in pixels. /// /// Lines will be simplified when they are finished. A value of 0 (default) /// will mean no simplification. + /// {@endtemplate} double get simplificationTolerance => throw _privateConstructorUsedError; @optionalTypeArgs TResult when({ @@ -152,8 +154,13 @@ mixin _$ScribbleState { required TResult orElse(), }) => throw _privateConstructorUsedError; + + /// Serializes this ScribbleState to a JSON map. Map toJson() => throw _privateConstructorUsedError; - @JsonKey(ignore: true) + + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) $ScribbleStateCopyWith get copyWith => throw _privateConstructorUsedError; } @@ -187,6 +194,8 @@ class _$ScribbleStateCopyWithImpl<$Res, $Val extends ScribbleState> // ignore: unused_field final $Res Function($Val) _then; + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -230,6 +239,8 @@ class _$ScribbleStateCopyWithImpl<$Res, $Val extends ScribbleState> ) as $Val); } + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SketchCopyWith<$Res> get sketch { @@ -238,6 +249,8 @@ class _$ScribbleStateCopyWithImpl<$Res, $Val extends ScribbleState> }); } + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $PointCopyWith<$Res>? get pointerPosition { @@ -285,6 +298,8 @@ class __$$DrawingImplCopyWithImpl<$Res> _$DrawingImpl _value, $Res Function(_$DrawingImpl) _then) : super(_value, _then); + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -338,6 +353,8 @@ class __$$DrawingImplCopyWithImpl<$Res> )); } + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @override @pragma('vm:prefer-inline') $SketchLineCopyWith<$Res>? get activeLine { @@ -425,10 +442,12 @@ class _$DrawingImpl extends Drawing { @JsonKey() final double scaleFactor; + /// {@template view.state.scribble_state.simplification_tolerance} /// The current tolerance of simplification, in pixels. /// /// Lines will be simplified when they are finished. A value of 0 (default) /// will mean no simplification. + /// {@endtemplate} @override @JsonKey() final double simplificationTolerance; @@ -466,7 +485,7 @@ class _$DrawingImpl extends Drawing { other.simplificationTolerance == simplificationTolerance)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -480,7 +499,9 @@ class _$DrawingImpl extends Drawing { scaleFactor, simplificationTolerance); - @JsonKey(ignore: true) + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$DrawingImplCopyWith<_$DrawingImpl> get copyWith => @@ -652,35 +673,33 @@ abstract class Drawing extends ScribbleState { factory Drawing.fromJson(Map json) = _$DrawingImpl.fromJson; - @override - /// The current state of the sketch + @override Sketch get sketch; /// The line that is currently being drawn SketchLine? get activeLine; - @override /// Which pointers are allowed for drawing and will be captured by the /// scribble widget. - ScribblePointerMode get allowedPointersMode; @override + ScribblePointerMode get allowedPointersMode; /// The ids of all supported pointers that are currently interacting with /// the widget. - List get activePointerIds; @override + List get activePointerIds; /// The current position of the pointer + @override Point? get pointerPosition; /// The color that is currently being drawn with int get selectedColor; - @override /// The current width of the pen - double get selectedWidth; @override + double get selectedWidth; /// {@template view.state.scribble_state.scale_factor} /// How much the widget is scaled at the moment. @@ -688,16 +707,22 @@ abstract class Drawing extends ScribbleState { /// Can be used if zoom functionality is needed /// (e.g. through InteractiveViewer) so that the pen width remains the same. /// {@endtemplate} - double get scaleFactor; @override + double get scaleFactor; + /// {@template view.state.scribble_state.simplification_tolerance} /// The current tolerance of simplification, in pixels. /// /// Lines will be simplified when they are finished. A value of 0 (default) /// will mean no simplification. + /// {@endtemplate} + @override double get simplificationTolerance; + + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$DrawingImplCopyWith<_$DrawingImpl> get copyWith => throw _privateConstructorUsedError; } @@ -733,6 +758,8 @@ class __$$ErasingImplCopyWithImpl<$Res> _$ErasingImpl _value, $Res Function(_$ErasingImpl) _then) : super(_value, _then); + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @pragma('vm:prefer-inline') @override $Res call({ @@ -875,7 +902,7 @@ class _$ErasingImpl extends Erasing { other.simplificationTolerance == simplificationTolerance)); } - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) @override int get hashCode => Object.hash( runtimeType, @@ -887,7 +914,9 @@ class _$ErasingImpl extends Erasing { scaleFactor, simplificationTolerance); - @JsonKey(ignore: true) + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. + @JsonKey(includeFromJson: false, includeToJson: false) @override @pragma('vm:prefer-inline') _$$ErasingImplCopyWith<_$ErasingImpl> get copyWith => @@ -1033,44 +1062,46 @@ abstract class Erasing extends ScribbleState { factory Erasing.fromJson(Map json) = _$ErasingImpl.fromJson; - @override - /// The current state of the sketch - Sketch get sketch; @override + Sketch get sketch; /// Which pointers are allowed for drawing and will be captured by the /// scribble widget. - ScribblePointerMode get allowedPointersMode; @override + ScribblePointerMode get allowedPointersMode; /// The ids of all supported pointers that are currently interacting with /// the widget. - List get activePointerIds; @override + List get activePointerIds; /// The current position of the pointer - Point? get pointerPosition; @override + Point? get pointerPosition; /// The current width of the pen - double get selectedWidth; @override + double get selectedWidth; /// How much the widget is scaled at the moment. /// /// Can be used if zoom functionality is needed /// (e.g. through InteractiveViewer) so that the pen width remains the same. - double get scaleFactor; @override + double get scaleFactor; /// The current tolerance of simplification, in pixels. /// /// Lines will be simplified when they are finished. A value of 0 (default) /// will mean no simplification. + @override double get simplificationTolerance; + + /// Create a copy of ScribbleState + /// with the given fields replaced by the non-null parameter values. @override - @JsonKey(ignore: true) + @JsonKey(includeFromJson: false, includeToJson: false) _$$ErasingImplCopyWith<_$ErasingImpl> get copyWith => throw _privateConstructorUsedError; } diff --git a/lib/src/view/state/scribble.state.g.dart b/lib/src/view/state/scribble.state.g.dart index 115c817..a76c097 100644 --- a/lib/src/view/state/scribble.state.g.dart +++ b/lib/src/view/state/scribble.state.g.dart @@ -16,13 +16,13 @@ _$DrawingImpl _$$DrawingImplFromJson(Map json) => _$ScribblePointerModeEnumMap, json['allowedPointersMode']) ?? ScribblePointerMode.all, activePointerIds: (json['activePointerIds'] as List?) - ?.map((e) => e as int) + ?.map((e) => (e as num).toInt()) .toList() ?? const [], pointerPosition: json['pointerPosition'] == null ? null : Point.fromJson(json['pointerPosition'] as Map), - selectedColor: json['selectedColor'] as int? ?? 0xFF000000, + selectedColor: (json['selectedColor'] as num?)?.toInt() ?? 0xFF000000, selectedWidth: (json['selectedWidth'] as num?)?.toDouble() ?? 5, scaleFactor: (json['scaleFactor'] as num?)?.toDouble() ?? 1, simplificationTolerance: @@ -59,7 +59,7 @@ _$ErasingImpl _$$ErasingImplFromJson(Map json) => _$ScribblePointerModeEnumMap, json['allowedPointersMode']) ?? ScribblePointerMode.all, activePointerIds: (json['activePointerIds'] as List?) - ?.map((e) => e as int) + ?.map((e) => (e as num).toInt()) .toList() ?? const [], pointerPosition: json['pointerPosition'] == null diff --git a/melos.yaml b/melos.yaml index 1428d21..a788465 100644 --- a/melos.yaml +++ b/melos.yaml @@ -49,16 +49,16 @@ scripts: run: melos run coverage:select --no-select description: Generate coverage for all packages. - build_runner: - run: flutter pub run build_runner build --delete-conflicting-outputs + generate:select: + description: Run code generation for selected packages. + run: dart run build_runner build --delete-conflicting-outputs exec: - failFast: false + concurrency: 1 + failFast: true packageFilters: - dependsOn: build_runner - description: Run `flutter pub run build_runner build --delete-conflicting-outputs` in all packages. + dependsOn: + - build_runner - fix: - run: dart fix --apply - exec: - failFast: true - description: Run `dart fix --apply` in all packages. \ No newline at end of file + generate: + description: Run code generation for all packages. + run: melos run generate:select --no-select \ No newline at end of file diff --git a/pubspec.yaml b/pubspec.yaml index fc35215..08ef6a8 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -12,7 +12,7 @@ environment: dependencies: flutter: sdk: flutter - freezed_annotation: ^2.4.1 + freezed_annotation: ">=2.4.1 <3.0.0" perfect_freehand: ^2.3.2 simpli: ^0.1.1 value_notifier_tools: ^0.1.2 @@ -22,9 +22,9 @@ dev_dependencies: flutter_test: sdk: flutter freezed: ^2.4.7 - json_serializable: ^6.7.1 - lintervention: ^0.1.1 - melos: ">=5.2.1 <7.0.0" + json_serializable: ^6.9.5 + lintervention: ^0.3.1 + melos: ^6.3.3 mocktail: ^1.0.3 flutter: diff --git a/test/src/domain/model/sketch/sketch_test.dart b/test/src/domain/model/sketch/sketch_test.dart index 6f546b4..6ce00e4 100644 --- a/test/src/domain/model/sketch/sketch_test.dart +++ b/test/src/domain/model/sketch/sketch_test.dart @@ -11,15 +11,21 @@ void main() { final sketch = Sketch.fromJson(json); expect(sketch.lines.length, 14); expect(sketch.lines.where((l) => l.color == 0xFF000000).length, 10); - expect(sketch.lines.where((l) => l.color == Colors.red.value).length, 1); expect( - sketch.lines.where((l) => l.color == Colors.green.value).length, - 1, + sketch.lines.where((l) => l.color == Colors.red.toARGB32()), + hasLength(1), ); - expect(sketch.lines.where((l) => l.color == Colors.blue.value).length, 1); expect( - sketch.lines.where((l) => l.color == Colors.yellow.value).length, - 1, + sketch.lines.where((l) => l.color == Colors.green.toARGB32()), + hasLength(1), + ); + expect( + sketch.lines.where((l) => l.color == Colors.blue.toARGB32()), + hasLength(1), + ); + expect( + sketch.lines.where((l) => l.color == Colors.yellow.toARGB32()), + hasLength(1), ); }); });