Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
weixin_43355755
engine
提交
833c6a2e
E
engine
项目概览
weixin_43355755
/
engine
与 Fork 源项目一致
从无法访问的项目Fork
通知
1
Star
0
Fork
0
代码
文件
提交
分支
Tags
贡献者
分支图
Diff
Issue
0
列表
看板
标记
里程碑
合并请求
0
Wiki
0
Wiki
分析
仓库
DevOps
项目成员
Pages
E
engine
项目概览
项目概览
详情
发布
仓库
仓库
文件
提交
分支
标签
贡献者
分支图
比较
Issue
0
Issue
0
列表
看板
标记
里程碑
合并请求
0
合并请求
0
Pages
分析
分析
仓库分析
DevOps
Wiki
0
Wiki
成员
成员
收起侧边栏
关闭侧边栏
动态
分支图
创建新Issue
提交
Issue看板
体验新版 GitCode,发现更多精彩内容 >>
未验证
提交
833c6a2e
编写于
9月 10, 2020
作者:
C
chunhtai
提交者:
GitHub
9月 10, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
Implement browser history class for router widget (#20794)
上级
0c6c265a
变更
7
隐藏空白更改
内联
并排
Showing
7 changed file
with
610 addition
and
227 deletion
+610
-227
lib/web_ui/lib/src/engine/browser_location.dart
lib/web_ui/lib/src/engine/browser_location.dart
+16
-6
lib/web_ui/lib/src/engine/history.dart
lib/web_ui/lib/src/engine/history.dart
+244
-83
lib/web_ui/lib/src/engine/test_embedding.dart
lib/web_ui/lib/src/engine/test_embedding.dart
+7
-2
lib/web_ui/lib/src/engine/window.dart
lib/web_ui/lib/src/engine/window.dart
+52
-10
lib/web_ui/test/engine/history_test.dart
lib/web_ui/test/engine/history_test.dart
+262
-57
lib/web_ui/test/engine/navigation_test.dart
lib/web_ui/test/engine/navigation_test.dart
+7
-57
lib/web_ui/test/window_test.dart
lib/web_ui/test/window_test.dart
+22
-12
未找到文件。
lib/web_ui/lib/src/engine/browser_location.dart
浏览文件 @
833c6a2e
...
...
@@ -25,6 +25,9 @@ abstract class LocationStrategy {
/// The active path in the browser history.
String
get
path
;
/// The state of the current browser history entry.
dynamic
get
state
;
/// Given a path that's internal to the app, create the external url that
/// will be used in the browser.
String
prepareExternalUrl
(
String
internalUrl
);
...
...
@@ -36,7 +39,7 @@ abstract class LocationStrategy {
void
replaceState
(
dynamic
state
,
String
title
,
String
url
);
/// Go to the previous history entry.
Future
<
void
>
back
();
Future
<
void
>
back
(
{
int
count
=
1
}
);
}
/// This is an implementation of [LocationStrategy] that uses the browser URL's
...
...
@@ -82,6 +85,9 @@ class HashLocationStrategy extends LocationStrategy {
return
path
.
substring
(
1
);
}
@override
dynamic
get
state
=>
_platformLocation
.
state
;
@override
String
prepareExternalUrl
(
String
internalUrl
)
{
// It's convention that if the hash path is empty, we omit the `#`; however,
...
...
@@ -104,8 +110,8 @@ class HashLocationStrategy extends LocationStrategy {
}
@override
Future
<
void
>
back
()
{
_platformLocation
.
back
();
Future
<
void
>
back
(
{
int
count
=
1
}
)
{
_platformLocation
.
back
(
count
);
return
_waitForPopState
();
}
...
...
@@ -142,10 +148,11 @@ abstract class PlatformLocation {
String
get
pathname
;
String
get
search
;
String
?
get
hash
;
dynamic
get
state
;
void
pushState
(
dynamic
state
,
String
title
,
String
url
);
void
replaceState
(
dynamic
state
,
String
title
,
String
url
);
void
back
();
void
back
(
int
count
);
}
/// An implementation of [PlatformLocation] for the browser.
...
...
@@ -184,6 +191,9 @@ class BrowserPlatformLocation extends PlatformLocation {
@override
String
get
hash
=>
_location
.
hash
;
@override
dynamic
get
state
=>
_history
.
state
;
@override
void
pushState
(
dynamic
state
,
String
title
,
String
url
)
{
_history
.
pushState
(
state
,
title
,
url
);
...
...
@@ -195,7 +205,7 @@ class BrowserPlatformLocation extends PlatformLocation {
}
@override
void
back
()
{
_history
.
back
(
);
void
back
(
int
count
)
{
_history
.
go
(-
count
);
}
}
lib/web_ui/lib/src/engine/history.dart
浏览文件 @
833c6a2e
...
...
@@ -5,96 +5,264 @@
// @dart = 2.10
part of
engine
;
const
MethodCall
_popRouteMethodCall
=
MethodCall
(
'popRoute'
);
Map
<
String
,
bool
>
_originState
=
<
String
,
bool
>{
'origin'
:
true
};
Map
<
String
,
bool
>
_flutterState
=
<
String
,
bool
>{
'flutter'
:
true
};
/// The origin entry is the history entry that the Flutter app landed on. It's
/// created by the browser when the user navigates to the url of the app.
bool
_isOriginEntry
(
dynamic
state
)
{
return
state
is
Map
&&
state
[
'origin'
]
==
true
;
}
/// The flutter entry is a history entry that we maintain on top of the origin
/// entry. It allows us to catch popstate events when the user hits the back
/// button.
bool
_isFlutterEntry
(
dynamic
state
)
{
return
state
is
Map
&&
state
[
'flutter'
]
==
true
;
}
/// The [BrowserHistory] class is responsible for integrating Flutter Web apps
/// with the browser history so that the back button works as expected.
/// An abstract class that provides the API for [EngineWindow] to delegate its
/// navigating events.
///
/// It does that by always keeping a single entry (conventionally called the
/// "flutter" entry) at the top of the browser history. That way, the browser's
/// back button always triggers a `popstate` event and never closes the app (we
/// close the app programmatically by calling [SystemNavigator.pop] when there
/// are no more app routes to be popped).
/// Subclasses will have access to [BrowserHistory.locationStrategy] to
/// interact with the html browser history and should come up with their own
/// ways to manage the states in the browser history.
///
/// There should only be one global instance of this class.
class
BrowserHistory
{
LocationStrategy
?
_locationStrategy
;
ui
.
VoidCallback
?
_unsubscribe
;
/// There should only be one global instance among all all subclasses.
///
/// See also:
///
/// * [SingleEntryBrowserHistory]: which creates a single fake browser history
/// entry and delegates all browser navigating events to the flutter
/// framework.
/// * [MultiEntriesBrowserHistory]: which creates a set of states that records
/// the navigating events happened in the framework.
abstract
class
BrowserHistory
{
late
ui
.
VoidCallback
_unsubscribe
;
/// Changing the location strategy will unsubscribe from the old strategy's
/// event listeners, and subscribe to the new one.
///
/// If the given [strategy] is the same as the existing one, nothing will
/// happen.
/// The strategy to interact with html browser history.
LocationStrategy
?
get
locationStrategy
=>
_locationStrategy
;
LocationStrategy
?
_locationStrategy
;
/// Updates the strategy.
///
///
If the given strategy is null, it will render this [BrowserHistory]
///
instance inactive
.
set
locationStrategy
(
LocationStrategy
?
strategy
)
{
///
This method will also remove any previous modifications to the html
///
browser history and start anew
.
Future
<
void
>
setLocationStrategy
(
LocationStrategy
?
strategy
)
async
{
if
(
strategy
!=
_locationStrategy
)
{
_tearoffStrategy
(
_locationStrategy
);
await
_tearoffStrategy
(
_locationStrategy
);
_locationStrategy
=
strategy
;
_setupStrategy
(
_locationStrategy
);
await
_setupStrategy
(
_locationStrategy
);
}
}
/// Returns the currently active location strategy.
@visibleForTesting
LocationStrategy
?
get
locationStrategy
=>
_locationStrategy
;
Future
<
void
>
_setupStrategy
(
LocationStrategy
?
strategy
)
async
{
if
(
strategy
==
null
)
{
return
;
}
_unsubscribe
=
strategy
.
onPopState
(
onPopState
as
dynamic
Function
(
html
.
Event
));
await
setup
();
}
/// The path of the current location of the user's browser.
String
get
currentPath
=>
_locationStrategy
?.
path
??
'/'
;
Future
<
void
>
_tearoffStrategy
(
LocationStrategy
?
strategy
)
async
{
if
(
strategy
==
null
)
{
return
;
}
_unsubscribe
();
/// Update the url with the given [routeName].
void
setRouteName
(
String
?
routeName
)
{
await
tearDown
();
}
/// Exit this application and return to the previous page.
Future
<
void
>
exit
()
async
{
if
(
_locationStrategy
!=
null
)
{
_setupFlutterEntry
(
_locationStrategy
!,
replace:
true
,
path:
routeName
);
await
_tearoffStrategy
(
_locationStrategy
);
// Now the history should be in the original state, back one more time to
// exit the application.
await
_locationStrategy
!.
back
();
_locationStrategy
=
null
;
}
}
/// This method does the same thing as the browser back button.
Future
<
void
>
back
()
{
if
(
_
locationStrategy
!=
null
)
{
return
_
locationStrategy
!.
back
();
if
(
locationStrategy
!=
null
)
{
return
locationStrategy
!.
back
();
}
return
Future
<
void
>.
value
();
}
/// This method exits the app and goes to whatever website was active before.
Future
<
void
>
exit
()
{
if
(
_locationStrategy
!=
null
)
{
_tearoffStrategy
(
_locationStrategy
);
// After tearing off the location strategy, we should be on the "origin"
// entry. So we need to go back one more time to exit the app.
final
Future
<
void
>
backFuture
=
_locationStrategy
!.
back
();
_locationStrategy
=
null
;
return
backFuture
;
/// The path of the current location of the user's browser.
String
get
currentPath
=>
locationStrategy
?.
path
??
'/'
;
/// The state of the current location of the user's browser.
dynamic
get
currentState
=>
locationStrategy
?.
state
;
/// Update the url with the given [routeName] and [state].
void
setRouteName
(
String
?
routeName
,
{
dynamic
?
state
});
/// A callback method to handle browser backward or forward buttons.
///
/// Subclasses should send appropriate system messages to update the flutter
/// applications accordingly.
@protected
void
onPopState
(
covariant
html
.
PopStateEvent
event
);
/// Sets up any prerequisites to use this browser history class.
@protected
Future
<
void
>
setup
()
=>
Future
<
void
>.
value
();
/// Restore any modifications to the html browser history during the lifetime
/// of this class.
@protected
Future
<
void
>
tearDown
()
=>
Future
<
void
>.
value
();
}
/// A browser history class that creates a set of browser history entries to
/// support browser backward and forward button natively.
///
/// This class pushes a browser history entry every time the framework reports
/// a route change and sends a `pushRouteInformation` method call to the
/// framework when the browser jumps to a specific browser history entry.
///
/// The web engine uses this class to manage its browser history when the
/// framework uses a Router for routing.
///
/// See also:
///
/// * [SingleEntryBrowserHistory], which is used when the framework does not use
/// a Router for routing.
class
MultiEntriesBrowserHistory
extends
BrowserHistory
{
late
int
_lastSeenSerialCount
;
int
get
_currentSerialCount
{
if
(
_hasSerialCount
(
currentState
))
{
return
currentState
[
'serialCount'
]
as
int
;
}
return
0
;
}
dynamic
_tagWithSerialCount
(
dynamic
originialState
,
int
count
)
{
return
<
dynamic
,
dynamic
>
{
'serialCount'
:
count
,
'state'
:
originialState
,
};
}
bool
_hasSerialCount
(
dynamic
state
)
{
return
state
is
Map
&&
state
[
'serialCount'
]
!=
null
;
}
@override
void
setRouteName
(
String
?
routeName
,
{
dynamic
?
state
})
{
if
(
locationStrategy
!=
null
)
{
assert
(
routeName
!=
null
);
_lastSeenSerialCount
+=
1
;
locationStrategy
!.
pushState
(
_tagWithSerialCount
(
state
,
_lastSeenSerialCount
),
'flutter'
,
routeName
!,
);
}
}
@override
void
onPopState
(
covariant
html
.
PopStateEvent
event
)
{
assert
(
locationStrategy
!=
null
);
// May be a result of direct url access while the flutter application is
// already running.
if
(!
_hasSerialCount
(
event
.
state
))
{
// In this case we assume this will be the next history entry from the
// last seen entry.
locationStrategy
!.
replaceState
(
_tagWithSerialCount
(
event
.
state
,
_lastSeenSerialCount
+
1
),
'flutter'
,
currentPath
);
}
_lastSeenSerialCount
=
_currentSerialCount
;
if
(
window
.
_onPlatformMessage
!=
null
)
{
window
.
invokeOnPlatformMessage
(
'flutter/navigation'
,
const
JSONMethodCodec
().
encodeMethodCall
(
MethodCall
(
'pushRouteInformation'
,
<
dynamic
,
dynamic
>{
'location'
:
currentPath
,
'state'
:
event
.
state
?[
'state'
],
})
),
(
_
)
{},
);
}
}
@override
Future
<
void
>
setup
()
{
if
(!
_hasSerialCount
(
currentState
))
{
locationStrategy
!.
replaceState
(
_tagWithSerialCount
(
currentState
,
0
),
'flutter'
,
currentPath
);
}
// If we retore from a page refresh, the _currentSerialCount may not be 0.
_lastSeenSerialCount
=
_currentSerialCount
;
return
Future
<
void
>.
value
();
}
@override
Future
<
void
>
tearDown
()
async
{
// Restores the html browser history.
assert
(
_hasSerialCount
(
currentState
));
int
backCount
=
_currentSerialCount
;
if
(
backCount
>
0
)
{
await
locationStrategy
!.
back
(
count:
backCount
);
}
// Unwrap state.
assert
(
_hasSerialCount
(
currentState
)
&&
_currentSerialCount
==
0
);
locationStrategy
!.
replaceState
(
currentState
[
'state'
],
'flutter'
,
currentPath
,
);
}
}
/// The browser history class is responsible for integrating Flutter Web apps
/// with the browser history so that the back button works as expected.
///
/// It does that by always keeping a single entry (conventionally called the
/// "flutter" entry) at the top of the browser history. That way, the browser's
/// back button always triggers a `popstate` event and never closes the app (we
/// close the app programmatically by calling [SystemNavigator.pop] when there
/// are no more app routes to be popped).
///
/// The web engine uses this class when the framework does not use Router for
/// routing, and it does not support browser forward button.
///
/// See also:
///
/// * [MultiEntriesBrowserHistory], which is used when the framework uses a
/// Router for routing.
class
SingleEntryBrowserHistory
extends
BrowserHistory
{
static
const
MethodCall
_popRouteMethodCall
=
MethodCall
(
'popRoute'
);
static
const
String
_kFlutterTag
=
'flutter'
;
static
const
String
_kOriginTag
=
'origin'
;
Map
<
String
,
dynamic
>
_wrapOriginState
(
dynamic
state
)
{
return
<
String
,
dynamic
>{
_kOriginTag:
true
,
'state'
:
state
};
}
dynamic
_unwrapOriginState
(
dynamic
state
)
{
assert
(
_isOriginEntry
(
state
));
final
Map
<
dynamic
,
dynamic
>
originState
=
state
as
Map
<
dynamic
,
dynamic
>;
return
originState
[
'state'
];
}
Map
<
String
,
bool
>
_flutterState
=
<
String
,
bool
>{
_kFlutterTag:
true
};
/// The origin entry is the history entry that the Flutter app landed on. It's
/// created by the browser when the user navigates to the url of the app.
bool
_isOriginEntry
(
dynamic
state
)
{
return
state
is
Map
&&
state
[
_kOriginTag
]
==
true
;
}
/// The flutter entry is a history entry that we maintain on top of the origin
/// entry. It allows us to catch popstate events when the user hits the back
/// button.
bool
_isFlutterEntry
(
dynamic
state
)
{
return
state
is
Map
&&
state
[
_kFlutterTag
]
==
true
;
}
@override
void
setRouteName
(
String
?
routeName
,
{
dynamic
?
state
})
{
if
(
locationStrategy
!=
null
)
{
_setupFlutterEntry
(
locationStrategy
!,
replace:
true
,
path:
routeName
);
}
}
String
?
_userProvidedRouteName
;
void
_popStateListener
(
covariant
html
.
PopStateEvent
event
)
{
@override
void
onPopState
(
covariant
html
.
PopStateEvent
event
)
{
if
(
_isOriginEntry
(
event
.
state
))
{
// If we find ourselves in the origin entry, it means that the user
// clicked the back button.
// 1. Re-push the flutter entry to keep it always at the top of history.
_setupFlutterEntry
(
_locationStrategy
!);
// 2. Send a 'popRoute' platform message so the app can handle it accordingly.
...
...
@@ -146,7 +314,7 @@ class BrowserHistory {
/// [_isOriginEntry] inside [_popStateListener].
void
_setupOriginEntry
(
LocationStrategy
strategy
)
{
assert
(
strategy
!=
null
);
// ignore: unnecessary_null_comparison
strategy
.
replaceState
(
_
originState
,
'origin'
,
''
);
strategy
.
replaceState
(
_
wrapOriginState
(
currentState
)
,
'origin'
,
''
);
}
/// This method is used manipulate the Flutter Entry which is always the
...
...
@@ -165,11 +333,8 @@ class BrowserHistory {
}
}
void
_setupStrategy
(
LocationStrategy
?
strategy
)
{
if
(
strategy
==
null
)
{
return
;
}
@override
Future
<
void
>
setup
()
{
final
String
path
=
currentPath
;
if
(
_isFlutterEntry
(
html
.
window
.
history
.
state
))
{
// This could happen if the user, for example, refreshes the page. They
...
...
@@ -177,23 +342,19 @@ class BrowserHistory {
// the "origin" and "flutter" entries, we can safely assume they are
// already setup.
}
else
{
_setupOriginEntry
(
strategy
);
_setupFlutterEntry
(
strategy
,
replace:
false
,
path:
path
);
_setupOriginEntry
(
locationStrategy
!
);
_setupFlutterEntry
(
locationStrategy
!
,
replace:
false
,
path:
path
);
}
_unsubscribe
=
strategy
.
onPopState
(
_popStateListener
as
dynamic
Function
(
html
.
Event
)
);
return
Future
<
void
>.
value
(
);
}
void
_tearoffStrategy
(
LocationStrategy
?
strategy
)
{
if
(
strategy
==
null
)
{
return
;
@override
Future
<
void
>
tearDown
()
async
{
if
(
locationStrategy
!=
null
)
{
// We need to remove the flutter entry that we pushed in setup.
await
locationStrategy
!.
back
();
// Restores original state.
locationStrategy
!.
replaceState
(
_unwrapOriginState
(
currentState
),
'flutter'
,
currentPath
);
}
assert
(
_unsubscribe
!=
null
);
_unsubscribe
!();
_unsubscribe
=
null
;
// Remove the "flutter" entry and go back to the "origin" entry so that the
// next location strategy can start from the right spot.
strategy
.
back
();
}
}
lib/web_ui/lib/src/engine/test_embedding.dart
浏览文件 @
833c6a2e
...
...
@@ -39,6 +39,11 @@ class TestLocationStrategy extends LocationStrategy {
@override
String
get
path
=>
currentEntry
.
url
;
@override
dynamic
get
state
{
return
currentEntry
.
state
;
}
int
_currentEntryIndex
;
int
get
currentEntryIndex
=>
_currentEntryIndex
;
...
...
@@ -100,12 +105,12 @@ class TestLocationStrategy extends LocationStrategy {
}
@override
Future
<
void
>
back
()
{
Future
<
void
>
back
(
{
int
count
=
1
}
)
{
assert
(
withinAppHistory
);
// Browsers don't move back in history immediately. They do it at the next
// event loop. So let's simulate that.
return
_nextEventLoop
(()
{
_currentEntryIndex
--
;
_currentEntryIndex
=
_currentEntryIndex
-
count
;
if
(
withinAppHistory
)
{
_firePopStateEvent
();
}
...
...
lib/web_ui/lib/src/engine/window.dart
浏览文件 @
833c6a2e
...
...
@@ -153,7 +153,45 @@ class EngineWindow extends ui.Window {
/// Handles the browser history integration to allow users to use the back
/// button, etc.
final
BrowserHistory
_browserHistory
=
BrowserHistory
();
@visibleForTesting
BrowserHistory
get
browserHistory
=>
_browserHistory
;
BrowserHistory
_browserHistory
=
MultiEntriesBrowserHistory
();
@visibleForTesting
Future
<
void
>
debugSwitchBrowserHistory
({
required
bool
useSingle
})
async
{
if
(
useSingle
)
await
_useSingleEntryBrowserHistory
();
else
await
_useMultiEntryBrowserHistory
();
}
/// This function should only be used for test setup. In real application, we
/// only allow one time switch from the MultiEntriesBrowserHistory to
/// the SingleEntryBrowserHistory to prevent the application to switch back
/// forth between router and non-router.
Future
<
void
>
_useMultiEntryBrowserHistory
()
async
{
if
(
_browserHistory
is
MultiEntriesBrowserHistory
)
{
return
;
}
final
LocationStrategy
?
strategy
=
_browserHistory
.
locationStrategy
;
if
(
strategy
!=
null
)
await
_browserHistory
.
setLocationStrategy
(
null
);
_browserHistory
=
MultiEntriesBrowserHistory
();
if
(
strategy
!=
null
)
await
_browserHistory
.
setLocationStrategy
(
strategy
);
}
Future
<
void
>
_useSingleEntryBrowserHistory
()
async
{
if
(
_browserHistory
is
SingleEntryBrowserHistory
)
{
return
;
}
final
LocationStrategy
?
strategy
=
_browserHistory
.
locationStrategy
;
if
(
strategy
!=
null
)
await
_browserHistory
.
setLocationStrategy
(
null
);
_browserHistory
=
SingleEntryBrowserHistory
();
if
(
strategy
!=
null
)
await
_browserHistory
.
setLocationStrategy
(
strategy
);
}
/// Simulates clicking the browser's back button.
Future
<
void
>
webOnlyBack
()
=>
_browserHistory
.
back
();
...
...
@@ -181,7 +219,7 @@ class EngineWindow extends ui.Window {
///
/// By setting this to null, the browser history will be disabled.
set
locationStrategy
(
LocationStrategy
?
strategy
)
{
_browserHistory
.
locationStrategy
=
strategy
;
_browserHistory
.
setLocationStrategy
(
strategy
)
;
}
/// Returns the currently active location strategy.
...
...
@@ -608,16 +646,20 @@ class EngineWindow extends ui.Window {
final
Map
<
String
,
dynamic
>?
message
=
decoded
.
arguments
;
switch
(
decoded
.
method
)
{
case
'routeUpdated'
:
case
'routePushed'
:
case
'routeReplaced'
:
_browserHistory
.
setRouteName
(
message
![
'routeName'
]);
_replyToPlatformMessage
(
callback
,
codec
.
encodeSuccessEnvelope
(
true
)
);
_useSingleEntryBrowserHistory
().
then
((
void
data
)
{
_browserHistory
.
setRouteName
(
message
![
'routeName'
]);
_replyToPlatformMessage
(
callback
,
codec
.
encodeSuccessEnvelope
(
true
));
}
);
break
;
case
'routePopped'
:
_browserHistory
.
setRouteName
(
message
![
'previousRouteName'
]);
case
'routeInformationUpdated'
:
assert
(
_browserHistory
is
MultiEntriesBrowserHistory
);
_browserHistory
.
setRouteName
(
message
![
'location'
],
state:
message
![
'state'
],
);
_replyToPlatformMessage
(
callback
,
codec
.
encodeSuccessEnvelope
(
true
));
callback
,
codec
.
encodeSuccessEnvelope
(
true
));
break
;
}
// As soon as Flutter starts taking control of the app navigation, we
...
...
lib/web_ui/test/engine/history_test.dart
浏览文件 @
833c6a2e
...
...
@@ -16,10 +16,20 @@ import 'package:ui/src/engine.dart';
import
'../spy.dart'
;
TestLocationStrategy
_strategy
;
TestLocationStrategy
get
strategy
=>
_strategy
;
set
strategy
(
TestLocationStrategy
newStrategy
)
{
window
.
locationStrategy
=
_strategy
=
newStrategy
;
TestLocationStrategy
get
strategy
=>
window
.
browserHistory
.
locationStrategy
;
Future
<
void
>
setStrategy
(
TestLocationStrategy
newStrategy
)
async
{
await
window
.
browserHistory
.
setLocationStrategy
(
newStrategy
);
}
Map
<
String
,
dynamic
>
_wrapOriginState
(
dynamic
state
)
{
return
<
String
,
dynamic
>{
'origin'
:
true
,
'state'
:
state
};
}
Map
<
String
,
dynamic
>
_tagStateWithSerialCount
(
dynamic
state
,
int
serialCount
)
{
return
<
String
,
dynamic
>
{
'serialCount'
:
serialCount
,
'state'
:
state
,
};
}
const
Map
<
String
,
bool
>
originState
=
<
String
,
bool
>{
'origin'
:
true
};
...
...
@@ -34,28 +44,29 @@ void main() {
}
void
testMain
(
)
{
group
(
'
$BrowserHistory
'
,
()
{
group
(
'
$
SingleEntry
BrowserHistory
'
,
()
{
final
PlatformMessagesSpy
spy
=
PlatformMessagesSpy
();
setUp
(()
{
setUp
(()
async
{
await
window
.
debugSwitchBrowserHistory
(
useSingle:
true
);
spy
.
setUp
();
});
tearDown
(()
{
tearDown
(()
async
{
spy
.
tearDown
();
strategy
=
null
;
await
setStrategy
(
null
)
;
});
test
(
'basic setup works'
,
()
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
));
test
(
'basic setup works'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
))
)
;
// There should be two entries: origin and flutter.
expect
(
strategy
.
history
,
hasLength
(
2
));
// The origin entry is setup but its path should remain unchanged.
final
TestHistoryEntry
originEntry
=
strategy
.
history
[
0
];
expect
(
originEntry
.
state
,
originState
);
expect
(
originEntry
.
state
,
_wrapOriginState
(
'initial state'
)
);
expect
(
originEntry
.
url
,
'/initial'
);
// The flutter entry is pushed and its path should be derived from the
...
...
@@ -71,15 +82,12 @@ void testMain() {
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'browser back button pops routes correctly'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
)));
// Initially, we should be on the flutter entry.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
pushRoute
(
'/page1'
);
await
routeUpdated
(
'/page1'
);
// The number of entries shouldn't change.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
...
...
@@ -107,11 +115,10 @@ void testMain() {
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'multiple browser back clicks'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
)));
pushRoute
(
'/page1'
);
pushRoute
(
'/page2'
);
await
routeUpdated
(
'/page1'
);
await
routeUpdated
(
'/page2'
);
// Make sure we are on page2.
expect
(
strategy
.
history
,
hasLength
(
2
));
...
...
@@ -128,7 +135,7 @@ void testMain() {
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/page1'
);
await
routeUpdated
(
'/page1'
);
// 3. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
...
...
@@ -144,15 +151,18 @@ void testMain() {
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/home'
);
await
routeUpdated
(
'/home'
);
// 3. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
// The next browser back will exit the app.
await
strategy
.
back
();
// The next browser back will exit the app. We store the strategy locally
// because it will be remove from the browser history class once it exits
// the app.
TestLocationStrategy
originalStrategy
=
strategy
;
await
originalStrategy
.
back
();
// 1. The engine sends a `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
...
...
@@ -164,17 +174,16 @@ void testMain() {
await
systemNavigatorPop
();
// 3. The active entry doesn't belong to our history anymore because we
// navigated past it.
expect
(
s
trategy
.
currentEntryIndex
,
-
1
);
expect
(
originalS
trategy
.
currentEntryIndex
,
-
1
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
||
browserEngine
==
BrowserEngine
.
webkit
);
test
(
'handle user-provided url'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
)));
await
_
strategy
.
simulateUserTypingUrl
(
'/page3'
);
await
strategy
.
simulateUserTypingUrl
(
'/page3'
);
// This delay is necessary to wait for [BrowserHistory] because it
// performs a `back` operation which results in a new event loop.
await
Future
<
void
>.
delayed
(
Duration
.
zero
);
...
...
@@ -185,7 +194,7 @@ void testMain() {
expect
(
spy
.
messages
[
0
].
methodArguments
,
'/page3'
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePushed` platform message.
pushRoute
(
'/page3'
);
await
routeUpdated
(
'/page3'
);
// 3. The history state should reflect that /page3 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
1
);
...
...
@@ -201,7 +210,7 @@ void testMain() {
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/home'
);
await
routeUpdated
(
'/home'
);
// 3. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
...
...
@@ -212,10 +221,9 @@ void testMain() {
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'user types unknown url'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
)));
await
_
strategy
.
simulateUserTypingUrl
(
'/unknown'
);
await
strategy
.
simulateUserTypingUrl
(
'/unknown'
);
// This delay is necessary to wait for [BrowserHistory] because it
// performs a `back` operation which results in a new event loop.
await
Future
<
void
>.
delayed
(
Duration
.
zero
);
...
...
@@ -236,6 +244,212 @@ void testMain() {
skip:
browserEngine
==
BrowserEngine
.
edge
);
});
group
(
'
$MultiEntriesBrowserHistory
'
,
()
{
final
PlatformMessagesSpy
spy
=
PlatformMessagesSpy
();
setUp
(()
async
{
await
window
.
debugSwitchBrowserHistory
(
useSingle:
false
);
spy
.
setUp
();
});
tearDown
(()
async
{
spy
.
tearDown
();
await
setStrategy
(
null
);
});
test
(
'basic setup works'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
)));
// There should be only one entry.
expect
(
strategy
.
history
,
hasLength
(
1
));
// The origin entry is tagged and its path should remain unchanged.
final
TestHistoryEntry
taggedOriginEntry
=
strategy
.
history
[
0
];
expect
(
taggedOriginEntry
.
state
,
_tagStateWithSerialCount
(
'initial state'
,
0
));
expect
(
taggedOriginEntry
.
url
,
'/initial'
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'browser back button push route infromation correctly'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/home'
)));
// Initially, we should be on the flutter entry.
expect
(
strategy
.
history
,
hasLength
(
1
));
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'initial state'
,
0
));
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
await
routeInfomrationUpdated
(
'/page1'
,
'page1 state'
);
// Should have two history entries now.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
// But the url of the current entry (flutter entry) should be updated.
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page1 state'
,
1
));
expect
(
strategy
.
currentEntry
.
url
,
'/page1'
);
// No platform messages have been sent so far.
expect
(
spy
.
messages
,
isEmpty
);
// Clicking back should take us to page1.
await
strategy
.
back
();
// First, the framework should've received a `pushRouteInformation`
// platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/home'
,
'state'
:
'initial state'
,
});
// There are still two browser history entries, but we are back to the
// original state.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
0
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'initial state'
,
0
));
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'multiple browser back clicks'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/home'
)));
await
routeInfomrationUpdated
(
'/page1'
,
'page1 state'
);
await
routeInfomrationUpdated
(
'/page2'
,
'page2 state'
);
// Make sure we are on page2.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
2
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page2 state'
,
2
));
expect
(
strategy
.
currentEntry
.
url
,
'/page2'
);
// Back to page1.
await
strategy
.
back
();
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/page1'
,
'state'
:
'page1 state'
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page1 state'
,
1
));
expect
(
strategy
.
currentEntry
.
url
,
'/page1'
);
// Back to home.
await
strategy
.
back
();
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/home'
,
'state'
:
'initial state'
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
0
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'initial state'
,
0
));
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
||
browserEngine
==
BrowserEngine
.
webkit
);
test
(
'handle user-provided url'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/home'
)));
await
strategy
.
simulateUserTypingUrl
(
'/page3'
);
// This delay is necessary to wait for [BrowserHistory] because it
// performs a `back` operation which results in a new event loop.
await
Future
<
void
>.
delayed
(
Duration
.
zero
);
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/page3'
,
'state'
:
null
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page3 is currently active.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
null
,
1
));
expect
(
strategy
.
currentEntry
.
url
,
'/page3'
);
// Back to home.
await
strategy
.
back
();
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/home'
,
'state'
:
'initial state'
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
0
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'initial state'
,
0
));
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
);
test
(
'forward button works'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/home'
)));
await
routeInfomrationUpdated
(
'/page1'
,
'page1 state'
);
await
routeInfomrationUpdated
(
'/page2'
,
'page2 state'
);
// Make sure we are on page2.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
2
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page2 state'
,
2
));
expect
(
strategy
.
currentEntry
.
url
,
'/page2'
);
// Back to page1.
await
strategy
.
back
();
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/page1'
,
'state'
:
'page1 state'
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page1 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page1 state'
,
1
));
expect
(
strategy
.
currentEntry
.
url
,
'/page1'
);
// Forward to page2
await
strategy
.
back
(
count:
-
1
);
// 1. The engine sends a `pushRouteInformation` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRouteInformation'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
<
dynamic
,
dynamic
>{
'location'
:
'/page2'
,
'state'
:
'page2 state'
,
});
spy
.
messages
.
clear
();
// 2. The history state should reflect that /page2 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
2
);
expect
(
strategy
.
currentEntry
.
state
,
_tagStateWithSerialCount
(
'page2 state'
,
2
));
expect
(
strategy
.
currentEntry
.
url
,
'/page2'
);
},
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
skip:
browserEngine
==
BrowserEngine
.
edge
);
});
group
(
'
$HashLocationStrategy
'
,
()
{
TestPlatformLocation
location
;
...
...
@@ -272,40 +486,30 @@ void testMain() {
});
}
void
pushRoute
(
String
routeName
)
{
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'routePushed'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
routeName
},
)),
emptyCallback
,
);
}
void
replaceRoute
(
String
routeName
)
{
Future
<
void
>
routeUpdated
(
String
routeName
)
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'route
Replac
ed'
,
<
String
,
dynamic
>{
'
previousRouteName'
:
'/foo'
,
'
routeName'
:
routeName
},
'route
Updat
ed'
,
<
String
,
dynamic
>{
'routeName'
:
routeName
},
)),
emptyCallback
,
(
_
)
=>
completer
.
complete
()
,
);
return
completer
.
future
;
}
void
popRoute
(
String
previousRouteName
)
{
Future
<
void
>
routeInfomrationUpdated
(
String
location
,
dynamic
state
)
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'routePopped'
,
<
String
,
dynamic
>{
'previousRouteName'
:
previousRouteName
,
'routeName'
:
'/foo'
},
'routeInformationUpdated'
,
<
String
,
dynamic
>{
'location'
:
location
,
'state'
:
state
},
)),
emptyCallback
,
(
_
)
=>
completer
.
complete
()
,
);
return
completer
.
future
;
}
Future
<
void
>
systemNavigatorPop
()
{
...
...
@@ -323,6 +527,7 @@ class TestPlatformLocation extends PlatformLocation {
String
pathname
;
String
search
;
String
hash
;
dynamic
state
;
void
onPopState
(
html
.
EventListener
fn
)
{
throw
UnimplementedError
();
...
...
@@ -348,7 +553,7 @@ class TestPlatformLocation extends PlatformLocation {
throw
UnimplementedError
();
}
void
back
()
{
void
back
(
int
count
)
{
throw
UnimplementedError
();
}
}
lib/web_ui/test/engine/navigation_test.dart
浏览文件 @
833c6a2e
...
...
@@ -3,6 +3,7 @@
// found in the LICENSE file.
// @dart = 2.6
import
'dart:async'
;
import
'dart:typed_data'
;
import
'package:test/bootstrap/browser.dart'
;
...
...
@@ -28,68 +29,17 @@ void testMain() {
engine
.
window
.
locationStrategy
=
_strategy
=
null
;
});
test
(
'Tracks pushed, replaced and popped routes'
,
()
{
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routePushed'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/'
,
'routeName'
:
'/foo'
},
)),
emptyCallback
,
);
expect
(
_strategy
.
path
,
'/foo'
);
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routePushed'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
'/bar'
},
)),
emptyCallback
,
);
expect
(
_strategy
.
path
,
'/bar'
);
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routePopped'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
'/bar'
},
)),
emptyCallback
,
);
expect
(
_strategy
.
path
,
'/foo'
);
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routePushed'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
'/bar/baz'
},
)),
emptyCallback
,
);
expect
(
_strategy
.
path
,
'/bar/baz'
);
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routeReplaced'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/bar/baz'
,
'routeName'
:
'/bar/baz2'
,
},
)),
emptyCallback
,
);
expect
(
_strategy
.
path
,
'/bar/baz2'
);
test
(
'Tracks pushed, replaced and popped routes'
,
()
async
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
engine
.
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
const
engine
.
MethodCall
(
'routeUpdated'
,
<
String
,
dynamic
>{
'
previousRouteName'
:
'/bar/baz2'
,
'routeName'
:
'/foo/foo/2
'
},
<
String
,
dynamic
>{
'
routeName'
:
'/foo
'
},
)),
emptyCallback
,
(
_
)
=>
completer
.
complete
()
,
);
expect
(
_strategy
.
path
,
'/foo/foo/2'
);
await
completer
.
future
;
expect
(
_strategy
.
path
,
'/foo'
);
});
}
lib/web_ui/test/window_test.dart
浏览文件 @
833c6a2e
...
...
@@ -16,13 +16,21 @@ const MethodCodec codec = JSONMethodCodec();
void
emptyCallback
(
ByteData
date
)
{}
Future
<
void
>
setStrategy
(
TestLocationStrategy
newStrategy
)
async
{
await
window
.
browserHistory
.
setLocationStrategy
(
newStrategy
);
}
void
main
(
)
{
internalBootstrapBrowserTest
(()
=>
testMain
);
}
void
testMain
(
)
{
test
(
'window.defaultRouteName should not change'
,
()
{
window
.
locationStrategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
));
setUp
(()
async
{
await
window
.
debugSwitchBrowserHistory
(
useSingle:
true
);
});
test
(
'window.defaultRouteName should not change'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
)));
expect
(
window
.
defaultRouteName
,
'/initial'
);
// Changing the URL in the address bar later shouldn't affect [window.defaultRouteName].
...
...
@@ -30,17 +38,16 @@ void testMain() {
expect
(
window
.
defaultRouteName
,
'/initial'
);
});
test
(
'window.defaultRouteName should reset after navigation platform message'
,
()
{
window
.
locationStrategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
));
test
(
'window.defaultRouteName should reset after navigation platform message'
,
()
async
{
await
setStrategy
(
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
'initial state'
,
null
,
'/initial'
)
));
// Reading it multiple times should return the same value.
expect
(
window
.
defaultRouteName
,
'/initial'
);
expect
(
window
.
defaultRouteName
,
'/initial'
);
window
.
sendPlatformMessage
(
'flutter/navigation'
,
JSONMethodCodec
().
encodeMethodCall
(
MethodCall
(
'route
Push
ed'
,
<
String
,
dynamic
>{
'
previousRouteName'
:
'/foo'
,
'
routeName'
:
'/bar'
},
'route
Updat
ed'
,
<
String
,
dynamic
>{
'routeName'
:
'/bar'
},
)),
emptyCallback
,
);
...
...
@@ -50,22 +57,25 @@ void testMain() {
});
test
(
'can disable location strategy'
,
()
async
{
await
window
.
debugSwitchBrowserHistory
(
useSingle:
true
);
final
testStrategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/'
),
TestHistoryEntry
(
'initial state'
,
null
,
'/'
),
);
window
.
locationStrategy
=
testStrategy
;
await
setStrategy
(
testStrategy
)
;
expect
(
window
.
locationStrategy
,
testStrategy
);
// A single listener should've been setup.
expect
(
testStrategy
.
listeners
,
hasLength
(
1
));
// The initial entry should be there, plus another "flutter" entry.
expect
(
testStrategy
.
history
,
hasLength
(
2
));
expect
(
testStrategy
.
history
[
0
].
state
,
<
String
,
bool
>{
'origin'
:
true
});
expect
(
testStrategy
.
history
[
0
].
state
,
<
String
,
dynamic
>{
'origin'
:
true
,
'state'
:
'initial state'
});
expect
(
testStrategy
.
history
[
1
].
state
,
<
String
,
bool
>{
'flutter'
:
true
});
expect
(
testStrategy
.
currentEntry
,
testStrategy
.
history
[
1
]);
// Now, let's disable location strategy and make sure things get cleaned up.
expect
(()
=>
jsSetLocationStrategy
(
null
),
returnsNormally
);
// The locationStrategy is teared down asynchronously.
await
Future
<
void
>.
delayed
(
Duration
.
zero
);
expect
(
window
.
locationStrategy
,
isNull
);
// The listener is removed asynchronously.
...
...
@@ -73,8 +83,8 @@ void testMain() {
// No more listeners.
expect
(
testStrategy
.
listeners
,
isEmpty
);
// History should've moved back to the initial
entry
.
expect
(
testStrategy
.
history
[
0
].
state
,
<
String
,
bool
>{
'origin'
:
true
}
);
// History should've moved back to the initial
state
.
expect
(
testStrategy
.
history
[
0
].
state
,
"initial state"
);
expect
(
testStrategy
.
currentEntry
,
testStrategy
.
history
[
0
]);
});
...
...
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录