| // Copyright 2013 The Flutter Authors. All rights reserved. | |
| // Use of this source code is governed by a BSD-style license that can be | |
| // found in the LICENSE file. | |
| // @dart = 2.6 | |
| import 'package:test/test.dart'; | |
| import 'dart:js_util' as js_util; | |
| import 'dart:html' as html; | |
| import 'package:ui/ui.dart' hide window; | |
| import 'package:ui/src/engine.dart'; | |
| import 'matchers.dart'; | |
| void main() { | |
| test('Should have no subpaths when created', () { | |
| final SurfacePath path = SurfacePath(); | |
| expect(path.subpaths.length, 0); | |
| }); | |
| test('LineTo should add command', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.moveTo(5.0, 10.0); | |
| path.lineTo(20.0, 40.0); | |
| path.lineTo(30.0, 50.0); | |
| expect(path.subpaths.length, 1); | |
| expect(path.subpaths[0].currentX, 30.0); | |
| expect(path.subpaths[0].currentY, 50.0); | |
| }); | |
| test('LineTo should add moveTo 0,0 when first call to Path API', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.lineTo(20.0, 40.0); | |
| expect(path.subpaths.length, 1); | |
| expect(path.subpaths[0].currentX, 20.0); | |
| expect(path.subpaths[0].currentY, 40.0); | |
| }); | |
| test('relativeLineTo should increments currentX', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.moveTo(5.0, 10.0); | |
| path.lineTo(20.0, 40.0); | |
| path.relativeLineTo(5.0, 5.0); | |
| expect(path.subpaths.length, 1); | |
| expect(path.subpaths[0].currentX, 25.0); | |
| expect(path.subpaths[0].currentY, 45.0); | |
| }); | |
| test('Should allow calling relativeLineTo before moveTo', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.relativeLineTo(5.0, 5.0); | |
| path.moveTo(5.0, 10.0); | |
| expect(path.subpaths.length, 2); | |
| expect(path.subpaths[0].currentX, 5.0); | |
| expect(path.subpaths[0].currentY, 5.0); | |
| expect(path.subpaths[1].currentX, 5.0); | |
| expect(path.subpaths[1].currentY, 10.0); | |
| }); | |
| test('Should allow relativeLineTo after reset', () { | |
| final SurfacePath path = SurfacePath(); | |
| final Path subPath = Path(); | |
| subPath.moveTo(50.0, 60.0); | |
| subPath.lineTo(200.0, 200.0); | |
| path.extendWithPath(subPath, const Offset(0.0, 0.0)); | |
| path.reset(); | |
| path.relativeLineTo(5.0, 5.0); | |
| expect(path.subpaths.length, 1); | |
| expect(path.subpaths[0].currentX, 5.0); | |
| expect(path.subpaths[0].currentY, 5.0); | |
| }); | |
| test('Should detect rectangular path', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0)); | |
| expect(path.webOnlyPathAsRect, const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0)); | |
| }); | |
| test('Should detect non rectangular path if empty', () { | |
| final SurfacePath path = SurfacePath(); | |
| expect(path.webOnlyPathAsRect, null); | |
| }); | |
| test('Should detect non rectangular path if there are multiple subpaths', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0)); | |
| path.addRect(const Rect.fromLTWH(5.0, 6.0, 7.0, 8.0)); | |
| expect(path.webOnlyPathAsRect, null); | |
| }); | |
| test('Should detect rounded rectangular path', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.addRRect(RRect.fromRectAndRadius( | |
| const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), const Radius.circular(2.0))); | |
| expect( | |
| path.webOnlyPathAsRoundedRect, | |
| RRect.fromRectAndRadius(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), | |
| const Radius.circular(2.0))); | |
| }); | |
| test('Should detect non rounded rectangular path if empty', () { | |
| final SurfacePath path = SurfacePath(); | |
| expect(path.webOnlyPathAsRoundedRect, null); | |
| }); | |
| test('Should detect rectangular path is not round', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.addRect(const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0)); | |
| expect(path.webOnlyPathAsRoundedRect, null); | |
| }); | |
| test( | |
| 'Should detect non rounded rectangular path if there are ' | |
| 'multiple subpaths', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.addRRect(RRect.fromRectAndRadius( | |
| const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), const Radius.circular(2.0))); | |
| path.addRRect(RRect.fromRectAndRadius( | |
| const Rect.fromLTWH(1.0, 2.0, 3.0, 4.0), const Radius.circular(2.0))); | |
| expect(path.webOnlyPathAsRoundedRect, null); | |
| }); | |
| test('Should compute bounds as empty for empty and moveTo only path', () { | |
| final Path emptyPath = Path(); | |
| expect(emptyPath.getBounds(), Rect.zero); | |
| final SurfacePath path = SurfacePath(); | |
| path.moveTo(50, 60); | |
| expect(path.getBounds(), const Rect.fromLTRB(50, 60, 50, 60)); | |
| }); | |
| test('Should compute bounds for multiple addRect calls', () { | |
| final Path emptyPath = Path(); | |
| expect(emptyPath.getBounds(), Rect.zero); | |
| final SurfacePath path = SurfacePath(); | |
| path.addRect(Rect.fromLTWH(0, 0, 270, 45)); | |
| path.addRect(Rect.fromLTWH(134.5, 0, 1, 45)); | |
| expect(path.getBounds(), const Rect.fromLTRB(0, 0, 270, 45)); | |
| }); | |
| test('Should compute bounds for lines', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.moveTo(25, 30); | |
| path.lineTo(100, 200); | |
| expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200)); | |
| final SurfacePath path2 = SurfacePath(); | |
| path2.moveTo(250, 300); | |
| path2.lineTo(50, 60); | |
| expect(path2.getBounds(), const Rect.fromLTRB(50, 60, 250, 300)); | |
| }); | |
| test('Should compute bounds for quadraticBezierTo', () { | |
| final SurfacePath path1 = SurfacePath(); | |
| path1.moveTo(285.2, 682.1); | |
| path1.quadraticBezierTo(432.0, 431.4, 594.9, 681.2); | |
| expect( | |
| path1.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(285.2, 556.5, 594.9, 682.1))); | |
| // Control point below start , end. | |
| final SurfacePath path2 = SurfacePath(); | |
| path2.moveTo(285.2, 682.1); | |
| path2.quadraticBezierTo(447.4, 946.8, 594.9, 681.2); | |
| expect( | |
| path2.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(285.2, 681.2, 594.9, 814.2))); | |
| // Control point to the right of end point. | |
| final SurfacePath path3 = SurfacePath(); | |
| path3.moveTo(468.3, 685.6); | |
| path3.quadraticBezierTo(644.7, 555.2, 594.9, 681.2); | |
| expect( | |
| path3.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(468.3, 619.3, 605.9, 685.6))); | |
| }); | |
| test('Should compute bounds for cubicTo', () { | |
| final SurfacePath path1 = SurfacePath(); | |
| path1.moveTo(220, 300); | |
| path1.cubicTo(230, 120, 400, 125, 410, 280); | |
| expect( | |
| path1.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 164.3, 410.0, 300.0))); | |
| // control point 1 to the right of control point 2 | |
| final SurfacePath path2 = SurfacePath(); | |
| path2.moveTo(220, 300); | |
| path2.cubicTo(564.2, 13.7, 400.0, 125.0, 410.0, 280.0); | |
| expect( | |
| path2.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 122.8, 440.5, 300.0))); | |
| // control point 1 to the right of control point 2 inflection | |
| final SurfacePath path3 = SurfacePath(); | |
| path3.moveTo(220, 300); | |
| path3.cubicTo(839.8, 67.9, 400.0, 125.0, 410.0, 280.0); | |
| expect( | |
| path3.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 144.5, 552.1, 300.0))); | |
| // control point 1 below and between start and end points | |
| final SurfacePath path4 = SurfacePath(); | |
| path4.moveTo(220.0, 300.0); | |
| path4.cubicTo(354.8, 388.3, 400.0, 125.0, 410.0, 280.0); | |
| expect( | |
| path4.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 230.0, 410.0, 318.6))); | |
| // control points inverted below | |
| final SurfacePath path5 = SurfacePath(); | |
| path5.moveTo(220.0, 300.0); | |
| path5.cubicTo(366.5, 487.3, 256.4, 489.9, 410.0, 280.0); | |
| expect( | |
| path5.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 280.0, 410.0, 439.0))); | |
| // control points inverted below wide | |
| final SurfacePath path6 = SurfacePath(); | |
| path6.moveTo(220.0, 300.0); | |
| path6.cubicTo(496.1, 485.5, 121.4, 491.6, 410.0, 280.0); | |
| expect( | |
| path6.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 280.0, 410.0, 439.0))); | |
| // control point 2 and end point swapped | |
| final SurfacePath path7 = SurfacePath(); | |
| path7.moveTo(220.0, 300.0); | |
| path7.cubicTo(230.0, 120.0, 394.5, 296.1, 382.3, 124.1); | |
| expect( | |
| path7.getBounds(), | |
| within<Rect>( | |
| distance: 0.1, | |
| from: const Rect.fromLTRB(220.0, 124.1, 382.9, 300.0))); | |
| }); | |
| // Regression test for https://github.com/flutter/flutter/issues/46813. | |
| test('Should deep copy path', () { | |
| final SurfacePath path = SurfacePath(); | |
| path.moveTo(25, 30); | |
| path.lineTo(100, 200); | |
| expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200)); | |
| final SurfacePath path2 = SurfacePath.from(path); | |
| path2.lineTo(250, 300); | |
| expect(path2.getBounds(), const Rect.fromLTRB(25, 30, 250, 300)); | |
| // Expect original path to stay the same. | |
| expect(path.getBounds(), const Rect.fromLTRB(25, 30, 100, 200)); | |
| }); | |
| // Regression test for https://github.com/flutter/flutter/issues/44470 | |
| test('Should handle contains for devicepixelratio != 1.0', () { | |
| js_util.setProperty(html.window, 'devicePixelRatio', 4.0); | |
| window.debugOverrideDevicePixelRatio(4.0); | |
| final path = Path() | |
| ..moveTo(50, 0) | |
| ..lineTo(100, 100) | |
| ..lineTo(0, 100) | |
| ..lineTo(50, 0) | |
| ..close(); | |
| expect(path.contains(Offset(50, 50)), isTrue); | |
| js_util.setProperty(html.window, 'devicePixelRatio', 1.0); | |
| window.debugOverrideDevicePixelRatio(1.0); | |
| // TODO: Investigate failure on CI. Locally this passes. | |
| // [Exception... "Failure" nsresult: "0x80004005 (NS_ERROR_FAILURE)" | |
| }, skip: browserEngine == BrowserEngine.firefox); | |
| // Path contains should handle case where invalid RRect with large | |
| // corner radius is used for hit test. Use case is a RenderPhysicalShape | |
| // with a clipper that contains RRect of width/height 50 but corner radius | |
| // of 100. | |
| // | |
| // Regression test for https://github.com/flutter/flutter/issues/48887 | |
| test('Should hit test correctly for malformed rrect', () { | |
| // Correctly formed rrect. | |
| final path1 = Path() | |
| ..addRRect(RRect.fromLTRBR(50, 50, 100, 100, Radius.circular(20))); | |
| expect(path1.contains(Offset(75, 75)), isTrue); | |
| expect(path1.contains(Offset(52, 75)), isTrue); | |
| expect(path1.contains(Offset(50, 50)), isFalse); | |
| expect(path1.contains(Offset(100, 50)), isFalse); | |
| expect(path1.contains(Offset(100, 100)), isFalse); | |
| expect(path1.contains(Offset(50, 100)), isFalse); | |
| final path2 = Path() | |
| ..addRRect(RRect.fromLTRBR(50, 50, 100, 100, Radius.circular(100))); | |
| expect(path2.contains(Offset(75, 75)), isTrue); | |
| expect(path2.contains(Offset(52, 75)), isTrue); | |
| expect(path2.contains(Offset(50, 50)), isFalse); | |
| expect(path2.contains(Offset(100, 50)), isFalse); | |
| expect(path2.contains(Offset(100, 100)), isFalse); | |
| expect(path2.contains(Offset(50, 100)), isFalse); | |
| }); | |
| } |