Skip to content

Commit aa00d50

Browse files
authored
[web] Don't allow empty initial route (flutter#17936)
1 parent 3b0e415 commit aa00d50

File tree

2 files changed

+79
-5
lines changed

2 files changed

+79
-5
lines changed

lib/web_ui/lib/src/engine/browser_location.dart

+7-4
Original file line numberDiff line numberDiff line change
@@ -73,10 +73,13 @@ class HashLocationStrategy extends LocationStrategy {
7373
// and if it is empty then it will stay empty
7474
String path = _platformLocation.hash ?? '';
7575
assert(path.isEmpty || path.startsWith('#'));
76-
// Dart will complain if a call to substring is
77-
// executed with a position value that exceeds the
78-
// length of string.
79-
return path.isEmpty ? path : path.substring(1);
76+
77+
// We don't want to return an empty string as a path. Instead we default to "/".
78+
if (path.isEmpty || path == '#') {
79+
return '/';
80+
}
81+
// At this point, we know [path] starts with "#" and isn't empty.
82+
return path.substring(1);
8083
}
8184

8285
@override

lib/web_ui/test/engine/history_test.dart

+72-1
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@
77
// TODO(nurhan): https://github.com/flutter/flutter/issues/51169
88

99
import 'dart:async';
10+
import 'dart:html' as html;
1011
import 'dart:typed_data';
1112

1213
import 'package:test/test.dart';
@@ -28,7 +29,7 @@ const MethodCodec codec = JSONMethodCodec();
2829
void emptyCallback(ByteData date) {}
2930

3031
void main() {
31-
group('BrowserHistory', () {
32+
group('$BrowserHistory', () {
3233
final PlatformMessagesSpy spy = PlatformMessagesSpy();
3334

3435
setUp(() {
@@ -229,6 +230,41 @@ void main() {
229230
// TODO(nurhan): https://github.com/flutter/flutter/issues/50836
230231
skip: browserEngine == BrowserEngine.edge);
231232
});
233+
234+
group('$HashLocationStrategy', () {
235+
TestPlatformLocation location;
236+
237+
setUp(() {
238+
location = TestPlatformLocation();
239+
});
240+
241+
tearDown(() {
242+
location = null;
243+
});
244+
245+
test('leading slash is optional', () {
246+
final HashLocationStrategy strategy = HashLocationStrategy(location);
247+
248+
location.hash = '#/';
249+
expect(strategy.path, '/');
250+
251+
location.hash = '#/foo';
252+
expect(strategy.path, '/foo');
253+
254+
location.hash = '#foo';
255+
expect(strategy.path, 'foo');
256+
});
257+
258+
test('path should not be empty', () {
259+
final HashLocationStrategy strategy = HashLocationStrategy(location);
260+
261+
location.hash = '';
262+
expect(strategy.path, '/');
263+
264+
location.hash = '#';
265+
expect(strategy.path, '/');
266+
});
267+
});
232268
}
233269

234270
void pushRoute(String routeName) {
@@ -276,3 +312,38 @@ Future<void> systemNavigatorPop() {
276312
);
277313
return completer.future;
278314
}
315+
316+
/// A mock implementation of [PlatformLocation] that doesn't access the browser.
317+
class TestPlatformLocation extends PlatformLocation {
318+
String pathname;
319+
String search;
320+
String hash;
321+
322+
void onPopState(html.EventListener fn) {
323+
throw UnimplementedError();
324+
}
325+
326+
void offPopState(html.EventListener fn) {
327+
throw UnimplementedError();
328+
}
329+
330+
void onHashChange(html.EventListener fn) {
331+
throw UnimplementedError();
332+
}
333+
334+
void offHashChange(html.EventListener fn) {
335+
throw UnimplementedError();
336+
}
337+
338+
void pushState(dynamic state, String title, String url) {
339+
throw UnimplementedError();
340+
}
341+
342+
void replaceState(dynamic state, String title, String url) {
343+
throw UnimplementedError();
344+
}
345+
346+
void back() {
347+
throw UnimplementedError();
348+
}
349+
}

0 commit comments

Comments
 (0)