blob: 0c87f13ea7e2567e2dceb444ff92e974e2650713 [file] [log] [blame]
// 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.
@TestOn('browser') // This file contains web-only library.
library;
import 'dart:js_interop';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:web/web.dart' as web;
extension on web.HTMLCollection {
Iterable<web.Element?> get iterable => Iterable<web.Element?>.generate(length, (int index) => item(index));
}
extension on web.CSSRuleList {
Iterable<web.CSSRule?> get iterable => Iterable<web.CSSRule?>.generate(length, (int index) => item(index));
}
void main() {
web.HTMLElement? element;
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = (String viewType, Object Function(int viewId) fn, {bool isVisible = true}) {
element = fn(0) as web.HTMLElement;
// The element needs to be attached to the document body to receive mouse
// events.
web.document.body!.append(element! as JSAny);
};
// This force register the dom element.
PlatformSelectableRegionContextMenu(child: const Placeholder());
PlatformSelectableRegionContextMenu.debugOverrideRegisterViewFactory = null;
test('DOM element is set up correctly', () async {
expect(element, isNotNull);
expect(element!.style.width, '100%');
expect(element!.style.height, '100%');
expect(element!.classList.length, 1);
final String className = element!.className;
expect(web.document.head!.children.iterable, isNotEmpty);
bool foundStyle = false;
for (final web.Element? element in web.document.head!.children.iterable) {
expect(element, isNotNull);
if (element!.tagName != 'STYLE') {
continue;
}
final web.CSSRuleList? rules = (element as web.HTMLStyleElement).sheet?.rules;
if (rules != null) {
foundStyle = rules.iterable.any((web.CSSRule? rule) => rule!.cssText.contains(className));
}
if (foundStyle) {
break;
}
}
expect(foundStyle, isTrue);
});
testWidgets('right click can trigger select word', (WidgetTester tester) async {
final FocusNode focusNode = FocusNode();
addTearDown(focusNode.dispose);
final UniqueKey spy = UniqueKey();
await tester.pumpWidget(
MaterialApp(
home: SelectableRegion(
focusNode: focusNode,
selectionControls: materialTextSelectionControls,
child: SelectionSpy(key: spy),
),
)
);
expect(element, isNotNull);
focusNode.requestFocus();
await tester.pump();
// Dispatch right click.
element!.dispatchEvent(
web.MouseEvent(
'mousedown',
web.MouseEventInit(
button: 2,
clientX: 200,
clientY: 300,
),
),
);
final RenderSelectionSpy renderSelectionSpy = tester.renderObject<RenderSelectionSpy>(find.byKey(spy));
expect(renderSelectionSpy.events, isNotEmpty);
SelectWordSelectionEvent? selectWordEvent;
for (final SelectionEvent event in renderSelectionSpy.events) {
if (event is SelectWordSelectionEvent) {
selectWordEvent = event;
break;
}
}
expect(selectWordEvent, isNotNull);
expect((selectWordEvent!.globalPosition.dx - 200).abs() < precisionErrorTolerance, isTrue);
expect((selectWordEvent.globalPosition.dy - 300).abs() < precisionErrorTolerance, isTrue);
});
}
class SelectionSpy extends LeafRenderObjectWidget {
const SelectionSpy({
super.key,
});
@override
RenderObject createRenderObject(BuildContext context) {
return RenderSelectionSpy(
SelectionContainer.maybeOf(context),
);
}
@override
void updateRenderObject(BuildContext context, covariant RenderObject renderObject) { }
}
class RenderSelectionSpy extends RenderProxyBox
with Selectable, SelectionRegistrant {
RenderSelectionSpy(
SelectionRegistrar? registrar,
) {
this.registrar = registrar;
}
final Set<VoidCallback> listeners = <VoidCallback>{};
List<SelectionEvent> events = <SelectionEvent>[];
@override
Size get size => _size;
Size _size = Size.zero;
@override
List<Rect> get boundingBoxes => _boundingBoxes;
final List<Rect> _boundingBoxes = <Rect>[];
@override
Size computeDryLayout(BoxConstraints constraints) {
_size = Size(constraints.maxWidth, constraints.maxHeight);
_boundingBoxes.add(Rect.fromLTWH(0.0, 0.0, constraints.maxWidth, constraints.maxHeight));
return _size;
}
@override
void addListener(VoidCallback listener) => listeners.add(listener);
@override
void removeListener(VoidCallback listener) => listeners.remove(listener);
@override
SelectionResult dispatchSelectionEvent(SelectionEvent event) {
events.add(event);
return SelectionResult.end;
}
@override
SelectedContent? getSelectedContent() {
return const SelectedContent(plainText: 'content');
}
@override
final SelectionGeometry value = const SelectionGeometry(
hasContent: true,
status: SelectionStatus.uncollapsed,
startSelectionPoint: SelectionPoint(
localPosition: Offset.zero,
lineHeight: 0.0,
handleType: TextSelectionHandleType.left,
),
endSelectionPoint: SelectionPoint(
localPosition: Offset.zero,
lineHeight: 0.0,
handleType: TextSelectionHandleType.left,
),
);
@override
void pushHandleLayers(LayerLink? startHandle, LayerLink? endHandle) { }
}