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
35b55d51
Unverified
Commit
35b55d51
authored
Jul 31, 2018
by
xster
Committed by
GitHub
Jul 31, 2018
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Add image stream error handling mechanism (#18424)
parent
e9c8e36b
Changes
4
Hide whitespace changes
Inline
Side-by-side
Showing
4 changed files
with
756 additions
and
294 deletions
+756
-294
image_stream.dart
packages/flutter/lib/src/painting/image_stream.dart
+147
-48
image.dart
packages/flutter/lib/src/widgets/image.dart
+24
-3
image_stream_test.dart
packages/flutter/test/painting/image_stream_test.dart
+274
-240
image_test.dart
packages/flutter/test/widgets/image_test.dart
+311
-3
No files found.
packages/flutter/lib/src/painting/image_stream.dart
View file @
35b55d51
...
...
@@ -68,6 +68,17 @@ class ImageInfo {
/// same stack frame as the call to [ImageStream.addListener]).
typedef
void
ImageListener
(
ImageInfo
image
,
bool
synchronousCall
);
/// Signature for reporting errors when resolving images.
///
/// Used by [ImageStream] and [precacheImage] to report errors.
typedef
void
ImageErrorListener
(
dynamic
exception
,
StackTrace
stackTrace
);
class
_ImageListenerPair
{
_ImageListenerPair
(
this
.
listener
,
this
.
errorListener
);
final
ImageListener
listener
;
final
ImageErrorListener
errorListener
;
}
/// A handle to an image resource.
///
/// ImageStream represents a handle to a [dart:ui.Image] object and its scale
...
...
@@ -96,7 +107,7 @@ class ImageStream extends Diagnosticable {
ImageStreamCompleter
get
completer
=>
_completer
;
ImageStreamCompleter
_completer
;
List
<
ImageListene
r
>
_listeners
;
List
<
_ImageListenerPai
r
>
_listeners
;
/// Assigns a particular [ImageStreamCompleter] to this [ImageStream].
///
...
...
@@ -110,9 +121,14 @@ class ImageStream extends Diagnosticable {
assert
(
_completer
==
null
);
_completer
=
value
;
if
(
_listeners
!=
null
)
{
final
List
<
ImageListene
r
>
initialListeners
=
_listeners
;
final
List
<
_ImageListenerPai
r
>
initialListeners
=
_listeners
;
_listeners
=
null
;
initialListeners
.
forEach
(
_completer
.
addListener
);
for
(
_ImageListenerPair
listenerPair
in
initialListeners
)
{
_completer
.
addListener
(
listenerPair
.
listener
,
onError:
listenerPair
.
errorListener
,
);
}
}
}
...
...
@@ -127,19 +143,32 @@ class ImageStream extends Diagnosticable {
/// occurred. If the listener is added within a render object paint function,
/// then use this flag to avoid calling [RenderObject.markNeedsPaint] during
/// a paint.
void
addListener
(
ImageListener
listener
)
{
///
/// An [ImageErrorListener] can also optionally be added along with the
/// `listener`. If an error occurred, `onError` will be called instead of
/// `listener`.
///
/// Many `listener`s can have the same `onError` and one `listener` can also
/// have multiple `onError` by invoking [addListener] multiple times with
/// a different `onError` each time.
///
/// Repeated `onError` will only be called once when an error occurs.
void
addListener
(
ImageListener
listener
,
{
ImageErrorListener
onError
})
{
if
(
_completer
!=
null
)
return
_completer
.
addListener
(
listener
);
_listeners
??=
<
ImageListene
r
>[];
_listeners
.
add
(
listener
);
return
_completer
.
addListener
(
listener
,
onError:
onError
);
_listeners
??=
<
_ImageListenerPai
r
>[];
_listeners
.
add
(
new
_ImageListenerPair
(
listener
,
onError
)
);
}
/// Stop listening for new concrete [ImageInfo] objects.
/// Stop listening for new concrete [ImageInfo] objects and errors from
/// the `listener`'s associated [ImageErrorListener].
void
removeListener
(
ImageListener
listener
)
{
if
(
_completer
!=
null
)
return
_completer
.
removeListener
(
listener
);
assert
(
_listeners
!=
null
);
_listeners
.
remove
(
listener
);
_listeners
.
removeWhere
((
_ImageListenerPair
listenerPair
)
{
return
listenerPair
.
listener
==
listener
;
});
}
/// Returns an object which can be used with `==` to determine if this
...
...
@@ -164,7 +193,7 @@ class ImageStream extends Diagnosticable {
ifPresent:
_completer
?.
toStringShort
(),
ifNull:
'unresolved'
,
));
properties
.
add
(
new
ObjectFlagProperty
<
List
<
ImageListene
r
>>(
properties
.
add
(
new
ObjectFlagProperty
<
List
<
_ImageListenerPai
r
>>(
'listeners'
,
_listeners
,
ifPresent:
'
${_listeners?.length}
listener
${_listeners?.length == 1 ? "" : "s" }
'
,
...
...
@@ -182,12 +211,14 @@ class ImageStream extends Diagnosticable {
/// [ImageProvider] subclass will return an [ImageStream] and automatically
/// configure it with the right [ImageStreamCompleter] when possible.
abstract
class
ImageStreamCompleter
extends
Diagnosticable
{
final
List
<
ImageListener
>
_listeners
=
<
ImageListener
>[];
ImageInfo
_current
;
final
List
<
_ImageListenerPair
>
_listeners
=
<
_ImageListenerPair
>[];
ImageInfo
_currentImage
;
FlutterErrorDetails
_currentError
;
/// Adds a listener callback that is called whenever a new concrete [ImageInfo]
/// object is available. If a concrete image is already available, this object
/// will call the listener synchronously.
/// object is available or an error is reported. If a concrete image is
/// already available, or if an error has been already reported, this object
/// will call the listener or error listener synchronously.
///
/// If the [ImageStreamCompleter] completes multiple images over its lifetime,
/// this listener will fire multiple times.
...
...
@@ -196,45 +227,116 @@ abstract class ImageStreamCompleter extends Diagnosticable {
/// occurred. If the listener is added within a render object paint function,
/// then use this flag to avoid calling [RenderObject.markNeedsPaint] during
/// a paint.
void
addListener
(
ImageListener
listener
)
{
_listeners
.
add
(
listener
);
if
(
_current
!=
null
)
{
void
addListener
(
ImageListener
listener
,
{
ImageErrorListener
onError
}
)
{
_listeners
.
add
(
new
_ImageListenerPair
(
listener
,
onError
)
);
if
(
_current
Image
!=
null
)
{
try
{
listener
(
_current
,
true
);
listener
(
_current
Image
,
true
);
}
catch
(
exception
,
stack
)
{
_handleImageError
(
'by a synchronously-called image listener'
,
exception
,
stack
);
reportError
(
context:
'by a synchronously-called image listener'
,
exception:
exception
,
stack:
stack
,
);
}
}
if
(
_currentError
!=
null
&&
onError
!=
null
)
{
try
{
onError
(
_currentError
.
exception
,
_currentError
.
stack
);
}
catch
(
exception
,
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
exception:
exception
,
library
:
'image resource service'
,
context:
'by a synchronously-called image error listener'
,
stack:
stack
,
),
);
}
}
}
/// Stop listening for new concrete [ImageInfo] objects.
/// Stop listening for new concrete [ImageInfo] objects and errors from
/// its associated [ImageErrorListener].
void
removeListener
(
ImageListener
listener
)
{
_listeners
.
remove
(
listener
);
_listeners
.
removeWhere
((
_ImageListenerPair
listenerPair
)
{
return
listenerPair
.
listener
==
listener
;
});
}
/// Calls all the registered listeners to notify them of a new image.
@protected
void
setImage
(
ImageInfo
image
)
{
_current
=
image
;
_current
Image
=
image
;
if
(
_listeners
.
isEmpty
)
return
;
final
List
<
ImageListener
>
localListeners
=
new
List
<
ImageListener
>.
from
(
_listeners
);
final
List
<
ImageListener
>
localListeners
=
_listeners
.
map
<
ImageListener
>(
(
_ImageListenerPair
listenerPair
)
=>
listenerPair
.
listener
).
toList
();
for
(
ImageListener
listener
in
localListeners
)
{
try
{
listener
(
image
,
false
);
}
catch
(
exception
,
stack
)
{
_handleImageError
(
'by an image listener'
,
exception
,
stack
);
reportError
(
context:
'by an image listener'
,
exception:
exception
,
stack:
stack
,
);
}
}
}
void
_handleImageError
(
String
context
,
dynamic
exception
,
dynamic
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
/// Calls all the registered error listeners to notify them of an error that
/// occurred while resolving the image.
///
/// If the same error listener is attached with multiple listeners, that
/// error listener will only be notified once.
///
/// If no error listeners are attached, a [FlutterError] will be reported
/// instead.
@protected
void
reportError
({
String
context
,
dynamic
exception
,
StackTrace
stack
,
InformationCollector
informationCollector
,
bool
silent
=
false
,
})
{
_currentError
=
new
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'image resource service'
,
context:
context
));
context:
context
,
informationCollector:
informationCollector
,
silent:
silent
,
);
// Many listeners can have the same error listener. De-duplicate.
final
List
<
ImageErrorListener
>
localErrorListeners
=
_listeners
.
map
<
ImageErrorListener
>(
(
_ImageListenerPair
listenerPair
)
=>
listenerPair
.
errorListener
).
where
(
(
ImageErrorListener
errorListener
)
=>
errorListener
!=
null
).
toList
();
if
(
localErrorListeners
.
isEmpty
)
{
FlutterError
.
reportError
(
_currentError
);
}
else
{
for
(
ImageErrorListener
errorListener
in
localErrorListeners
)
{
try
{
errorListener
(
exception
,
stack
);
}
catch
(
exception
,
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
context:
'by an image error listener'
,
library
:
'image resource service'
,
exception:
exception
,
stack:
stack
,
),
);
}
}
}
}
/// Accumulates a list of strings describing the object's state. Subclasses
...
...
@@ -242,8 +344,8 @@ abstract class ImageStreamCompleter extends Diagnosticable {
@override
void
debugFillProperties
(
DiagnosticPropertiesBuilder
description
)
{
super
.
debugFillProperties
(
description
);
description
.
add
(
new
DiagnosticsProperty
<
ImageInfo
>(
'current'
,
_current
,
ifNull:
'unresolved'
,
showName:
false
));
description
.
add
(
new
ObjectFlagProperty
<
List
<
ImageListene
r
>>(
description
.
add
(
new
DiagnosticsProperty
<
ImageInfo
>(
'current'
,
_current
Image
,
ifNull:
'unresolved'
,
showName:
false
));
description
.
add
(
new
ObjectFlagProperty
<
List
<
_ImageListenerPai
r
>>(
'listeners'
,
_listeners
,
ifPresent:
'
${_listeners?.length}
listener
${_listeners?.length == 1 ? "" : "s" }
'
,
...
...
@@ -271,14 +373,13 @@ class OneFrameImageStreamCompleter extends ImageStreamCompleter {
OneFrameImageStreamCompleter
(
Future
<
ImageInfo
>
image
,
{
InformationCollector
informationCollector
})
:
assert
(
image
!=
null
)
{
image
.
then
<
void
>(
setImage
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
reportError
(
context:
'resolving a single-frame image stream'
,
exception:
error
,
stack:
stack
,
library
:
'services'
,
context:
'resolving a single-frame image stream'
,
informationCollector:
informationCollector
,
silent:
true
,
)
)
;
);
});
}
}
...
...
@@ -334,14 +435,13 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
_framesEmitted
=
0
,
_timer
=
null
{
codec
.
then
<
void
>(
_handleCodecReady
,
onError:
(
dynamic
error
,
StackTrace
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
reportError
(
context:
'resolving an image codec'
,
exception:
error
,
stack:
stack
,
library
:
'services'
,
context:
'resolving an image codec'
,
informationCollector:
informationCollector
,
silent:
true
,
)
)
;
);
});
}
...
...
@@ -397,14 +497,13 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
try
{
_nextFrame
=
await
_codec
.
getNextFrame
();
}
catch
(
exception
,
stack
)
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
exception:
exception
,
stack:
stack
,
library
:
'services'
,
context:
'resolving an image frame'
,
informationCollector:
_informationCollector
,
silent:
true
,
));
reportError
(
context:
'resolving an image frame'
,
exception:
exception
,
stack:
stack
,
informationCollector:
_informationCollector
,
silent:
true
,
);
return
;
}
if
(
_codec
.
frameCount
==
1
)
{
...
...
@@ -424,11 +523,11 @@ class MultiFrameImageStreamCompleter extends ImageStreamCompleter {
bool
get
_hasActiveListeners
=>
_listeners
.
isNotEmpty
;
@override
void
addListener
(
ImageListener
listener
)
{
void
addListener
(
ImageListener
listener
,
{
ImageErrorListener
onError
}
)
{
if
(!
_hasActiveListeners
&&
_codec
!=
null
)
{
_decodeNextFrameAndSchedule
();
}
super
.
addListener
(
listener
);
super
.
addListener
(
listener
,
onError:
onError
);
}
@override
...
...
packages/flutter/lib/src/widgets/image.dart
View file @
35b55d51
...
...
@@ -55,7 +55,7 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
/// Prefetches an image into the image cache.
///
/// Returns a [Future] that will complete when the first image yielded by the
/// [ImageProvider] is available.
/// [ImageProvider] is available
or failed to load
.
///
/// If the image is later used by an [Image] or [BoxDecoration] or [FadeInImage],
/// it will probably be loaded faster. The consumer of the image does not need
...
...
@@ -65,17 +65,38 @@ ImageConfiguration createLocalImageConfiguration(BuildContext context, { Size si
/// The [BuildContext] and [Size] are used to select an image configuration
/// (see [createLocalImageConfiguration]).
///
/// The `onError` argument can be used to manually handle errors while precaching.
///
/// See also:
///
/// * [ImageCache], which holds images that may be reused.
Future
<
Null
>
precacheImage
(
ImageProvider
provider
,
BuildContext
context
,
{
Size
size
})
{
Future
<
Null
>
precacheImage
(
ImageProvider
provider
,
BuildContext
context
,
{
Size
size
,
ImageErrorListener
onError
,
})
{
final
ImageConfiguration
config
=
createLocalImageConfiguration
(
context
,
size:
size
);
final
Completer
<
Null
>
completer
=
new
Completer
<
Null
>();
final
ImageStream
stream
=
provider
.
resolve
(
config
);
void
listener
(
ImageInfo
image
,
bool
sync
)
{
completer
.
complete
();
}
stream
.
addListener
(
listener
);
void
errorListener
(
dynamic
exception
,
StackTrace
stackTrace
)
{
completer
.
complete
();
if
(
onError
!=
null
)
{
onError
(
exception
,
stackTrace
);
}
else
{
FlutterError
.
reportError
(
new
FlutterErrorDetails
(
context:
'image failed to precache'
,
library
:
'image resource service'
,
exception:
exception
,
stack:
stackTrace
,
silent:
true
,
));
}
}
stream
.
addListener
(
listener
,
onError:
errorListener
);
completer
.
future
.
then
((
Null
_
)
{
stream
.
removeListener
(
listener
);
});
return
completer
.
future
;
}
...
...
packages/flutter/test/painting/image_stream_test.dart
View file @
35b55d51
...
...
@@ -151,288 +151,322 @@ void main() {
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame
.
image
)]));
});
testWidgets
(
'ImageStream emits frames (animated images)'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'ImageStream emits frames (animated images)'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// We are waiting for the next animation tick, so at this point no frames
// should have been emitted.
expect
(
emittedImages
.
length
,
0
);
await
tester
.
pump
();
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// The duration for the current frame was 200ms, so we don't emit the next
// frame yet even though it is ready.
expect
(
emittedImages
.
length
,
1
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
// Let the pending timer for the next frame to complete so we can cleanly
// quit the test without pending timers.
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
});
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// We are waiting for the next animation tick, so at this point no frames
// should have been emitted.
expect
(
emittedImages
.
length
,
0
);
await
tester
.
pump
();
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
// The duration for the current frame was 200ms, so we don't emit the next
// frame yet even though it is ready.
expect
(
emittedImages
.
length
,
1
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
100
));
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
// Let the pending timer for the next frame to complete so we can cleanly
// quit the test without pending timers.
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
});
testWidgets
(
'animation wraps back'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'animation wraps back'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 3rd frame
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
new
ImageInfo
(
image:
frame1
.
image
),
]));
// Let the pending timer for the next frame to complete so we can cleanly
// quit the test without pending timers.
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
});
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 3rd frame
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
new
ImageInfo
(
image:
frame1
.
image
),
]));
// Let the pending timer for the next frame to complete so we can cleanly
// quit the test without pending timers.
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
});
testWidgets
(
'animation doesnt repeat more than specified'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
0
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'animation doesnt repeat more than specified'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
0
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
final
List
<
ImageInfo
>
emittedImages
=
<
ImageInfo
>[];
imageStream
.
addListener
((
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages
.
add
(
image
);
});
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
mockCodec
.
completeNextFrame
(
frame1
);
// allow another frame to complete (but we shouldn't be asking for it as
// this animation should not repeat.
await
tester
.
idle
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
mockCodec
.
completeNextFrame
(
frame1
);
// allow another frame to complete (but we shouldn't be asking for it as
// this animation should not repeat.
await
tester
.
idle
();
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
});
expect
(
emittedImages
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
});
testWidgets
(
'frames are only decoded when there are active listeners'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'frames are only decoded when there are active listeners'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
imageStream
.
removeListener
(
listener
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 2nd frame.
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
imageStream
.
removeListener
(
listener
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 2nd frame.
// Decoding of the 3rd frame should not start as there are no registered
// listeners to the stream
expect
(
mockCodec
.
numFramesAsked
,
2
);
// Decoding of the 3rd frame should not start as there are no registered
// listeners to the stream
expect
(
mockCodec
.
numFramesAsked
,
2
);
imageStream
.
addListener
(
listener
);
await
tester
.
idle
();
// let nextFrameFuture complete
expect
(
mockCodec
.
numFramesAsked
,
3
);
});
imageStream
.
addListener
(
listener
);
await
tester
.
idle
();
// let nextFrameFuture complete
expect
(
mockCodec
.
numFramesAsked
,
3
);
});
testWidgets
(
'multiple stream listeners'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'multiple stream listeners'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
List
<
ImageInfo
>
emittedImages1
=
<
ImageInfo
>[];
final
ImageListener
listener1
=
(
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages1
.
add
(
image
);
};
final
List
<
ImageInfo
>
emittedImages2
=
<
ImageInfo
>[];
final
ImageListener
listener2
=
(
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages2
.
add
(
image
);
};
imageStream
.
addListener
(
listener1
);
imageStream
.
addListener
(
listener2
);
final
List
<
ImageInfo
>
emittedImages1
=
<
ImageInfo
>[];
final
ImageListener
listener1
=
(
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages1
.
add
(
image
);
};
final
List
<
ImageInfo
>
emittedImages2
=
<
ImageInfo
>[];
final
ImageListener
listener2
=
(
ImageInfo
image
,
bool
synchronousCall
)
{
emittedImages2
.
add
(
image
);
};
imageStream
.
addListener
(
listener1
);
imageStream
.
addListener
(
listener2
);
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
expect
(
emittedImages1
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
expect
(
emittedImages2
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// next app frame will schedule a timer.
imageStream
.
removeListener
(
listener1
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 2nd frame.
expect
(
emittedImages1
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
expect
(
emittedImages2
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
});
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
expect
(
emittedImages1
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
expect
(
emittedImages2
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// next app frame will schedule a timer.
imageStream
.
removeListener
(
listener1
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
400
));
// emit 2nd frame.
expect
(
emittedImages1
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
)]));
expect
(
emittedImages2
,
equals
(<
ImageInfo
>[
new
ImageInfo
(
image:
frame1
.
image
),
new
ImageInfo
(
image:
frame2
.
image
),
]));
});
testWidgets
(
'timer is canceled when listeners are removed'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'timer is canceled when listeners are removed'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
imageStream
.
removeListener
(
listener
);
// The test framework will fail this if there are pending timers at this
// point.
});
imageStream
.
removeListener
(
listener
);
// The test framework will fail this if there are pending timers at this
// point.
});
testWidgets
(
'timeDilation affects animation frame timers'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
testWidgets
(
'timeDilation affects animation frame timers'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
2
;
mockCodec
.
repetitionCount
=
-
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageStreamCompleter
imageStream
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
final
ImageListener
listener
=
(
ImageInfo
image
,
bool
synchronousCall
)
{};
imageStream
.
addListener
(
listener
);
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
codecCompleter
.
complete
(
mockCodec
);
await
tester
.
idle
();
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
final
FrameInfo
frame1
=
new
FakeFrameInfo
(
20
,
10
,
const
Duration
(
milliseconds:
200
));
final
FrameInfo
frame2
=
new
FakeFrameInfo
(
200
,
100
,
const
Duration
(
milliseconds:
400
));
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
timeDilation
=
2.0
;
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// schedule next app frame
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
// Decoding of the 3rd frame should not start after 200 ms, as time is
// dilated by a factor of 2.
expect
(
mockCodec
.
numFramesAsked
,
2
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
expect
(
mockCodec
.
numFramesAsked
,
3
);
});
mockCodec
.
completeNextFrame
(
frame1
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// first animation frame shows on first app frame.
testWidgets
(
'error handlers can intercept errors'
,
(
WidgetTester
tester
)
async
{
final
MockCodec
mockCodec
=
new
MockCodec
();
mockCodec
.
frameCount
=
1
;
final
Completer
<
Codec
>
codecCompleter
=
new
Completer
<
Codec
>();
timeDilation
=
2.0
;
mockCodec
.
completeNextFrame
(
frame2
);
await
tester
.
idle
();
// let nextFrameFuture complete
await
tester
.
pump
();
// schedule next app frame
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
// Decoding of the 3rd frame should not start after 200 ms, as time is
// dilated by a factor of 2.
expect
(
mockCodec
.
numFramesAsked
,
2
);
await
tester
.
pump
(
const
Duration
(
milliseconds:
200
));
// emit 2nd frame.
expect
(
mockCodec
.
numFramesAsked
,
3
);
final
ImageStreamCompleter
streamUnderTest
=
new
MultiFrameImageStreamCompleter
(
codec:
codecCompleter
.
future
,
scale:
1.0
,
);
});
dynamic
capturedException
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
};
streamUnderTest
.
addListener
(
(
ImageInfo
image
,
bool
synchronousCall
)
{},
onError:
errorListener
,
);
codecCompleter
.
complete
(
mockCodec
);
// MultiFrameImageStreamCompleter only sets an error handler for the next
// frame future after the codec future has completed.
// Idling here lets the MultiFrameImageStreamCompleter advance and set the
// error handler for the nextFrame future.
await
tester
.
idle
();
mockCodec
.
failNextFrame
(
'frame completion error'
);
await
tester
.
idle
();
// No exception is passed up.
expect
(
tester
.
takeException
(),
isNull
);
expect
(
capturedException
,
'frame completion error'
);
});
}
packages/flutter/test/widgets/image_test.dart
View file @
35b55d51
...
...
@@ -316,6 +316,280 @@ void main() {
expect
(
image
.
toString
(),
equalsIgnoringHashCodes
(
'_ImageState#00000(lifecycle state: defunct, not mounted, stream: ImageStream#00000(OneFrameImageStreamCompleter#00000, [100×100] @ 1.0x, 0 listeners), pixels: [100×100] @ 1.0x)'
));
});
testWidgets
(
'Stream completer errors can be listened to by attaching before resolving'
,
(
WidgetTester
tester
)
async
{
dynamic
capturedException
;
StackTrace
capturedStackTrace
;
ImageInfo
capturedImage
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
capturedStackTrace
=
stackTrace
;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect
(
capturedException
,
testException
);
expect
(
capturedStackTrace
,
testStack
);
// If there is an error listener, there should be no FlutterError reported.
expect
(
tester
.
takeException
(),
isNull
);
});
testWidgets
(
'Stream completer errors can be listened to by attaching after resolving'
,
(
WidgetTester
tester
)
async
{
dynamic
capturedException
;
StackTrace
capturedStackTrace
;
dynamic
reportedException
;
StackTrace
reportedStackTrace
;
ImageInfo
capturedImage
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
capturedStackTrace
=
stackTrace
;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
FlutterError
.
onError
=
(
FlutterErrorDetails
flutterError
)
{
reportedException
=
flutterError
.
exception
;
reportedStackTrace
=
flutterError
.
stack
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
final
ImageStream
streamUnderTest
=
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
// Since there's no listeners attached yet, report error up via
// FlutterError.
expect
(
reportedException
,
testException
);
expect
(
reportedStackTrace
,
testStack
);
streamUnderTest
.
addListener
(
listener
,
onError:
errorListener
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect
(
capturedException
,
testException
);
expect
(
capturedStackTrace
,
testStack
);
});
testWidgets
(
'Duplicate listener registration does not affect error listeners'
,
(
WidgetTester
tester
)
async
{
dynamic
capturedException
;
StackTrace
capturedStackTrace
;
ImageInfo
capturedImage
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
capturedStackTrace
=
stackTrace
;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
// Add the exact same listener a second time without the errorListener.
imageProvider
.
_streamCompleter
.
addListener
(
listener
);
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect
(
capturedException
,
testException
);
expect
(
capturedStackTrace
,
testStack
);
// If there is an error listener, there should be no FlutterError reported.
expect
(
tester
.
takeException
(),
isNull
);
});
testWidgets
(
'Duplicate error listeners are all called'
,
(
WidgetTester
tester
)
async
{
dynamic
capturedException
;
StackTrace
capturedStackTrace
;
ImageInfo
capturedImage
;
int
errorListenerCalled
=
0
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
capturedStackTrace
=
stackTrace
;
errorListenerCalled
++;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
// Add the exact same errorListener a second time.
imageProvider
.
_streamCompleter
.
addListener
(
null
,
onError:
errorListener
);
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
// The image stream error handler should have the original exception.
expect
(
capturedException
,
testException
);
expect
(
capturedStackTrace
,
testStack
);
expect
(
errorListenerCalled
,
2
);
// If there is an error listener, there should be no FlutterError reported.
expect
(
tester
.
takeException
(),
isNull
);
});
testWidgets
(
'Error listeners are removed along with listeners'
,
(
WidgetTester
tester
)
async
{
bool
errorListenerCalled
=
false
;
dynamic
reportedException
;
StackTrace
reportedStackTrace
;
ImageInfo
capturedImage
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
errorListenerCalled
=
true
;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
FlutterError
.
onError
=
(
FlutterErrorDetails
flutterError
)
{
reportedException
=
flutterError
.
exception
;
reportedStackTrace
=
flutterError
.
stack
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
// Now remove the listener the error listener is attached to.
// Don't explicitly remove the error listener.
imageProvider
.
_streamCompleter
.
removeListener
(
listener
);
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
expect
(
errorListenerCalled
,
false
);
// Since the error listener is removed, bubble up to FlutterError.
expect
(
reportedException
,
testException
);
expect
(
reportedStackTrace
,
testStack
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
});
testWidgets
(
'Removing duplicate listeners removes error listeners'
,
(
WidgetTester
tester
)
async
{
bool
errorListenerCalled
=
false
;
dynamic
reportedException
;
StackTrace
reportedStackTrace
;
ImageInfo
capturedImage
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
errorListenerCalled
=
true
;
};
final
ImageListener
listener
=
(
ImageInfo
info
,
bool
synchronous
)
{
capturedImage
=
info
;
};
FlutterError
.
onError
=
(
FlutterErrorDetails
flutterError
)
{
reportedException
=
flutterError
.
exception
;
reportedStackTrace
=
flutterError
.
stack
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
// Duplicates the same set of listener and errorListener.
imageProvider
.
_streamCompleter
.
addListener
(
listener
,
onError:
errorListener
);
// Now remove all specified listeners and associated error listeners.
// Don't explicitly remove the error listener.
imageProvider
.
_streamCompleter
.
removeListener
(
listener
);
ImageConfiguration
configuration
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
configuration
=
createLocalImageConfiguration
(
context
);
return
new
Container
();
},
),
);
imageProvider
.
resolve
(
configuration
);
imageProvider
.
fail
(
testException
,
testStack
);
expect
(
tester
.
binding
.
microtaskCount
,
1
);
await
tester
.
idle
();
// Let the failed completer's future hit the stream completer.
expect
(
tester
.
binding
.
microtaskCount
,
0
);
expect
(
errorListenerCalled
,
false
);
// Since the error listener is removed, bubble up to FlutterError.
expect
(
reportedException
,
testException
);
expect
(
reportedStackTrace
,
testStack
);
expect
(
capturedImage
,
isNull
);
// The image stream listeners should never be called.
});
testWidgets
(
'Image.memory control test'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
new
Image
.
memory
(
new
Uint8List
.
fromList
(
kTransparentImage
),
excludeFromSemantics:
true
,));
});
...
...
@@ -356,6 +630,36 @@ void main() {
expect
(
isSync
,
isTrue
);
});
testWidgets
(
'Precache completes with onError on error'
,
(
WidgetTester
tester
)
async
{
dynamic
capturedException
;
StackTrace
capturedStackTrace
;
final
ImageErrorListener
errorListener
=
(
dynamic
exception
,
StackTrace
stackTrace
)
{
capturedException
=
exception
;
capturedStackTrace
=
stackTrace
;
};
final
Exception
testException
=
new
Exception
(
'cannot resolve host'
);
final
StackTrace
testStack
=
StackTrace
.
current
;
final
TestImageProvider
imageProvider
=
new
TestImageProvider
();
Future
<
Null
>
precache
;
await
tester
.
pumpWidget
(
new
Builder
(
builder:
(
BuildContext
context
)
{
precache
=
precacheImage
(
imageProvider
,
context
,
onError:
errorListener
);
return
new
Container
();
}
)
);
imageProvider
.
fail
(
testException
,
testStack
);
await
precache
;
// The image stream error handler should have the original exception.
expect
(
capturedException
,
testException
);
expect
(
capturedStackTrace
,
testStack
);
// If there is an error listener, there should be no FlutterError reported.
expect
(
tester
.
takeException
(),
isNull
);
});
testWidgets
(
'TickerMode controls stream registration'
,
(
WidgetTester
tester
)
async
{
final
TestImageStreamCompleter
imageStreamCompleter
=
new
TestImageStreamCompleter
();
final
Image
image
=
new
Image
(
...
...
@@ -524,16 +828,20 @@ class TestImageProvider extends ImageProvider<TestImageProvider> {
_completer
.
complete
(
new
ImageInfo
(
image:
new
TestImage
()));
}
void
fail
(
dynamic
exception
,
StackTrace
stackTrace
)
{
_completer
.
completeError
(
exception
,
stackTrace
);
}
@override
String
toString
()
=>
'
${describeIdentity(this)}
()'
;
}
class
TestImageStreamCompleter
extends
ImageStreamCompleter
{
final
List
<
ImageListener
>
listeners
=
<
ImageListener
>
[]
;
final
Map
<
ImageListener
,
ImageErrorListener
>
listeners
=
<
ImageListener
,
ImageErrorListener
>
{}
;
@override
void
addListener
(
ImageListener
listener
)
{
listeners
.
add
(
listener
)
;
void
addListener
(
ImageListener
listener
,
{
ImageErrorListener
onError
}
)
{
listeners
[
listener
]
=
onError
;
}
@override
...
...
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