549 lines
19 KiB
Dart
549 lines
19 KiB
Dart
// Copyright 2014 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.
|
|
|
|
import 'package:flutter/widgets.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
class TestCanvas implements Canvas {
|
|
final List<Invocation> invocations = <Invocation>[];
|
|
|
|
@override
|
|
void noSuchMethod(Invocation invocation) {
|
|
invocations.add(invocation);
|
|
}
|
|
}
|
|
|
|
void main() {
|
|
test('Border.fromBorderSide constructor', () {
|
|
const side = BorderSide();
|
|
const border = Border.fromBorderSide(side);
|
|
expect(border.left, same(side));
|
|
expect(border.top, same(side));
|
|
expect(border.right, same(side));
|
|
expect(border.bottom, same(side));
|
|
});
|
|
|
|
test('Border.symmetric constructor', () {
|
|
const side1 = BorderSide(color: Color(0xFFFFFFFF));
|
|
const side2 = BorderSide();
|
|
const border = Border.symmetric(vertical: side1, horizontal: side2);
|
|
expect(border.left, same(side1));
|
|
expect(border.top, same(side2));
|
|
expect(border.right, same(side1));
|
|
expect(border.bottom, same(side2));
|
|
});
|
|
|
|
test('Border.merge', () {
|
|
const magenta3 = BorderSide(color: Color(0xFFFF00FF), width: 3.0);
|
|
const magenta6 = BorderSide(color: Color(0xFFFF00FF), width: 6.0);
|
|
const yellow2 = BorderSide(color: Color(0xFFFFFF00), width: 2.0);
|
|
const yellowNone0 = BorderSide(color: Color(0xFFFFFF00), width: 0.0, style: BorderStyle.none);
|
|
expect(
|
|
Border.merge(const Border(top: yellow2), const Border(right: magenta3)),
|
|
const Border(top: yellow2, right: magenta3),
|
|
);
|
|
expect(
|
|
Border.merge(const Border(bottom: magenta3), const Border(bottom: magenta3)),
|
|
const Border(bottom: magenta6),
|
|
);
|
|
expect(
|
|
Border.merge(const Border(left: magenta3, right: yellowNone0), const Border(right: yellow2)),
|
|
const Border(left: magenta3, right: yellow2),
|
|
);
|
|
expect(Border.merge(const Border(), const Border()), const Border());
|
|
expect(
|
|
() => Border.merge(const Border(left: magenta3), const Border(left: yellow2)),
|
|
throwsAssertionError,
|
|
);
|
|
});
|
|
|
|
test('Border.add', () {
|
|
const magenta3 = BorderSide(color: Color(0xFFFF00FF), width: 3.0);
|
|
const magenta6 = BorderSide(color: Color(0xFFFF00FF), width: 6.0);
|
|
const yellow2 = BorderSide(color: Color(0xFFFFFF00), width: 2.0);
|
|
const yellowNone0 = BorderSide(color: Color(0xFFFFFF00), width: 0.0, style: BorderStyle.none);
|
|
expect(
|
|
const Border(top: yellow2) + const Border(right: magenta3),
|
|
const Border(top: yellow2, right: magenta3),
|
|
);
|
|
expect(
|
|
const Border(bottom: magenta3) + const Border(bottom: magenta3),
|
|
const Border(bottom: magenta6),
|
|
);
|
|
expect(
|
|
const Border(left: magenta3, right: yellowNone0) + const Border(right: yellow2),
|
|
const Border(left: magenta3, right: yellow2),
|
|
);
|
|
expect(const Border() + const Border(), const Border());
|
|
expect(
|
|
const Border(left: magenta3) + const Border(left: yellow2),
|
|
isNot(isA<Border>()), // see shape_border_test.dart for better tests of this case
|
|
);
|
|
const b3 = Border(top: magenta3);
|
|
const b6 = Border(top: magenta6);
|
|
expect(b3 + b3, b6);
|
|
const b0 = Border(top: yellowNone0);
|
|
const bZ = Border();
|
|
expect(b0 + b0, bZ);
|
|
expect(bZ + bZ, bZ);
|
|
expect(b0 + bZ, bZ);
|
|
expect(bZ + b0, bZ);
|
|
});
|
|
|
|
test('Border.scale', () {
|
|
const magenta3 = BorderSide(color: Color(0xFFFF00FF), width: 3.0);
|
|
const magenta6 = BorderSide(color: Color(0xFFFF00FF), width: 6.0);
|
|
const yellow2 = BorderSide(color: Color(0xFFFFFF00), width: 2.0);
|
|
const yellowNone0 = BorderSide(color: Color(0xFFFFFF00), width: 0.0, style: BorderStyle.none);
|
|
const b3 = Border(left: magenta3);
|
|
const b6 = Border(left: magenta6);
|
|
expect(b3.scale(2.0), b6);
|
|
const bY0 = Border(top: yellowNone0);
|
|
expect(bY0.scale(3.0), bY0);
|
|
const bY2 = Border(top: yellow2);
|
|
expect(bY2.scale(0.0), bY0);
|
|
});
|
|
|
|
test('Border.dimensions', () {
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(width: 2.0),
|
|
top: BorderSide(width: 3.0),
|
|
bottom: BorderSide(width: 5.0),
|
|
right: BorderSide(width: 7.0),
|
|
).dimensions,
|
|
const EdgeInsets.fromLTRB(2.0, 3.0, 7.0, 5.0),
|
|
);
|
|
});
|
|
|
|
test('Border.isUniform', () {
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(width: 3.0),
|
|
top: BorderSide(width: 3.0),
|
|
right: BorderSide(width: 3.0),
|
|
bottom: BorderSide(width: 3.1),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(width: 3.0),
|
|
top: BorderSide(width: 3.0),
|
|
right: BorderSide(width: 3.0),
|
|
bottom: BorderSide(width: 3.0),
|
|
).isUniform,
|
|
true,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(color: Color(0xFFFFFFFE)),
|
|
top: BorderSide(color: Color(0xFFFFFFFF)),
|
|
right: BorderSide(color: Color(0xFFFFFFFF)),
|
|
bottom: BorderSide(color: Color(0xFFFFFFFF)),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(color: Color(0xFFFFFFFF)),
|
|
top: BorderSide(color: Color(0xFFFFFFFF)),
|
|
right: BorderSide(color: Color(0xFFFFFFFF)),
|
|
bottom: BorderSide(color: Color(0xFFFFFFFF)),
|
|
).isUniform,
|
|
true,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(style: BorderStyle.none),
|
|
top: BorderSide(style: BorderStyle.none),
|
|
right: BorderSide(style: BorderStyle.none),
|
|
bottom: BorderSide(width: 0.0),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(style: BorderStyle.none),
|
|
top: BorderSide(style: BorderStyle.none),
|
|
right: BorderSide(style: BorderStyle.none),
|
|
bottom: BorderSide(width: 0.0),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(style: BorderStyle.none),
|
|
top: BorderSide(style: BorderStyle.none),
|
|
right: BorderSide(style: BorderStyle.none),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(
|
|
const Border(
|
|
left: BorderSide(),
|
|
top: BorderSide(strokeAlign: BorderSide.strokeAlignCenter),
|
|
right: BorderSide(strokeAlign: BorderSide.strokeAlignOutside),
|
|
).isUniform,
|
|
false,
|
|
);
|
|
expect(const Border().isUniform, true);
|
|
expect(const Border().isUniform, true);
|
|
});
|
|
|
|
test('Border.lerp', () {
|
|
const visualWithTop10 = Border(top: BorderSide(width: 10.0));
|
|
const atMinus100 = Border(left: BorderSide(width: 0.0), right: BorderSide(width: 300.0));
|
|
const at0 = Border(left: BorderSide(width: 100.0), right: BorderSide(width: 200.0));
|
|
const at25 = Border(left: BorderSide(width: 125.0), right: BorderSide(width: 175.0));
|
|
const at75 = Border(left: BorderSide(width: 175.0), right: BorderSide(width: 125.0));
|
|
const at100 = Border(left: BorderSide(width: 200.0), right: BorderSide(width: 100.0));
|
|
const at200 = Border(left: BorderSide(width: 300.0), right: BorderSide(width: 0.0));
|
|
|
|
expect(Border.lerp(null, null, -1.0), null);
|
|
expect(Border.lerp(visualWithTop10, null, -1.0), const Border(top: BorderSide(width: 20.0)));
|
|
expect(Border.lerp(null, visualWithTop10, -1.0), const Border());
|
|
expect(Border.lerp(at0, at100, -1.0), atMinus100);
|
|
|
|
expect(Border.lerp(null, null, 0.0), null);
|
|
expect(Border.lerp(visualWithTop10, null, 0.0), const Border(top: BorderSide(width: 10.0)));
|
|
expect(Border.lerp(null, visualWithTop10, 0.0), const Border());
|
|
expect(Border.lerp(at0, at100, 0.0), at0);
|
|
|
|
expect(Border.lerp(null, null, 0.25), null);
|
|
expect(Border.lerp(visualWithTop10, null, 0.25), const Border(top: BorderSide(width: 7.5)));
|
|
expect(Border.lerp(null, visualWithTop10, 0.25), const Border(top: BorderSide(width: 2.5)));
|
|
expect(Border.lerp(at0, at100, 0.25), at25);
|
|
|
|
expect(Border.lerp(null, null, 0.75), null);
|
|
expect(Border.lerp(visualWithTop10, null, 0.75), const Border(top: BorderSide(width: 2.5)));
|
|
expect(Border.lerp(null, visualWithTop10, 0.75), const Border(top: BorderSide(width: 7.5)));
|
|
expect(Border.lerp(at0, at100, 0.75), at75);
|
|
|
|
expect(Border.lerp(null, null, 1.0), null);
|
|
expect(Border.lerp(visualWithTop10, null, 1.0), const Border());
|
|
expect(Border.lerp(null, visualWithTop10, 1.0), const Border(top: BorderSide(width: 10.0)));
|
|
expect(Border.lerp(at0, at100, 1.0), at100);
|
|
|
|
expect(Border.lerp(null, null, 2.0), null);
|
|
expect(Border.lerp(visualWithTop10, null, 2.0), const Border());
|
|
expect(Border.lerp(null, visualWithTop10, 2.0), const Border(top: BorderSide(width: 20.0)));
|
|
expect(Border.lerp(at0, at100, 2.0), at200);
|
|
});
|
|
|
|
test('Border - throws correct exception with strokeAlign', () {
|
|
late FlutterError error;
|
|
try {
|
|
final canvas = TestCanvas();
|
|
// Border.all supports all StrokeAlign values.
|
|
// Border() supports [BorderSide.strokeAlignInside] only.
|
|
const Border(
|
|
left: BorderSide(strokeAlign: BorderSide.strokeAlignCenter, color: Color(0xff000001)),
|
|
right: BorderSide(strokeAlign: BorderSide.strokeAlignOutside, color: Color(0xff000002)),
|
|
).paint(canvas, const Rect.fromLTWH(10.0, 20.0, 30.0, 40.0));
|
|
} on FlutterError catch (e) {
|
|
error = e;
|
|
}
|
|
expect(error, isNotNull);
|
|
expect(error.diagnostics.length, 1);
|
|
expect(
|
|
error.diagnostics[0].toStringDeep(),
|
|
'A Border can only draw strokeAlign different than\nBorderSide.strokeAlignInside on borders with uniform colors.\n',
|
|
);
|
|
});
|
|
|
|
test('Border.dimension', () {
|
|
final insideBorder = Border.all(width: 10);
|
|
expect(insideBorder.dimensions, const EdgeInsets.all(10));
|
|
|
|
final centerBorder = Border.all(width: 10, strokeAlign: BorderSide.strokeAlignCenter);
|
|
expect(centerBorder.dimensions, const EdgeInsets.all(5));
|
|
|
|
final outsideBorder = Border.all(width: 10, strokeAlign: BorderSide.strokeAlignOutside);
|
|
expect(outsideBorder.dimensions, EdgeInsets.zero);
|
|
|
|
const insideSide = BorderSide(width: 10);
|
|
const insideBorderDirectional = BorderDirectional(
|
|
top: insideSide,
|
|
bottom: insideSide,
|
|
start: insideSide,
|
|
end: insideSide,
|
|
);
|
|
expect(insideBorderDirectional.dimensions, const EdgeInsetsDirectional.all(10));
|
|
|
|
const centerSide = BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter);
|
|
const centerBorderDirectional = BorderDirectional(
|
|
top: centerSide,
|
|
bottom: centerSide,
|
|
start: centerSide,
|
|
end: centerSide,
|
|
);
|
|
expect(centerBorderDirectional.dimensions, const EdgeInsetsDirectional.all(5));
|
|
|
|
const outsideSide = BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignOutside);
|
|
const outsideBorderDirectional = BorderDirectional(
|
|
top: outsideSide,
|
|
bottom: outsideSide,
|
|
start: outsideSide,
|
|
end: outsideSide,
|
|
);
|
|
expect(outsideBorderDirectional.dimensions, EdgeInsetsDirectional.zero);
|
|
|
|
const nonUniformBorder = Border(
|
|
left: BorderSide(width: 5),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
right: BorderSide(width: 15, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 20),
|
|
);
|
|
expect(nonUniformBorder.dimensions, const EdgeInsets.fromLTRB(5, 5, 0, 20));
|
|
|
|
const nonUniformBorderDirectional = BorderDirectional(
|
|
start: BorderSide(width: 5),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
end: BorderSide(width: 15, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 20),
|
|
);
|
|
expect(
|
|
nonUniformBorderDirectional.dimensions,
|
|
const EdgeInsetsDirectional.fromSTEB(5, 5, 0, 20),
|
|
);
|
|
|
|
const uniformWidthNonUniformStrokeAlignBorder = Border(
|
|
left: BorderSide(width: 10),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
right: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 10),
|
|
);
|
|
expect(
|
|
uniformWidthNonUniformStrokeAlignBorder.dimensions,
|
|
const EdgeInsets.fromLTRB(10, 5, 0, 10),
|
|
);
|
|
|
|
const uniformWidthNonUniformStrokeAlignBorderDirectional = BorderDirectional(
|
|
start: BorderSide(width: 10),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
end: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 10),
|
|
);
|
|
expect(
|
|
uniformWidthNonUniformStrokeAlignBorderDirectional.dimensions,
|
|
const EdgeInsetsDirectional.fromSTEB(10, 5, 0, 10),
|
|
);
|
|
});
|
|
|
|
testWidgets('Non-Uniform Border variations', (WidgetTester tester) async {
|
|
Widget buildWidget({
|
|
required BoxBorder border,
|
|
BorderRadius? borderRadius,
|
|
BoxShape boxShape = BoxShape.rectangle,
|
|
}) {
|
|
return Directionality(
|
|
textDirection: TextDirection.ltr,
|
|
child: DecoratedBox(
|
|
decoration: BoxDecoration(shape: boxShape, border: border, borderRadius: borderRadius),
|
|
),
|
|
);
|
|
}
|
|
|
|
// This is used to test every allowed non-uniform border combination.
|
|
const allowedBorderVariations = Border(
|
|
left: BorderSide(width: 5),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
right: BorderSide(width: 15, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 20),
|
|
);
|
|
|
|
// This falls into non-uniform border because of strokeAlign.
|
|
await tester.pumpWidget(buildWidget(border: allowedBorderVariations));
|
|
expect(
|
|
tester.takeException(),
|
|
isAssertionError,
|
|
reason: 'Border with non-uniform strokeAlign should fail.',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(border: allowedBorderVariations, borderRadius: BorderRadius.circular(25)),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(border: allowedBorderVariations, boxShape: BoxShape.circle),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(
|
|
border: const Border(
|
|
left: BorderSide(width: 5, style: BorderStyle.none),
|
|
top: BorderSide(width: 10),
|
|
right: BorderSide(width: 15),
|
|
bottom: BorderSide(width: 20),
|
|
),
|
|
borderRadius: BorderRadius.circular(25),
|
|
),
|
|
);
|
|
expect(
|
|
tester.takeException(),
|
|
isNull,
|
|
reason: 'Border with non-uniform styles should work with borderRadius.',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(
|
|
border: const Border(
|
|
left: BorderSide(width: 5, color: Color(0xff123456)),
|
|
top: BorderSide(width: 10),
|
|
right: BorderSide(width: 15),
|
|
bottom: BorderSide(width: 20),
|
|
),
|
|
borderRadius: BorderRadius.circular(20),
|
|
),
|
|
);
|
|
expect(
|
|
tester.takeException(),
|
|
isAssertionError,
|
|
reason: 'Border with non-uniform colors should fail with borderRadius.',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(
|
|
border: const Border(bottom: BorderSide(width: 0)),
|
|
borderRadius: BorderRadius.zero,
|
|
),
|
|
);
|
|
expect(
|
|
tester.takeException(),
|
|
isNull,
|
|
reason: 'Border with a side.width == 0 should work without borderRadius (hairline border).',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(
|
|
border: const Border(bottom: BorderSide(width: 0)),
|
|
borderRadius: BorderRadius.circular(40),
|
|
),
|
|
);
|
|
expect(
|
|
tester.takeException(),
|
|
isAssertionError,
|
|
reason: 'Border with width == 0 and borderRadius should fail (hairline border).',
|
|
);
|
|
|
|
// Tests for BorderDirectional.
|
|
const allowedBorderDirectionalVariations = BorderDirectional(
|
|
start: BorderSide(width: 5),
|
|
top: BorderSide(width: 10, strokeAlign: BorderSide.strokeAlignCenter),
|
|
end: BorderSide(width: 15, strokeAlign: BorderSide.strokeAlignOutside),
|
|
bottom: BorderSide(width: 20),
|
|
);
|
|
|
|
await tester.pumpWidget(buildWidget(border: allowedBorderDirectionalVariations));
|
|
expect(tester.takeException(), isAssertionError);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(
|
|
border: allowedBorderDirectionalVariations,
|
|
borderRadius: BorderRadius.circular(25),
|
|
),
|
|
);
|
|
expect(
|
|
tester.takeException(),
|
|
isNull,
|
|
reason: 'BorderDirectional should not fail with uniform styles and colors.',
|
|
);
|
|
|
|
await tester.pumpWidget(
|
|
buildWidget(border: allowedBorderDirectionalVariations, boxShape: BoxShape.circle),
|
|
);
|
|
expect(tester.takeException(), isNull);
|
|
});
|
|
|
|
test('Compound borders with differing preferPaintInteriors', () {
|
|
expect(ShapeWithInterior().preferPaintInterior, isTrue);
|
|
expect(ShapeWithoutInterior().preferPaintInterior, isFalse);
|
|
expect((ShapeWithInterior() + ShapeWithInterior()).preferPaintInterior, isTrue);
|
|
expect((ShapeWithInterior() + ShapeWithoutInterior()).preferPaintInterior, isFalse);
|
|
expect((ShapeWithoutInterior() + ShapeWithInterior()).preferPaintInterior, isFalse);
|
|
expect((ShapeWithoutInterior() + ShapeWithoutInterior()).preferPaintInterior, isFalse);
|
|
});
|
|
|
|
test('BoxBorder factories', () {
|
|
const side1 = BorderSide();
|
|
const side2 = BorderSide(width: 2);
|
|
const side3 = BorderSide(width: 3);
|
|
const side4 = BorderSide(width: 4);
|
|
expect(
|
|
BoxBorder.fromLTRB(left: side1, top: side2, right: side3, bottom: side4),
|
|
const Border(left: side1, top: side2, right: side3, bottom: side4),
|
|
);
|
|
expect(BoxBorder.all(width: 4), Border.all(width: 4));
|
|
expect(const BoxBorder.fromBorderSide(side3), const Border.fromBorderSide(side3));
|
|
expect(
|
|
const BoxBorder.symmetric(horizontal: side2, vertical: side3),
|
|
const Border.symmetric(horizontal: side2, vertical: side3),
|
|
);
|
|
expect(
|
|
BoxBorder.fromSTEB(start: side1, top: side2, end: side3, bottom: side4),
|
|
const BorderDirectional(start: side1, top: side2, end: side3, bottom: side4),
|
|
);
|
|
});
|
|
}
|
|
|
|
class ShapeWithInterior extends ShapeBorder {
|
|
@override
|
|
bool get preferPaintInterior => true;
|
|
|
|
@override
|
|
ShapeBorder scale(double t) {
|
|
return this;
|
|
}
|
|
|
|
@override
|
|
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
|
|
|
|
@override
|
|
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
|
|
return Path();
|
|
}
|
|
|
|
@override
|
|
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
|
|
return Path();
|
|
}
|
|
|
|
@override
|
|
void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) {}
|
|
|
|
@override
|
|
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
|
|
}
|
|
|
|
class ShapeWithoutInterior extends ShapeBorder {
|
|
@override
|
|
bool get preferPaintInterior => false;
|
|
|
|
@override
|
|
ShapeBorder scale(double t) {
|
|
return this;
|
|
}
|
|
|
|
@override
|
|
EdgeInsetsGeometry get dimensions => EdgeInsets.zero;
|
|
|
|
@override
|
|
Path getInnerPath(Rect rect, {TextDirection? textDirection}) {
|
|
return Path();
|
|
}
|
|
|
|
@override
|
|
Path getOuterPath(Rect rect, {TextDirection? textDirection}) {
|
|
return Path();
|
|
}
|
|
|
|
@override
|
|
void paintInterior(Canvas canvas, Rect rect, Paint paint, {TextDirection? textDirection}) {}
|
|
|
|
@override
|
|
void paint(Canvas canvas, Rect rect, {TextDirection? textDirection}) {}
|
|
}
|