Skip to content

Commit

Permalink
ci: add rxdart_flutter tests to GHA (#783)
Browse files Browse the repository at this point in the history
* Update pubspec.yaml

* Update pubspec.yaml

* Update packages/rxdart_flutter/pubspec.yaml

* local ValueSubject

* local ValueSubject
  • Loading branch information
hoc081098 authored Feb 1, 2025
1 parent f3c39ca commit afd7f41
Show file tree
Hide file tree
Showing 9 changed files with 251 additions and 8 deletions.
5 changes: 4 additions & 1 deletion .github/workflows/rxdart-test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -59,8 +59,11 @@ jobs:
- name: Active coverage
run: dart pub global activate coverage

- name: Run tests
- name: Run rxdart tests
run: melos run test-rxdart

- uses: codecov/[email protected]
if: ${{ matrix.flutter == 'stable' }}

- name: Run rxdart_flutter tests
run: melos run test-rxdart-flutter
6 changes: 5 additions & 1 deletion melos.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -24,4 +24,8 @@ scripts:
dart pub global run coverage:format_coverage --lcov --in=coverage.json --out=lcov.info --report-on=lib
generate:
run: melos exec --depends-on=build_runner -- "dart run build_runner build -d"
description: Build all generated files for Dart & Flutter packages in this project.
description: Build all generated files for Dart & Flutter packages in this project.
test-rxdart-flutter:
run: |
cd \$MELOS_ROOT_PATH/packages/rxdart_flutter
flutter test --no-pub
4 changes: 3 additions & 1 deletion packages/rxdart_flutter/lib/src/errors.dart
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import 'package:flutter/foundation.dart';
import 'package:flutter/widgets.dart';
import 'package:rxdart/rxdart.dart';
import 'package:meta/meta.dart';

const _bullet = ' • ';
const _indent = ' ';
Expand Down Expand Up @@ -71,6 +72,7 @@ ${_indent}https://github.com/ReactiveX/rxdart/issues/new
}
}

@internal
void reportError(ErrorAndStackTrace error) {
FlutterError.reportError(
FlutterErrorDetails(
Expand All @@ -81,7 +83,7 @@ void reportError(ErrorAndStackTrace error) {
);
}

// @pragma('vm:notify-debugger-on-exception')
@internal
ErrorAndStackTrace? validateValueStreamInitialValue<T>(ValueStream<T> stream) {
ErrorAndStackTrace? error;

Expand Down
12 changes: 11 additions & 1 deletion packages/rxdart_flutter/lib/src/value_stream_listener.dart
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,7 @@ class _ValueStreamListenerState<T> extends State<ValueStreamListener<T>> {
} else {
skipCount = 0;
if (_initialized) {
WidgetsBinding.instance.addPostFrameCallback((_) {
_ambiguate(WidgetsBinding.instance)!.addPostFrameCallback((_) {
_notifyListener(stream.value);
});
}
Expand Down Expand Up @@ -174,3 +174,13 @@ class _ValueStreamListenerState<T> extends State<ValueStreamListener<T>> {
return widget.child;
}
}

/// Reference: https://docs.flutter.dev/release/release-notes/release-notes-3.0.0#your-code
///
/// This allows a value of type T or T?
/// to be treated as a value of type T?.
///
/// We use this so that APIs that have become
/// non-nullable can still be used with `!` and `?`
/// to support older versions of the API as well.
T? _ambiguate<T>(T? value) => value;
1 change: 0 additions & 1 deletion packages/rxdart_flutter/pubspec.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,6 @@ dev_dependencies:
flutter_test:
sdk: flutter
flutter_lints: ^1.0.4
rxdart_ext: ^0.3.0

topics:
- rxdart
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart_ext/rxdart_ext.dart';
import 'package:rxdart_flutter/rxdart_flutter.dart';
import 'package:rxdart_flutter/src/errors.dart';

import 'rxdart_ext/value_subject.dart';

class BuilderApp<T> extends StatefulWidget {
const BuilderApp({
required this.stream1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart_ext/rxdart_ext.dart';
import 'package:rxdart_flutter/rxdart_flutter.dart';
import 'package:rxdart_flutter/src/errors.dart';

import 'rxdart_ext/value_subject.dart';

class ConsumerApp<T> extends StatefulWidget {
const ConsumerApp({
required this.stream1,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,11 @@ import 'dart:async';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:rxdart_ext/rxdart_ext.dart';
import 'package:rxdart_flutter/rxdart_flutter.dart';
import 'package:rxdart_flutter/src/errors.dart';

import 'rxdart_ext/value_subject.dart';

typedef Value<T> = void Function(T previous, T current);

class ListenerApp<T> extends StatefulWidget {
Expand Down
222 changes: 222 additions & 0 deletions packages/rxdart_flutter/test/src/rxdart_ext/value_subject.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,222 @@
// COPIED from: https://github.com/hoc081098/rxdart_ext/blob/ed5ad736ac0b531348cebef9c1bf5a3130045e89/lib/src/not_replay_value_stream/value_subject.dart

import 'dart:async';

import 'package:meta/meta.dart';
import 'package:rxdart/rxdart.dart';

/// A special [StreamController] that captures the latest item that has been
/// added to the controller.
///
/// [ValueSubject] is the same as [PublishSubject], with the ability to capture
/// the latest item has been added to the controller.
/// This [ValueSubject] always has the value, ie. [hasValue] is always true.
///
/// [ValueSubject] is, by default, a broadcast (aka hot) controller, in order
/// to fulfill the Rx Subject contract. This means the Subject's `stream` can
/// be listened to multiple times.
///
/// ### Example
///
/// final subject = ValueSubject<int>(1);
///
/// print(subject.value); // prints 1
///
/// // observers will receive 3 and done events.
/// subject.stream.listen(print); // prints 2
/// subject.stream.listen(print); // prints 2
/// subject.stream.listen(print); // prints 2
///
/// subject.add(2);
/// subject.close();
@sealed
class ValueSubject<T> extends Subject<T> implements ValueStream<T> {
final _StreamEvent<T> _event;

ValueSubject._(
StreamController<T> controller,
this._event,
) : super(controller, controller.stream);

/// Constructs a [ValueSubject], optionally pass handlers for
/// [onListen], [onCancel] and a flag to handle events [sync].
///
/// [seedValue] becomes the current [value] of Subject.
///
/// See also [StreamController.broadcast].
factory ValueSubject(
T seedValue, {
void Function()? onListen,
void Function()? onCancel,
bool sync = false,
}) {
final controller = StreamController<T>.broadcast(
onListen: onListen,
onCancel: onCancel,
sync: sync,
);

return ValueSubject._(
controller,
_StreamEvent.data(seedValue),
);
}

@override
void onAdd(T event) => _event.onData(event);

@override
void onAddError(Object error, [StackTrace? stackTrace]) =>
_event.onError(ErrorAndStackTrace(error, stackTrace));

@override
ValueStream<T> get stream => _ValueSubjectStream(this);

@nonVirtual
@override
Object get error {
final errorAndSt = _event.errorAndStackTrace;
if (errorAndSt != null) {
return errorAndSt.error;
}
throw ValueStreamError.hasNoError();
}

@nonVirtual
@override
Object? get errorOrNull => _event.errorAndStackTrace?.error;

@nonVirtual
@override
bool get hasError => _event.errorAndStackTrace != null;

@nonVirtual
@override
StackTrace? get stackTrace => _event.errorAndStackTrace?.stackTrace;

@nonVirtual
@override
T get value => _event.value;

@nonVirtual
@override
T get valueOrNull => _event.value;

@nonVirtual
@override
bool get hasValue => true;

@nonVirtual
@override
StreamNotification<T>? get lastEventOrNull {
// data event
if (_event.lastEventIsData) {
return DataNotification(value);
}

// error event
final errorAndSt = _event.errorAndStackTrace;
if (errorAndSt != null) {
return ErrorNotification(errorAndSt);
}

// no event
return null;
}
}

class _ValueSubjectStream<T> extends Stream<T> implements ValueStream<T> {
final ValueSubject<T> _subject;

_ValueSubjectStream(this._subject);

@override
bool get isBroadcast => true;

// Override == and hashCode so that new streams returned by the same
// subject are considered equal.
// The subject returns a new stream each time it's queried,
// but doesn't have to cache the result.

@override
int get hashCode => _subject.hashCode ^ 0x35323532;

@override
bool operator ==(Object other) {
if (identical(this, other)) return true;
return other is _ValueSubjectStream && identical(other._subject, _subject);
}

@override
StreamSubscription<T> listen(
void Function(T event)? onData, {
Function? onError,
void Function()? onDone,
bool? cancelOnError,
}) =>
_subject.listen(
onData,
onError: onError,
onDone: onDone,
cancelOnError: cancelOnError,
);

@override
Object get error => _subject.error;

@override
Object? get errorOrNull => _subject.errorOrNull;

@override
bool get hasError => _subject.hasError;

@override
bool get hasValue => _subject.hasValue;

@override
StreamNotification<T>? get lastEventOrNull => _subject.lastEventOrNull;

@override
StackTrace? get stackTrace => _subject.stackTrace;

@override
T get value => _subject.value;

@override
T? get valueOrNull => _subject.valueOrNull;
}

/// Class that holds latest value and lasted error emitted from Stream.
class _StreamEvent<T> {
T _value;
ErrorAndStackTrace? _errorAndStacktrace;
var _lastEventIsData = false;

/// Construct a [_StreamEvent] with data event.
_StreamEvent.data(T seedValue)
: _value = seedValue,
_lastEventIsData = true;

/// Keep error state.
void onError(ErrorAndStackTrace errorAndStacktrace) {
_errorAndStacktrace = errorAndStacktrace;
_lastEventIsData = false;
}

/// Keep data state.
void onData(T value) {
_value = value;
_lastEventIsData = true;
}

/// Last emitted value
/// or null if no data added.
T get value => _value;

/// Last emitted error and the corresponding stack trace,
/// or null if no error added.
ErrorAndStackTrace? get errorAndStackTrace => _errorAndStacktrace;

/// Check if the last emitted event is data event.
bool get lastEventIsData => _lastEventIsData;
}

0 comments on commit afd7f41

Please sign in to comment.