Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 16 additions & 10 deletions packages/signals_core/lib/src/async/signal.dart
Original file line number Diff line number Diff line change
Expand Up @@ -238,20 +238,26 @@ class AsyncSignal<T> extends Signal<AsyncState<T>>

/// Reload the future
Future<void> reload() async {
value = switch (value) {
AsyncData<T> data => AsyncDataReloading<T>(data.value),
AsyncError<T> err => AsyncErrorReloading<T>(err.error, err.stackTrace),
AsyncLoading<T>() => AsyncLoading<T>(),
};
batch(() {
if (completer.isCompleted) completer = Completer<bool>();
value = switch (value) {
AsyncData<T> data => AsyncDataReloading<T>(data.value),
AsyncError<T> err => AsyncErrorReloading<T>(err.error, err.stackTrace),
AsyncLoading<T>() => AsyncLoading<T>(),
};
});
}

/// Refresh the future
Future<void> refresh() async {
value = switch (value) {
AsyncData<T> data => AsyncDataRefreshing<T>(data.value),
AsyncError<T> err => AsyncErrorRefreshing<T>(err.error, err.stackTrace),
AsyncLoading<T>() => AsyncLoading<T>(),
};
batch(() {
if (completer.isCompleted) completer = Completer<bool>();
value = switch (value) {
AsyncData<T> data => AsyncDataRefreshing<T>(data.value),
AsyncError<T> err => AsyncErrorRefreshing<T>(err.error, err.stackTrace),
AsyncLoading<T>() => AsyncLoading<T>(),
};
});
}

@override
Expand Down
64 changes: 60 additions & 4 deletions packages/signals_core/test/async/future_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -80,30 +80,58 @@ void main() {
Future<int> 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<AsyncState<int>> 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<int>>[
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 {
Expand All @@ -112,30 +140,58 @@ void main() {
Future<int> 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<AsyncState<int>> 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<int>>[
AsyncState.loading(),
AsyncState.data(10),
AsyncState.dataRefreshing(10),
AsyncState.data(20),
AsyncState.dataRefreshing(20),
AsyncState.dataRefreshing(20),
AsyncState.data(40),
]);
});

test('dependencies', () async {
Expand Down
72 changes: 65 additions & 7 deletions packages/signals_core/test/async/stream_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -60,30 +60,60 @@ void main() {
Stream<int> 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<AsyncState<int>> 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<int>>[
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 {
Expand All @@ -92,13 +122,18 @@ void main() {
Stream<int> 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<AsyncState<int>> states = [];
addTearDown(signal.subscribe((v) => states.add(v)));
expect(states.length, 1);

await signal.future;

expect(calls, 1);
Expand All @@ -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<int>>[
AsyncState.loading(),
AsyncState.data(10),
AsyncState.dataRefreshing(10),
AsyncState.data(20),
AsyncState.dataRefreshing(20),
AsyncState.dataRefreshing(20),
AsyncState.data(40),
]);
});

test('onDone', () async {
Expand Down Expand Up @@ -212,7 +270,7 @@ void main() {

test('cancelOnError', () async {
final signal = streamSignal(
() => _stream(true),
() => _stream(error: true),
cancelOnError: true,
);

Expand All @@ -226,10 +284,10 @@ void main() {
});
}

Stream<int> _stream([bool error = false]) async* {
Stream<int> _stream({bool error = false, int value = 10}) async* {
await Future.delayed(const Duration(milliseconds: 5));
if (!error) {
yield 10;
yield value;
} else {
throw Exception();
}
Expand Down