Skip to content
Projects
Groups
Snippets
Help
Loading...
Help
Submit feedback
Sign in
Toggle navigation
F
Front-End
Project
Project
Details
Activity
Releases
Cycle Analytics
Repository
Repository
Files
Commits
Branches
Tags
Contributors
Graph
Compare
Charts
Issues
0
Issues
0
List
Board
Labels
Milestones
Merge Requests
0
Merge Requests
0
CI / CD
CI / CD
Pipelines
Jobs
Schedules
Charts
Wiki
Wiki
Snippets
Snippets
Members
Members
Collapse sidebar
Close sidebar
Activity
Graph
Charts
Create a new issue
Jobs
Commits
Issue Boards
Open sidebar
abdullh.alsoleman
Front-End
Commits
06d80f22
Commit
06d80f22
authored
May 20, 2016
by
Ian Hickson
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Identify the widgets you tap on in live tests (#4079)
parent
94636bd2
Changes
6
Show whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
275 additions
and
45 deletions
+275
-45
simple_smoke_test.dart
examples/flutter_gallery/test/simple_smoke_test.dart
+65
-0
view.dart
packages/flutter/lib/src/rendering/view.dart
+2
-2
binding.dart
packages/flutter_test/lib/src/binding.dart
+81
-21
controller.dart
packages/flutter_test/lib/src/controller.dart
+4
-3
test_pointer.dart
packages/flutter_test/lib/src/test_pointer.dart
+13
-17
widget_tester.dart
packages/flutter_test/lib/src/widget_tester.dart
+110
-2
No files found.
examples/flutter_gallery/test/simple_smoke_test.dart
0 → 100644
View file @
06d80f22
// Copyright 2016 The Chromium 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/material.dart'
;
import
'package:flutter_test/flutter_test.dart'
;
import
'package:flutter_gallery/main.dart'
as
flutter_gallery_main
;
void
main
(
)
{
TestWidgetsFlutterBinding
binding
=
TestWidgetsFlutterBinding
.
ensureInitialized
();
if
(
binding
is
LiveTestWidgetsFlutterBinding
)
binding
.
allowAllFrames
=
true
;
testWidgets
(
'Flutter Gallery app simple smoke test'
,
(
WidgetTester
tester
)
async
{
flutter_gallery_main
.
main
();
// builds the app and schedules a frame but doesn't trigger one
await
tester
.
pump
();
// see https://github.com/flutter/flutter/issues/1865
await
tester
.
pump
();
// triggers a frame
Finder
finder
=
find
.
byWidgetPredicate
((
Widget
widget
)
{
return
widget
is
Tooltip
&&
widget
.
message
==
'Open navigation menu'
;
});
expect
(
finder
,
findsOneWidget
);
// Open drawer
await
tester
.
tap
(
finder
);
await
tester
.
pump
();
// start animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// end animation
// Change theme
await
tester
.
tap
(
find
.
text
(
'Dark'
));
await
tester
.
pump
();
// start animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// end animation
// Close drawer
await
tester
.
tap
(
find
.
byType
(
DrawerController
));
await
tester
.
pump
();
// start animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// end animation
// Open Demos
await
tester
.
tap
(
find
.
text
(
'Demos'
));
await
tester
.
pump
();
// start animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// end animation
// Open Flexible space toolbar
await
tester
.
tap
(
find
.
text
(
'Flexible space toolbar'
));
await
tester
.
pump
();
// start animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// end animation
// Scroll it up
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
scroll
(
find
.
text
(
'(650) 555-1234'
),
const
Offset
(
0.0
,
-
50.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
await
tester
.
pump
(
const
Duration
(
hours:
100
));
// for testing
});
}
packages/flutter/lib/src/rendering/view.dart
View file @
06d80f22
...
...
@@ -71,7 +71,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
if
(
configuration
==
value
)
return
;
_configuration
=
value
;
replaceRootLayer
(
new
TransformLayer
(
transform:
_
configuration
.
toMatrix
()));
replaceRootLayer
(
new
TransformLayer
(
transform:
configuration
.
toMatrix
()));
markNeedsLayout
();
}
...
...
@@ -79,7 +79,7 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
void
scheduleInitialFrame
()
{
assert
(
owner
!=
null
);
scheduleInitialLayout
();
scheduleInitialPaint
(
new
TransformLayer
(
transform:
_
configuration
.
toMatrix
()));
scheduleInitialPaint
(
new
TransformLayer
(
transform:
configuration
.
toMatrix
()));
owner
.
requestVisualUpdate
();
}
...
...
packages/flutter_test/lib/src/binding.dart
View file @
06d80f22
...
...
@@ -148,6 +148,16 @@ abstract class TestWidgetsFlutterBinding extends BindingBase
return
new
Future
<
Null
>.
value
();
}
/// Convert the given point from the global coodinate system (as used by
/// pointer events from the device) to the coordinate system used by the
/// tests (an 800 by 600 window).
Point
globalToLocal
(
Point
point
)
=>
point
;
/// Convert the given point from the coordinate system used by the tests (an
/// 800 by 600 window) to the global coodinate system (as used by pointer
/// events from the device).
Point
localToGlobal
(
Point
point
)
=>
point
;
@override
void
dispatchEvent
(
PointerEvent
event
,
HitTestResult
result
,
{
TestBindingEventSource
source
:
TestBindingEventSource
.
device
...
...
@@ -595,11 +605,21 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override
_LiveTestRenderView
get
renderView
=>
super
.
renderView
;
/// An object to which real device events should be routed.
///
/// Normally, device events are silently dropped. However, if this property is
/// set to a non-null value, then the events will be routed to its
/// [HitTestDispatcher.dispatchEvent] method instead.
///
/// Events dispatched by [TestGesture] are not affected by this.
HitTestDispatcher
deviceEventDispatcher
;
@override
void
dispatchEvent
(
PointerEvent
event
,
HitTestResult
result
,
{
TestBindingEventSource
source
:
TestBindingEventSource
.
device
})
{
if
(
source
==
TestBindingEventSource
.
test
)
{
switch
(
source
)
{
case
TestBindingEventSource
.
test
:
if
(!
renderView
.
_pointers
.
containsKey
(
event
.
pointer
))
{
assert
(
event
.
down
);
renderView
.
_pointers
[
event
.
pointer
]
=
new
_LiveTestPointerRecord
(
event
.
pointer
,
event
.
position
);
...
...
@@ -610,10 +630,12 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
}
renderView
.
markNeedsPaint
();
super
.
dispatchEvent
(
event
,
result
,
source
:
source
);
return
;
break
;
case
TestBindingEventSource
.
device
:
if
(
deviceEventDispatcher
!=
null
)
deviceEventDispatcher
.
dispatchEvent
(
event
,
result
);
break
;
}
// we eat all device events for now
// TODO(ianh): do something useful with device events
}
@override
...
...
@@ -654,8 +676,31 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
@override
ViewConfiguration
createViewConfiguration
()
{
final
double
actualWidth
=
ui
.
window
.
size
.
width
*
ui
.
window
.
devicePixelRatio
;
final
double
actualHeight
=
ui
.
window
.
size
.
height
*
ui
.
window
.
devicePixelRatio
;
return
new
_TestViewConfiguration
(
// TODO(ianh): that these are not the same is https://github.com/flutter/flutter/issues/1360
_getMatrix
(
ui
.
window
.
devicePixelRatio
),
_getMatrix
(
1.0
)
);
}
@override
Point
globalToLocal
(
Point
point
)
{
Matrix4
transform
=
renderView
.
configuration
.
toHitTestMatrix
();
double
det
=
transform
.
invert
();
assert
(
det
!=
0.0
);
Point
result
=
MatrixUtils
.
transformPoint
(
transform
,
point
);
return
result
;
}
@override
Point
localToGlobal
(
Point
point
)
{
Matrix4
transform
=
renderView
.
configuration
.
toHitTestMatrix
();
return
MatrixUtils
.
transformPoint
(
transform
,
point
);
}
Matrix4
_getMatrix
(
double
devicePixelRatio
)
{
final
double
actualWidth
=
ui
.
window
.
size
.
width
*
devicePixelRatio
;
final
double
actualHeight
=
ui
.
window
.
size
.
height
*
devicePixelRatio
;
final
double
desiredWidth
=
_kTestViewportSize
.
width
;
final
double
desiredHeight
=
_kTestViewportSize
.
height
;
double
scale
,
shiftX
,
shiftY
;
...
...
@@ -671,19 +716,22 @@ class LiveTestWidgetsFlutterBinding extends TestWidgetsFlutterBinding {
final
Matrix4
matrix
=
new
Matrix4
.
compose
(
new
Vector3
(
shiftX
,
shiftY
,
0.0
),
// translation
new
Quaternion
.
identity
(),
// rotation
new
Vector3
(
scale
,
scale
,
0
.0
)
// scale
new
Vector3
(
scale
,
scale
,
1
.0
)
// scale
);
return
new
_TestViewConfiguration
(
matrix
)
;
return
matrix
;
}
}
class
_TestViewConfiguration
extends
ViewConfiguration
{
_TestViewConfiguration
(
this
.
m
atrix
)
:
super
(
size:
_kTestViewportSize
);
_TestViewConfiguration
(
this
.
paintMatrix
,
this
.
hitTestM
atrix
)
:
super
(
size:
_kTestViewportSize
);
final
Matrix4
matrix
;
final
Matrix4
paintMatrix
;
final
Matrix4
hitTestMatrix
;
@override
Matrix4
toMatrix
()
=>
matrix
;
Matrix4
toMatrix
()
=>
paintMatrix
.
clone
();
Matrix4
toHitTestMatrix
()
=>
hitTestMatrix
.
clone
();
@override
String
toString
()
=>
'TestViewConfiguration'
;
...
...
@@ -709,8 +757,20 @@ class _LiveTestRenderView extends RenderView {
ViewConfiguration
configuration
})
:
super
(
configuration:
configuration
);
@override
_TestViewConfiguration
get
configuration
=>
super
.
configuration
;
final
Map
<
int
,
_LiveTestPointerRecord
>
_pointers
=
<
int
,
_LiveTestPointerRecord
>{};
@override
bool
hitTest
(
HitTestResult
result
,
{
Point
position
})
{
Matrix4
transform
=
configuration
.
toHitTestMatrix
();
double
det
=
transform
.
invert
();
assert
(
det
!=
0.0
);
position
=
MatrixUtils
.
transformPoint
(
transform
,
position
);
return
super
.
hitTest
(
result
,
position:
position
);
}
@override
void
paint
(
PaintingContext
context
,
Offset
offset
)
{
assert
(
offset
==
Offset
.
zero
);
...
...
packages/flutter_test/lib/src/controller.dart
View file @
06d80f22
...
...
@@ -273,7 +273,7 @@ class WidgetController {
assert
(
offset
.
distance
>
0.0
);
assert
(
velocity
!=
0.0
);
// velocity is pixels/second
final
TestPointer
p
=
new
TestPointer
(
pointer
);
final
HitTestResult
result
=
_hitTest
(
startLocation
);
final
HitTestResult
result
=
hitTestOnBinding
(
startLocation
);
const
int
kMoveCount
=
50
;
// Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
final
double
timeStampDelta
=
1000.0
*
offset
.
distance
/
(
kMoveCount
*
velocity
);
double
timeStamp
=
0.0
;
...
...
@@ -311,10 +311,11 @@ class WidgetController {
/// Begins a gesture at a particular point, and returns the
/// [TestGesture] object which you can use to continue the gesture.
Future
<
TestGesture
>
startGesture
(
Point
downLocation
,
{
int
pointer:
1
})
{
return
TestGesture
.
down
(
downLocation
,
pointer:
pointer
,
dispatcher:
sendEventToBinding
);
return
TestGesture
.
down
(
downLocation
,
pointer:
pointer
,
hitTester:
hitTestOnBinding
,
dispatcher:
sendEventToBinding
);
}
HitTestResult
_hitTest
(
Point
location
)
{
/// Forwards the given location to the binding's hitTest logic.
HitTestResult
hitTestOnBinding
(
Point
location
)
{
final
HitTestResult
result
=
new
HitTestResult
();
binding
.
hitTest
(
result
,
location
);
return
result
;
...
...
packages/flutter_test/lib/src/test_pointer.dart
View file @
06d80f22
...
...
@@ -107,9 +107,12 @@ class TestPointer {
}
}
///
An
callback that can dispatch events and returns a future that
///
Signature for a
callback that can dispatch events and returns a future that
/// completes when the event dispatch is complete.
typedef
Future
<
Null
>
AsyncHitTestDispatcher
(
PointerEvent
event
,
HitTestResult
result
);
typedef
Future
<
Null
>
EventDispatcher
(
PointerEvent
event
,
HitTestResult
result
);
/// Signature for callbacks that perform hit-testing at a given location.
typedef
HitTestResult
HitTester
(
Point
location
);
/// A class for performing gestures in tests.
///
...
...
@@ -124,28 +127,21 @@ class TestGesture {
/// By default, the pointer ID used is 1. This can be overridden by
/// providing the `pointer` argument.
///
/// By default, the global binding is used for hit testing. The
/// object to use for hit testing can be overridden by providing
/// `hitTestTarget`.
///
/// An object to use for dispatching events must be provided via the
/// `dispatcher` argument.
/// A function to use for hit testing should be provided via the `hitTester`
/// argument, and a function to use for dispatching events should be provided
/// via the `dispatcher` argument.
static
Future
<
TestGesture
>
down
(
Point
downLocation
,
{
int
pointer:
1
,
HitTest
able
target
,
AsyncHitTes
tDispatcher
dispatcher
HitTest
er
hitTester
,
Even
tDispatcher
dispatcher
})
async
{
assert
(
hitTester
!=
null
);
assert
(
dispatcher
!=
null
);
final
Completer
<
TestGesture
>
completer
=
new
Completer
<
TestGesture
>();
TestGesture
result
;
TestAsyncUtils
.
guard
(()
async
{
// hit test
final
HitTestResult
hitTestResult
=
new
HitTestResult
();
target
??=
GestureBinding
.
instance
;
assert
(
target
!=
null
);
target
.
hitTest
(
hitTestResult
,
downLocation
);
// dispatch down event
final
HitTestResult
hitTestResult
=
hitTester
(
downLocation
);
final
TestPointer
testPointer
=
new
TestPointer
(
pointer
);
await
dispatcher
(
testPointer
.
down
(
downLocation
),
hitTestResult
);
...
...
@@ -158,7 +154,7 @@ class TestGesture {
return
completer
.
future
;
}
final
AsyncHitTes
tDispatcher
_dispatcher
;
final
Even
tDispatcher
_dispatcher
;
final
HitTestResult
_result
;
final
TestPointer
_pointer
;
...
...
packages/flutter_test/lib/src/widget_tester.dart
View file @
06d80f22
...
...
@@ -5,9 +5,11 @@
import
'dart:async'
;
import
'package:flutter/gestures.dart'
;
import
'package:flutter/rendering.dart'
;
import
'package:flutter/widgets.dart'
;
import
'package:test/test.dart'
as
test_package
;
import
'all_elements.dart'
;
import
'binding.dart'
;
import
'controller.dart'
;
import
'finders.dart'
;
...
...
@@ -123,8 +125,11 @@ void expectSync(dynamic actual, dynamic matcher, {
}
/// Class that programmatically interacts with widgets and the test environment.
class
WidgetTester
extends
WidgetController
{
WidgetTester
.
_
(
TestWidgetsFlutterBinding
binding
)
:
super
(
binding
);
class
WidgetTester
extends
WidgetController
implements
HitTestDispatcher
{
WidgetTester
.
_
(
TestWidgetsFlutterBinding
binding
)
:
super
(
binding
)
{
if
(
binding
is
LiveTestWidgetsFlutterBinding
)
binding
.
deviceEventDispatcher
=
this
;
}
/// The binding instance used by the testing framework.
@override
...
...
@@ -157,6 +162,12 @@ class WidgetTester extends WidgetController {
return
TestAsyncUtils
.
guard
(()
=>
binding
.
pump
(
duration
,
phase
));
}
@override
HitTestResult
hitTestOnBinding
(
Point
location
)
{
location
=
binding
.
localToGlobal
(
location
);
return
super
.
hitTestOnBinding
(
location
);
}
@override
Future
<
Null
>
sendEventToBinding
(
PointerEvent
event
,
HitTestResult
result
)
{
return
TestAsyncUtils
.
guard
(()
async
{
...
...
@@ -165,6 +176,103 @@ class WidgetTester extends WidgetController {
});
}
/// Handler for device events caught by the binding in live test mode.
@override
void
dispatchEvent
(
PointerEvent
event
,
HitTestResult
result
)
{
if
(
event
is
PointerDownEvent
)
{
final
RenderObject
innerTarget
=
result
.
path
.
firstWhere
(
(
HitTestEntry
candidate
)
=>
candidate
.
target
is
RenderObject
,
orElse:
()
=>
null
)?.
target
;
if
(
innerTarget
==
null
)
return
null
;
final
Element
innerTargetElement
=
collectAllElementsFrom
(
binding
.
renderViewElement
)
.
lastWhere
((
Element
element
)
=>
element
.
renderObject
==
innerTarget
);
final
List
<
Element
>
candidates
=
<
Element
>[];
innerTargetElement
.
visitAncestorElements
((
Element
element
)
{
candidates
.
add
(
element
);
return
true
;
});
assert
(
candidates
.
isNotEmpty
);
String
descendantText
;
int
numberOfWithTexts
=
0
;
int
numberOfTypes
=
0
;
int
totalNumber
=
0
;
print
(
'Some possible finders for the widgets at
${binding.globalToLocal(event.position)}
:'
);
for
(
Element
element
in
candidates
)
{
if
(
totalNumber
>
10
)
break
;
totalNumber
+=
1
;
if
(
element
.
widget
is
Text
)
{
assert
(
descendantText
==
null
);
final
Text
widget
=
element
.
widget
;
final
Iterable
<
Element
>
matches
=
find
.
text
(
widget
.
data
).
evaluate
();
descendantText
=
widget
.
data
;
if
(
matches
.
length
==
1
)
{
print
(
' find.text(
\'
${widget.data}
\'
)'
);
continue
;
}
}
if
(
element
.
widget
.
key
is
ValueKey
<
dynamic
>)
{
final
ValueKey
<
dynamic
>
key
=
element
.
widget
.
key
;
String
keyLabel
;
if
((
key
is
ValueKey
<
int
>
||
key
is
ValueKey
<
double
>
||
key
is
ValueKey
<
bool
>))
{
keyLabel
=
'const
${element.widget.key.runtimeType}
(
${key.value}
)'
;
}
else
if
(
key
is
ValueKey
<
String
>)
{
keyLabel
=
'const
${element.widget.key.runtimeType}
(
\'
${key.value}
\'
)'
;
}
if
(
keyLabel
!=
null
)
{
final
Iterable
<
Element
>
matches
=
find
.
byKey
(
key
).
evaluate
();
if
(
matches
.
length
==
1
)
{
print
(
' find.byKey(
$keyLabel
)'
);
continue
;
}
}
}
if
(!
_isPrivate
(
element
.
widget
.
runtimeType
))
{
if
(
numberOfTypes
<
5
)
{
final
Iterable
<
Element
>
matches
=
find
.
byType
(
element
.
widget
.
runtimeType
).
evaluate
();
if
(
matches
.
length
==
1
)
{
print
(
' find.byType(
${element.widget.runtimeType}
)'
);
numberOfTypes
+=
1
;
continue
;
}
}
if
(
descendantText
!=
null
&&
numberOfWithTexts
<
5
)
{
final
Iterable
<
Element
>
matches
=
find
.
widgetWithText
(
element
.
widget
.
runtimeType
,
descendantText
).
evaluate
();
if
(
matches
.
length
==
1
)
{
print
(
' find.widgetWithText(
${element.widget.runtimeType}
,
\'
$descendantText
\'
)'
);
numberOfWithTexts
+=
1
;
continue
;
}
}
}
if
(!
_isPrivate
(
element
.
runtimeType
))
{
final
Iterable
<
Element
>
matches
=
find
.
byElementType
(
element
.
runtimeType
).
evaluate
();
if
(
matches
.
length
==
1
)
{
print
(
' find.byElementType(
${element.runtimeType}
)'
);
continue
;
}
}
totalNumber
-=
1
;
// if we got here, we didn't actually find something to say about it
}
if
(
totalNumber
==
0
)
print
(
' <could not come up with any unique finders>'
);
}
}
bool
_isPrivate
(
Type
type
)
{
return
'_'
.
matchAsPrefix
(
type
.
toString
())
!=
null
;
}
/// Returns the exception most recently caught by the Flutter framework.
///
/// See [TestWidgetsFlutterBinding.takeException] for details.
...
...
Write
Preview
Markdown
is supported
0%
Try again
or
attach a new file
Attach a file
Cancel
You are about to add
0
people
to the discussion. Proceed with caution.
Finish editing this message first!
Cancel
Please
register
or
sign in
to comment