|
8 | 8 | import 'dart:async';
|
9 | 9 | import 'dart:html' as html;
|
10 | 10 |
|
| 11 | +import 'package:quiver/testing/async.dart'; |
11 | 12 | import 'package:test/bootstrap/browser.dart';
|
12 | 13 | import 'package:test/test.dart';
|
13 | 14 | import 'package:ui/src/engine.dart' show window;
|
@@ -121,6 +122,68 @@ void testMain() {
|
121 | 122 | // TODO(mdebbar): https://github.com/flutter/flutter/issues/50836
|
122 | 123 | skip: browserEngine == BrowserEngine.edge);
|
123 | 124 |
|
| 125 | + test('disposes of its listener without touching history', () async { |
| 126 | + const String unwrappedOriginState = 'initial state'; |
| 127 | + final Map<String, dynamic> wrappedOriginState = _wrapOriginState(unwrappedOriginState); |
| 128 | + |
| 129 | + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( |
| 130 | + const TestHistoryEntry(unwrappedOriginState, null, '/initial'), |
| 131 | + ); |
| 132 | + expect(strategy.listeners, isEmpty); |
| 133 | + |
| 134 | + await window.debugInitializeHistory(strategy, useSingle: true); |
| 135 | + |
| 136 | + |
| 137 | + // There should be one `popstate` listener and two history entries. |
| 138 | + expect(strategy.listeners, hasLength(1)); |
| 139 | + expect(strategy.history, hasLength(2)); |
| 140 | + expect(strategy.history[0].state, wrappedOriginState); |
| 141 | + expect(strategy.history[0].url, '/initial'); |
| 142 | + expect(strategy.history[1].state, flutterState); |
| 143 | + expect(strategy.history[1].url, '/initial'); |
| 144 | + |
| 145 | + FakeAsync().run((FakeAsync fakeAsync) { |
| 146 | + window.browserHistory.dispose(); |
| 147 | + // The `TestUrlStrategy` implementation uses microtasks to schedule the |
| 148 | + // removal of event listeners. |
| 149 | + fakeAsync.flushMicrotasks(); |
| 150 | + }); |
| 151 | + |
| 152 | + // After disposing, there should no listeners, and the history entries |
| 153 | + // remain unaffected. |
| 154 | + expect(strategy.listeners, isEmpty); |
| 155 | + expect(strategy.history, hasLength(2)); |
| 156 | + expect(strategy.history[0].state, wrappedOriginState); |
| 157 | + expect(strategy.history[0].url, '/initial'); |
| 158 | + expect(strategy.history[1].state, flutterState); |
| 159 | + expect(strategy.history[1].url, '/initial'); |
| 160 | + |
| 161 | + // An extra call to dispose should be safe. |
| 162 | + FakeAsync().run((FakeAsync fakeAsync) { |
| 163 | + expect(() => window.browserHistory.dispose(), returnsNormally); |
| 164 | + fakeAsync.flushMicrotasks(); |
| 165 | + }); |
| 166 | + |
| 167 | + // Same expectations should remain true after the second dispose. |
| 168 | + expect(strategy.listeners, isEmpty); |
| 169 | + expect(strategy.history, hasLength(2)); |
| 170 | + expect(strategy.history[0].state, wrappedOriginState); |
| 171 | + expect(strategy.history[0].url, '/initial'); |
| 172 | + expect(strategy.history[1].state, flutterState); |
| 173 | + expect(strategy.history[1].url, '/initial'); |
| 174 | + |
| 175 | + // Can still teardown after being disposed. |
| 176 | + await window.browserHistory.tearDown(); |
| 177 | + expect(strategy.history, hasLength(2)); |
| 178 | + expect(strategy.currentEntry.state, unwrappedOriginState); |
| 179 | + expect(strategy.currentEntry.url, '/initial'); |
| 180 | + }); |
| 181 | + |
| 182 | + test('disposes gracefully when url strategy is null', () async { |
| 183 | + await window.debugInitializeHistory(null, useSingle: true); |
| 184 | + expect(() => window.browserHistory.dispose(), returnsNormally); |
| 185 | + }); |
| 186 | + |
124 | 187 | test('browser back button pops routes correctly', () async {
|
125 | 188 | final TestUrlStrategy strategy = TestUrlStrategy.fromEntry(
|
126 | 189 | const TestHistoryEntry(null, null, '/home'),
|
@@ -326,6 +389,62 @@ void testMain() {
|
326 | 389 | // TODO(mdebbar): https://github.com/flutter/flutter/issues/50836
|
327 | 390 | skip: browserEngine == BrowserEngine.edge);
|
328 | 391 |
|
| 392 | + test('disposes of its listener without touching history', () async { |
| 393 | + const String untaggedState = 'initial state'; |
| 394 | + final Map<String, dynamic> taggedState = _tagStateWithSerialCount(untaggedState, 0); |
| 395 | + |
| 396 | + final TestUrlStrategy strategy = TestUrlStrategy.fromEntry( |
| 397 | + const TestHistoryEntry(untaggedState, null, '/initial'), |
| 398 | + ); |
| 399 | + expect(strategy.listeners, isEmpty); |
| 400 | + |
| 401 | + await window.debugInitializeHistory(strategy, useSingle: false); |
| 402 | + |
| 403 | + |
| 404 | + // There should be one `popstate` listener and one history entry. |
| 405 | + expect(strategy.listeners, hasLength(1)); |
| 406 | + expect(strategy.history, hasLength(1)); |
| 407 | + expect(strategy.history.single.state, taggedState); |
| 408 | + expect(strategy.history.single.url, '/initial'); |
| 409 | + |
| 410 | + FakeAsync().run((FakeAsync fakeAsync) { |
| 411 | + window.browserHistory.dispose(); |
| 412 | + // The `TestUrlStrategy` implementation uses microtasks to schedule the |
| 413 | + // removal of event listeners. |
| 414 | + fakeAsync.flushMicrotasks(); |
| 415 | + }); |
| 416 | + |
| 417 | + // After disposing, there should no listeners, and the history entries |
| 418 | + // remain unaffected. |
| 419 | + expect(strategy.listeners, isEmpty); |
| 420 | + expect(strategy.history, hasLength(1)); |
| 421 | + expect(strategy.history.single.state, taggedState); |
| 422 | + expect(strategy.history.single.url, '/initial'); |
| 423 | + |
| 424 | + // An extra call to dispose should be safe. |
| 425 | + FakeAsync().run((FakeAsync fakeAsync) { |
| 426 | + expect(() => window.browserHistory.dispose(), returnsNormally); |
| 427 | + fakeAsync.flushMicrotasks(); |
| 428 | + }); |
| 429 | + |
| 430 | + // Same expectations should remain true after the second dispose. |
| 431 | + expect(strategy.listeners, isEmpty); |
| 432 | + expect(strategy.history, hasLength(1)); |
| 433 | + expect(strategy.history.single.state, taggedState); |
| 434 | + expect(strategy.history.single.url, '/initial'); |
| 435 | + |
| 436 | + // Can still teardown after being disposed. |
| 437 | + await window.browserHistory.tearDown(); |
| 438 | + expect(strategy.history, hasLength(1)); |
| 439 | + expect(strategy.history.single.state, untaggedState); |
| 440 | + expect(strategy.history.single.url, '/initial'); |
| 441 | + }); |
| 442 | + |
| 443 | + test('disposes gracefully when url strategy is null', () async { |
| 444 | + await window.debugInitializeHistory(null, useSingle: false); |
| 445 | + expect(() => window.browserHistory.dispose(), returnsNormally); |
| 446 | + }); |
| 447 | + |
329 | 448 | test('browser back button push route information correctly', () async {
|
330 | 449 | final TestUrlStrategy strategy = TestUrlStrategy.fromEntry(
|
331 | 450 | const TestHistoryEntry('initial state', null, '/home'),
|
|
0 commit comments