query_cache related bug fix using query_cache_test

This commit is contained in:
Kingkor Roy Tirtho
2022-02-18 10:36:20 +06:00
parent 0b4c030806
commit 3d3f3381e9
8 changed files with 280 additions and 50 deletions
+2 -2
View File
@@ -537,7 +537,7 @@ class FetchQueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
/// The time after data is considered stale.
/// If the data is fresh it will be returned from the cache.
Duration? staleTime;
FetchQueryOptions(
FetchQueryOptions({
ShouldRetryFunction<TError>? retry,
RetryDelayFunction<TError>? retryDelay,
Duration? cacheTime,
@@ -554,7 +554,7 @@ class FetchQueryOptions<TQueryFnData extends Map<String, dynamic>, TError,
GetNextPageParamFunction<TQueryFnData>? getNextPageParam,
bool? defaulted,
this.staleTime,
) : super(
}) : super(
retry: retry,
retryDelay: retryDelay,
cacheTime: cacheTime,
+1 -1
View File
@@ -280,7 +280,7 @@ class Query<TQueryFnData extends Map<String, dynamic>, TError,
data = prevData as TData;
} else if (this.options.structuralSharing != false) {
// Structurally share data between prev and new data if needed
data = replaceEqualDeep(prevData ?? {} as TData, data);
data = replaceEqualDeep(prevData, data);
}
// Set data and mark it as cached
_dispatch(Action(
+21 -22
View File
@@ -139,26 +139,31 @@ class QueryCache extends Subscribable<QueryCacheListener> {
}
Query<TQueryFnData, TError, TData>? find<
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(
QueryKey queryKey,
QueryFilters queryFilters,
) {
TQueryFnData extends Map<String, dynamic>,
TError,
TData extends Map<String, dynamic>>(QueryKey queryKey,
[QueryFilters? queryFilters]) {
queryFilters ??= QueryFilters();
queryFilters.exact ??= true;
return _queries.firstWhereOrNull((query) => matchQuery(queryFilters, query))
as Query<TQueryFnData, TError, TData>?;
return _queries.firstWhereOrNull((query) => matchQuery(
queryFilters!,
query,
queryKey,
)) as Query<TQueryFnData, TError, TData>?;
}
List<Query> findAll(QueryKey? queryKey, [QueryFilters? filters]) {
if (queryKey == null && filters == null)
throw Exception(
"[QueryCache.findAll] both `queryKey` & `filters` can't be null");
bool filterIsEmpty =
filters?.toJson().entries.every((map) => map.value == null) ?? false;
return filterIsEmpty
List<Query> findAll([QueryKey? queryKeys, QueryFilters? filters]) {
return filters == null && queryKeys == null
? _queries
: _queries.where((query) => matchQuery(filters!, query)).toList();
: _queries
.where(
(query) => matchQuery(
filters ?? QueryFilters(),
query,
queryKeys,
),
)
.toList();
}
void notify(QueryCacheNotifyEvent event) {
@@ -169,12 +174,6 @@ class QueryCache extends Subscribable<QueryCacheListener> {
});
}
@override
void onSubscribe() {}
@override
void onUnsubscribe() {}
/// Dummy function just to keep the API similar to react-query
void onFocus() {}
@@ -97,10 +97,10 @@ class QueryClient {
}
List<QueryData<TData>> getQueriesData<TData extends Map<String, dynamic>>({
QueryKey? queryKey,
QueryKey? queryKeys,
QueryFilters? filters,
}) {
return getQueryCache().findAll(queryKey, filters).map((query) {
return getQueryCache().findAll(queryKeys, filters).map((query) {
return QueryData<TData>(
data: query.state.data as TData,
queryKey: query.queryKey,
@@ -130,15 +130,15 @@ class QueryClient {
List<QueryData> setQueriesData<TData extends Map<String, dynamic>>({
required DataUpdateFunction<TData?, TData> updater,
QueryKey? queryKey,
QueryKey? queryKeys,
QueryFilters? filters,
DateTime? updatedAt,
}) {
if (queryKey == null && filters == null)
if (queryKeys == null && filters == null)
throw Exception(
"[QueryClient.setQueriesData] both `queryKey` & `filters` can't be null at the same time");
return notifyManager
.batch(() => getQueryCache().findAll(queryKey, filters).map(
.batch(() => getQueryCache().findAll(queryKeys, filters).map(
(query) => QueryData(
queryKey: query.queryKey,
data: setQueryData<TData>(
@@ -164,10 +164,10 @@ class QueryClient {
?.state as QueryState<TData, TError>?;
}
void removeQueries({QueryKey? queryKey, QueryFilters? filters}) {
void removeQueries({QueryKey? queryKeys, QueryFilters? filters}) {
notifyManager.batch(
() => {
_queryCache.findAll(queryKey, filters).forEach((query) {
_queryCache.findAll(queryKeys, filters).forEach((query) {
_queryCache.remove(query);
})
},
@@ -175,7 +175,7 @@ class QueryClient {
}
Future<void> resetQueries<TPageData>({
QueryKey? queryKey,
QueryKey? queryKeys,
RefetchableQueryFilters<TPageData>? filters,
bool? throwOnError,
}) {
@@ -186,7 +186,7 @@ class QueryClient {
});
return notifyManager.batch(() {
_queryCache.findAll(queryKey, filters).forEach((query) {
_queryCache.findAll(queryKeys, filters).forEach((query) {
query.reset();
});
return refetchQueries(
@@ -197,13 +197,13 @@ class QueryClient {
}
Future<void> cancelQueries({
QueryKey? queryKey,
QueryKey? queryKeys,
QueryFilters? filters,
bool? revert = true,
bool? silent,
}) {
var futures = notifyManager.batch(() =>
_queryCache.findAll(queryKey, filters).map((query) => query.cancel(
_queryCache.findAll(queryKeys, filters).map((query) => query.cancel(
revert: revert,
silent: silent,
)));
@@ -211,7 +211,7 @@ class QueryClient {
}
Future<void> invalidateQueries<TPageData>({
QueryKey? queryKey,
QueryKey? queryKeys,
InvalidateQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
@@ -223,7 +223,7 @@ class QueryClient {
"inactive": filters?.refetchInactive ?? false,
});
return notifyManager.batch(() {
_queryCache.findAll(queryKey, filters).forEach((query) {
_queryCache.findAll(queryKeys, filters).forEach((query) {
query.invalidate();
});
return this.refetchQueries(
@@ -234,12 +234,12 @@ class QueryClient {
}
Future<void> refetchQueries<TPageData>({
QueryKey? queryKey,
QueryKey? queryKeys,
RefetchableQueryFilters<TPageData>? filters,
RefetchOptions? options,
}) {
var futures = notifyManager.batch(
() => _queryCache.findAll(queryKey, filters).map(
() => _queryCache.findAll(queryKeys, filters).map(
(query) => query.fetch(
null,
ObserverFetchOptions(
@@ -6,14 +6,14 @@ abstract class Subscribable<TListener extends Function> {
Subscribable() : listeners = [];
void Function() subscribe([TListener? listener]) {
var callback = listener ?? (() => null);
listener ??= (() => null) as TListener;
listeners.add(callback as TListener);
listeners.add(listener);
onSubscribe();
return () {
listeners = listeners.where((x) => x != callback).toList();
listeners = listeners.where((x) => x != listener).toList();
onUnsubscribe();
};
}
@@ -23,8 +23,8 @@ abstract class Subscribable<TListener extends Function> {
}
@protected
void onSubscribe();
void onSubscribe() {}
@protected
void onUnsubscribe();
void onUnsubscribe() {}
}
+13 -5
View File
@@ -39,12 +39,20 @@ QueryStatusFilter mapQueryStatusFilter(
}
}
bool matchQuery(QueryFilters filters, Query query, [QueryKey? queryKey]) {
if (queryKey != null) {
if (filters.exact! &&
query.queryHash != hashQueryKeyByOptions(queryKey, query.options))
bool matchQuery(
QueryFilters filters,
Query query, [
/// multiple queryKeys to find the query
QueryKey? queryKeys,
]) {
if (queryKeys != null) {
if (filters.exact == true &&
query.queryHash != hashQueryKeyByOptions(queryKeys, query.options))
return false;
else if (query.queryKey.key != queryKey) return false;
else if (query.queryKey.key != queryKeys.key &&
!queryKeys.keyAsList.contains(query.queryKey.key) &&
!query.queryKey.keyAsList.contains(queryKeys.key)) return false;
}
QueryStatusFilter queryStatusFilter =
mapQueryStatusFilter(filters.active, filters.inactive);
@@ -0,0 +1,8 @@
import 'package:fl_query/src/core/core.dart';
import 'package:uuid/uuid.dart';
Uuid uuid = Uuid();
QueryKey queryKey() {
return QueryKey("query_${uuid.v4()}");
}
@@ -0,0 +1,215 @@
import 'package:fl_query/src/core/core.dart';
import 'package:fl_query/src/core/query_cache.dart';
import 'package:test/expect.dart';
import 'package:test/scaffolding.dart';
import '../../helpers/utils.dart';
void main() {
group('QueryCache', () {
late QueryClient queryClient;
late QueryCache queryCache;
setUp(() {
queryClient = new QueryClient();
queryCache = queryClient.getQueryCache();
});
tearDown(() {
queryClient.clear();
});
group('subscribe', () {
test('Should pass the correct query', () async {
final QueryKey key = queryKey();
var _event;
subscriber(event) {
_event ??= event;
}
final unsubscribe = queryCache.subscribe(subscriber);
final Map<String, dynamic> data = {'foo': "foo"};
queryClient.setQueryData(key, (_) => data);
final query = queryCache.find(key);
await Future.delayed(Duration(milliseconds: 1));
expect(_event, isA<QueryCacheNotifyEvent>());
expect(
(_event as QueryCacheNotifyEvent).type,
QueryCacheNotifyEventType.queryAdded,
);
expect(
(_event as QueryCacheNotifyEvent).query,
same(query),
);
unsubscribe();
});
test('Should notify listeners When new query is added', () async {
final key = queryKey();
late bool called;
callback(_) {
called = true;
}
queryCache.subscribe(callback);
queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) async => {'data': "data"},
);
await Future.delayed(Duration(milliseconds: 100));
expect(called, isTrue);
});
test('Should include the queryCache and query When notifying listeners',
() async {
final key = queryKey();
QueryCacheNotifyEvent? _event;
callback(event) => _event ??= event;
queryCache.subscribe(callback);
queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {'data': "data"},
);
final query = queryCache.find(key);
await Future.delayed(Duration(milliseconds: 100));
expect(_event, isA<QueryCacheNotifyEvent>());
expect(_event?.type, QueryCacheNotifyEventType.queryAdded);
expect(_event?.query, same(query));
});
test('Should notify subscribers When new query with initialData is added',
() async {
final key = queryKey();
late bool called;
callback(_) => called = true;
queryCache.subscribe(callback);
queryClient
.prefetchQuery<Map<String, dynamic>, dynamic, Map<String, dynamic>>(
queryKey: key,
queryFn: (_) => {'data': "Data"},
options: FetchQueryOptions(initialData: {"data": "initial-data"}),
);
await Future.delayed(Duration(milliseconds: 100));
expect(called, isTrue);
});
});
group('find', () {
test('Should filter correctly', () async {
final key = queryKey();
await queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {"data": 'data1'},
);
final query = queryCache.find(key);
expect(query, isNotNull);
});
test(
'Should filter correctly When called with exact set to false',
() async {
final key = queryKey();
await queryClient.prefetchQuery(
queryKey: key,
queryFn: (_) => {"data": 'data1'},
);
final query = queryCache.find(key, QueryFilters(exact: false));
expect(query, isNotNull);
},
);
});
group('findAll', () {
test('Should filter correctly', () async {
final key1 = queryKey();
final key2 = queryKey();
final key3 = QueryKey.fromList(['posts', "1"]);
await queryClient.prefetchQuery(
queryKey: key1,
queryFn: (_) => {"data": 'data1'},
);
await queryClient.prefetchQuery(
queryKey: key2,
queryFn: (_) => {"data": 'data2'},
);
await queryClient.prefetchQuery(
queryKey: key3,
queryFn: (_) => {"data": 'data4'},
);
await queryClient.invalidateQueries(queryKeys: key2);
final query1 = queryCache.find(key1);
final query2 = queryCache.find(key2);
final query4 = queryCache.find(key3);
expect(queryCache.findAll(key1), equals([query1]));
expect(queryCache.findAll(), equals([query1, query2, query4]));
expect(
queryCache.findAll(key1, QueryFilters(active: false)),
equals([query1]),
);
expect(
queryCache.findAll(key1, QueryFilters(active: true)), equals([]));
expect(queryCache.findAll(key1, QueryFilters(stale: true)), equals([]));
expect(
queryCache.findAll(key1, QueryFilters(stale: false)),
equals([query1]),
);
expect(
queryCache.findAll(key1, QueryFilters(stale: false, active: true)),
equals([]),
);
expect(
queryCache.findAll(key1, QueryFilters(active: false, stale: false)),
equals([query1]),
);
expect(
queryCache.findAll(
key1,
QueryFilters(active: false, stale: false, exact: true),
),
equals([query1]),
);
expect(queryCache.findAll(key2), equals([query2]));
expect(
queryCache.findAll(key2, QueryFilters(stale: null)),
equals([query2]),
);
expect(
queryCache.findAll(key2, QueryFilters(stale: true)),
equals([query2]),
);
expect(
queryCache.findAll(key2, QueryFilters(stale: false)),
equals([]),
);
expect(
queryCache.findAll(
null,
QueryFilters(predicate: (query) => query == query4),
),
equals([query4]),
);
expect(queryCache.findAll(QueryKey('posts')), equals([query4]));
});
test('Should return all the queries When no filters are defined',
() async {
final key1 = queryKey();
final key2 = queryKey();
await queryClient.prefetchQuery(
queryKey: key1,
queryFn: (_) => {"data": 'data1'},
);
await queryClient.prefetchQuery(
queryKey: key2,
queryFn: (_) {
return {"data": 'data2'};
},
);
expect(queryCache.findAll().length, 2);
});
});
});
}