mirror of
https://github.com/flutter/flutter.git
synced 2026-02-04 12:57:44 +08:00
Such that nobody `implements` it. Currently it has private APIs used by the `RenderObject` class (e.g., the dirty lists). If someone were to implement a `PipelineOwner` outside of object.dart, they will get a runtime `noSuchMethod` crash since they won't be able to provide those private APIs and RenderObjects actually expect those APIs to exist. Will be slow to update/respond due to extremely spotty internet :( ## Pre-launch Checklist - [ ] I read the [Contributor Guide] and followed the process outlined there for submitting PRs. - [ ] I read the [Tree Hygiene] wiki page, which explains my responsibilities. - [ ] I read and followed the [Flutter Style Guide], including [Features we expect every widget to implement]. - [ ] I signed the [CLA]. - [ ] I listed at least one issue that this PR fixes in the description above. - [ ] I updated/added relevant documentation (doc comments with `///`). - [ ] I added new tests to check the change I am making, or this PR is [test-exempt]. - [ ] I followed the [breaking change policy] and added [Data Driven Fixes] where supported. - [ ] All existing and new tests are passing. If you need help, consider asking for advice on the #hackers-new channel on [Discord]. <!-- 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
217 lines
7.9 KiB
Dart
217 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 'dart:ui';
|
|
|
|
import 'package:flutter/rendering.dart';
|
|
import 'package:flutter_test/flutter_test.dart';
|
|
|
|
void main() {
|
|
final RendererBinding binding = RenderingFlutterBinding.ensureInitialized();
|
|
|
|
test('Adding/removing renderviews updates renderViews getter', () {
|
|
final FlutterView flutterView = FakeFlutterView();
|
|
final RenderView view = RenderView(view: flutterView);
|
|
|
|
expect(binding.renderViews, isEmpty);
|
|
binding.addRenderView(view);
|
|
expect(binding.renderViews, contains(view));
|
|
expect(view.configuration.devicePixelRatio, flutterView.devicePixelRatio);
|
|
expect(
|
|
view.configuration.logicalConstraints,
|
|
BoxConstraints.tight(flutterView.physicalSize) / flutterView.devicePixelRatio,
|
|
);
|
|
|
|
binding.removeRenderView(view);
|
|
expect(binding.renderViews, isEmpty);
|
|
});
|
|
|
|
test('illegal add/remove renderviews', () {
|
|
final FlutterView flutterView = FakeFlutterView();
|
|
final RenderView view1 = RenderView(view: flutterView);
|
|
final RenderView view2 = RenderView(view: flutterView);
|
|
final RenderView view3 = RenderView(view: FakeFlutterView(viewId: 200));
|
|
|
|
expect(binding.renderViews, isEmpty);
|
|
binding.addRenderView(view1);
|
|
expect(binding.renderViews, contains(view1));
|
|
|
|
expect(() => binding.addRenderView(view1), throwsAssertionError);
|
|
expect(() => binding.addRenderView(view2), throwsAssertionError);
|
|
expect(() => binding.removeRenderView(view2), throwsAssertionError);
|
|
expect(() => binding.removeRenderView(view3), throwsAssertionError);
|
|
|
|
expect(binding.renderViews, contains(view1));
|
|
binding.removeRenderView(view1);
|
|
expect(binding.renderViews, isEmpty);
|
|
expect(() => binding.removeRenderView(view1), throwsAssertionError);
|
|
expect(() => binding.removeRenderView(view2), throwsAssertionError);
|
|
});
|
|
|
|
test('changing metrics updates configuration', () {
|
|
final FakeFlutterView flutterView = FakeFlutterView();
|
|
final RenderView view = RenderView(view: flutterView);
|
|
binding.addRenderView(view);
|
|
expect(view.configuration.devicePixelRatio, 2.5);
|
|
expect(view.configuration.logicalConstraints.isTight, isTrue);
|
|
expect(view.configuration.logicalConstraints.minWidth, 160.0);
|
|
expect(view.configuration.logicalConstraints.minHeight, 240.0);
|
|
|
|
flutterView.devicePixelRatio = 3.0;
|
|
flutterView.physicalSize = const Size(300, 300);
|
|
binding.handleMetricsChanged();
|
|
expect(view.configuration.devicePixelRatio, 3.0);
|
|
expect(view.configuration.logicalConstraints.isTight, isTrue);
|
|
expect(view.configuration.logicalConstraints.minWidth, 100.0);
|
|
expect(view.configuration.logicalConstraints.minHeight, 100.0);
|
|
|
|
binding.removeRenderView(view);
|
|
});
|
|
|
|
test('semantics actions are performed on the right view', () {
|
|
final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
|
|
final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
|
|
final RenderView renderView1 = RenderView(view: flutterView1);
|
|
final RenderView renderView2 = RenderView(view: flutterView2);
|
|
final PipelineOwnerSpy owner1 = PipelineOwnerSpy()..rootNode = renderView1;
|
|
final PipelineOwnerSpy owner2 = PipelineOwnerSpy()..rootNode = renderView2;
|
|
|
|
binding.addRenderView(renderView1);
|
|
binding.addRenderView(renderView2);
|
|
|
|
binding.performSemanticsAction(
|
|
const SemanticsActionEvent(type: SemanticsAction.copy, viewId: 1, nodeId: 11),
|
|
);
|
|
expect(owner1.semanticsOwner.performedActions.single, (11, SemanticsAction.copy, null));
|
|
expect(owner2.semanticsOwner.performedActions, isEmpty);
|
|
owner1.semanticsOwner.performedActions.clear();
|
|
|
|
binding.performSemanticsAction(
|
|
const SemanticsActionEvent(type: SemanticsAction.tap, viewId: 2, nodeId: 22),
|
|
);
|
|
expect(owner1.semanticsOwner.performedActions, isEmpty);
|
|
expect(owner2.semanticsOwner.performedActions.single, (22, SemanticsAction.tap, null));
|
|
owner2.semanticsOwner.performedActions.clear();
|
|
|
|
binding.performSemanticsAction(
|
|
const SemanticsActionEvent(type: SemanticsAction.tap, viewId: 3, nodeId: 22),
|
|
);
|
|
expect(owner1.semanticsOwner.performedActions, isEmpty);
|
|
expect(owner2.semanticsOwner.performedActions, isEmpty);
|
|
|
|
binding.removeRenderView(renderView1);
|
|
binding.removeRenderView(renderView2);
|
|
});
|
|
|
|
test('all registered renderviews are asked to composite frame', () {
|
|
final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
|
|
final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
|
|
final RenderView renderView1 = RenderView(view: flutterView1);
|
|
final RenderView renderView2 = RenderView(view: flutterView2);
|
|
final PipelineOwner owner1 = PipelineOwner()..rootNode = renderView1;
|
|
final PipelineOwner owner2 = PipelineOwner()..rootNode = renderView2;
|
|
binding.rootPipelineOwner.adoptChild(owner1);
|
|
binding.rootPipelineOwner.adoptChild(owner2);
|
|
binding.addRenderView(renderView1);
|
|
binding.addRenderView(renderView2);
|
|
renderView1.prepareInitialFrame();
|
|
renderView2.prepareInitialFrame();
|
|
|
|
expect(flutterView1.renderedScenes, isEmpty);
|
|
expect(flutterView2.renderedScenes, isEmpty);
|
|
|
|
binding.handleBeginFrame(Duration.zero);
|
|
binding.handleDrawFrame();
|
|
|
|
expect(flutterView1.renderedScenes, hasLength(1));
|
|
expect(flutterView2.renderedScenes, hasLength(1));
|
|
|
|
binding.removeRenderView(renderView1);
|
|
|
|
binding.handleBeginFrame(Duration.zero);
|
|
binding.handleDrawFrame();
|
|
|
|
expect(flutterView1.renderedScenes, hasLength(1));
|
|
expect(flutterView2.renderedScenes, hasLength(2));
|
|
|
|
binding.removeRenderView(renderView2);
|
|
|
|
binding.handleBeginFrame(Duration.zero);
|
|
binding.handleDrawFrame();
|
|
|
|
expect(flutterView1.renderedScenes, hasLength(1));
|
|
expect(flutterView2.renderedScenes, hasLength(2));
|
|
});
|
|
|
|
test('hit-testing reaches the right view', () {
|
|
final FakeFlutterView flutterView1 = FakeFlutterView(viewId: 1);
|
|
final FakeFlutterView flutterView2 = FakeFlutterView(viewId: 2);
|
|
final RenderView renderView1 = RenderView(view: flutterView1);
|
|
final RenderView renderView2 = RenderView(view: flutterView2);
|
|
binding.addRenderView(renderView1);
|
|
binding.addRenderView(renderView2);
|
|
|
|
HitTestResult result = HitTestResult();
|
|
binding.hitTestInView(result, Offset.zero, 1);
|
|
expect(result.path, hasLength(2));
|
|
expect(result.path.first.target, renderView1);
|
|
expect(result.path.last.target, binding);
|
|
|
|
result = HitTestResult();
|
|
binding.hitTestInView(result, Offset.zero, 2);
|
|
expect(result.path, hasLength(2));
|
|
expect(result.path.first.target, renderView2);
|
|
expect(result.path.last.target, binding);
|
|
|
|
result = HitTestResult();
|
|
binding.hitTestInView(result, Offset.zero, 3);
|
|
expect(result.path.single.target, binding);
|
|
|
|
binding.removeRenderView(renderView1);
|
|
binding.removeRenderView(renderView2);
|
|
});
|
|
}
|
|
|
|
class FakeFlutterView extends Fake implements FlutterView {
|
|
FakeFlutterView({
|
|
this.viewId = 100,
|
|
this.devicePixelRatio = 2.5,
|
|
this.physicalSize = const Size(400, 600),
|
|
this.padding = FakeViewPadding.zero,
|
|
});
|
|
|
|
@override
|
|
final int viewId;
|
|
@override
|
|
double devicePixelRatio;
|
|
@override
|
|
Size physicalSize;
|
|
@override
|
|
ViewConstraints get physicalConstraints => ViewConstraints.tight(physicalSize);
|
|
@override
|
|
ViewPadding padding;
|
|
|
|
List<Scene> renderedScenes = <Scene>[];
|
|
|
|
@override
|
|
void render(Scene scene, {Size? size}) {
|
|
renderedScenes.add(scene);
|
|
}
|
|
}
|
|
|
|
final class PipelineOwnerSpy extends PipelineOwner {
|
|
@override
|
|
final SemanticsOwnerSpy semanticsOwner = SemanticsOwnerSpy();
|
|
}
|
|
|
|
class SemanticsOwnerSpy extends Fake implements SemanticsOwner {
|
|
final List<(int, SemanticsAction, Object?)> performedActions =
|
|
<(int, SemanticsAction, Object?)>[];
|
|
|
|
@override
|
|
void performAction(int id, SemanticsAction action, [Object? args]) {
|
|
performedActions.add((id, action, args));
|
|
}
|
|
}
|