flutter_flutter/packages/flutter/test/rendering/table_border_test.dart
ParkJuneWoo 35b5342f9c
Add non uniform TableBorder (#175773)
Re-implementation of PR #172441

<!--
Thanks for filing a pull request!
Reviewers are typically assigned within a week of filing a request.
To learn more about code review, see our documentation on Tree Hygiene:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
-->

## Add non-uniform border radius support to TableBorder
This PR extends TableBorder to support border radius even when the outer
border sides have different widths but the same color, similar to the
non-uniform border support added to Border in #121921.

### Changes
- Enhanced border rendering logic: TableBorder can now apply border
radius when outer border sides have uniform colors but non-uniform
widths/styles
- New helper methods: Added `outerBorderIsUniform` property and
`distinctVisibleOuterColors()` method to determine border uniformity
- Optimized paint method: Refactored the paint logic to handle three
scenarios:
  1. Fully uniform borders (existing optimized path)
2. Outer borders with uniform colors but non-uniform widths (new
non-uniform border radius support)
3. Completely non-uniform borders (fallback to standard border painting)

### Before/After
- Before: TableBorder with border radius required all sides (including
inner borders) to be completely identical.
- After: TableBorder with border radius only requires outer border
colors to be the same, allowing different widths per side.

### Example Usage
```dart
TableBorder(
  top: BorderSide(color: Colors.blue, width: 3.0),
  right: BorderSide(color: Colors.blue, width: 1.0),  // Different width
  bottom: BorderSide(color: Colors.blue, width: 2.0), // Different width  
  left: BorderSide.none,                              // No border
  horizontalInside: BorderSide(color: Colors.red, width: 1.0), // Different color OK
  verticalInside: BorderSide(color: Colors.red, width: 1.0),   // Different color OK
  borderRadius: BorderRadius.circular(8), //  Now works!
)
```
Fixes: #173193


## Pre-launch Checklist

- [x] I read the [Contributor Guide] and followed the process outlined
there for submitting PRs.
- [x] I read the [Tree Hygiene] wiki page, which explains my
responsibilities.
- [x] I read and followed the [Flutter Style Guide], including [Features
we expect every widget to implement].
- [x] I signed the [CLA].
- [x] I listed at least one issue that this PR fixes in the description
above.
- [x] I updated/added relevant documentation (doc comments with `///`).
- [x] I added new tests to check the change I am making, or this PR is
[test-exempt].
- [x] I followed the [breaking change policy] and added [Data Driven
Fixes] where supported.
- [x] All existing and new tests are passing.

<!-- Links -->
[Contributor Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#overview
[Tree Hygiene]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md
[test-exempt]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#tests
[Flutter Style Guide]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md
[Features we expect every widget to implement]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Style-guide-for-Flutter-repo.md#features-we-expect-every-widget-to-implement
[CLA]: https://cla.developers.google.com/
[flutter/tests]: https://github.com/flutter/tests
[breaking change policy]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Tree-hygiene.md#handling-breaking-changes
[Discord]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Chat.md
[Data Driven Fixes]:
https://github.com/flutter/flutter/blob/main/docs/contributing/Data-driven-Fixes.md
2025-09-24 01:59:24 +00:00

199 lines
7.9 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/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
test('TableBorder constructor', () {
const TableBorder border1 = TableBorder(
left: BorderSide(),
right: BorderSide(color: Color(0xFF00FF00)),
verticalInside: BorderSide(),
);
expect(border1.top, BorderSide.none);
expect(border1.right, const BorderSide(color: Color(0xFF00FF00)));
expect(border1.bottom, BorderSide.none);
expect(border1.left, const BorderSide());
expect(border1.horizontalInside, BorderSide.none);
expect(border1.verticalInside, const BorderSide());
expect(border1.dimensions, const EdgeInsets.symmetric(horizontal: 1.0));
expect(border1.isUniform, isFalse);
expect(
border1.scale(2.0),
const TableBorder(
left: BorderSide(width: 2.0),
right: BorderSide(width: 2.0, color: Color(0xFF00FF00)),
verticalInside: BorderSide(width: 2.0),
),
);
});
test('TableBorder.all constructor', () {
final TableBorder border2 = TableBorder.all(width: 2.0, color: const Color(0xFF00FFFF));
expect(border2.top, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.right, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.bottom, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.left, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.horizontalInside, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.verticalInside, const BorderSide(width: 2.0, color: Color(0xFF00FFFF)));
expect(border2.dimensions, const EdgeInsets.symmetric(horizontal: 2.0, vertical: 2.0));
expect(border2.isUniform, isTrue);
expect(border2.scale(0.5), TableBorder.all(color: const Color(0xFF00FFFF)));
});
test('TableBorder.symmetric constructor', () {
const TableBorder border3 = TableBorder.symmetric(
inside: BorderSide(width: 3.0),
outside: BorderSide(color: Color(0xFFFF0000)),
);
expect(border3.top, const BorderSide(color: Color(0xFFFF0000)));
expect(border3.right, const BorderSide(color: Color(0xFFFF0000)));
expect(border3.bottom, const BorderSide(color: Color(0xFFFF0000)));
expect(border3.left, const BorderSide(color: Color(0xFFFF0000)));
expect(border3.horizontalInside, const BorderSide(width: 3.0));
expect(border3.verticalInside, const BorderSide(width: 3.0));
expect(border3.dimensions, const EdgeInsets.symmetric(horizontal: 1.0, vertical: 1.0));
expect(border3.isUniform, isFalse);
expect(
border3.scale(0.0),
const TableBorder.symmetric(
outside: BorderSide(width: 0.0, color: Color(0xFFFF0000), style: BorderStyle.none),
),
);
});
test('TableBorder.lerp', () {
const BorderSide side1 = BorderSide(color: Color(0x00000001));
const BorderSide side2 = BorderSide(width: 2.0, color: Color(0x00000002));
const BorderSide side3 = BorderSide(width: 3.0, color: Color(0x00000003));
const BorderSide side4 = BorderSide(width: 4.0, color: Color(0x00000004));
const BorderSide side5 = BorderSide(width: 5.0, color: Color(0x00000005));
const BorderSide side6 = BorderSide(width: 6.0, color: Color(0x00000006));
const TableBorder tableA = TableBorder(
top: side1,
right: side2,
bottom: side3,
left: side4,
horizontalInside: side5,
verticalInside: side6,
);
expect(tableA.isUniform, isFalse);
expect(tableA.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0));
final TableBorder tableB = TableBorder(
top: side1.scale(2.0),
right: side2.scale(2.0),
bottom: side3.scale(2.0),
left: side4.scale(2.0),
horizontalInside: side5.scale(2.0),
verticalInside: side6.scale(2.0),
);
expect(tableB.isUniform, isFalse);
expect(tableB.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0) * 2.0);
final TableBorder tableC = TableBorder(
top: side1.scale(3.0),
right: side2.scale(3.0),
bottom: side3.scale(3.0),
left: side4.scale(3.0),
horizontalInside: side5.scale(3.0),
verticalInside: side6.scale(3.0),
);
expect(tableC.isUniform, isFalse);
expect(tableC.dimensions, const EdgeInsets.fromLTRB(4.0, 1.0, 2.0, 3.0) * 3.0);
expect(TableBorder.lerp(tableA, tableC, 0.5), tableB);
expect(TableBorder.lerp(tableA, tableB, 2.0), tableC);
expect(TableBorder.lerp(tableB, tableC, -1.0), tableA);
expect(TableBorder.lerp(tableA, tableC, 0.9195)!.isUniform, isFalse);
expect(
TableBorder.lerp(tableA, tableC, 0.9195)!.dimensions,
EdgeInsets.lerp(tableA.dimensions, tableC.dimensions, 0.9195),
);
});
test('TableBorder.lerp identical a,b', () {
expect(TableBorder.lerp(null, null, 0), null);
const TableBorder border = TableBorder();
expect(identical(TableBorder.lerp(border, border, 0.5), border), true);
});
test('TableBorder.lerp with nulls', () {
final TableBorder table2 = TableBorder.all(width: 2.0);
final TableBorder table1 = TableBorder.all();
expect(TableBorder.lerp(table2, null, 0.5), table1);
expect(TableBorder.lerp(null, table2, 0.5), table1);
expect(TableBorder.lerp(null, null, 0.5), null);
});
test('TableBorder Object API', () {
expect(const TableBorder(), isNot(1.0));
expect(
const TableBorder().hashCode,
isNot(const TableBorder(top: BorderSide(width: 0.0)).hashCode),
);
});
test('TableBorder Object API', () {
final String none = BorderSide.none.toString();
final String zeroRadius = BorderRadius.zero.toString();
expect(
const TableBorder().toString(),
'TableBorder($none, $none, $none, $none, $none, $none, $zeroRadius)',
);
});
test('TableBorder.all with a borderRadius', () {
final TableBorder tableA = TableBorder.all(
borderRadius: const BorderRadius.all(Radius.circular(8.0)),
);
expect(tableA.borderRadius, const BorderRadius.all(Radius.circular(8.0)));
});
test('TableBorder outer border uniformity', () {
const TableBorder uniformOuter = TableBorder(
top: BorderSide(width: 2.0),
right: BorderSide(width: 2.0),
bottom: BorderSide(width: 2.0),
left: BorderSide(width: 2.0),
horizontalInside: BorderSide(color: Color(0xFF0000FF)),
verticalInside: BorderSide(color: Color(0xFF0000FF)),
);
expect(uniformOuter.isUniform, isFalse);
final BorderSide topSide = uniformOuter.top;
expect(uniformOuter.right, equals(topSide));
expect(uniformOuter.bottom, equals(topSide));
expect(uniformOuter.left, equals(topSide));
const TableBorder nonUniformOuter = TableBorder(
top: BorderSide(width: 2.0),
right: BorderSide(color: Color(0xFF00FF00), width: 2.0),
bottom: BorderSide(width: 2.0),
left: BorderSide(width: 2.0),
);
expect(nonUniformOuter.right, isNot(equals(nonUniformOuter.top)));
});
test('TableBorder with non-uniform widths but uniform colors applies border radius', () {
const TableBorder borderWithRadius = TableBorder(
top: BorderSide(width: 3.0, color: Color(0xFF0000FF)),
right: BorderSide(color: Color(0xFF0000FF)),
bottom: BorderSide(width: 2.0, color: Color(0xFF0000FF)),
left: BorderSide(width: 1.5, color: Color(0xFF0000FF)),
borderRadius: BorderRadius.all(Radius.circular(8.0)),
);
expect(borderWithRadius.top.width, isNot(equals(borderWithRadius.bottom.width)));
expect(borderWithRadius.left.width, isNot(equals(borderWithRadius.right.width)));
final Color topColor = borderWithRadius.top.color;
expect(borderWithRadius.right.color, equals(topColor));
expect(borderWithRadius.bottom.color, equals(topColor));
expect(borderWithRadius.left.color, equals(topColor));
expect(borderWithRadius.borderRadius, const BorderRadius.all(Radius.circular(8.0)));
});
}