-
Notifications
You must be signed in to change notification settings - Fork 562
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Implemented autosave on a 5-second timer (#3037)
* Implemented autosave on a 5-second timer * Documented * Removed double quotes * Use appModel instead of codeMirror * Moved autosave functionality to LocalStorage singleton * Added unit tests * Fixed lints * Dart format * Use callback to get fallback snippet * Dart format * Test for more cases * Removed local_storage conditional import * Formatted * Reverted async function to .then * Got rid of _autosave() on dispose() * Revert "Removed local_storage conditional import" This reverts commit c952120.
- Loading branch information
1 parent
e50eaf1
commit 4b1ab01
Showing
9 changed files
with
205 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'local_storage/stub.dart' | ||
if (dart.library.js_util) 'local_storage/web.dart'; | ||
|
||
abstract class LocalStorage { | ||
static LocalStorage instance = LocalStorageImpl(); | ||
|
||
void saveUserCode(String code); | ||
String? getUserCode(); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import '../local_storage.dart'; | ||
import '../utils.dart'; | ||
|
||
class LocalStorageImpl extends LocalStorage { | ||
String? _code; | ||
|
||
@override | ||
void saveUserCode(String code) => _code = code; | ||
|
||
@override | ||
String? getUserCode() => _code?.nullIfEmpty; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'package:web/web.dart' as web; | ||
|
||
import '../local_storage.dart'; | ||
import '../utils.dart'; | ||
|
||
const _userInputKey = 'user_'; | ||
|
||
class LocalStorageImpl extends LocalStorage { | ||
@override | ||
void saveUserCode(String code) => | ||
web.window.localStorage.setItem(_userInputKey, code); | ||
|
||
@override | ||
String? getUserCode() => | ||
web.window.localStorage.getItem(_userInputKey)?.nullIfEmpty; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,128 @@ | ||
// Copyright (c) 2024, the Dart project authors. Please see the AUTHORS file | ||
// for details. All rights reserved. Use of this source code is governed by a | ||
// BSD-style license that can be found in the LICENSE file. | ||
|
||
import 'package:dartpad_ui/local_storage.dart'; | ||
import 'package:dartpad_ui/model.dart'; | ||
import 'package:dartpad_ui/samples.g.dart'; | ||
import 'package:dartpad_ui/utils.dart'; | ||
|
||
import 'package:test/test.dart'; | ||
|
||
String getFallback() => | ||
LocalStorage.instance.getUserCode() ?? Samples.defaultSnippet(); | ||
|
||
Never throwingFallback() => | ||
throw StateError('DartPad tried to load the fallback'); | ||
|
||
void main() { | ||
const channel = Channel.stable; | ||
group('Autosave:', () { | ||
test('empty content is treated as null', () { | ||
expect(''.nullIfEmpty, isNull); | ||
|
||
LocalStorage.instance.saveUserCode('non-empty'); | ||
expect(LocalStorage.instance.getUserCode(), isNotNull); | ||
|
||
LocalStorage.instance.saveUserCode(''); | ||
expect(LocalStorage.instance.getUserCode(), isNull); | ||
}); | ||
|
||
test('null content means sample snippet is shown', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
LocalStorage.instance.saveUserCode(''); | ||
expect(LocalStorage.instance.getUserCode(), isNull); | ||
|
||
await services.performInitialLoad( | ||
getFallback: getFallback, | ||
); | ||
expect(model.sourceCodeController.text, equals(Samples.defaultSnippet())); | ||
}); | ||
|
||
group('non-null content is shown with', () { | ||
const sample = 'Hello, World!'; | ||
setUp(() => LocalStorage.instance.saveUserCode(sample)); | ||
|
||
test('only fallback', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
await services.performInitialLoad( | ||
getFallback: getFallback, | ||
); | ||
expect(model.sourceCodeController.text, equals(sample)); | ||
}); | ||
|
||
test('invalid sample ID', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
await services.performInitialLoad( | ||
getFallback: getFallback, | ||
sampleId: 'This is hopefully not a valid sample ID', | ||
); | ||
expect(model.sourceCodeController.text, equals(sample)); | ||
}); | ||
|
||
test('invalid Flutter sample ID', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
await services.performInitialLoad( | ||
getFallback: getFallback, | ||
flutterSampleId: 'This is hopefully not a valid sample ID', | ||
); | ||
expect(model.sourceCodeController.text, equals(sample)); | ||
}); | ||
|
||
test('invalid Gist ID', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
const gistId = 'This is hopefully not a valid Gist ID'; | ||
await services.performInitialLoad( | ||
getFallback: getFallback, | ||
gistId: gistId, | ||
); | ||
expect(model.sourceCodeController.text, equals(sample)); | ||
}); | ||
}); | ||
|
||
group('content is not shown with', () { | ||
const sample = 'Hello, World!'; | ||
setUp(() => LocalStorage.instance.saveUserCode(sample)); | ||
// Not testing flutterSampleId to avoid breaking when the Flutter docs change | ||
|
||
test('Gist', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
// From gists_tests.dart | ||
const gistId = 'd3bd83918d21b6d5f778bdc69c3d36d6'; | ||
await services.performInitialLoad( | ||
getFallback: throwingFallback, | ||
gistId: gistId, | ||
); | ||
expect(model.sourceCodeController.text, isNot(equals(sample))); | ||
}); | ||
|
||
test('sample', () async { | ||
final model = AppModel(); | ||
final services = AppServices(model, channel); | ||
expect(LocalStorage.instance.getUserCode(), equals(sample)); | ||
|
||
await services.performInitialLoad( | ||
getFallback: throwingFallback, | ||
sampleId: 'dart', | ||
); | ||
expect(model.sourceCodeController.text, isNot(equals(sample))); | ||
}); | ||
}); | ||
}); | ||
} |