From f75ea334ce10b7914fdade142259916a1f3de419 Mon Sep 17 00:00:00 2001 From: Martin Kamleithner Date: Sun, 12 Mar 2023 20:48:54 +0100 Subject: [PATCH 1/2] feat(ferry_cache): algorithm for cascade eviction --- .../lib/src/utils/evict_cascade2.dart | 241 ++++++++++++++++++ .../test/cascade_eviction_test2.dart | 241 ++++++++++++++++++ 2 files changed, 482 insertions(+) create mode 100644 packages/ferry_cache/lib/src/utils/evict_cascade2.dart create mode 100644 packages/ferry_cache/test/cascade_eviction_test2.dart diff --git a/packages/ferry_cache/lib/src/utils/evict_cascade2.dart b/packages/ferry_cache/lib/src/utils/evict_cascade2.dart new file mode 100644 index 00000000..f9eb718d --- /dev/null +++ b/packages/ferry_cache/lib/src/utils/evict_cascade2.dart @@ -0,0 +1,241 @@ +import 'package:collection/collection.dart'; +import 'package:ferry_cache/src/cache.dart'; +import 'package:meta/meta.dart'; +import 'package:normalize/src/utils/constants.dart'; + +@internal +abstract class NodeRef { + void evict(Cache cache); +} + +/// A reference to a node in the cache, by id. +@internal +class IdNodeRef extends NodeRef { + final String id; + + IdNodeRef(this.id); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is IdNodeRef && runtimeType == other.runtimeType && id == other.id; + + @override + int get hashCode => id.hashCode; + + @override + void evict(Cache cache) { + cache.store.delete(id); + } + + @override + String toString() { + return 'IdNodeRef{id: $id}'; + } +} + +/// A reference to a node in the cache, by id and a path the a field in that node. +@internal +class PathNodeRef extends NodeRef { + final String id; + + /// The path to the field in the node. + /// each element in the path is either a String or an int. + /// if the element is a String, it is the name of a field in the node. + /// if the element is an int, it is the index of an element in a list. + final List< /*String | int */ Object> path; + + PathNodeRef(this.id, this.path); + + @override + bool operator ==(Object other) => + identical(this, other) || + other is PathNodeRef && + runtimeType == other.runtimeType && + id == other.id && + const ListEquality().equals(path, other.path); + + @override + int get hashCode => id.hashCode ^ const ListEquality().hash(path); + + @override + void evict(Cache cache) { + final data = cache.store.get(id); + + if (data == null) { + return; + } + + final copy = data; + + dynamic current = copy; + + for (var i = 0; i < path.length - 2; i++) { + final key = path[i]; + final next = current[key]; + if (next == null) { + return; + } + current = next; + } + + final last = path.last; + + if (last is int) { + final list = current as List; + if (last >= list.length) { + return; + } + list.removeAt(last); + } else { + final map = current as Map; + if (!map.containsKey(last)) { + return; + } + map.remove(last); + } + + cache.store.put(id, copy); + } + + @override + String toString() { + return 'PathNodeRef{id: $id, path: $path}'; + } +} + +Set nodesReferencingId( + Set keys, Map? Function(String) read, String id, + {String refKey = kDefaultReferenceKey}) { + final visited = {}; + final matches = {}; + + final currentNodePath = []; + + print('nodesReferencingId $id'); + + void fieldEntered(String fieldName) { + print('fieldEntered $fieldName'); + final last = currentNodePath.removeLast(); + + if(last is IdNodeRef) { + currentNodePath.add(PathNodeRef(last.id, [fieldName])); + } else if(last is PathNodeRef) { + currentNodePath.add(PathNodeRef(last.id, [...last.path, fieldName])); + } + + print('fieldEntered end $currentNodePath'); + + } + + void fieldExited() { + print('fieldExited begin $currentNodePath'); + final last = currentNodePath.removeLast(); + + assert(last is PathNodeRef); + + if(last is PathNodeRef) { + if(last.path.length == 1) { + currentNodePath.add(IdNodeRef(last.id)); + } else { + currentNodePath.add(PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); + } + } + + print('fieldExited end $currentNodePath'); + } + + void indexEntered(int index) { + print('indexEntered $index'); + final last = currentNodePath.removeLast(); + + assert(last is PathNodeRef); + + if(last is PathNodeRef) { + currentNodePath.add(PathNodeRef(last.id, [...last.path, index])); + } + } + + void indexExited() { + print('indexExited $currentNodePath'); + final last = currentNodePath.removeLast(); + + assert(last is PathNodeRef); + + if(last is PathNodeRef) { + currentNodePath.add(PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); + } + } + + void visitKey(String key) { + print('visiting key $key'); + + if (key == id) { + print('found match $currentNodePath'); + matches.addAll(currentNodePath); + } + + if (!visited.add(key)) { + print('already visited $key 1'); + + return; + } + currentNodePath.add(IdNodeRef(key)); + + final data = read(key); + + void visitNode(Map? data) { + print('visiting node $data'); + if (data == null) { + return; + } + + if (data[refKey] == id) { + print('found match $currentNodePath'); + matches.addAll((currentNodePath)); + visitKey(id); + } else if (data.containsKey(refKey)) { + /// data is a ref to another node + final refId = data[refKey]; + + if (refId == id) { + throw ('refId == id unpossible'); + } + + + visitKey(refId); + } else { + for (final fieldName in data.keys) { + fieldEntered(fieldName); + final field = data[fieldName]; + if (field is Map) { + visitNode(field); + } else if (field is List) { + for (var i = 0; i < field.length; i++) { + indexEntered(i); + final item = field[i]; + if (item is Map) { + visitNode(item); + } + indexExited(); + } + } + + fieldExited(); + } + } + } + + visitNode(data); + + print('popping ${currentNodePath.last} from $currentNodePath '); + + currentNodePath.removeLast(); + } + + for (final key in keys) { + visitKey(key); + } + + return matches; +} diff --git a/packages/ferry_cache/test/cascade_eviction_test2.dart b/packages/ferry_cache/test/cascade_eviction_test2.dart new file mode 100644 index 00000000..5d7c3a4f --- /dev/null +++ b/packages/ferry_cache/test/cascade_eviction_test2.dart @@ -0,0 +1,241 @@ + + + + + +import 'package:ferry_cache/src/utils/evict_cascade2.dart'; +import 'package:test/test.dart'; + +final _postData = { + 'Query': { + '__typename': 'Query', + 'onePost': {'\$ref': 'Post:123'}, + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + 'parent': {'\$ref': 'Post:456'}, + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + }, +}; + +final _listPostData = { + 'Query': { + '__typename': 'Query', + 'posts': [ + {'\$ref': 'Post:123'}, + {'\$ref': 'Post:456'}, + ], + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + }, +}; + +final _circularPosts = { + 'Query': { + '__typename': 'Query', + 'onePost': {'\$ref': 'Post:123'}, + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + 'parent': {'\$ref': 'Post:456'}, + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + 'parent': {'\$ref': 'Post:123'}, + }, +}; + +final _listPostDataNestedNormalized = { + 'Query': { + '__typename': 'Query', + 'posts': [ + {'\$ref' : 'NestedPost:123'}, + {'\$ref' : 'NestedPost:456'}, + ], + + }, + 'NestedPost:123': { + '__typename' : 'NestedPost', + 'post': {'\$ref': 'Post:123'}, + }, + 'NestedPost:456': { + '__typename' : 'NestedPost', + 'post': {'\$ref': 'Post:456'}, + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + }, + +}; + +final _listPostDataNested = { + 'Query': { + '__typename': 'Query', + 'posts': [ + {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, + {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:456'}}, + + ], + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + }, + +}; + +final _listPostDataDoubleNested = { + 'Query': { + '__typename': 'Query', + 'posts': [ + {'__typename' : 'NestedPost', 'post': {'__typename' : 'DoubleNested', 'post' : {'\$ref' : 'Post:123'} }}, + ], + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + }, + +}; + +final _listPostDataDoubleRefNested = { + 'Query': { + '__typename': 'Query', + 'posts': [ + {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, + {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, + + ], + }, + 'Post:123': { + 'id': '123', + '__typename': 'Post', + }, + 'Post:456': { + 'id': '456', + '__typename': 'Post', + }, + +}; + + + +void main() { + + + test('can find nodes which reference an id', () { + + + final ids = nodesReferencingId(_postData.keys.toSet(), (String id) => _postData[id], 'Post:123',); + + expect(ids, { PathNodeRef('Query', ['onePost' ]),}); + }); + + test('can find nodes which indirectly reference an id', () { + + + final ids = nodesReferencingId(_postData.keys.toSet(), (String id) => _postData[id], 'Post:456',); + + expect(ids, { PathNodeRef('Query', ['onePost' ]),}); + + + }); + + + test('does not enter endless loop on circular references' ,() { + + nodesReferencingId(_circularPosts.keys.toSet(), (String id) => _circularPosts[id], 'Post:456',); + + }, timeout: Timeout(Duration(seconds: 10))); + + + test('can find references in lists', (){ + + final ids = nodesReferencingId(_listPostData.keys.toSet(), (String id) => _listPostData[id], 'Post:123',); + + expect(ids, { PathNodeRef('Query', ['posts', 0 ]),}); + + }); + + + + test('can find references in nested lists which are not normalized', () { + + + final ids = nodesReferencingId(_listPostDataNested.keys.toSet(), (String id) => _listPostDataNested[id], 'Post:123',); + expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); + + + }); + + + + test('can find references in nested lists which are normalized', () { + + + //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataNestedNormalized[id], cascadeMode: CascadeMode.all,); + //expect(ids, { EvictId('NestedPost:123'), EvictField('Query', 'posts'),}); + + final ids = nodesReferencingId(_listPostDataNestedNormalized.keys.toSet(), (String id) => _listPostDataNestedNormalized[id], 'Post:123',); + expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); + + + }); + + + test('can find references in nested lists which is not normalized', () { + + + //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataNestedNormalized[id], cascadeMode: CascadeMode.untilList,); + //expect(ids, { RemoveFromList('Query', ['posts'] , 0)}); + + final ids = nodesReferencingId(_listPostDataNested.keys.toSet(), (String id) => _listPostDataNested[id], 'Post:123',); + + expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); + + }); + + + test('can find references in double nested lists which are not normalized', () { + + + //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataDoubleNested[id], cascadeMode: CascadeMode.untilList,); + //expect(ids, { RemoveFromList('Query', ['posts'] , 0)}); + + final ids = nodesReferencingId(_listPostDataDoubleNested.keys.toSet(), (String id) => _listPostDataDoubleNested[id], 'Post:123',); + expect(ids, { PathNodeRef('Query', ['posts', 0, 'post', 'post' ]),}); + + }); + + test('removes list items in reverse order', () { + + // final ids = calculateEvictions('Post:123', read: (id) => _listPostDataDoubleRefNested[id], cascadeMode: CascadeMode.untilList,); + // expect(ids, { RemoveFromList('Query', ['posts'] , 1), RemoveFromList('Query', ['posts'] , 0)}); + + final ids = nodesReferencingId(_listPostDataDoubleRefNested.keys.toSet(), (String id) => _listPostDataDoubleRefNested[id], 'Post:123',); + expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]), PathNodeRef('Query', ['posts', 1, 'post' ]),}); + + }); + +} \ No newline at end of file From 3f11fa9e300a49b946522072765c54bfc75a0860 Mon Sep 17 00:00:00 2001 From: Martin Kamleithner Date: Sun, 12 Mar 2023 20:51:39 +0100 Subject: [PATCH 2/2] feat(ferry_cache): fmt --- .../isolate/handle_command_test.mocks.dart | 13 +- .../lib/src/utils/evict_cascade2.dart | 28 +-- .../test/cascade_eviction_test2.dart | 195 ++++++++++-------- 3 files changed, 127 insertions(+), 109 deletions(-) diff --git a/packages/ferry/test/isolate/handle_command_test.mocks.dart b/packages/ferry/test/isolate/handle_command_test.mocks.dart index f73e14fe..c93542cd 100644 --- a/packages/ferry/test/isolate/handle_command_test.mocks.dart +++ b/packages/ferry/test/isolate/handle_command_test.mocks.dart @@ -442,10 +442,9 @@ class MockReceivePort extends _i1.Mock implements _i4.ReceivePort { @override _i3.Future reduce( dynamic Function( - dynamic, - dynamic, - )? - combine) => + dynamic, + dynamic, + )? combine) => (super.noSuchMethod( Invocation.method( #reduce, @@ -460,8 +459,7 @@ class MockReceivePort extends _i1.Mock implements _i4.ReceivePort { S Function( S, dynamic, - )? - combine, + )? combine, ) => (super.noSuchMethod( Invocation.method( @@ -599,8 +597,7 @@ class MockReceivePort extends _i1.Mock implements _i4.ReceivePort { [bool Function( dynamic, dynamic, - )? - equals]) => + )? equals]) => (super.noSuchMethod( Invocation.method( #distinct, diff --git a/packages/ferry_cache/lib/src/utils/evict_cascade2.dart b/packages/ferry_cache/lib/src/utils/evict_cascade2.dart index f9eb718d..fcbcca6a 100644 --- a/packages/ferry_cache/lib/src/utils/evict_cascade2.dart +++ b/packages/ferry_cache/lib/src/utils/evict_cascade2.dart @@ -116,29 +116,29 @@ Set nodesReferencingId( void fieldEntered(String fieldName) { print('fieldEntered $fieldName'); - final last = currentNodePath.removeLast(); + final last = currentNodePath.removeLast(); - if(last is IdNodeRef) { + if (last is IdNodeRef) { currentNodePath.add(PathNodeRef(last.id, [fieldName])); - } else if(last is PathNodeRef) { + } else if (last is PathNodeRef) { currentNodePath.add(PathNodeRef(last.id, [...last.path, fieldName])); } print('fieldEntered end $currentNodePath'); - } void fieldExited() { print('fieldExited begin $currentNodePath'); - final last = currentNodePath.removeLast(); + final last = currentNodePath.removeLast(); assert(last is PathNodeRef); - if(last is PathNodeRef) { - if(last.path.length == 1) { + if (last is PathNodeRef) { + if (last.path.length == 1) { currentNodePath.add(IdNodeRef(last.id)); } else { - currentNodePath.add(PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); + currentNodePath.add( + PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); } } @@ -147,23 +147,24 @@ Set nodesReferencingId( void indexEntered(int index) { print('indexEntered $index'); - final last = currentNodePath.removeLast(); + final last = currentNodePath.removeLast(); assert(last is PathNodeRef); - if(last is PathNodeRef) { + if (last is PathNodeRef) { currentNodePath.add(PathNodeRef(last.id, [...last.path, index])); } } void indexExited() { print('indexExited $currentNodePath'); - final last = currentNodePath.removeLast(); + final last = currentNodePath.removeLast(); assert(last is PathNodeRef); - if(last is PathNodeRef) { - currentNodePath.add(PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); + if (last is PathNodeRef) { + currentNodePath.add( + PathNodeRef(last.id, last.path.sublist(0, last.path.length - 1))); } } @@ -202,7 +203,6 @@ Set nodesReferencingId( throw ('refId == id unpossible'); } - visitKey(refId); } else { for (final fieldName in data.keys) { diff --git a/packages/ferry_cache/test/cascade_eviction_test2.dart b/packages/ferry_cache/test/cascade_eviction_test2.dart index 5d7c3a4f..475bc9f9 100644 --- a/packages/ferry_cache/test/cascade_eviction_test2.dart +++ b/packages/ferry_cache/test/cascade_eviction_test2.dart @@ -1,8 +1,3 @@ - - - - - import 'package:ferry_cache/src/utils/evict_cascade2.dart'; import 'package:test/test.dart'; @@ -40,7 +35,7 @@ final _listPostData = { }, }; -final _circularPosts = { +final _circularPosts = { 'Query': { '__typename': 'Query', 'onePost': {'\$ref': 'Post:123'}, @@ -61,33 +56,36 @@ final _listPostDataNestedNormalized = { 'Query': { '__typename': 'Query', 'posts': [ - {'\$ref' : 'NestedPost:123'}, - {'\$ref' : 'NestedPost:456'}, + {'\$ref': 'NestedPost:123'}, + {'\$ref': 'NestedPost:456'}, ], - }, 'NestedPost:123': { - '__typename' : 'NestedPost', + '__typename': 'NestedPost', 'post': {'\$ref': 'Post:123'}, }, 'NestedPost:456': { - '__typename' : 'NestedPost', + '__typename': 'NestedPost', 'post': {'\$ref': 'Post:456'}, }, 'Post:123': { 'id': '123', '__typename': 'Post', }, - }; final _listPostDataNested = { 'Query': { '__typename': 'Query', 'posts': [ - {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, - {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:456'}}, - + { + '__typename': 'NestedPost', + 'post': {'\$ref': 'Post:123'} + }, + { + '__typename': 'NestedPost', + 'post': {'\$ref': 'Post:456'} + }, ], }, 'Post:123': { @@ -98,14 +96,19 @@ final _listPostDataNested = { 'id': '456', '__typename': 'Post', }, - }; final _listPostDataDoubleNested = { 'Query': { '__typename': 'Query', 'posts': [ - {'__typename' : 'NestedPost', 'post': {'__typename' : 'DoubleNested', 'post' : {'\$ref' : 'Post:123'} }}, + { + '__typename': 'NestedPost', + 'post': { + '__typename': 'DoubleNested', + 'post': {'\$ref': 'Post:123'} + } + }, ], }, 'Post:123': { @@ -116,16 +119,20 @@ final _listPostDataDoubleNested = { 'id': '456', '__typename': 'Post', }, - }; final _listPostDataDoubleRefNested = { 'Query': { '__typename': 'Query', 'posts': [ - {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, - {'__typename' : 'NestedPost', 'post': {'\$ref': 'Post:123'}}, - + { + '__typename': 'NestedPost', + 'post': {'\$ref': 'Post:123'} + }, + { + '__typename': 'NestedPost', + 'post': {'\$ref': 'Post:123'} + }, ], }, 'Post:123': { @@ -136,106 +143,120 @@ final _listPostDataDoubleRefNested = { 'id': '456', '__typename': 'Post', }, - }; - - void main() { - - test('can find nodes which reference an id', () { - - - final ids = nodesReferencingId(_postData.keys.toSet(), (String id) => _postData[id], 'Post:123',); - - expect(ids, { PathNodeRef('Query', ['onePost' ]),}); + final ids = nodesReferencingId( + _postData.keys.toSet(), + (String id) => _postData[id], + 'Post:123', + ); + + expect(ids, { + PathNodeRef('Query', ['onePost']), + }); }); test('can find nodes which indirectly reference an id', () { - - - final ids = nodesReferencingId(_postData.keys.toSet(), (String id) => _postData[id], 'Post:456',); - - expect(ids, { PathNodeRef('Query', ['onePost' ]),}); - - + final ids = nodesReferencingId( + _postData.keys.toSet(), + (String id) => _postData[id], + 'Post:456', + ); + + expect(ids, { + PathNodeRef('Query', ['onePost']), + }); }); - - test('does not enter endless loop on circular references' ,() { - - nodesReferencingId(_circularPosts.keys.toSet(), (String id) => _circularPosts[id], 'Post:456',); - + test('does not enter endless loop on circular references', () { + nodesReferencingId( + _circularPosts.keys.toSet(), + (String id) => _circularPosts[id], + 'Post:456', + ); }, timeout: Timeout(Duration(seconds: 10))); + test('can find references in lists', () { + final ids = nodesReferencingId( + _listPostData.keys.toSet(), + (String id) => _listPostData[id], + 'Post:123', + ); - test('can find references in lists', (){ - - final ids = nodesReferencingId(_listPostData.keys.toSet(), (String id) => _listPostData[id], 'Post:123',); - - expect(ids, { PathNodeRef('Query', ['posts', 0 ]),}); - + expect(ids, { + PathNodeRef('Query', ['posts', 0]), + }); }); - - test('can find references in nested lists which are not normalized', () { - - - final ids = nodesReferencingId(_listPostDataNested.keys.toSet(), (String id) => _listPostDataNested[id], 'Post:123',); - expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); - - + final ids = nodesReferencingId( + _listPostDataNested.keys.toSet(), + (String id) => _listPostDataNested[id], + 'Post:123', + ); + expect(ids, { + PathNodeRef('Query', ['posts', 0, 'post']), + }); }); - - test('can find references in nested lists which are normalized', () { - - //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataNestedNormalized[id], cascadeMode: CascadeMode.all,); //expect(ids, { EvictId('NestedPost:123'), EvictField('Query', 'posts'),}); - final ids = nodesReferencingId(_listPostDataNestedNormalized.keys.toSet(), (String id) => _listPostDataNestedNormalized[id], 'Post:123',); - expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); - - + final ids = nodesReferencingId( + _listPostDataNestedNormalized.keys.toSet(), + (String id) => _listPostDataNestedNormalized[id], + 'Post:123', + ); + expect(ids, { + PathNodeRef('Query', ['posts', 0, 'post']), + }); }); - test('can find references in nested lists which is not normalized', () { - - //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataNestedNormalized[id], cascadeMode: CascadeMode.untilList,); //expect(ids, { RemoveFromList('Query', ['posts'] , 0)}); - final ids = nodesReferencingId(_listPostDataNested.keys.toSet(), (String id) => _listPostDataNested[id], 'Post:123',); - - expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]),}); + final ids = nodesReferencingId( + _listPostDataNested.keys.toSet(), + (String id) => _listPostDataNested[id], + 'Post:123', + ); + expect(ids, { + PathNodeRef('Query', ['posts', 0, 'post']), + }); }); - - test('can find references in double nested lists which are not normalized', () { - - + test('can find references in double nested lists which are not normalized', + () { //final ids = calculateEvictions('Post:123', read: (id) => _listPostDataDoubleNested[id], cascadeMode: CascadeMode.untilList,); //expect(ids, { RemoveFromList('Query', ['posts'] , 0)}); - final ids = nodesReferencingId(_listPostDataDoubleNested.keys.toSet(), (String id) => _listPostDataDoubleNested[id], 'Post:123',); - expect(ids, { PathNodeRef('Query', ['posts', 0, 'post', 'post' ]),}); - + final ids = nodesReferencingId( + _listPostDataDoubleNested.keys.toSet(), + (String id) => _listPostDataDoubleNested[id], + 'Post:123', + ); + expect(ids, { + PathNodeRef('Query', ['posts', 0, 'post', 'post']), + }); }); test('removes list items in reverse order', () { - - // final ids = calculateEvictions('Post:123', read: (id) => _listPostDataDoubleRefNested[id], cascadeMode: CascadeMode.untilList,); - // expect(ids, { RemoveFromList('Query', ['posts'] , 1), RemoveFromList('Query', ['posts'] , 0)}); - - final ids = nodesReferencingId(_listPostDataDoubleRefNested.keys.toSet(), (String id) => _listPostDataDoubleRefNested[id], 'Post:123',); - expect(ids, { PathNodeRef('Query', ['posts', 0, 'post' ]), PathNodeRef('Query', ['posts', 1, 'post' ]),}); - + // final ids = calculateEvictions('Post:123', read: (id) => _listPostDataDoubleRefNested[id], cascadeMode: CascadeMode.untilList,); + // expect(ids, { RemoveFromList('Query', ['posts'] , 1), RemoveFromList('Query', ['posts'] , 0)}); + + final ids = nodesReferencingId( + _listPostDataDoubleRefNested.keys.toSet(), + (String id) => _listPostDataDoubleRefNested[id], + 'Post:123', + ); + expect(ids, { + PathNodeRef('Query', ['posts', 0, 'post']), + PathNodeRef('Query', ['posts', 1, 'post']), + }); }); - -} \ No newline at end of file +}