From 74a3b101c8e48572820dd30a6680eeec71216a9f Mon Sep 17 00:00:00 2001 From: Tim Shedor Date: Mon, 28 Oct 2024 16:04:06 -0700 Subject: [PATCH] finish writing tests --- packages/brick_offline_first/CHANGELOG.md | 1 + .../lib/src/offline_first_repository.dart | 10 +- ...ffline_first_with_supabase_repository.dart | 30 +- .../test/__mocks__.dart | 8 +- ...e_first_with_supabase_repository_test.dart | 455 ++++++++++++------ .../brick_sqlite/lib/src/sqlite_provider.dart | 8 + .../lib/src/testing/supabase_mock_server.dart | 53 +- .../lib/src/testing/supabase_response.dart | 10 +- 8 files changed, 406 insertions(+), 169 deletions(-) diff --git a/packages/brick_offline_first/CHANGELOG.md b/packages/brick_offline_first/CHANGELOG.md index e0597dd0..a21b8dda 100644 --- a/packages/brick_offline_first/CHANGELOG.md +++ b/packages/brick_offline_first/CHANGELOG.md @@ -2,6 +2,7 @@ - Added `subscriptionByQuery` to `OfflineFirstRepository#notifySubscriptionsWithLocalData` to pass a custom map of `StreamControllers` - Add `GetFirstMixin` for convenient retrieval of the first results of `OfflineFirstRepository#get` +- Close all controllers in `OfflineFirstRepository#subscriptions` and clear the map on `OfflineFirstRepository#reset` ## 3.2.1 diff --git a/packages/brick_offline_first/lib/src/offline_first_repository.dart b/packages/brick_offline_first/lib/src/offline_first_repository.dart index 8329aef0..3dcb4b23 100644 --- a/packages/brick_offline_first/lib/src/offline_first_repository.dart +++ b/packages/brick_offline_first/lib/src/offline_first_repository.dart @@ -304,9 +304,9 @@ abstract class OfflineFirstRepository @visibleForTesting Future notifySubscriptionsWithLocalData({ bool notifyWhenEmpty = true, - Map>>? subscriptionByQuery, + Map>>? subscriptionsByQuery, }) async { - final queriesControllers = (subscriptionByQuery ?? subscriptions[TModel])?.entries; + final queriesControllers = (subscriptionsByQuery ?? subscriptions[TModel])?.entries; if (queriesControllers?.isEmpty ?? true) return; // create a copy of the controllers to avoid concurrent modification while looping @@ -337,6 +337,12 @@ abstract class OfflineFirstRepository Future reset() async { await sqliteProvider.resetDb(); memoryCacheProvider.reset(); + for (final subscription in subscriptions.values) { + for (final controller in subscription.values) { + await controller.close(); + } + } + subscriptions.clear(); } /// Listen for streaming changes when the [sqliteProvider]. diff --git a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart index 2262a7e1..d32871e2 100644 --- a/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart +++ b/packages/brick_offline_first_with_supabase/lib/src/offline_first_with_supabase_repository.dart @@ -170,6 +170,19 @@ abstract class OfflineFirstWithSupabaseRepository ); } + @override + Future reset() async { + await super.reset(); + for (final subscription in supabaseRealtimeSubscriptions.values) { + for (final eventType in subscription.values) { + for (final controller in eventType.values) { + await controller.close(); + } + } + } + supabaseRealtimeSubscriptions.clear(); + } + /// Subscribes to realtime updates using /// [Supabase channels](https://supabase.com/docs/guides/realtime?queryGroups=language&language=dart). /// **This will only work if your Supabase table has realtime enabled.** @@ -182,10 +195,7 @@ abstract class OfflineFirstWithSupabaseRepository /// /// See [subscribe] for reactivity without using realtime. /// - /// `eventType` is the triggering remote event. It is **strongly recommended to not use - /// `PostgresChangeEvent.all`**. This event type requires manual reconcilliation - /// of local and remote data and can be extremely expensive. Instead, use individual - /// events like `PostgresChangeEvent.insert` or `PostgresChangeEvent.delete`. + /// `eventType` is the triggering remote event. /// /// `policy` determines how data is fetched (local or remote). When `.localOnly`, /// Supabase channels will not be used. @@ -221,6 +231,11 @@ abstract class OfflineFirstWithSupabaseRepository filter: queryToPostgresChangeFilter(query), callback: (payload) async { switch (payload.eventType) { + // This code path is likely never hit; `PostgresChangeEvent.all` is used + // to listen to changes but as far as can be determined is not delivered within + // the payload of the callback. + // + // It's handled just in case this behavior changes. case PostgresChangeEvent.all: final localResults = await sqliteProvider.get(repository: this); final remoteResults = @@ -263,12 +278,15 @@ abstract class OfflineFirstWithSupabaseRepository provider: remoteProvider, repository: this, ); - await sqliteProvider.delete(instance as TModel, repository: this); + final primaryKey = + await sqliteProvider.primaryKeyByUniqueColumns(instance as TModel); + instance.primaryKey = primaryKey; + await sqliteProvider.delete(instance, repository: this); memoryCacheProvider.delete(instance, repository: this); } await notifySubscriptionsWithLocalData( - subscriptionByQuery: supabaseRealtimeSubscriptions[TModel]![eventType], + subscriptionsByQuery: supabaseRealtimeSubscriptions[TModel]![eventType], ); }, ) diff --git a/packages/brick_offline_first_with_supabase/test/__mocks__.dart b/packages/brick_offline_first_with_supabase/test/__mocks__.dart index 4fa1dc15..6f6300a7 100644 --- a/packages/brick_offline_first_with_supabase/test/__mocks__.dart +++ b/packages/brick_offline_first_with_supabase/test/__mocks__.dart @@ -39,10 +39,14 @@ class Customer extends OfflineFirstWithSupabaseModel { }); @override - int get hashCode => id.hashCode; + int get hashCode => id.hashCode ^ firstName.hashCode ^ lastName.hashCode; @override - bool operator ==(Object other) => other is Customer && other.id == id; + bool operator ==(Object other) => + other is Customer && + other.id == id && + other.firstName == firstName && + other.lastName == lastName; } @ConnectOfflineFirstWithSupabase( diff --git a/packages/brick_offline_first_with_supabase/test/offline_first_with_supabase_repository_test.dart b/packages/brick_offline_first_with_supabase/test/offline_first_with_supabase_repository_test.dart index 31db7b6a..191ac31a 100644 --- a/packages/brick_offline_first_with_supabase/test/offline_first_with_supabase_repository_test.dart +++ b/packages/brick_offline_first_with_supabase/test/offline_first_with_supabase_repository_test.dart @@ -86,7 +86,7 @@ void main() async { final customers = await repository.get(); expect(customers, hasLength(1)); - final localPizzas = await repository.sqliteProvider.get(); + final localPizzas = await repository.sqliteProvider.get(repository: repository); expect(localPizzas, hasLength(1)); }); }); @@ -197,79 +197,90 @@ void main() async { }); }); - group('#subscribeToRealtime', () { - group('#supabaseRealtimeSubscriptions', () { - test('adds controller and query to #supabaseRealtimeSubscriptions', () async { - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - final query = Query.where('firstName', 'Thomas'); - repository.subscribeToRealtime(query: query); - expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); - expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries - .first.key, - query, - ); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries - .first.value, - isNotNull, - ); - }); + group('#supabaseRealtimeSubscriptions', () { + test('adds controller and query to #supabaseRealtimeSubscriptions', () async { + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + final query = Query.where('firstName', 'Thomas'); + final subscription = repository.subscribeToRealtime(query: query).listen((_) {}); - test('subscription succeeds when policy is non-default .alwaysHydrate', () async { - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - final query = Query.where('firstName', 'Thomas'); - repository.subscribeToRealtime( - policy: OfflineFirstGetPolicy.alwaysHydrate, - query: query, - ); - expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!, - hasLength(1), - ); - }); + expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); + expect( + repository + .supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries.first.key, + query, + ); + expect( + repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries + .first.value, + isNotNull, + ); + await subscription.cancel(); + await Future.delayed(Duration(milliseconds: 10)); + }); - test('adds controller and null query to #supabaseRealtimeSubscriptions', () async { - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - repository.subscribeToRealtime(); - expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); - expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries - .first.key, - isNotNull, - ); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries - .first.value, - isNotNull, - ); - }); + test('subscription succeeds when policy is non-default .alwaysHydrate', () async { + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + final query = Query.where('firstName', 'Thomas'); + final subscription = repository + .subscribeToRealtime( + policy: OfflineFirstGetPolicy.alwaysHydrate, + query: query, + ) + .listen((_) {}); + expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); + expect( + repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!, + hasLength(1), + ); + await subscription.cancel(); + await Future.delayed(Duration(milliseconds: 10)); + }); - test('cancelling removes from #supabaseRealtimeSubscriptions', () async { - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - final subscription = repository.subscribeToRealtime().listen((event) {}); - expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); - await subscription.cancel(); - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - }); + test('adds controller and null query to #supabaseRealtimeSubscriptions', () async { + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + final subscription = repository.subscribeToRealtime().listen((_) {}); + expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); + expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); + expect( + repository + .supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries.first.key, + isNotNull, + ); + expect( + repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries + .first.value, + isNotNull, + ); + await subscription.cancel(); + await Future.delayed(Duration(milliseconds: 10)); + }); - test('pausing does not remove from #supabaseRealtimeSubscriptions', () async { - expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); - final subscription = repository.subscribeToRealtime().listen((event) {}); - expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); - subscription.pause(); - expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); - expect( - repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries - .first.value.isPaused, - isTrue, - ); - }); + test('cancelling removes from #supabaseRealtimeSubscriptions', () async { + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + final subscription = repository.subscribeToRealtime().listen((event) {}); + expect(repository.supabaseRealtimeSubscriptions[Customer], hasLength(1)); + await subscription.cancel(); + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + await Future.delayed(Duration(milliseconds: 10)); + }); + + test('pausing does not remove from #supabaseRealtimeSubscriptions', () async { + expect(repository.supabaseRealtimeSubscriptions, hasLength(0)); + final subscription = repository.subscribeToRealtime().listen((event) {}); + expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); + subscription.pause(); + expect(repository.supabaseRealtimeSubscriptions, hasLength(1)); + expect( + repository.supabaseRealtimeSubscriptions[Customer]![PostgresChangeEvent.all]!.entries + .first.value.isPaused, + isTrue, + ); + await subscription.cancel(); + await Future.delayed(Duration(milliseconds: 10)); }); + }); + group('#subscribeToRealtime', () { test('uses #subscribe for localOnly', () async { final customer = Customer( id: 1, @@ -287,85 +298,261 @@ void main() async { expect(customers, emits([customer])); }); - test('PostgresChangeEvent.insert', () async { - final customer = Customer( - id: 1, - firstName: 'Thomas', - lastName: 'Guy', - pizzas: [ - Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), - ], - ); + group('eventType:', () { + test('PostgresChangeEvent.insert', () async { + final customer = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); - final sqliteResults = await repository.sqliteProvider.get(); - expect(sqliteResults, isEmpty); + final sqliteResults = + await repository.sqliteProvider.get(repository: repository); + expect(sqliteResults, isEmpty); - final customers = - repository.subscribeToRealtime(eventType: PostgresChangeEvent.insert); - expect( - customers, - emitsInOrder([ - [], - [customer], - ]), - ); + final customers = + repository.subscribeToRealtime(eventType: PostgresChangeEvent.insert); + expect( + customers, + emitsInOrder([ + [], + [customer], + ]), + ); - final req = SupabaseRequest(); - final resp = SupabaseResponse( - await mock.serialize( - customer, + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer, + realtimeEvent: PostgresChangeEvent.insert, + repository: repository, + ), realtimeEvent: PostgresChangeEvent.insert, - repository: repository, - ), - realtimeEvent: PostgresChangeEvent.insert, - ); - mock.handle({req: resp}); + ); + mock.handle({req: resp}); - await Future.delayed(const Duration(milliseconds: 100)); + // Wait for request to be handled + await Future.delayed(const Duration(milliseconds: 200)); - final results = await repository.sqliteProvider.get(repository: repository); - expect(results, [customer]); - }); + final results = await repository.sqliteProvider.get(repository: repository); + expect(results, [customer]); + }); - test('PostgresChangeEvent.delete', () async { - final customer = Customer( - id: 1, - firstName: 'Thomas', - lastName: 'Guy', - pizzas: [ - Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), - ], - ); + test('PostgresChangeEvent.delete', () async { + final customer = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); - final id = - await repository.sqliteProvider.upsert(customer, repository: repository); - expect(id, isNotNull); + final id = + await repository.sqliteProvider.upsert(customer, repository: repository); + expect(id, isNotNull); - final customers = - repository.subscribeToRealtime(eventType: PostgresChangeEvent.delete); - expect( - customers, - emitsInOrder([ - [customer], - [], - ]), - ); + final customers = + repository.subscribeToRealtime(eventType: PostgresChangeEvent.delete); + expect( + customers, + emitsInOrder([ + [customer], + [], + ]), + ); - final req = SupabaseRequest(); - final resp = SupabaseResponse( - await mock.serialize( - customer, + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer, + realtimeEvent: PostgresChangeEvent.delete, + repository: repository, + ), realtimeEvent: PostgresChangeEvent.delete, - repository: repository, - ), - realtimeEvent: PostgresChangeEvent.delete, - ); - mock.handle({req: resp}); + ); + mock.handle({req: resp}); - await Future.delayed(const Duration(milliseconds: 100)); + // Wait for request to be handled + await Future.delayed(const Duration(milliseconds: 200)); - final results = await repository.sqliteProvider.get(repository: repository); - expect(results, isEmpty); + final results = await repository.sqliteProvider.get(repository: repository); + expect(results, isEmpty); + }); + + test('PostgresChangeEvent.update', () async { + final customer1 = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + final customer2 = Customer( + id: 1, + firstName: 'Guy', + lastName: 'Thomas', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + + final id = + await repository.sqliteProvider.upsert(customer1, repository: repository); + expect(id, isNotNull); + + final customers = + repository.subscribeToRealtime(eventType: PostgresChangeEvent.update); + expect( + customers, + emitsInOrder([ + [customer1], + [customer2], + ]), + ); + + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer2, + realtimeEvent: PostgresChangeEvent.update, + repository: repository, + ), + realtimeEvent: PostgresChangeEvent.update, + ); + mock.handle({req: resp}); + }); + + group('as .all and ', () { + test('PostgresChangeEvent.insert', () async { + final customer = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + + final sqliteResults = + await repository.sqliteProvider.get(repository: repository); + expect(sqliteResults, isEmpty); + + final customers = repository.subscribeToRealtime(); + expect( + customers, + emitsInOrder([ + [], + [customer], + ]), + ); + + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer, + realtimeEvent: PostgresChangeEvent.insert, + repository: repository, + ), + realtimeEvent: PostgresChangeEvent.insert, + ); + mock.handle({req: resp}); + + // Wait for request to be handled + await Future.delayed(const Duration(milliseconds: 100)); + + final results = await repository.sqliteProvider.get(repository: repository); + expect(results, [customer]); + }); + + test('PostgresChangeEvent.delete', () async { + final customer = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + + final id = + await repository.sqliteProvider.upsert(customer, repository: repository); + expect(id, isNotNull); + + final customers = repository.subscribeToRealtime(); + expect( + customers, + emitsInOrder([ + [customer], + [], + ]), + ); + + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer, + realtimeEvent: PostgresChangeEvent.delete, + repository: repository, + ), + realtimeEvent: PostgresChangeEvent.delete, + ); + mock.handle({req: resp}); + + // Wait for request to be handled + await Future.delayed(const Duration(milliseconds: 200)); + + final results = await repository.sqliteProvider.get(repository: repository); + expect(results, isEmpty); + }); + + test('PostgresChangeEvent.update', () async { + final customer1 = Customer( + id: 1, + firstName: 'Thomas', + lastName: 'Guy', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + final customer2 = Customer( + id: 1, + firstName: 'Guy', + lastName: 'Thomas', + pizzas: [ + Pizza(id: 2, toppings: [Topping.pepperoni], frozen: false), + ], + ); + + final id = + await repository.sqliteProvider.upsert(customer1, repository: repository); + expect(id, isNotNull); + + final customers = repository.subscribeToRealtime(); + expect( + customers, + emitsInOrder([ + [customer1], + [customer2], + ]), + ); + + final req = SupabaseRequest(); + final resp = SupabaseResponse( + await mock.serialize( + customer2, + realtimeEvent: PostgresChangeEvent.update, + repository: repository, + ), + realtimeEvent: PostgresChangeEvent.update, + ); + mock.handle({req: resp}); + }); + }); }); }); }); diff --git a/packages/brick_sqlite/lib/src/sqlite_provider.dart b/packages/brick_sqlite/lib/src/sqlite_provider.dart index ceb96be2..27dbb3c8 100644 --- a/packages/brick_sqlite/lib/src/sqlite_provider.dart +++ b/packages/brick_sqlite/lib/src/sqlite_provider.dart @@ -203,6 +203,14 @@ class SqliteProvider implements Provider { } } + /// Retrieve a primary key by querying its unique columns. + /// Advanced use only. + Future primaryKeyByUniqueColumns(TModel instance) async { + final adapter = modelDictionary.adapterFor[TModel]!; + final db = await getDb(); + return await adapter.primaryKeyByUniqueColumns(instance, db); + } + /// Fetch results for model with a custom SQL statement. /// It is recommended to use [get] whenever possible. **Advanced use only**. Future> rawGet( diff --git a/packages/brick_supabase/lib/src/testing/supabase_mock_server.dart b/packages/brick_supabase/lib/src/testing/supabase_mock_server.dart index fc2e5638..d067d325 100644 --- a/packages/brick_supabase/lib/src/testing/supabase_mock_server.dart +++ b/packages/brick_supabase/lib/src/testing/supabase_mock_server.dart @@ -37,11 +37,14 @@ class SupabaseMockServer { await client.dispose(); await client.removeAllChannels(); + await listener?.cancel(); + + hasListener = false; + listeners.clear(); await webSocket?.close(); - await listener?.cancel(); await server.close(force: true); } @@ -95,28 +98,30 @@ class SupabaseMockServer { if (matching == null) return; - final replyString = jsonEncode({ - 'event': 'phx_reply', - 'payload': { - 'response': { - 'postgres_changes': matching.value.flattenedResponses.map((r) { - final data = Map.from(r.data as Map); - - return { - 'id': _realtimeEventToId(r.realtimeEvent!), - 'event': r.realtimeEvent!.name.toUpperCase(), - 'schema': data['payload']['data']['schema'], - 'table': data['payload']['data']['table'], - if (realtimeFilter != null) 'filter': realtimeFilter, - }; - }).toList(), + if (requestJson['payload']['config']['postgres_changes'].first['event'] != '*') { + final replyString = jsonEncode({ + 'event': 'phx_reply', + 'payload': { + 'response': { + 'postgres_changes': matching.value.flattenedResponses.map((r) { + final data = Map.from(r.data as Map); + + return { + 'id': _realtimeEventToId(r.realtimeEvent!), + 'event': r.realtimeEvent!.name.toUpperCase(), + 'schema': data['payload']['data']['schema'], + 'table': data['payload']['data']['table'], + if (realtimeFilter != null) 'filter': realtimeFilter, + }; + }).toList(), + }, + 'status': 'ok', }, - 'status': 'ok', - }, - 'ref': ref, - 'topic': topic, - }); - webSocket!.add(replyString); + 'ref': ref, + 'topic': topic, + }); + webSocket!.add(replyString); + } for (final realtimeResponses in matching.value.flattenedResponses) { await Future.delayed(matching.value.realtimeDelayBetweenResponses); @@ -179,7 +184,7 @@ class SupabaseMockServer { case PostgresChangeEvent.delete: return 48673474; case PostgresChangeEvent.all: - return 12345678; + return 0; } } @@ -193,6 +198,8 @@ class SupabaseMockServer { PostgresChangeEvent? realtimeEvent, ModelRepository? repository, }) async { + assert(realtimeEvent != PostgresChangeEvent.all, '.all realtime events are not serialized'); + final adapter = modelDictionary.adapterFor[TModel]!; final serialized = await adapter.toSupabase( instance, diff --git a/packages/brick_supabase/lib/src/testing/supabase_response.dart b/packages/brick_supabase/lib/src/testing/supabase_response.dart index 30b874d1..daf64526 100644 --- a/packages/brick_supabase/lib/src/testing/supabase_response.dart +++ b/packages/brick_supabase/lib/src/testing/supabase_response.dart @@ -12,10 +12,16 @@ class SupabaseResponse { final List realtimeResponses; List get flattenedResponses { - return realtimeResponses.fold([this], (acc, r) { + final starter = []; + if (realtimeEvent != PostgresChangeEvent.all) { + starter.add(this); + } + return realtimeResponses.fold(starter, (acc, r) { void recurse(SupabaseResponse response) { if (response.realtimeResponses.isNotEmpty) { - acc.addAll(response.realtimeResponses); + acc.addAll( + response.realtimeResponses.where((e) => e.realtimeEvent != PostgresChangeEvent.all), + ); response.realtimeResponses.forEach(recurse); } }