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
c6757570
Commit
c6757570
authored
Feb 14, 2017
by
Ian Hickson
Committed by
GitHub
Feb 14, 2017
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Improve testing of the RefreshIndicator widget. (#8111)
parent
88a01ac4
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
293 additions
and
66 deletions
+293
-66
refresh_indicator.dart
packages/flutter/lib/src/material/refresh_indicator.dart
+45
-51
refresh_indicator_test.dart
packages/flutter/test/material/refresh_indicator_test.dart
+191
-10
tabs_test.dart
packages/flutter/test/material/tabs_test.dart
+55
-3
controller.dart
packages/flutter_test/lib/src/controller.dart
+2
-2
No files found.
packages/flutter/lib/src/material/refresh_indicator.dart
View file @
c6757570
...
...
@@ -17,8 +17,8 @@ const double _kDragContainerExtentPercentage = 0.25;
// displacement; max displacement = _kDragSizeFactorLimit * displacement.
const
double
_kDragSizeFactorLimit
=
1.5
;
// How far the indicator must be dragged to trigger the refresh callback.
const
double
_kDragThresholdFactor
=
0.75
;
// When the scroll ends, the duration of the refresh indicator's animation
// to the RefreshIndicator's displacment.
...
...
@@ -44,9 +44,6 @@ enum RefreshIndicatorLocation {
/// The refresh indicator will appear at the bottom of the scrollable.
bottom
,
/// The refresh indicator will appear at both ends of the scrollable.
both
}
// The state machine moves through these modes only when the scrollable
...
...
@@ -56,12 +53,12 @@ enum _RefreshIndicatorMode {
armed
,
// Dragged far enough that an up event will run the refresh callback.
snap
,
// Animating to the indicator's final "displacement".
refresh
,
// Running the refresh callback.
dismiss
// Animating the indicator's fade-out.
dismiss
,
// Animating the indicator's fade-out.
}
enum
_DismissTransition
{
shrink
,
// Refresh callback completed, scale the indicator to 0.
slide
// No refresh, translate the indicator out of view.
slide
,
// No refresh, translate the indicator out of view.
}
/// A widget that supports the Material "swipe to refresh" idiom.
...
...
@@ -191,8 +188,6 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
return
scrollOffset
<=
minScrollOffset
;
case
RefreshIndicatorLocation
.
bottom
:
return
scrollOffset
>=
maxScrollOffset
;
case
RefreshIndicatorLocation
.
both
:
return
scrollOffset
<=
minScrollOffset
||
scrollOffset
>=
maxScrollOffset
;
}
return
false
;
}
...
...
@@ -206,14 +201,6 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
return
scrollOffset
<=
minScrollOffset
?
-
_dragOffset
:
0.0
;
case
RefreshIndicatorLocation
.
bottom
:
return
scrollOffset
>=
maxScrollOffset
?
_dragOffset
:
0.0
;
case
RefreshIndicatorLocation
.
both
:
{
if
(
scrollOffset
<=
minScrollOffset
)
return
-
_dragOffset
;
else
if
(
scrollOffset
>=
maxScrollOffset
)
return
_dragOffset
;
else
return
0.0
;
}
}
return
0.0
;
}
...
...
@@ -266,6 +253,8 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
// Stop showing the refresh indicator
Future
<
Null
>
_dismiss
(
_DismissTransition
transition
)
async
{
// This can only be called from _show() when refreshing
// and _handlePointerUp when dragging.
setState
(()
{
_mode
=
_RefreshIndicatorMode
.
dismiss
;
});
...
...
@@ -284,38 +273,44 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
}
}
Future
<
Null
>
_show
()
async
{
void
_show
()
{
assert
(
_mode
!=
_RefreshIndicatorMode
.
refresh
);
assert
(
_mode
!=
_RefreshIndicatorMode
.
snap
);
Completer
<
Null
>
completer
=
new
Completer
<
Null
>();
_pendingRefreshFuture
=
completer
.
future
;
_mode
=
_RefreshIndicatorMode
.
snap
;
await
_sizeController
.
animateTo
(
1.0
/
_kDragSizeFactorLimit
,
duration:
_kIndicatorSnapDuration
);
if
(
mounted
&&
_mode
==
_RefreshIndicatorMode
.
snap
)
{
assert
(
config
.
refresh
!=
null
);
setState
(()
{
_mode
=
_RefreshIndicatorMode
.
refresh
;
// Show the indeterminate progress indicator.
_sizeController
.
animateTo
(
1.0
/
_kDragSizeFactorLimit
,
duration:
_kIndicatorSnapDuration
)
.
whenComplete
(()
{
if
(
mounted
&&
_mode
==
_RefreshIndicatorMode
.
snap
)
{
assert
(
config
.
refresh
!=
null
);
setState
(()
{
// Show the indeterminate progress indicator.
_mode
=
_RefreshIndicatorMode
.
refresh
;
});
config
.
refresh
().
whenComplete
(()
{
if
(
mounted
&&
_mode
==
_RefreshIndicatorMode
.
refresh
)
{
completer
.
complete
();
_dismiss
(
_DismissTransition
.
slide
);
}
});
}
});
// Only one refresh callback is allowed to run at a time. If the user
// attempts to start a refresh while one is still running ("pending") we
// just continue to wait on the pending refresh.
if
(
_pendingRefreshFuture
==
null
)
_pendingRefreshFuture
=
config
.
refresh
();
await
_pendingRefreshFuture
;
bool
completed
=
_pendingRefreshFuture
!=
null
;
_pendingRefreshFuture
=
null
;
if
(
mounted
&&
completed
&&
_mode
==
_RefreshIndicatorMode
.
refresh
)
_dismiss
(
_DismissTransition
.
slide
);
}
}
Future
<
Null
>
_doHandlePointerUp
(
PointerUpEvent
event
)
async
{
if
(
_mode
==
_RefreshIndicatorMode
.
armed
)
_show
();
else
if
(
_mode
==
_RefreshIndicatorMode
.
drag
)
_dismiss
(
_DismissTransition
.
shrink
);
}
void
_handlePointerUp
(
PointerEvent
event
)
{
_doHandlePointerUp
(
event
);
switch
(
_mode
)
{
case
_RefreshIndicatorMode
.
armed
:
_show
();
break
;
case
_RefreshIndicatorMode
.
drag
:
_dismiss
(
_DismissTransition
.
shrink
);
break
;
default
:
// do nothing
break
;
}
}
/// Show the refresh indicator and run the refresh callback as if it had
...
...
@@ -324,12 +319,14 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
///
/// Creating the RefreshIndicator with a [GlobalKey<RefreshIndicatorState>]
/// makes it possible to refer to the [RefreshIndicatorState].
Future
<
Null
>
show
()
async
{
if
(
_mode
!=
_RefreshIndicatorMode
.
refresh
)
{
Future
<
Null
>
show
()
{
if
(
_mode
!=
_RefreshIndicatorMode
.
refresh
&&
_mode
!=
_RefreshIndicatorMode
.
snap
)
{
_sizeController
.
value
=
0.0
;
_scaleController
.
value
=
0.0
;
await
_show
();
_show
();
}
return
_pendingRefreshFuture
;
}
ScrollableEdge
get
_clampOverscrollsEdge
{
...
...
@@ -338,8 +335,6 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
return
ScrollableEdge
.
leading
;
case
RefreshIndicatorLocation
.
bottom
:
return
ScrollableEdge
.
trailing
;
case
RefreshIndicatorLocation
.
both
:
return
ScrollableEdge
.
both
;
}
return
ScrollableEdge
.
none
;
}
...
...
@@ -354,8 +349,7 @@ class RefreshIndicatorState extends State<RefreshIndicator> with TickerProviderS
_valueColor
=
new
ColorTween
(
begin:
(
config
.
color
??
theme
.
accentColor
).
withOpacity
(
0.0
),
end:
(
config
.
color
??
theme
.
accentColor
).
withOpacity
(
1.0
)
)
.
animate
(
new
CurvedAnimation
(
).
animate
(
new
CurvedAnimation
(
parent:
_sizeController
,
curve:
const
Interval
(
0.0
,
1.0
/
_kDragSizeFactorLimit
)
));
...
...
packages/flutter/test/material/refresh_indicator_test.dart
View file @
c6757570
...
...
@@ -9,15 +9,21 @@ import 'package:flutter/material.dart';
final
GlobalKey
<
ScrollableState
>
scrollableKey
=
new
GlobalKey
<
ScrollableState
>();
void
main
(
)
{
bool
refreshCalled
=
false
;
bool
refreshCalled
=
false
;
Future
<
Null
>
refresh
()
{
refreshCalled
=
true
;
return
new
Future
<
Null
>.
value
();
}
Future
<
Null
>
refresh
()
{
refreshCalled
=
true
;
return
new
Future
<
Null
>.
value
();
}
Future
<
Null
>
holdRefresh
()
{
refreshCalled
=
true
;
return
new
Completer
<
Null
>().
future
;
}
void
main
(
)
{
testWidgets
(
'RefreshIndicator'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
...
...
@@ -29,16 +35,191 @@ void main() {
height:
200.0
,
child:
new
Text
(
item
)
);
}).
toList
()
)
)
}).
toList
()
,
)
,
)
,
);
await
tester
.
fling
(
find
.
text
(
'A'
),
const
Offset
(
0.0
,
300.0
),
-
1000.0
);
await
tester
.
fling
(
find
.
text
(
'A'
),
const
Offset
(
0.0
,
300.0
),
1000.0
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the scroll animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the indicator settle animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the indicator hide animation
expect
(
refreshCalled
,
true
);
});
testWidgets
(
'RefreshIndicator - bottom'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
refresh:
refresh
,
location:
RefreshIndicatorLocation
.
bottom
,
child:
new
Block
(
scrollableKey:
scrollableKey
,
children:
<
Widget
>[
new
SizedBox
(
height:
200.0
,
child:
new
Text
(
'X'
)
),
],
),
),
);
await
tester
.
fling
(
find
.
text
(
'X'
),
const
Offset
(
0.0
,
-
300.0
),
1000.0
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the scroll animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the indicator settle animation
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the indicator hide animation
expect
(
refreshCalled
,
true
);
});
testWidgets
(
'RefreshIndicator - not enough'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
refresh:
refresh
,
child:
new
Block
(
scrollableKey:
scrollableKey
,
children:
<
Widget
>[
new
SizedBox
(
height:
200.0
,
child:
new
Text
(
'X'
)
),
],
),
),
);
await
tester
.
fling
(
find
.
text
(
'X'
),
const
Offset
(
0.0
,
100.0
),
1000.0
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
false
);
});
testWidgets
(
'RefreshIndicator - show - slow'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
refresh:
holdRefresh
,
// this one never returns
child:
new
Block
(
scrollableKey:
scrollableKey
,
children:
<
Widget
>[
new
SizedBox
(
height:
200.0
,
child:
new
Text
(
'X'
)
),
],
),
),
);
bool
completed
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed
=
true
;
});
await
tester
.
pump
();
expect
(
completed
,
false
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
true
);
expect
(
completed
,
false
);
completed
=
false
;
refreshCalled
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed
=
true
;
});
await
tester
.
pump
();
expect
(
completed
,
false
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
false
);
});
testWidgets
(
'RefreshIndicator - show - fast'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
refresh:
refresh
,
child:
new
Block
(
scrollableKey:
scrollableKey
,
children:
<
Widget
>[
new
SizedBox
(
height:
200.0
,
child:
new
Text
(
'X'
)
),
],
),
),
);
bool
completed
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed
=
true
;
});
await
tester
.
pump
();
expect
(
completed
,
false
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
true
);
expect
(
completed
,
true
);
completed
=
false
;
refreshCalled
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed
=
true
;
});
await
tester
.
pump
();
expect
(
completed
,
false
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
true
);
expect
(
completed
,
true
);
});
testWidgets
(
'RefreshIndicator - show - fast - twice'
,
(
WidgetTester
tester
)
async
{
refreshCalled
=
false
;
await
tester
.
pumpWidget
(
new
RefreshIndicator
(
scrollableKey:
scrollableKey
,
refresh:
refresh
,
child:
new
Block
(
scrollableKey:
scrollableKey
,
children:
<
Widget
>[
new
SizedBox
(
height:
200.0
,
child:
new
Text
(
'X'
)
),
],
),
),
);
bool
completed1
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed1
=
true
;
});
bool
completed2
=
false
;
tester
.
state
<
RefreshIndicatorState
>(
find
.
byType
(
RefreshIndicator
))
.
show
()
.
then
<
Null
>((
Null
value
)
{
completed2
=
true
;
});
await
tester
.
pump
();
expect
(
completed1
,
false
);
expect
(
completed2
,
false
);
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
expect
(
refreshCalled
,
true
);
expect
(
completed1
,
true
);
expect
(
completed2
,
true
);
});
}
packages/flutter/test/material/tabs_test.dart
View file @
c6757570
...
...
@@ -308,8 +308,51 @@ void main() {
expect
(
find
.
text
(
'RIGHT CHILD'
),
findsNothing
);
});
testWidgets
(
'TabBar left/right fling reverse (1)'
,
(
WidgetTester
tester
)
async
{
List
<
String
>
tabs
=
<
String
>[
'LEFT'
,
'RIGHT'
];
await
tester
.
pumpWidget
(
buildLeftRightApp
(
tabs:
tabs
,
value:
'LEFT'
));
expect
(
find
.
text
(
'LEFT'
),
findsOneWidget
);
expect
(
find
.
text
(
'RIGHT'
),
findsOneWidget
);
expect
(
find
.
text
(
'LEFT CHILD'
),
findsOneWidget
);
expect
(
find
.
text
(
'RIGHT CHILD'
),
findsNothing
);
TabController
controller
=
DefaultTabController
.
of
(
tester
.
element
(
find
.
text
(
'LEFT'
)));
expect
(
controller
.
index
,
0
);
Point
flingStart
=
tester
.
getCenter
(
find
.
text
(
'LEFT CHILD'
));
await
tester
.
flingFrom
(
flingStart
,
const
Offset
(
200.0
,
0.0
),
10000.0
);
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the scroll animation
expect
(
controller
.
index
,
0
);
expect
(
find
.
text
(
'LEFT CHILD'
),
findsOneWidget
);
expect
(
find
.
text
(
'RIGHT CHILD'
),
findsNothing
);
});
testWidgets
(
'TabBar left/right fling reverse (2)'
,
(
WidgetTester
tester
)
async
{
List
<
String
>
tabs
=
<
String
>[
'LEFT'
,
'RIGHT'
];
await
tester
.
pumpWidget
(
buildLeftRightApp
(
tabs:
tabs
,
value:
'LEFT'
));
expect
(
find
.
text
(
'LEFT'
),
findsOneWidget
);
expect
(
find
.
text
(
'RIGHT'
),
findsOneWidget
);
expect
(
find
.
text
(
'LEFT CHILD'
),
findsOneWidget
);
expect
(
find
.
text
(
'RIGHT CHILD'
),
findsNothing
);
TabController
controller
=
DefaultTabController
.
of
(
tester
.
element
(
find
.
text
(
'LEFT'
)));
expect
(
controller
.
index
,
0
);
Point
flingStart
=
tester
.
getCenter
(
find
.
text
(
'LEFT CHILD'
));
await
tester
.
flingFrom
(
flingStart
,
const
Offset
(-
200.0
,
0.0
),
10000.0
);
await
tester
.
pump
();
// this is similar to a test above, but that one does many more pumps
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the scroll animation
expect
(
controller
.
index
,
1
);
expect
(
find
.
text
(
'LEFT CHILD'
),
findsNothing
);
expect
(
find
.
text
(
'RIGHT CHILD'
),
findsOneWidget
);
});
// A regression test for https://github.com/flutter/flutter/issues/5095
testWidgets
(
'TabBar left/right fling reverse'
,
(
WidgetTester
tester
)
async
{
testWidgets
(
'TabBar left/right fling reverse
(2)
'
,
(
WidgetTester
tester
)
async
{
List
<
String
>
tabs
=
<
String
>[
'LEFT'
,
'RIGHT'
];
await
tester
.
pumpWidget
(
buildLeftRightApp
(
tabs:
tabs
,
value:
'LEFT'
));
...
...
@@ -321,11 +364,20 @@ void main() {
TabController
controller
=
DefaultTabController
.
of
(
tester
.
element
(
find
.
text
(
'LEFT'
)));
expect
(
controller
.
index
,
0
);
Point
flingStart
=
tester
.
getCenter
(
find
.
text
(
'LEFT CHILD'
));
TestGesture
gesture
=
await
tester
.
startGesture
(
flingStart
);
for
(
int
index
=
0
;
index
>
50
;
index
+=
1
)
{
await
gesture
.
moveBy
(
const
Offset
(-
10.0
,
0.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
1
));
}
// End the fling by reversing direction. This should cause not cause
// a change to the selected tab, everything should just settle back to
// to where it started.
Point
flingStart
=
tester
.
getCenter
(
find
.
text
(
'LEFT CHILD'
));
await
tester
.
flingFrom
(
flingStart
,
const
Offset
(-
200.0
,
0.0
),
-
10000.0
);
for
(
int
index
=
0
;
index
>
50
;
index
+=
1
)
{
await
gesture
.
moveBy
(
const
Offset
(
10.0
,
0.0
));
await
tester
.
pump
(
const
Duration
(
milliseconds:
1
));
}
await
gesture
.
up
();
await
tester
.
pump
();
await
tester
.
pump
(
const
Duration
(
seconds:
1
));
// finish the scroll animation
expect
(
controller
.
index
,
0
);
...
...
packages/flutter_test/lib/src/controller.dart
View file @
c6757570
...
...
@@ -313,9 +313,9 @@ class WidgetController {
/// then one frame is pumped each time that amount of time elapses while
/// sending events, or each time an event is synthesised, whichever is rarer.
Future
<
Null
>
flingFrom
(
Point
startLocation
,
Offset
offset
,
double
velocity
,
{
int
pointer:
1
,
Duration
frameInterval:
const
Duration
(
milliseconds:
16
)
})
{
assert
(
offset
.
distance
>
0.0
);
assert
(
velocity
>
0.0
);
// velocity is pixels/second
return
TestAsyncUtils
.
guard
(()
async
{
assert
(
offset
.
distance
>
0.0
);
assert
(
velocity
!=
0.0
);
// velocity is pixels/second
final
TestPointer
p
=
new
TestPointer
(
pointer
);
final
HitTestResult
result
=
hitTestOnBinding
(
startLocation
);
const
int
kMoveCount
=
50
;
// Needs to be >= kHistorySize, see _LeastSquaresVelocityTrackerStrategy
...
...
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