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 90f1d55e..1a8d6864 100644 --- a/packages/signals_core/test/async/future_test.dart +++ b/packages/signals_core/test/async/future_test.dart @@ -80,30 +80,58 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - return 10; + // Make it behave dynamically by returning the number of times it's called times 10 + return calls * 10; } 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(); expect(calls, 2); - expect(signal.value.value, 10); + 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), + ]); }); test('check refresh calls', () async { @@ -112,30 +140,58 @@ void main() { Future future() async { calls++; await Future.delayed(const Duration(milliseconds: 5)); - return 10; + // Make it behave dynamically by returning the number of times it's called times 10 + return calls * 10; } 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.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), + ]); }); 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..ca370ed0 100644 --- a/packages/signals_core/test/async/stream_test.dart +++ b/packages/signals_core/test/async/stream_test.dart @@ -60,30 +60,60 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - yield* _stream(); + // Make it behave dynamically by returning the number of times it's called times 10 + yield* _stream(value: calls * 10); } 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.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), + ]); }); test('check refresh calls', () async { @@ -92,13 +122,18 @@ void main() { Stream stream() async* { calls++; await Future.delayed(const Duration(milliseconds: 5)); - yield* _stream(); + // Make it behave dynamically by returning the number of times it's called times 10 + yield* _stream(value: calls * 10); } 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 +147,33 @@ 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.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), + ]); }); test('onDone', () async { @@ -212,7 +270,7 @@ void main() { test('cancelOnError', () async { final signal = streamSignal( - () => _stream(true), + () => _stream(error: true), cancelOnError: true, ); @@ -226,10 +284,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(); }