Skip to content

Commit

Permalink
feat: add unit tests for wallets data providers, local storage (#138)
Browse files Browse the repository at this point in the history
### Added unit tests for wallet-related providers and LocalStorage

This PR adds unit tests for the following components:

**currentWalletIdProvider:**
- Verifies correct wallet ID selection based on existing data
- Handles edge cases like non-existent IDs and empty wallet lists

**currentWalletDataProvider:**
- Ensures correct wallet data retrieval
- Validates error handling for non-existent wallet IDs

**walletByIdProvider:**
- Confirms accurate wallet data retrieval by ID
- Tests error cases for invalid IDs

**WalletsDataNotifier:**
- Validates initial state setup
- Tests CRUD operations (add, update, delete) for wallets
- Verifies exception handling for duplicate and non-existent wallets

**SelectedWalletIdNotifier:**
- Tests initialization from local storage
- Verifies data persistence to local storage
- Ensures proper reaction to local storage changes

**LocalStorage:**
- Comprehensive tests for various data types (bool, double, string,
enum)
- Validates default value handling and error cases
  • Loading branch information
ice-alcides authored Aug 7, 2024
1 parent 77b8ceb commit e51e49b
Show file tree
Hide file tree
Showing 12 changed files with 542 additions and 28 deletions.
3 changes: 2 additions & 1 deletion lib/app/features/core/providers/init_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,9 @@ Future<void> initApp(InitAppRef ref) async {
await Future.wait(<Future<void>>[
ref.read(appTemplateProvider.future),
ref.read(authProvider.notifier).rehydrate(),
LocalStorage.initialize(),
]);

await ref.watch(sharedPreferencesProvider.future);
ref.watch(relaysProvider.notifier);
await ref.read(permissionsProvider.notifier).checkAllPermissions();
}
21 changes: 13 additions & 8 deletions lib/app/features/user/providers/user_preferences_provider.dart
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,16 @@ class UserPreferencesNotifier extends _$UserPreferencesNotifier {

@override
UserPreferences build() {
final isBalanceVisible = LocalStorage.getBool(isBalanceVisibleKey, defaultValue: true);
final localStorage = ref.watch(localStorageProvider);
final isBalanceVisible = localStorage.getBool(isBalanceVisibleKey, defaultValue: true);
final isZeroValueAssetsVisible =
LocalStorage.getBool(isZeroValueAssetsVisibleKey, defaultValue: true);
final nftLayoutType = LocalStorage.getEnum<NftLayoutType>(
localStorage.getBool(isZeroValueAssetsVisibleKey, defaultValue: true);
final nftLayoutType = localStorage.getEnum<NftLayoutType>(
nftLayoutTypeKey,
NftLayoutType.values,
defaultValue: NftLayoutType.list,
);
final nftSortingType = LocalStorage.getEnum<NftSortingType>(
final nftSortingType = localStorage.getEnum<NftSortingType>(
nftSortingTypeKey,
NftSortingType.values,
defaultValue: NftSortingType.desc,
Expand All @@ -38,38 +39,42 @@ class UserPreferencesNotifier extends _$UserPreferencesNotifier {
}

void switchBalanceVisibility() {
final localStorage = ref.read(localStorageProvider);
state = state.copyWith(isBalanceVisible: !state.isBalanceVisible);
LocalStorage.setBool(
localStorage.setBool(
key: isBalanceVisibleKey,
value: state.isBalanceVisible,
);
}

void switchZeroValueAssetsVisibility() {
final localStorage = ref.read(localStorageProvider);
state = state.copyWith(
isZeroValueAssetsVisible: !state.isZeroValueAssetsVisible,
);
LocalStorage.setBool(
localStorage.setBool(
key: isZeroValueAssetsVisibleKey,
value: state.isZeroValueAssetsVisible,
);
}

void setNftLayoutType(NftLayoutType newNftLayoutType) {
final localStorage = ref.read(localStorageProvider);
state = state.copyWith(
nftLayoutType: newNftLayoutType,
);
LocalStorage.setEnum<NftLayoutType>(
localStorage.setEnum<NftLayoutType>(
nftLayoutTypeKey,
newNftLayoutType,
);
}

void setNftSortingType(NftSortingType newNftSortingType) {
final localStorage = ref.read(localStorageProvider);
state = state.copyWith(
nftSortingType: newNftSortingType,
);
LocalStorage.setEnum<NftSortingType>(
localStorage.setEnum<NftSortingType>(
nftSortingTypeKey,
newNftSortingType,
);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,11 +10,12 @@ class SelectedWalletIdNotifier extends _$SelectedWalletIdNotifier {

@override
String? build() {
return LocalStorage.getString(selectedWalletIdKey) ?? mockedWalletDataArray[0].id;
return ref.watch(localStorageProvider).getString(selectedWalletIdKey) ??
mockedWalletDataArray.first.id;
}

set selectedWalletId(String selectedWalletId) {
state = selectedWalletId;
LocalStorage.setString(selectedWalletIdKey, selectedWalletId);
ref.read(localStorageProvider).setString(selectedWalletIdKey, selectedWalletId);
}
}
46 changes: 30 additions & 16 deletions lib/app/services/storage/local_storage.dart
Original file line number Diff line number Diff line change
@@ -1,43 +1,57 @@
import 'dart:async';

import 'package:ice/app/extensions/enum.dart';
import 'package:riverpod_annotation/riverpod_annotation.dart';
import 'package:shared_preferences/shared_preferences.dart';

part 'local_storage.g.dart';

@Riverpod(keepAlive: true)
Future<SharedPreferences> sharedPreferences(SharedPreferencesRef ref) =>
SharedPreferences.getInstance();

@Riverpod(keepAlive: true)
LocalStorage localStorage(LocalStorageRef ref) {
final prefs = ref.watch(sharedPreferencesProvider);

return LocalStorage(prefs.requireValue);
}

class LocalStorage {
static late SharedPreferences _prefs;
final SharedPreferences _prefs;

static Future<void> initialize() async {
_prefs = await SharedPreferences.getInstance();
}
const LocalStorage(this._prefs);

static Future<bool> setBool({required String key, required bool value}) {
return _prefs.setBool(key, value);
void setBool({required String key, required bool value}) {
return unawaited(_prefs.setBool(key, value));
}

static bool getBool(String key, {bool defaultValue = false}) {
bool getBool(String key, {bool defaultValue = false}) {
return _prefs.getBool(key) ?? defaultValue;
}

static Future<bool> setDouble(String key, double value) {
return _prefs.setDouble(key, value);
void setDouble(String key, double value) {
return unawaited(_prefs.setDouble(key, value));
}

static double getDouble(String key, {double defaultValue = 0.0}) {
double getDouble(String key, {double defaultValue = 0.0}) {
return _prefs.getDouble(key) ?? defaultValue;
}

static Future<bool> setString(String key, String value) {
return _prefs.setString(key, value);
void setString(String key, String value) {
return unawaited(_prefs.setString(key, value));
}

static String? getString(String key) {
String? getString(String key) {
return _prefs.getString(key);
}

static Future<bool> setEnum<T extends Enum>(String key, T value) {
return _prefs.setString(key, value.toShortString());
void setEnum<T extends Enum>(String key, T value) {
return unawaited(_prefs.setString(key, value.toShortString()));
}

// Get an enum value
static T getEnum<T extends Enum>(
T getEnum<T extends Enum>(
String key,
List<T> enumValues, {
required T defaultValue,
Expand Down
1 change: 0 additions & 1 deletion lib/l10n/app_es.arb

This file was deleted.

8 changes: 8 additions & 0 deletions pubspec.lock
Original file line number Diff line number Diff line change
Expand Up @@ -994,6 +994,14 @@ packages:
url: "https://pub.dev"
source: hosted
version: "1.0.5"
mocktail:
dependency: "direct main"
description:
name: mocktail
sha256: "890df3f9688106f25755f26b1c60589a92b3ab91a22b8b224947ad041bf172d8"
url: "https://pub.dev"
source: hosted
version: "1.0.4"
nested:
dependency: transitive
description:
Expand Down
1 change: 1 addition & 0 deletions pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ dependencies:
intl: any
json_annotation: ^4.8.1
lottie: ^3.1.0
mocktail: ^1.0.4
nostr_dart:
git: https://github.com/ice-blockchain/nostr-dart.git
permission_handler: ^11.3.0
Expand Down
20 changes: 20 additions & 0 deletions test/mocks.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import 'package:hooks_riverpod/hooks_riverpod.dart';
import 'package:ice/app/features/wallet/model/wallet_data.dart';
import 'package:ice/app/features/wallets/providers/selected_wallet_id_provider.dart';
import 'package:ice/app/features/wallets/providers/wallets_data_provider.dart';
import 'package:ice/app/services/storage/local_storage.dart';
import 'package:mocktail/mocktail.dart';

class Listener<T> extends Mock {
void call(T? previous, T value);
}

class MockLocalStorage extends Mock implements LocalStorage {}

class MockSelectedWalletIdNotifier extends Notifier<String?>
with Mock
implements SelectedWalletIdNotifier {}

class MockWalletsDataNotifier extends Notifier<List<WalletData>>
with Mock
implements WalletsDataNotifier {}
95 changes: 95 additions & 0 deletions test/services/local_storage/local_storage_test.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:ice/app/services/storage/local_storage.dart';
import 'package:shared_preferences/shared_preferences.dart';

enum TestEnum { one, two, three }

const testKey = 'testKey';
const testStringValue = 'testValue';
const testBoolValue = true;
const testDoubleValue = 1.5;
final testEnumValue = TestEnum.two;

void main() {
late LocalStorage localStorage;

setUp(() async {
SharedPreferences.setMockInitialValues({});
final prefs = await SharedPreferences.getInstance();
localStorage = LocalStorage(prefs);
});

group('LocalStorage', () {
test('setBool() and getBool()', () {
localStorage.setBool(key: testKey, value: testBoolValue);

expect(localStorage.getBool(testKey), testBoolValue);
});

test('getBool() returns default value when key not found', () {
expect(
localStorage.getBool(testKey, defaultValue: !testBoolValue),
!testBoolValue,
);
});

test('setDouble() and getDouble()', () {
localStorage.setDouble(testKey, testDoubleValue);

expect(localStorage.getDouble(testKey), testDoubleValue);
});

test('getDouble() returns default value when key not found', () {
expect(
localStorage.getDouble(testKey, defaultValue: 0.0),
0.0,
);
});

test('setString() and getString()', () {
localStorage.setString(testKey, testStringValue);

expect(
localStorage.getString(testKey),
testStringValue,
);
});

test('setEnum() and getEnum()', () {
localStorage.setEnum(testKey, testEnumValue);

expect(
localStorage.getEnum(
testKey,
TestEnum.values,
defaultValue: TestEnum.one,
),
testEnumValue,
);
});

test('getEnum() returns default value when key not found', () {
expect(
localStorage.getEnum(
testKey,
TestEnum.values,
defaultValue: TestEnum.one,
),
TestEnum.one,
);
});

test('getEnum() returns default value when value is invalid', () {
localStorage.setString(testKey, 'invalid');

expect(
localStorage.getEnum(
testKey,
TestEnum.values,
defaultValue: TestEnum.one,
),
TestEnum.one,
);
});
});
}
22 changes: 22 additions & 0 deletions test/test_utils.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import 'package:flutter_test/flutter_test.dart';
import 'package:riverpod/riverpod.dart';

/// A testing utility which creates a [ProviderContainer] and automatically
/// disposes it at the end of the test.
ProviderContainer createContainer({
ProviderContainer? parent,
List<Override> overrides = const [],
List<ProviderObserver>? observers,
}) {
// Create a ProviderContainer, and optionally allow specifying parameters.
final container = ProviderContainer(
parent: parent,
overrides: overrides,
observers: observers,
);

// When the test ends, dispose the container.
addTearDown(container.dispose);

return container;
}
Loading

0 comments on commit e51e49b

Please sign in to comment.