blob: f7f5ea355d1f39e4adfb11270d89367ba7568d93 [file] [log] [blame]
// 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 'package:flutter/services.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:shared_preferences_platform_interface/shared_preferences_platform_interface.dart';
import 'package:shared_preferences_platform_interface/types.dart';
void main() {
TestWidgetsFlutterBinding.ensureInitialized();
const String testString = 'hello world';
const bool testBool = true;
const int testInt = 42;
const double testDouble = 3.14159;
const List<String> testList = <String>['foo', 'bar'];
const Map<String, Object> testValues = <String, Object>{
'flutter.String': testString,
'flutter.bool': testBool,
'flutter.int': testInt,
'flutter.double': testDouble,
'flutter.List': testList,
};
const String testString2 = 'goodbye world';
const bool testBool2 = false;
const int testInt2 = 1337;
const double testDouble2 = 2.71828;
const List<String> testList2 = <String>['baz', 'quox'];
const Map<String, dynamic> testValues2 = <String, dynamic>{
'flutter.String': testString2,
'flutter.bool': testBool2,
'flutter.int': testInt2,
'flutter.double': testDouble2,
'flutter.List': testList2,
};
late FakeSharedPreferencesStore store;
late SharedPreferences preferences;
setUp(() async {
store = FakeSharedPreferencesStore(testValues);
SharedPreferencesStorePlatform.instance = store;
preferences = await SharedPreferences.getInstance();
store.log.clear();
});
test('reading', () async {
expect(preferences.get('String'), testString);
expect(preferences.get('bool'), testBool);
expect(preferences.get('int'), testInt);
expect(preferences.get('double'), testDouble);
expect(preferences.get('List'), testList);
expect(preferences.getString('String'), testString);
expect(preferences.getBool('bool'), testBool);
expect(preferences.getInt('int'), testInt);
expect(preferences.getDouble('double'), testDouble);
expect(preferences.getStringList('List'), testList);
expect(store.log, <Matcher>[]);
});
test('writing', () async {
await Future.wait(<Future<bool>>[
preferences.setString('String', testString2),
preferences.setBool('bool', testBool2),
preferences.setInt('int', testInt2),
preferences.setDouble('double', testDouble2),
preferences.setStringList('List', testList2)
]);
expect(
store.log,
<Matcher>[
isMethodCall('setValue', arguments: <dynamic>[
'String',
'flutter.String',
testString2,
]),
isMethodCall('setValue', arguments: <dynamic>[
'Bool',
'flutter.bool',
testBool2,
]),
isMethodCall('setValue', arguments: <dynamic>[
'Int',
'flutter.int',
testInt2,
]),
isMethodCall('setValue', arguments: <dynamic>[
'Double',
'flutter.double',
testDouble2,
]),
isMethodCall('setValue', arguments: <dynamic>[
'StringList',
'flutter.List',
testList2,
]),
],
);
store.log.clear();
expect(preferences.getString('String'), testString2);
expect(preferences.getBool('bool'), testBool2);
expect(preferences.getInt('int'), testInt2);
expect(preferences.getDouble('double'), testDouble2);
expect(preferences.getStringList('List'), testList2);
expect(store.log, equals(<MethodCall>[]));
});
test('removing', () async {
const String key = 'testKey';
await preferences.remove(key);
expect(
store.log,
List<Matcher>.filled(
1,
isMethodCall(
'remove',
arguments: 'flutter.$key',
),
growable: true,
));
});
test('containsKey', () async {
const String key = 'testKey';
expect(false, preferences.containsKey(key));
await preferences.setString(key, 'test');
expect(true, preferences.containsKey(key));
});
test('clearing', () async {
await preferences.clear();
expect(preferences.getString('String'), null);
expect(preferences.getBool('bool'), null);
expect(preferences.getInt('int'), null);
expect(preferences.getDouble('double'), null);
expect(preferences.getStringList('List'), null);
expect(store.log, <Matcher>[isMethodCall('clear', arguments: null)]);
});
test('reloading', () async {
await preferences.setString('String', testString);
expect(preferences.getString('String'), testString);
SharedPreferences.setMockInitialValues(testValues2.cast<String, Object>());
expect(preferences.getString('String'), testString);
await preferences.reload();
expect(preferences.getString('String'), testString2);
});
test('back to back calls should return same instance.', () async {
final Future<SharedPreferences> first = SharedPreferences.getInstance();
final Future<SharedPreferences> second = SharedPreferences.getInstance();
expect(await first, await second);
});
test('string list type is dynamic (usually from method channel)', () async {
SharedPreferences.setMockInitialValues(<String, Object>{
'dynamic_list': <dynamic>['1', '2']
});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final List<String>? value = prefs.getStringList('dynamic_list');
expect(value, <String>['1', '2']);
});
group('mocking', () {
const String key = 'dummy';
const String prefixedKey = 'flutter.$key';
test('test 1', () async {
SharedPreferences.setMockInitialValues(
<String, Object>{prefixedKey: 'my string'});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? value = prefs.getString(key);
expect(value, 'my string');
});
test('test 2', () async {
SharedPreferences.setMockInitialValues(
<String, Object>{prefixedKey: 'my other string'});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? value = prefs.getString(key);
expect(value, 'my other string');
});
});
test('writing copy of strings list', () async {
final List<String> myList = <String>[];
await preferences.setStringList('myList', myList);
myList.add('foobar');
final List<String> cachedList = preferences.getStringList('myList')!;
expect(cachedList, <String>[]);
cachedList.add('foobar2');
expect(preferences.getStringList('myList'), <String>[]);
});
test('calling mock initial values with non-prefixed keys succeeds', () async {
SharedPreferences.setMockInitialValues(<String, Object>{
'test': 'foo',
});
final SharedPreferences prefs = await SharedPreferences.getInstance();
final String? value = prefs.getString('test');
expect(value, 'foo');
});
test('getInstance always returns the same instance', () async {
SharedPreferencesStorePlatform.instance = SlowInitSharedPreferencesStore();
final Future<SharedPreferences> firstFuture =
SharedPreferences.getInstance();
final Future<SharedPreferences> secondFuture =
SharedPreferences.getInstance();
expect(identical(await firstFuture, await secondFuture), true);
});
test('calling setPrefix after getInstance throws', () async {
const String newPrefix = 'newPrefix';
await SharedPreferences.getInstance();
Object? err;
try {
SharedPreferences.setPrefix(newPrefix);
} catch (e) {
err = e;
}
expect(err, isA<StateError>());
});
test('using setPrefix allows setting and getting', () async {
const String newPrefix = 'newPrefix';
SharedPreferences.resetStatic();
SharedPreferences.setPrefix(newPrefix);
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('stringKey', 'test');
await prefs.setBool('boolKey', true);
await prefs.setDouble('doubleKey', 3.14);
final String? testString = prefs.getString('stringKey');
expect(testString, 'test');
final bool? testBool = prefs.getBool('boolKey');
expect(testBool, true);
final double? testDouble = prefs.getDouble('doubleKey');
expect(testDouble, 3.14);
});
test('allowList only gets allowed items', () async {
const Set<String> allowList = <String>{'stringKey', 'boolKey'};
SharedPreferences.resetStatic();
SharedPreferences.setPrefix('', allowList: allowList);
final SharedPreferences prefs = await SharedPreferences.getInstance();
await prefs.setString('stringKey', 'test');
await prefs.setBool('boolKey', true);
await prefs.setDouble('doubleKey', 3.14);
await prefs.reload();
final String? testString = prefs.getString('stringKey');
expect(testString, 'test');
final bool? testBool = prefs.getBool('boolKey');
expect(testBool, true);
final double? testDouble = prefs.getDouble('doubleKey');
expect(testDouble, null);
});
test('using reload after setPrefix properly reloads the cache', () async {
const String newPrefix = 'newPrefix';
SharedPreferences.resetStatic();
SharedPreferences.setPrefix(newPrefix);
final SharedPreferences prefs = await SharedPreferences.getInstance();
String? testString = prefs.getString('stringKey');
await prefs.setString('stringKey', 'test');
testString = prefs.getString('stringKey');
expect(testString, 'test');
await prefs.reload();
final String? testStrings = prefs.getString('stringKey');
expect(testStrings, 'test');
});
test('unimplemented errors in withParameters methods are updated', () async {
final UnimplementedSharedPreferencesStore localStore =
UnimplementedSharedPreferencesStore();
SharedPreferencesStorePlatform.instance = localStore;
SharedPreferences.resetStatic();
SharedPreferences.setPrefix('');
Object? err;
try {
await SharedPreferences.getInstance();
} catch (e) {
err = e;
}
expect(err, isA<UnimplementedError>());
expect(
err.toString(),
contains(
"Shared Preferences doesn't yet support the setPrefix method"));
});
test('non-Unimplemented errors pass through withParameters methods correctly',
() async {
final ThrowingSharedPreferencesStore localStore =
ThrowingSharedPreferencesStore();
SharedPreferencesStorePlatform.instance = localStore;
SharedPreferences.resetStatic();
SharedPreferences.setPrefix('');
Object? err;
try {
await SharedPreferences.getInstance();
} catch (e) {
err = e;
}
expect(err, isA<StateError>());
expect(err.toString(), contains('State Error'));
});
}
class FakeSharedPreferencesStore extends SharedPreferencesStorePlatform {
FakeSharedPreferencesStore(Map<String, Object> data)
: backend = InMemorySharedPreferencesStore.withData(data);
final InMemorySharedPreferencesStore backend;
final List<MethodCall> log = <MethodCall>[];
@override
Future<bool> clear() {
log.add(const MethodCall('clear'));
return backend.clear();
}
@override
Future<bool> clearWithParameters(ClearParameters parameters) {
log.add(const MethodCall('clearWithParameters'));
return backend.clearWithParameters(parameters);
}
@override
Future<Map<String, Object>> getAll() {
log.add(const MethodCall('getAll'));
return backend.getAll();
}
@override
Future<Map<String, Object>> getAllWithParameters(
GetAllParameters parameters) {
log.add(const MethodCall('getAllWithParameters'));
return backend.getAllWithParameters(parameters);
}
@override
Future<bool> remove(String key) {
log.add(MethodCall('remove', key));
return backend.remove(key);
}
@override
Future<bool> setValue(String valueType, String key, Object value) {
log.add(MethodCall('setValue', <dynamic>[valueType, key, value]));
return backend.setValue(valueType, key, value);
}
}
class UnimplementedSharedPreferencesStore
extends SharedPreferencesStorePlatform {
@override
Future<bool> clear() {
throw UnimplementedError();
}
@override
Future<Map<String, Object>> getAll() {
throw UnimplementedError();
}
@override
Future<bool> remove(String key) {
throw UnimplementedError();
}
@override
Future<bool> setValue(String valueType, String key, Object value) {
throw UnimplementedError();
}
}
class SlowInitSharedPreferencesStore
extends UnimplementedSharedPreferencesStore {
@override
Future<Map<String, Object>> getAll() async {
await Future<void>.delayed(const Duration(seconds: 1));
return <String, Object>{};
}
}
class ThrowingSharedPreferencesStore extends SharedPreferencesStorePlatform {
@override
Future<bool> clear() {
throw UnimplementedError();
}
@override
Future<Map<String, Object>> getAll() {
throw UnimplementedError();
}
@override
Future<bool> remove(String key) {
throw UnimplementedError();
}
@override
Future<bool> setValue(String valueType, String key, Object value) {
throw UnimplementedError();
}
@override
Future<Map<String, Object>> getAllWithParameters(
GetAllParameters parameters) {
throw StateError('State Error');
}
}