From 54b6a2bac3cf3d99ea945e85e5ada1b2453c7e0e Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 30 Nov 2025 17:46:56 +0100 Subject: [PATCH 1/2] update tests --- .../signals_core/test/async/future_test.dart | 38 +++++++++++++++-- .../signals_core/test/async/stream_test.dart | 42 +++++++++++++++---- 2 files changed, 69 insertions(+), 11 deletions(-) diff --git a/packages/signals_core/test/async/future_test.dart b/packages/signals_core/test/async/future_test.dart index 90f1d55e..3d4ca699 100644 --- a/packages/signals_core/test/async/future_test.dart +++ b/packages/signals_core/test/async/future_test.dart @@ -80,30 +80,46 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - return 10; + // After the first future, return another value + return calls == 1 ? 10 : 20; } final signal = futureSignal(() => future()); expect(signal.peek().isLoading, true); expect(calls, 1); + final List> states = []; + addTearDown(signal.subscribe((v) => states.add(v))); + expect(states.length, 1); + await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.reload(); + print(states); + expect(calls, 2); - expect(signal.value.value, 10); + expect(signal.value.value, 20); expect(signal.value.error, null); + + expect(states, >[ + AsyncState.loading(), + AsyncState.data(10), + AsyncState.dataReloading(10), + AsyncState.data(20), + ]); }); test('check refresh calls', () async { @@ -112,30 +128,44 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - return 10; + // After the first future, return another value + return calls == 1 ? 10 : 20; } final signal = futureSignal(() => future()); expect(signal.peek().isLoading, true); expect(calls, 1); + final List> states = []; + addTearDown(signal.subscribe((v) => states.add(v))); + expect(states.length, 1); + await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.refresh(); expect(calls, 2); - expect(signal.value.value, 10); + expect(signal.value.value, 20); expect(signal.value.error, null); + + expect(states, >[ + AsyncState.loading(), + AsyncState.data(10), + AsyncState.dataRefreshing(10), + AsyncState.data(20), + ]); }); test('dependencies', () async { diff --git a/packages/signals_core/test/async/stream_test.dart b/packages/signals_core/test/async/stream_test.dart index d453fc31..24c7311b 100644 --- a/packages/signals_core/test/async/stream_test.dart +++ b/packages/signals_core/test/async/stream_test.dart @@ -60,30 +60,45 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - yield* _stream(); + // After the first call, return another value + yield* _stream(value: calls == 1 ? 10 : 20); } final signal = streamSignal(() => stream()); expect(signal.peek().isLoading, true); expect(calls, 0); + final List> states = []; + addTearDown(signal.subscribe((v) => states.add(v))); + expect(states.length, 1); + await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.future; expect(calls, 1); expect(signal.value.value, 10); expect(signal.value.error, null); + expect(states.length, 2); await signal.reload(); + await signal.future; expect(calls, 2); - expect(signal.value.value, 10); + expect(signal.value.value, 20); expect(signal.value.error, null); + + expect(states, >[ + AsyncState.loading(), + AsyncState.data(10), + AsyncState.dataReloading(10), + AsyncState.data(20), + ]); }); test('check refresh calls', () async { @@ -92,13 +107,18 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - yield* _stream(); + // After the first call, return another value + yield* _stream(value: calls == 1 ? 10 : 20); } final signal = streamSignal(() => stream()); expect(signal.peek().isLoading, true); expect(calls, 0); + final List> states = []; + addTearDown(signal.subscribe((v) => states.add(v))); + expect(states.length, 1); + await signal.future; expect(calls, 1); @@ -112,10 +132,18 @@ void main() { expect(signal.value.error, null); await signal.refresh(); + await signal.future; expect(calls, 2); - expect(signal.value.value, 10); + expect(signal.value.value, 20); expect(signal.value.error, null); + + expect(states, >[ + AsyncState.loading(), + AsyncState.data(10), + AsyncState.dataRefreshing(10), + AsyncState.data(20), + ]); }); test('onDone', () async { @@ -212,7 +240,7 @@ void main() { test('cancelOnError', () async { final signal = streamSignal( - () => _stream(true), + () => _stream(error: true), cancelOnError: true, ); @@ -226,10 +254,10 @@ void main() { }); } -Stream _stream([bool error = false]) async* { +Stream _stream({bool error = false, int value = 10}) async* { await Future.delayed(const Duration(milliseconds: 5)); if (!error) { - yield 10; + yield value; } else { throw Exception(); } From 2a1867a7cc7327d493f6935ccbaa9ea695773a60 Mon Sep 17 00:00:00 2001 From: David Martos Date: Sun, 30 Nov 2025 18:15:27 +0100 Subject: [PATCH 2/2] add tests that check that async signals are executed multiple times if reloading while it didn't complete --- .../signals_core/lib/src/async/signal.dart | 26 ++++++++----- .../signals_core/test/async/future_test.dart | 38 ++++++++++++++++--- .../signals_core/test/async/stream_test.dart | 38 +++++++++++++++++-- 3 files changed, 82 insertions(+), 20 deletions(-) diff --git a/packages/signals_core/lib/src/async/signal.dart b/packages/signals_core/lib/src/async/signal.dart index 7b2f2374..ad3bcd81 100644 --- a/packages/signals_core/lib/src/async/signal.dart +++ b/packages/signals_core/lib/src/async/signal.dart @@ -238,20 +238,26 @@ class AsyncSignal extends Signal> /// Reload the future Future reload() async { - value = switch (value) { - AsyncData data => AsyncDataReloading(data.value), - AsyncError err => AsyncErrorReloading(err.error, err.stackTrace), - AsyncLoading() => AsyncLoading(), - }; + batch(() { + if (completer.isCompleted) completer = Completer(); + value = switch (value) { + AsyncData data => AsyncDataReloading(data.value), + AsyncError err => AsyncErrorReloading(err.error, err.stackTrace), + AsyncLoading() => AsyncLoading(), + }; + }); } /// Refresh the future Future refresh() async { - value = switch (value) { - AsyncData data => AsyncDataRefreshing(data.value), - AsyncError err => AsyncErrorRefreshing(err.error, err.stackTrace), - AsyncLoading() => AsyncLoading(), - }; + batch(() { + if (completer.isCompleted) completer = Completer(); + value = switch (value) { + AsyncData data => AsyncDataRefreshing(data.value), + AsyncError err => AsyncErrorRefreshing(err.error, err.stackTrace), + AsyncLoading() => AsyncLoading(), + }; + }); } @override diff --git a/packages/signals_core/test/async/future_test.dart b/packages/signals_core/test/async/future_test.dart index 3d4ca699..1a8d6864 100644 --- a/packages/signals_core/test/async/future_test.dart +++ b/packages/signals_core/test/async/future_test.dart @@ -80,8 +80,8 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - // After the first future, return another value - return calls == 1 ? 10 : 20; + // Make it behave dynamically by returning the number of times it's called times 10 + return calls * 10; } final signal = futureSignal(() => future()); @@ -108,17 +108,29 @@ void main() { await signal.reload(); - print(states); - expect(calls, 2); expect(signal.value.value, 20); expect(signal.value.error, null); + expect(states.length, 4); + + // Make sure that the future is triggered multiple times if calling reload while it didn't finish loading + await Future.wait([ + signal.reload(), + signal.reload(), + ]); + + expect(calls, 4); // 2 more calls + expect(signal.value.value, 40); + expect(signal.value.error, null); expect(states, >[ AsyncState.loading(), AsyncState.data(10), AsyncState.dataReloading(10), AsyncState.data(20), + AsyncState.dataReloading(20), + AsyncState.dataReloading(20), + AsyncState.data(40), ]); }); @@ -128,8 +140,8 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - // After the first future, return another value - return calls == 1 ? 10 : 20; + // Make it behave dynamically by returning the number of times it's called times 10 + return calls * 10; } final signal = futureSignal(() => future()); @@ -159,12 +171,26 @@ void main() { expect(calls, 2); expect(signal.value.value, 20); expect(signal.value.error, null); + expect(states.length, 4); + + // Make sure that the future is triggered multiple times if calling refresh while it didn't finish loading + await Future.wait([ + signal.refresh(), + signal.refresh(), + ]); + + expect(calls, 4); // 2 more calls + expect(signal.value.value, 40); + expect(signal.value.error, null); expect(states, >[ AsyncState.loading(), AsyncState.data(10), AsyncState.dataRefreshing(10), AsyncState.data(20), + AsyncState.dataRefreshing(20), + AsyncState.dataRefreshing(20), + AsyncState.data(40), ]); }); diff --git a/packages/signals_core/test/async/stream_test.dart b/packages/signals_core/test/async/stream_test.dart index 24c7311b..ca370ed0 100644 --- a/packages/signals_core/test/async/stream_test.dart +++ b/packages/signals_core/test/async/stream_test.dart @@ -60,8 +60,8 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - // After the first call, return another value - yield* _stream(value: calls == 1 ? 10 : 20); + // Make it behave dynamically by returning the number of times it's called times 10 + yield* _stream(value: calls * 10); } final signal = streamSignal(() => stream()); @@ -92,12 +92,27 @@ void main() { expect(calls, 2); expect(signal.value.value, 20); expect(signal.value.error, null); + expect(states.length, 4); + + // Make sure that the stream is triggered multiple times if calling reload while it didn't finish loading + await Future.wait([ + signal.reload(), + signal.reload(), + ]); + await signal.future; + + expect(calls, 4); // 2 more calls + expect(signal.value.value, 40); + expect(signal.value.error, null); expect(states, >[ AsyncState.loading(), AsyncState.data(10), AsyncState.dataReloading(10), AsyncState.data(20), + AsyncState.dataReloading(20), + AsyncState.dataReloading(20), + AsyncState.data(40), ]); }); @@ -107,8 +122,8 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - // After the first call, return another value - yield* _stream(value: calls == 1 ? 10 : 20); + // Make it behave dynamically by returning the number of times it's called times 10 + yield* _stream(value: calls * 10); } final signal = streamSignal(() => stream()); @@ -137,12 +152,27 @@ void main() { expect(calls, 2); expect(signal.value.value, 20); expect(signal.value.error, null); + expect(states.length, 4); + + // Make sure the stream is triggered multiple times if calling refresh while it didn't finish loading + await Future.wait([ + signal.refresh(), + signal.refresh(), + ]); + await signal.future; + + expect(calls, 4); // 2 more calls + expect(signal.value.value, 40); + expect(signal.value.error, null); expect(states, >[ AsyncState.loading(), AsyncState.data(10), AsyncState.dataRefreshing(10), AsyncState.data(20), + AsyncState.dataRefreshing(20), + AsyncState.dataRefreshing(20), + AsyncState.data(40), ]); });