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
ea5cfe09
Unverified
Commit
ea5cfe09
authored
May 19, 2023
by
chunhtai
Committed by
GitHub
May 19, 2023
Browse files
Options
Browse Files
Download
Email Patches
Plain Diff
Properly cleans up routes (#126453)
fixes
https://github.com/flutter/flutter/issues/126100
parent
af83c767
Changes
6
Hide whitespace changes
Inline
Side-by-side
Showing
6 changed files
with
143 additions
and
66 deletions
+143
-66
heroes.dart
packages/flutter/lib/src/widgets/heroes.dart
+37
-30
navigator.dart
packages/flutter/lib/src/widgets/navigator.dart
+94
-29
routes.dart
packages/flutter/lib/src/widgets/routes.dart
+6
-1
about_test.dart
packages/flutter/test/material/about_test.dart
+4
-4
navigator_test.dart
packages/flutter/test/widgets/navigator_test.dart
+1
-1
routes_test.dart
packages/flutter/test/widgets/routes_test.dart
+1
-1
No files found.
packages/flutter/lib/src/widgets/heroes.dart
View file @
ea5cfe09
...
@@ -849,40 +849,47 @@ class HeroController extends NavigatorObserver {
...
@@ -849,40 +849,47 @@ class HeroController extends NavigatorObserver {
HeroFlightDirection
flightType
,
HeroFlightDirection
flightType
,
bool
isUserGestureTransition
,
bool
isUserGestureTransition
,
)
{
)
{
if
(
toRoute
!=
fromRoute
&&
toRoute
is
PageRoute
<
dynamic
>
&&
fromRoute
is
PageRoute
<
dynamic
>)
{
if
(
toRoute
==
fromRoute
||
final
PageRoute
<
dynamic
>
from
=
fromRoute
;
toRoute
is
!
PageRoute
<
dynamic
>
||
final
PageRoute
<
dynamic
>
to
=
toRoute
;
fromRoute
is
!
PageRoute
<
dynamic
>)
{
return
;
}
// A user gesture may have already completed the pop, or we might be the initial route
final
PageRoute
<
dynamic
>
from
=
fromRoute
;
switch
(
flightType
)
{
final
PageRoute
<
dynamic
>
to
=
toRoute
;
case
HeroFlightDirection
.
pop
:
if
(
from
.
animation
!.
value
==
0.0
)
{
return
;
}
case
HeroFlightDirection
.
push
:
if
(
to
.
animation
!.
value
==
1.0
)
{
return
;
}
}
// For pop transitions driven by a user gesture: if the "to" page has
// A user gesture may have already completed the pop, or we might be the initial route
// maintainState = true, then the hero's final dimensions can be measured
switch
(
flightType
)
{
// immediately because their page's layout is still valid.
case
HeroFlightDirection
.
pop
:
if
(
isUserGestureTransition
&&
flightType
==
HeroFlightDirection
.
pop
&&
to
.
maintainState
)
{
if
(
from
.
animation
!.
value
==
0.0
)
{
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
return
;
}
else
{
}
// Otherwise, delay measuring until the end of the next frame to allow
case
HeroFlightDirection
.
push
:
// the 'to' route to build and layout.
if
(
to
.
animation
!.
value
==
1.0
)
{
return
;
}
}
// Putting a route offstage changes its animation value to 1.0. Once this
// For pop transitions driven by a user gesture: if the "to" page has
// frame completes, we'll know where the heroes in the `to` route are
// maintainState = true, then the hero's final dimensions can be measured
// going to end up, and the `to` route will go back onstage.
// immediately because their page's layout is still valid.
to
.
offstage
=
to
.
animation
!.
value
==
0.0
;
if
(
isUserGestureTransition
&&
flightType
==
HeroFlightDirection
.
pop
&&
to
.
maintainState
)
{
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
}
else
{
// Otherwise, delay measuring until the end of the next frame to allow
// the 'to' route to build and layout.
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
// Putting a route offstage changes its animation value to 1.0. Once this
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
// frame completes, we'll know where the heroes in the `to` route are
});
// going to end up, and the `to` route will go back onstage.
}
to
.
offstage
=
to
.
animation
!.
value
==
0.0
;
WidgetsBinding
.
instance
.
addPostFrameCallback
((
Duration
value
)
{
if
(
from
.
navigator
==
null
||
to
.
navigator
==
null
)
{
return
;
}
_startHeroTransition
(
from
,
to
,
flightType
,
isUserGestureTransition
);
});
}
}
}
}
...
...
packages/flutter/lib/src/widgets/navigator.dart
View file @
ea5cfe09
...
@@ -2786,6 +2786,7 @@ class Navigator extends StatefulWidget {
...
@@ -2786,6 +2786,7 @@ class Navigator extends StatefulWidget {
// \ |
// \ |
// dispose*
// dispose*
// |
// |
// disposing
// |
// |
// disposed
// disposed
// |
// |
...
@@ -2821,6 +2822,9 @@ enum _RouteLifecycle {
...
@@ -2821,6 +2822,9 @@ enum _RouteLifecycle {
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
removing
,
// we are waiting for subsequent routes to be done animating, then will switch to dispose
// routes that are completely removed from the navigator and overlay.
// routes that are completely removed from the navigator and overlay.
dispose
,
// we will dispose the route momentarily
dispose
,
// we will dispose the route momentarily
disposing
,
// The entry is waiting for its widget subtree to be disposed
// first. It is stored in _entryWaitingForSubTreeDisposal while
// awaiting that.
disposed
,
// we have disposed the route
disposed
,
// we have disposed the route
}
}
...
@@ -3047,9 +3051,26 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -3047,9 +3051,26 @@ class _RouteEntry extends RouteTransitionRecord {
currentState
=
_RouteLifecycle
.
dispose
;
currentState
=
_RouteLifecycle
.
dispose
;
}
}
void
dispose
()
{
/// Disposes this route entry and its [route] immediately.
///
/// This method does not wait for the widget subtree of the [route] to unmount
/// before disposing.
void
forcedDispose
()
{
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposed
.
index
);
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposed
.
index
);
currentState
=
_RouteLifecycle
.
disposed
;
currentState
=
_RouteLifecycle
.
disposed
;
route
.
dispose
();
}
/// Disposes this route entry and its [route].
///
/// This method waits for the widget subtree of the [route] to unmount before
/// disposing. If subtree is already unmounted, this method calls
/// [forcedDispose] immediately.
///
/// Use [forcedDispose] if the [route] need to be disposed immediately.
void
dispose
()
{
assert
(
currentState
.
index
<
_RouteLifecycle
.
disposing
.
index
);
currentState
=
_RouteLifecycle
.
disposing
;
// If the overlay entries are still mounted, widgets in the route's subtree
// If the overlay entries are still mounted, widgets in the route's subtree
// may still reference resources from the route and we delay disposal of
// may still reference resources from the route and we delay disposal of
...
@@ -3060,24 +3081,43 @@ class _RouteEntry extends RouteTransitionRecord {
...
@@ -3060,24 +3081,43 @@ class _RouteEntry extends RouteTransitionRecord {
final
Iterable
<
OverlayEntry
>
mountedEntries
=
route
.
overlayEntries
.
where
((
OverlayEntry
e
)
=>
e
.
mounted
);
final
Iterable
<
OverlayEntry
>
mountedEntries
=
route
.
overlayEntries
.
where
((
OverlayEntry
e
)
=>
e
.
mounted
);
if
(
mountedEntries
.
isEmpty
)
{
if
(
mountedEntries
.
isEmpty
)
{
route
.
dispose
();
forcedDispose
();
}
else
{
return
;
int
mounted
=
mountedEntries
.
length
;
}
assert
(
mounted
>
0
);
for
(
final
OverlayEntry
entry
in
mountedEntries
)
{
int
mounted
=
mountedEntries
.
length
;
late
VoidCallback
listener
;
assert
(
mounted
>
0
);
listener
=
()
{
final
NavigatorState
navigator
=
route
.
_navigator
!;
assert
(
mounted
>
0
);
navigator
.
_entryWaitingForSubTreeDisposal
.
add
(
this
);
assert
(!
entry
.
mounted
);
for
(
final
OverlayEntry
entry
in
mountedEntries
)
{
mounted
--;
late
VoidCallback
listener
;
entry
.
removeListener
(
listener
);
listener
=
()
{
if
(
mounted
==
0
)
{
assert
(
mounted
>
0
);
assert
(
route
.
overlayEntries
.
every
((
OverlayEntry
e
)
=>
!
e
.
mounted
));
assert
(!
entry
.
mounted
);
route
.
dispose
();
mounted
--;
}
entry
.
removeListener
(
listener
);
};
if
(
mounted
==
0
)
{
entry
.
addListener
(
listener
);
assert
(
route
.
overlayEntries
.
every
((
OverlayEntry
e
)
=>
!
e
.
mounted
));
}
// This is a listener callback of one of the overlayEntries in this
// route. Disposing the route also disposes its overlayEntries and
// violates the rule that a change notifier can't be disposed during
// its notifying callback.
//
// Use a microtask to ensure the overlayEntries have finished
// notifying their listeners before disposing.
return
scheduleMicrotask
(()
{
if
(!
navigator
.
_entryWaitingForSubTreeDisposal
.
remove
(
this
))
{
// This route must have been destroyed as a result of navigator
// force dispose.
assert
(
route
.
_navigator
==
null
&&
!
navigator
.
mounted
);
return
;
}
assert
(
currentState
==
_RouteLifecycle
.
disposing
);
forcedDispose
();
});
}
};
entry
.
addListener
(
listener
);
}
}
}
}
...
@@ -3257,6 +3297,15 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
...
@@ -3257,6 +3297,15 @@ class _NavigatorReplaceObservation extends _NavigatorObservation {
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
,
RestorationMixin
{
class
NavigatorState
extends
State
<
Navigator
>
with
TickerProviderStateMixin
,
RestorationMixin
{
late
GlobalKey
<
OverlayState
>
_overlayKey
;
late
GlobalKey
<
OverlayState
>
_overlayKey
;
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
List
<
_RouteEntry
>
_history
=
<
_RouteEntry
>[];
/// A set for entries that are waiting to dispose until their subtrees are
/// disposed.
///
/// These entries are not considered to be in the _history and will usually
/// remove themselves from this set once they can dispose.
///
/// The navigator keep track of these entries so that, in case the navigator
/// itself is disposed, it can dispose these entries immediately.
final
Set
<
_RouteEntry
>
_entryWaitingForSubTreeDisposal
=
<
_RouteEntry
>{};
final
_HistoryProperty
_serializableHistory
=
_HistoryProperty
();
final
_HistoryProperty
_serializableHistory
=
_HistoryProperty
();
final
Queue
<
_NavigatorObservation
>
_observedRouteAdditions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteAdditions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteDeletions
=
Queue
<
_NavigatorObservation
>();
final
Queue
<
_NavigatorObservation
>
_observedRouteDeletions
=
Queue
<
_NavigatorObservation
>();
...
@@ -3338,9 +3387,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3338,9 +3387,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
registerForRestoration
(
_serializableHistory
,
'history'
);
registerForRestoration
(
_serializableHistory
,
'history'
);
// Delete everything in the old history and clear the overlay.
// Delete everything in the old history and clear the overlay.
while
(
_history
.
isNotEmpty
)
{
_forcedDisposeAllRouteEntries
();
_history
.
removeLast
().
dispose
();
}
assert
(
_history
.
isEmpty
);
assert
(
_history
.
isEmpty
);
_overlayKey
=
GlobalKey
<
OverlayState
>();
_overlayKey
=
GlobalKey
<
OverlayState
>();
...
@@ -3423,6 +3470,28 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3423,6 +3470,28 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
}
}
}
}
/// Dispose all lingering router entries immediately.
void
_forcedDisposeAllRouteEntries
()
{
_entryWaitingForSubTreeDisposal
.
removeWhere
((
_RouteEntry
entry
)
{
entry
.
forcedDispose
();
return
true
;
});
while
(
_history
.
isNotEmpty
)
{
_disposeRouteEntry
(
_history
.
removeLast
(),
graceful:
false
);
}
}
static
void
_disposeRouteEntry
(
_RouteEntry
entry
,
{
required
bool
graceful
})
{
for
(
final
OverlayEntry
overlayEntry
in
entry
.
route
.
overlayEntries
)
{
overlayEntry
.
remove
();
}
if
(
graceful
)
{
entry
.
dispose
();
}
else
{
entry
.
forcedDispose
();
}
}
void
_updateHeroController
(
HeroController
?
newHeroController
)
{
void
_updateHeroController
(
HeroController
?
newHeroController
)
{
if
(
_heroControllerFromScope
!=
newHeroController
)
{
if
(
_heroControllerFromScope
!=
newHeroController
)
{
if
(
newHeroController
!=
null
)
{
if
(
newHeroController
!=
null
)
{
...
@@ -3595,9 +3664,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -3595,9 +3664,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
}());
}());
_updateHeroController
(
null
);
_updateHeroController
(
null
);
focusNode
.
dispose
();
focusNode
.
dispose
();
for
(
final
_RouteEntry
entry
in
_history
)
{
_forcedDisposeAllRouteEntries
();
entry
.
dispose
();
}
_rawNextPagelessRestorationScopeId
.
dispose
();
_rawNextPagelessRestorationScopeId
.
dispose
();
_serializableHistory
.
dispose
();
_serializableHistory
.
dispose
();
userGestureInProgressNotifier
.
dispose
();
userGestureInProgressNotifier
.
dispose
();
...
@@ -4022,6 +4089,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -4022,6 +4089,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
// Delay disposal until didChangeNext/didChangePrevious have been sent.
// Delay disposal until didChangeNext/didChangePrevious have been sent.
toBeDisposed
.
add
(
_history
.
removeAt
(
index
));
toBeDisposed
.
add
(
_history
.
removeAt
(
index
));
entry
=
next
;
entry
=
next
;
case
_RouteLifecycle
.
disposing
:
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
disposed
:
case
_RouteLifecycle
.
staging
:
case
_RouteLifecycle
.
staging
:
assert
(
false
);
assert
(
false
);
...
@@ -4051,10 +4119,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
...
@@ -4051,10 +4119,7 @@ class NavigatorState extends State<Navigator> with TickerProviderStateMixin, Res
// Lastly, removes the overlay entries of all marked entries and disposes
// Lastly, removes the overlay entries of all marked entries and disposes
// them.
// them.
for
(
final
_RouteEntry
entry
in
toBeDisposed
)
{
for
(
final
_RouteEntry
entry
in
toBeDisposed
)
{
for
(
final
OverlayEntry
overlayEntry
in
entry
.
route
.
overlayEntries
)
{
_disposeRouteEntry
(
entry
,
graceful:
true
);
overlayEntry
.
remove
();
}
entry
.
dispose
();
}
}
if
(
rearrangeOverlay
)
{
if
(
rearrangeOverlay
)
{
overlay
?.
rearrange
(
_allRouteOverlayEntries
);
overlay
?.
rearrange
(
_allRouteOverlayEntries
);
...
...
packages/flutter/lib/src/widgets/routes.dart
View file @
ea5cfe09
...
@@ -84,6 +84,9 @@ abstract class OverlayRoute<T> extends Route<T> {
...
@@ -84,6 +84,9 @@ abstract class OverlayRoute<T> extends Route<T> {
@override
@override
void
dispose
()
{
void
dispose
()
{
for
(
final
OverlayEntry
entry
in
_overlayEntries
)
{
entry
.
dispose
();
}
_overlayEntries
.
clear
();
_overlayEntries
.
clear
();
super
.
dispose
();
super
.
dispose
();
}
}
...
@@ -704,7 +707,9 @@ mixin LocalHistoryRoute<T> on Route<T> {
...
@@ -704,7 +707,9 @@ mixin LocalHistoryRoute<T> on Route<T> {
// elements during finalizeTree. The state is locked at this moment, and
// elements during finalizeTree. The state is locked at this moment, and
// we can only notify state has changed in the next frame.
// we can only notify state has changed in the next frame.
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
duration
)
{
SchedulerBinding
.
instance
.
addPostFrameCallback
((
Duration
duration
)
{
changedInternalState
();
if
(
isActive
)
{
changedInternalState
();
}
});
});
}
else
{
}
else
{
changedInternalState
();
changedInternalState
();
...
...
packages/flutter/test/material/about_test.dart
View file @
ea5cfe09
...
@@ -427,7 +427,7 @@ void main() {
...
@@ -427,7 +427,7 @@ void main() {
final
FakeLicenseEntry
licenseEntry
=
FakeLicenseEntry
();
final
FakeLicenseEntry
licenseEntry
=
FakeLicenseEntry
();
licenseCompleter
.
complete
(
licenseEntry
);
licenseCompleter
.
complete
(
licenseEntry
);
expect
(
licenseEntry
.
packagesCalled
,
false
);
expect
(
licenseEntry
.
packagesCalled
,
false
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage returns late if unmounted'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage returns late if unmounted'
,
(
WidgetTester
tester
)
async
{
final
Completer
<
LicenseEntry
>
licenseCompleter
=
Completer
<
LicenseEntry
>();
final
Completer
<
LicenseEntry
>
licenseCompleter
=
Completer
<
LicenseEntry
>();
...
@@ -452,7 +452,7 @@ void main() {
...
@@ -452,7 +452,7 @@ void main() {
await
tester
.
pumpAndSettle
();
await
tester
.
pumpAndSettle
();
expect
(
licenseEntry
.
packagesCalled
,
true
);
expect
(
licenseEntry
.
packagesCalled
,
true
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage logic defaults to executable name for app name'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage logic defaults to executable name for app name'
,
(
WidgetTester
tester
)
async
{
await
tester
.
pumpWidget
(
await
tester
.
pumpWidget
(
...
@@ -1128,7 +1128,7 @@ void main() {
...
@@ -1128,7 +1128,7 @@ void main() {
// Configure to show the default layout.
// Configure to show the default layout.
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'LicensePage master view layout position - rtl'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'LicensePage master view layout position - rtl'
,
(
WidgetTester
tester
)
async
{
const
TextDirection
textDirection
=
TextDirection
.
rtl
;
const
TextDirection
textDirection
=
TextDirection
.
rtl
;
...
@@ -1191,7 +1191,7 @@ void main() {
...
@@ -1191,7 +1191,7 @@ void main() {
// Configure to show the default layout.
// Configure to show the default layout.
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
await
tester
.
binding
.
setSurfaceSize
(
defaultSize
);
}
,
leakTrackingConfig:
const
LeakTrackingTestConfig
(
notDisposedAllowList:
<
String
,
int
?>{
'ValueNotifier<_OverlayEntryWidgetState?>'
:
null
}));
// TODO(goderbauer): Fix leak, https://github.com/flutter/flutter/issues/126100.
}
);
testWidgetsWithLeakTracking
(
'License page title in lateral UI does not use AppBarTheme.foregroundColor'
,
(
WidgetTester
tester
)
async
{
testWidgetsWithLeakTracking
(
'License page title in lateral UI does not use AppBarTheme.foregroundColor'
,
(
WidgetTester
tester
)
async
{
// This is a regression test for https://github.com/flutter/flutter/issues/108991
// This is a regression test for https://github.com/flutter/flutter/issues/108991
...
...
packages/flutter/test/widgets/navigator_test.dart
View file @
ea5cfe09
...
@@ -4171,7 +4171,7 @@ class RouteAnnouncementSpy extends Route<void> {
...
@@ -4171,7 +4171,7 @@ class RouteAnnouncementSpy extends Route<void> {
final
AnnouncementCallBack
?
onDidPopNext
;
final
AnnouncementCallBack
?
onDidPopNext
;
@override
@override
List
<
OverlayEntry
>
get
overlayEntries
=>
<
OverlayEntry
>[
final
List
<
OverlayEntry
>
overlayEntries
=
<
OverlayEntry
>[
OverlayEntry
(
OverlayEntry
(
builder:
(
BuildContext
context
)
=>
const
Placeholder
(),
builder:
(
BuildContext
context
)
=>
const
Placeholder
(),
),
),
...
...
packages/flutter/test/widgets/routes_test.dart
View file @
ea5cfe09
...
@@ -401,7 +401,7 @@ void main() {
...
@@ -401,7 +401,7 @@ void main() {
],
],
);
);
await
tester
.
pumpWidget
(
Container
());
await
tester
.
pumpWidget
(
Container
());
expect
(
results
,
equals
(<
String
>[
'
A: dispose'
,
'b
: dispose'
]));
expect
(
results
,
equals
(<
String
>[
'
b: dispose'
,
'A
: dispose'
]));
expect
(
routes
.
isEmpty
,
isTrue
);
expect
(
routes
.
isEmpty
,
isTrue
);
results
.
clear
();
results
.
clear
();
});
});
...
...
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