query_cache related bug fix using query_cache_test
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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() {}
|
||||
}
|
||||
|
||||
@@ -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);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
Reference in New Issue
Block a user