Skip to content
体验新版
项目
组织
正在加载...
登录
切换导航
打开侧边栏
qq_34031325
engine
提交
6162c41d
E
engine
项目概览
qq_34031325
/
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,发现更多精彩内容 >>
提交
6162c41d
编写于
1月 09, 2020
作者:
M
Mouad Debbar
提交者:
Flutter GitHub Bot
1月 09, 2020
浏览文件
操作
浏览文件
下载
电子邮件补丁
差异文件
[web] Tests for browser history implementation (#15324)
上级
3a1a3ba6
变更
3
隐藏空白更改
内联
并排
Showing
3 changed file
with
353 addition
and
12 deletion
+353
-12
lib/web_ui/lib/src/engine/test_embedding.dart
lib/web_ui/lib/src/engine/test_embedding.dart
+21
-12
lib/web_ui/test/engine/history_test.dart
lib/web_ui/test/engine/history_test.dart
+263
-0
lib/web_ui/test/spy.dart
lib/web_ui/test/spy.dart
+69
-0
未找到文件。
lib/web_ui/lib/src/engine/test_embedding.dart
浏览文件 @
6162c41d
...
...
@@ -6,12 +6,12 @@ part of engine;
const
bool
_debugLogHistoryActions
=
false
;
class
_
HistoryEntry
{
class
Test
HistoryEntry
{
final
dynamic
state
;
final
String
title
;
final
String
url
;
const
_
HistoryEntry
(
this
.
state
,
this
.
title
,
this
.
url
);
const
Test
HistoryEntry
(
this
.
state
,
this
.
title
,
this
.
url
);
@override
String
toString
()
{
...
...
@@ -25,24 +25,30 @@ class _HistoryEntry {
/// It keeps a list of history entries and event listeners in memory and
/// manipulates them in order to achieve the desired functionality.
class
TestLocationStrategy
extends
LocationStrategy
{
/// Passing a [defaultRouteName] will make the app start at that route. The
/// way it does it is by using it as a path on the first history entry.
TestLocationStrategy
([
String
defaultRouteName
=
''
])
/// Creates a instance of [TestLocationStrategy] with an empty string as the
/// path.
factory
TestLocationStrategy
()
=>
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
''
));
/// Creates an instance of [TestLocationStrategy] and populates it with a list
/// that has [initialEntry] as the only item.
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
initialEntry
)
:
_currentEntryIndex
=
0
,
history
=
<
_HistoryEntry
>[
_HistoryEntry
(
null
,
null
,
defaultRouteName
)
];
history
=
<
TestHistoryEntry
>[
initialEntry
];
@override
String
get
path
=>
ensureLeading
(
currentEntry
.
url
,
'/'
);
int
_currentEntryIndex
;
final
List
<
_HistoryEntry
>
history
;
int
get
currentEntryIndex
=>
_currentEntryIndex
;
final
List
<
TestHistoryEntry
>
history
;
_
HistoryEntry
get
currentEntry
{
Test
HistoryEntry
get
currentEntry
{
assert
(
withinAppHistory
);
return
history
[
_currentEntryIndex
];
}
set
currentEntry
(
_
HistoryEntry
entry
)
{
set
currentEntry
(
Test
HistoryEntry
entry
)
{
assert
(
withinAppHistory
);
history
[
_currentEntryIndex
]
=
entry
;
}
...
...
@@ -62,7 +68,7 @@ class TestLocationStrategy extends LocationStrategy {
// If the user goes A -> B -> C -> D, then goes back to B and pushes a new
// entry called E, we should end up with: A -> B -> E in the history list.
history
.
removeRange
(
_currentEntryIndex
,
history
.
length
);
history
.
add
(
_
HistoryEntry
(
state
,
title
,
url
));
history
.
add
(
Test
HistoryEntry
(
state
,
title
,
url
));
if
(
_debugLogHistoryActions
)
{
print
(
'
$runtimeType
.pushState(...) ->
$this
'
);
...
...
@@ -72,7 +78,10 @@ class TestLocationStrategy extends LocationStrategy {
@override
void
replaceState
(
dynamic
state
,
String
title
,
String
url
)
{
assert
(
withinAppHistory
);
currentEntry
=
_HistoryEntry
(
state
,
title
,
url
);
if
(
url
==
null
||
url
==
''
)
{
url
=
currentEntry
.
url
;
}
currentEntry
=
TestHistoryEntry
(
state
,
title
,
url
);
if
(
_debugLogHistoryActions
)
{
print
(
'
$runtimeType
.replaceState(...) ->
$this
'
);
...
...
@@ -149,7 +158,7 @@ class TestLocationStrategy extends LocationStrategy {
String
toString
()
{
final
List
<
String
>
lines
=
List
<
String
>(
history
.
length
);
for
(
int
i
=
0
;
i
<
history
.
length
;
i
++)
{
final
_
HistoryEntry
entry
=
history
[
i
];
final
Test
HistoryEntry
entry
=
history
[
i
];
lines
[
i
]
=
_currentEntryIndex
==
i
?
'*
$entry
'
:
'
$entry
'
;
}
return
'
$runtimeType
: [
\n
${lines.join('\n')}
\n
]'
;
...
...
lib/web_ui/test/engine/history_test.dart
0 → 100644
浏览文件 @
6162c41d
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:async'
;
import
'dart:typed_data'
;
import
'package:test/test.dart'
;
import
'package:ui/src/engine.dart'
;
import
'../spy.dart'
;
TestLocationStrategy
_strategy
;
TestLocationStrategy
get
strategy
=>
_strategy
;
set
strategy
(
TestLocationStrategy
newStrategy
)
{
window
.
locationStrategy
=
_strategy
=
newStrategy
;
}
const
Map
<
String
,
bool
>
originState
=
<
String
,
bool
>{
'origin'
:
true
};
const
Map
<
String
,
bool
>
flutterState
=
<
String
,
bool
>{
'flutter'
:
true
};
const
MethodCodec
codec
=
JSONMethodCodec
();
void
emptyCallback
(
ByteData
date
)
{}
void
main
(
)
{
group
(
'BrowserHistory'
,
()
{
final
PlatformMessagesSpy
spy
=
PlatformMessagesSpy
();
setUp
(()
{
spy
.
setUp
();
});
tearDown
(()
{
spy
.
tearDown
();
strategy
=
null
;
});
test
(
'basic setup works'
,
()
{
strategy
=
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
.
url
,
'/initial'
);
// The flutter entry is pushed and its path should be derived from the
// origin entry.
final
TestHistoryEntry
flutterEntry
=
strategy
.
history
[
1
];
expect
(
flutterEntry
.
state
,
flutterState
);
expect
(
flutterEntry
.
url
,
'/initial'
);
// The flutter entry is the current entry.
expect
(
strategy
.
currentEntry
,
flutterEntry
);
});
test
(
'browser back button pops routes correctly'
,
()
async
{
strategy
=
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'
);
// The number of entries shouldn't change.
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
,
flutterState
);
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 `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'popRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
// We still have 2 entries.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
// The url of the current entry (flutter entry) should go back to /home.
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
});
test
(
'multiple browser back clicks'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
pushRoute
(
'/page1'
);
pushRoute
(
'/page2'
);
// Make sure we are on page2.
expect
(
strategy
.
history
,
hasLength
(
2
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/page2'
);
// Back to page1.
await
strategy
.
back
();
// 1. The engine sends a `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'popRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/page1'
);
// 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
,
'/page1'
);
// Back to home.
await
strategy
.
back
();
// 1. The engine sends a `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'popRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/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
();
// 1. The engine sends a `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'popRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `SystemNavigator.pop` platform message
// because there are no more routes to pop.
await
systemNavigatorPop
();
// 3. The active entry doesn't belong to our history anymore because we
// navigated past it.
expect
(
strategy
.
currentEntryIndex
,
-
1
);
});
test
(
'handle user-provided url'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
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
.
delayed
(
Duration
.
zero
);
// 1. The engine sends a `pushRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
'/page3'
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePushed` platform message.
pushRoute
(
'/page3'
);
// 3. The history state should reflect that /page3 is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/page3'
);
// Back to home.
await
strategy
.
back
();
// 1. The engine sends a `popRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'popRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
isNull
);
spy
.
messages
.
clear
();
// 2. The framework sends a `routePopped` platform message.
popRoute
(
'/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'
);
});
test
(
'user types unknown url'
,
()
async
{
strategy
=
TestLocationStrategy
.
fromEntry
(
TestHistoryEntry
(
null
,
null
,
'/home'
));
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
.
delayed
(
Duration
.
zero
);
// 1. The engine sends a `pushRoute` platform message.
expect
(
spy
.
messages
,
hasLength
(
1
));
expect
(
spy
.
messages
[
0
].
channel
,
'flutter/navigation'
);
expect
(
spy
.
messages
[
0
].
methodName
,
'pushRoute'
);
expect
(
spy
.
messages
[
0
].
methodArguments
,
'/unknown'
);
spy
.
messages
.
clear
();
// 2. The framework doesn't recognize the route name and ignores it.
// 3. The history state should reflect that /home is currently active.
expect
(
strategy
.
history
,
hasLength
(
3
));
expect
(
strategy
.
currentEntryIndex
,
1
);
expect
(
strategy
.
currentEntry
.
state
,
flutterState
);
expect
(
strategy
.
currentEntry
.
url
,
'/home'
);
});
});
}
void
pushRoute
(
String
routeName
)
{
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'routePushed'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
routeName
},
)),
emptyCallback
,
);
}
void
replaceRoute
(
String
routeName
)
{
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'routeReplaced'
,
<
String
,
dynamic
>{
'previousRouteName'
:
'/foo'
,
'routeName'
:
routeName
},
)),
emptyCallback
,
);
}
void
popRoute
(
String
previousRouteName
)
{
window
.
sendPlatformMessage
(
'flutter/navigation'
,
codec
.
encodeMethodCall
(
MethodCall
(
'routePopped'
,
<
String
,
dynamic
>{
'previousRouteName'
:
previousRouteName
,
'routeName'
:
'/foo'
},
)),
emptyCallback
,
);
}
Future
<
void
>
systemNavigatorPop
()
{
final
Completer
<
void
>
completer
=
Completer
<
void
>();
window
.
sendPlatformMessage
(
'flutter/platform'
,
codec
.
encodeMethodCall
(
MethodCall
(
'SystemNavigator.pop'
)),
(
_
)
=>
completer
.
complete
(),
);
return
completer
.
future
;
}
lib/web_ui/test/spy.dart
0 → 100644
浏览文件 @
6162c41d
// Copyright 2013 The Flutter Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import
'dart:typed_data'
;
import
'package:ui/src/engine.dart'
hide
window
;
import
'package:ui/ui.dart'
;
/// Encapsulates the info of a platform message that was intercepted by
/// [PlatformMessagesSpy].
class
PlatformMessage
{
PlatformMessage
(
this
.
channel
,
this
.
methodCall
);
/// The name of the channel on which the message was sent.
final
String
channel
;
/// The [MethodCall] instance that was sent in the platform message.
final
MethodCall
methodCall
;
/// Shorthand for getting the name of the method call.
String
get
methodName
=>
methodCall
.
method
;
/// Shorthand for getting the arguments of the method call.
String
get
methodArguments
=>
methodCall
.
arguments
;
}
/// Intercepts platform messages sent from the engine to the framework.
///
/// It holds all intercepted platform messages in a [messages] list that can
/// be inspected in tests.
class
PlatformMessagesSpy
{
PlatformMessageCallback
_callback
;
PlatformMessageCallback
_backup
;
bool
get
_isActive
=>
_callback
!=
null
;
/// List of intercepted messages since the last [setUp] call.
final
List
<
PlatformMessage
>
messages
=
<
PlatformMessage
>[];
/// Start spying on platform messages.
///
/// This is typically called inside a test's `setUp` callback.
void
setUp
()
{
assert
(!
_isActive
);
_callback
=
(
String
channel
,
ByteData
data
,
PlatformMessageResponseCallback
callback
)
{
messages
.
add
(
PlatformMessage
(
channel
,
const
JSONMethodCodec
().
decodeMethodCall
(
data
),
));
};
_backup
=
window
.
onPlatformMessage
;
window
.
onPlatformMessage
=
_callback
;
}
/// Stop spying on platform messages and clear all intercepted messages.
///
/// Make sure this is called after each test that uses [PlatformMessagesSpy].
void
tearDown
()
{
assert
(
_isActive
);
// Make sure [window.onPlatformMessage] wasn't tampered with.
assert
(
window
.
onPlatformMessage
==
_callback
);
_callback
=
null
;
messages
.
clear
();
window
.
onPlatformMessage
=
_backup
;
}
}
编辑
预览
Markdown
is supported
0%
请重试
或
添加新附件
.
添加附件
取消
You are about to add
0
people
to the discussion. Proceed with caution.
先完成此消息的编辑!
取消
想要评论请
注册
或
登录