From 03bdec4c8e794d9429595becb7f62d048e239099 Mon Sep 17 00:00:00 2001 From: Brian Egan Date: Thu, 7 Nov 2019 17:36:26 +0100 Subject: [PATCH 1/3] Version 4.0.0 --- .travis.yml | 13 +++++-- CHANGELOG.md | 12 ++++++ README.md | 9 ----- analysis_options.yaml | 3 ++ example/README.md | 5 +++ example/combined_reducers/index.dart | 14 +++---- example/index.html | 15 ------- example/middleware/index.dart | 6 +-- example/vanilla_counter/index.dart | 4 +- lib/src/store.dart | 27 +++++++++++-- lib/src/utils.dart | 6 +++ pubspec.yaml | 12 +++--- test/middleware_test.dart | 22 +++++------ test/redux_test.dart | 9 +++++ test/store_test.dart | 8 ++-- test/test_data.dart | 2 +- test/utils_test.dart | 58 ++++++++++++++-------------- 17 files changed, 132 insertions(+), 93 deletions(-) create mode 100644 example/README.md delete mode 100644 example/index.html create mode 100644 test/redux_test.dart diff --git a/.travis.yml b/.travis.yml index 5357a04..0b3d816 100644 --- a/.travis.yml +++ b/.travis.yml @@ -4,6 +4,13 @@ dart: - stable - dev with_content_shell: false -dart_task: -- test: --platform vm -- dartanalyzer: --fatal-warnings lib +script: + - dartanalyzer --fatal-infos --fatal-warnings ./ + - dartfmt -n ./lib --set-exit-if-changed + - pub global activate coverage + - pub run test test/redux_test.dart + - dart --disable-service-auth-codes --enable-vm-service=8111 --pause-isolates-on-exit test/redux_test.dart & + - nohup pub global run coverage:collect_coverage --port=8111 --out=coverage.json --wait-paused --resume-isolates + - pub global run coverage:format_coverage --lcov --in=coverage.json --out=lcov.info --packages=.packages --report-on=lib +after_success: + - bash <(curl -s https://codecov.io/bash) diff --git a/CHANGELOG.md b/CHANGELOG.md index 7067818..181de42 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,15 @@ +# 4.0.0 + + * `dispatch` returns the provided action + * Removed Dart 1.x support + * Enforce Pedantic package lint rules + * Enforce `public_member_api_docs` lint rule and add docs to missing parts + * Update `travis.yml` with support for dartfmt, analysis, and code coverage + * Pub.dev updates + * Longer description + * https for package url + * update example folder to include README + # 3.0.1 * Update README based on feedback diff --git a/README.md b/README.md index 2e74d3e..a576482 100644 --- a/README.md +++ b/README.md @@ -4,15 +4,6 @@ [Redux](http://redux.js.org/) for Dart using generics for typed State. It includes a rich ecosystem of [Docs](#docs), [Middleware](#middleware), [Dev Tools](#dev-tools) and can be combined with Flutter using the [flutter_redux](https://pub.dartlang.org/packages/flutter_redux) package. -## Redux 3.0.0 Migration & Dart 2 support - -In order to support Dart 2, some of the APIs needed to change. The good news: This actually simplifies Redux! The bad news: you may need to update your projects. - - * Change `ReducerBinding` to `TypedReducer` - * Remove `combineTypedReducer`. Use `combineReducers` with a combination of normal reducers and/or `TypedReducer`s. - * Change `MiddlewareBinding` to `TypedMiddleware`. - * Remove `combineTypedMiddleware` -- no longer needed! Just create a normal `List>`! - ## Docs * [Motivation and Principles](https://github.com/johnpryan/redux.dart/blob/master/doc/why.md) - Learn why Redux might make sense for your app and the principles behind it. diff --git a/analysis_options.yaml b/analysis_options.yaml index ef23f96..3322acf 100644 --- a/analysis_options.yaml +++ b/analysis_options.yaml @@ -1,3 +1,5 @@ +include: package:pedantic/analysis_options.yaml + analyzer: strong-mode: implicit-casts: false @@ -12,3 +14,4 @@ linter: - test_types_in_equals - unrelated_type_equality_checks - valid_regexps + - public_member_api_docs diff --git a/example/README.md b/example/README.md new file mode 100644 index 0000000..3589e90 --- /dev/null +++ b/example/README.md @@ -0,0 +1,5 @@ +## Examples + +* [Counter example](https://github.com/johnpryan/redux.dart/tree/master/example/vanilla_counter) - an example of how to search as a user types, demonstrating both the Middleware and Epic approaches. +* [Middleware example](https://github.com/johnpryan/redux.dart/tree/master/example/middleware) - an example of how to search as a user types, demonstrating both the Middleware and Epic approaches. +* [Combining Reducers example](https://github.com/johnpryan/redux.dart/tree/master/example/combined_reducers) - a port of the standard "Counter Button" example from Flutter diff --git a/example/combined_reducers/index.dart b/example/combined_reducers/index.dart index 2f9e2ee..09ed37d 100644 --- a/example/combined_reducers/index.dart +++ b/example/combined_reducers/index.dart @@ -28,10 +28,10 @@ enum AppAction { increment, decrement } // to a console. For that, use Middleware. AppState counterReducer(AppState state, dynamic action) { if (action == AppAction.increment) { - return new AppState(state.count + 1, state.clickCount); + return AppState(state.count + 1, state.clickCount); } if (action == AppAction.decrement) { - return new AppState(state.count - 1, state.clickCount); + return AppState(state.count - 1, state.clickCount); } return state; @@ -41,10 +41,10 @@ AppState counterReducer(AppState state, dynamic action) { // can be used for Action and State. AppState clickCounterReducer(AppState state, dynamic action) { if (action == AppAction.increment) { - return new AppState(state.count, state.clickCount + 1); + return AppState(state.count, state.clickCount + 1); } if (action == AppAction.decrement) { - return new AppState(state.count, state.clickCount + 1); + return AppState(state.count, state.clickCount + 1); } return state; @@ -56,9 +56,9 @@ void main() { counterReducer, clickCounterReducer, ]); - final store = new Store( + final store = Store( combined, - initialState: new AppState(0, 0), + initialState: AppState(0, 0), ); render(store.state); @@ -79,7 +79,7 @@ void main() { }); querySelector('#incrementAsync').onClick.listen((_) { - new Future.delayed(new Duration(milliseconds: 1000)).then((_) { + Future.delayed(Duration(milliseconds: 1000)).then((_) { store.dispatch(AppAction.increment); }); }); diff --git a/example/index.html b/example/index.html deleted file mode 100644 index 287719c..0000000 --- a/example/index.html +++ /dev/null @@ -1,15 +0,0 @@ - - - - - Redux Examples - - -

Redux Web Examples

- - - \ No newline at end of file diff --git a/example/middleware/index.dart b/example/middleware/index.dart index 4316d84..5189abf 100644 --- a/example/middleware/index.dart +++ b/example/middleware/index.dart @@ -31,14 +31,14 @@ int counterReducer(int state, dynamic action) { // A piece of middleware that will log all actions with a timestamp to your // console! void loggingMiddleware(Store store, dynamic action, NextDispatcher next) { - print('${new DateTime.now()}: $action'); + print('${DateTime.now()}: $action'); next(action); } void main() { // Create a new reducer and store for the app. - final store = new Store( + final store = Store( counterReducer, initialState: 0, middleware: >[loggingMiddleware], @@ -62,7 +62,7 @@ void main() { }); querySelector('#incrementAsync').onClick.listen((_) { - new Future.delayed(new Duration(milliseconds: 1000)).then((_) { + Future.delayed(Duration(milliseconds: 1000)).then((_) { store.dispatch(Actions.increment); }); }); diff --git a/example/vanilla_counter/index.dart b/example/vanilla_counter/index.dart index 3b5d0fa..e321b48 100644 --- a/example/vanilla_counter/index.dart +++ b/example/vanilla_counter/index.dart @@ -30,7 +30,7 @@ int counterReducer(int state, dynamic action) { void main() { // Create a new reducer and store for the app. - final store = new Store(counterReducer, initialState: 0); + final store = Store(counterReducer, initialState: 0); render(store.state); store.onChange.listen(render); @@ -50,7 +50,7 @@ void main() { }); querySelector('#incrementAsync').onClick.listen((_) { - new Future.delayed(new Duration(milliseconds: 1000)).then((_) { + Future.delayed(Duration(milliseconds: 1000)).then((_) { store.dispatch(Actions.increment); }); }); diff --git a/lib/src/store.dart b/lib/src/store.dart index aa27432..93b49cd 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -45,6 +45,8 @@ typedef State Reducer(State state, dynamic action); /// /// final store = new Store(new CounterReducer()); abstract class ReducerClass { + /// The [Reducer] function that converts the current state and action into a + /// new state State call(State state, dynamic action); } @@ -98,6 +100,7 @@ typedef void Middleware( /// middleware: [new LoggingMiddleware()], /// ); abstract class MiddlewareClass { + /// A [Middleware] function that intercepts a dispatched action void call(Store store, dynamic action, NextDispatcher next); } @@ -159,11 +162,26 @@ class Store { State _state; List _dispatchers; + /// Creates an instance of a Redux Store. + /// + /// The [reducer] argument specifies how the state should be changed in + /// response to dispatched actions. + /// + /// The optional [initialState] argument defines the State of the store when + /// the Store is first created. + /// + /// The optional [middleware] argument takes a list of [Middleware] functions + /// or [MiddlewareClass]. See the [Middleware] documentation for information + /// on how they are used. + /// + /// The [syncStream] argument allows you to use a synchronous + /// [StreamController] instead of an async `StreamController` under the hood. + /// By default, the Stream is async. Store( this.reducer, { State initialState, List> middleware = const [], - bool syncStream: false, + bool syncStream = false, /// If set to true, the Store will not emit onChange events if the new State /// that is returned from your [reducer] in response to an Action is equal @@ -171,9 +189,9 @@ class Store { /// /// Under the hood, it will use the `==` method from your State class to /// determine whether or not the two States are equal. - bool distinct: false, + bool distinct = false, }) - : _changeController = new StreamController.broadcast(sync: syncStream) { + : _changeController = StreamController.broadcast(sync: syncStream) { _state = initialState; _dispatchers = _createDispatchers( middleware, @@ -246,8 +264,9 @@ class Store { /// to the state using the given [Reducer]. Please note: [Middleware] can /// intercept actions, and can modify actions or stop them from passing /// through to the reducer. - void dispatch(dynamic action) { + dynamic dispatch(dynamic action) { _dispatchers[0](action); + return action; } /// Closes down the Store so it will no longer be operational. Only use this diff --git a/lib/src/utils.dart b/lib/src/utils.dart index c5c8957..fefcc0e 100644 --- a/lib/src/utils.dart +++ b/lib/src/utils.dart @@ -98,8 +98,11 @@ import 'package:redux/src/store.dart'; /// ]); /// ``` class TypedReducer implements ReducerClass { + /// A [Reducer] function that only accepts an action of a specific type final State Function(State state, Action action) reducer; + /// Creates a reducer that will only be executed if the dispatched action + /// matches the [Action] type. TypedReducer(this.reducer); @override @@ -209,12 +212,15 @@ class TypedReducer implements ReducerClass { /// ]; /// ``` class TypedMiddleware implements MiddlewareClass { + /// A [Middleware] function that only works on actions of a specific type. final void Function( Store store, Action action, NextDispatcher next, ) middleware; + /// Create a [Middleware] that is only executed when the dispatched action + /// matches the [Action] type. TypedMiddleware(this.middleware); @override diff --git a/pubspec.yaml b/pubspec.yaml index af4b717..0b4236f 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -2,12 +2,14 @@ name: redux authors: - John Ryan - Brian Egan -description: Redux for Dart -homepage: http://github.com/johnpryan/redux.dart -version: 3.0.1 +description: Redux is a predictable state container for Dart and Flutter apps +homepage: https://github.com/johnpryan/redux.dart +version: 4.0.0 environment: - sdk: '>=1.24.0 <3.0.0' + sdk: '>=2.0.0 <3.0.0' dev_dependencies: - test: '>=0.12.24+2 <2.0.0' + test: '>=1.0.0 <2.0.0' + pedantic: ">=1.8.0+1 <2.0.0" + coverage: ">=0.13.0 <0.14.0" diff --git a/test/middleware_test.dart b/test/middleware_test.dart index 2c41e54..1010fb5 100644 --- a/test/middleware_test.dart +++ b/test/middleware_test.dart @@ -6,8 +6,8 @@ import 'test_data.dart'; void main() { group('Middleware', () { test('are invoked by the store', () { - final middleware = new IncrementMiddleware(); - final store = new Store( + final middleware = IncrementMiddleware(); + final store = Store( stringReducer, initialState: 'hello', middleware: [middleware], @@ -17,9 +17,9 @@ void main() { }); test('are applied in the correct order', () { - final middleware1 = new IncrementMiddleware(); - final middleware2 = new IncrementMiddleware(); - final store = new Store( + final middleware1 = IncrementMiddleware(); + final middleware2 = IncrementMiddleware(); + final store = Store( stringReducer, initialState: 'hello', middleware: [middleware1, middleware2], @@ -35,9 +35,9 @@ void main() { }); test('actions can be dispatched multiple times', () { - final middleware1 = new ExtraActionIncrementMiddleware(); - final middleware2 = new IncrementMiddleware(); - final store = new Store( + final middleware1 = ExtraActionIncrementMiddleware(); + final middleware2 = IncrementMiddleware(); + final store = Store( stringReducer, initialState: 'hello', middleware: [middleware1, middleware2], @@ -54,9 +54,9 @@ void main() { }); test('actions can be dispatched through entire chain', () { - final middleware1 = new ExtraActionIfDispatchedIncrementMiddleware(); - final middleware2 = new IncrementMiddleware(); - final store = new Store( + final middleware1 = ExtraActionIfDispatchedIncrementMiddleware(); + final middleware2 = IncrementMiddleware(); + final store = Store( stringReducer, initialState: 'hello', middleware: [middleware1, middleware2], diff --git a/test/redux_test.dart b/test/redux_test.dart new file mode 100644 index 0000000..ead9dfb --- /dev/null +++ b/test/redux_test.dart @@ -0,0 +1,9 @@ +import 'middleware_test.dart' as middleware_test; +import 'store_test.dart' as store_test; +import 'utils_test.dart' as utils_test; + +void main() { + middleware_test.main(); + store_test.main(); + utils_test.main(); +} diff --git a/test/store_test.dart b/test/store_test.dart index 7dcc3b6..de6edff 100644 --- a/test/store_test.dart +++ b/test/store_test.dart @@ -6,7 +6,7 @@ import 'test_data.dart'; void main() { group('Store', () { test('calls the reducer when an action is fired', () { - final store = new Store(stringReducer, initialState: 'Hello'); + final store = Store(stringReducer, initialState: 'Hello'); final action = 'test'; store.dispatch(action); expect(store.state, equals(action)); @@ -15,7 +15,7 @@ void main() { test('canceled subscriber should not be notified', () { var subscriber1Called = false; var subscriber2Called = false; - final store = new Store( + final store = Store( stringReducer, initialState: 'hello', syncStream: true, @@ -37,7 +37,7 @@ void main() { test('store emits current state to subscribers', () { final action = 'test'; final states = []; - final store = new Store( + final store = Store( stringReducer, initialState: 'hello', syncStream: true, @@ -54,7 +54,7 @@ void main() { test('store does not emit an onChange if distinct', () { final action = 'test'; final states = []; - final store = new Store(stringReducer, + final store = Store(stringReducer, initialState: 'hello', syncStream: true, distinct: true); store.onChange.listen((state) => states.add(state)); diff --git a/test/test_data.dart b/test/test_data.dart index 47302b6..3d091b7 100644 --- a/test/test_data.dart +++ b/test/test_data.dart @@ -29,7 +29,7 @@ class StringReducer extends ReducerClass { class IncrementMiddleware extends MiddlewareClass { int counter = 0; final _invocationsController = - new StreamController.broadcast(sync: true); + StreamController.broadcast(sync: true); Stream get invocations => _invocationsController.stream; diff --git a/test/utils_test.dart b/test/utils_test.dart index a6f10f3..53b0719 100644 --- a/test/utils_test.dart +++ b/test/utils_test.dart @@ -12,23 +12,23 @@ void main() { group('Typed Reducers', () { test('can be used standalone', () { - final store = new Store( - new TypedReducer(testAction1Reducer), + final store = Store( + TypedReducer(testAction1Reducer), initialState: 'hello', ); - store.dispatch(new TestAction1()); + store.dispatch(TestAction1()); expect(store.state, contains('TestAction1')); }); test('are not invoked if they do not handle the action type', () { final initialState = 'hello'; - final store = new Store( - new TypedReducer(testAction1Reducer), + final store = Store( + TypedReducer(testAction1Reducer), initialState: initialState, ); - store.dispatch(new TestAction3()); + store.dispatch(TestAction3()); // Since TestAction3 does not match any ReducerBindings, the state // should not be changed after dispatching TestAction3. @@ -43,7 +43,7 @@ void main() { reducer2, ]); - final store = new Store(combinedReducer, initialState: 'hello'); + final store = Store(combinedReducer, initialState: 'hello'); expect(store.state, equals('hello')); store.dispatch('helloReducer1'); expect(store.state, equals('reducer 1 reporting')); @@ -52,26 +52,26 @@ void main() { }); test('works with TypedReducers', () { - final store = new Store( + final store = Store( combineReducers([ - new TypedReducer(testAction1Reducer), - new TypedReducer(testAction2Reducer), + TypedReducer(testAction1Reducer), + TypedReducer(testAction2Reducer), ]), initialState: 'hello', ); - store.dispatch(new TestAction1()); + store.dispatch(TestAction1()); expect(store.state, contains('TestAction1')); - store.dispatch(new TestAction2()); + store.dispatch(TestAction2()); expect(store.state, contains('TestAction2')); }); test('can combine typed with non-typed reducers', () { - final store = new Store( + final store = Store( combineReducers([ reducer1, - new TypedReducer(testAction2Reducer), + TypedReducer(testAction2Reducer), ]), initialState: 'hello', ); @@ -79,7 +79,7 @@ void main() { store.dispatch('helloReducer1'); expect(store.state, equals('reducer 1 reporting')); - store.dispatch(new TestAction2()); + store.dispatch(TestAction2()); expect(store.state, contains('TestAction2')); }); }); @@ -102,19 +102,19 @@ void main() { } test('are invoked based on the type of action they accept', () { - final store = new Store( + final store = Store( stringReducer, initialState: 'hello', middleware: [ - new TypedMiddleware(testAction1Middleware), - new TypedMiddleware(testAction2Middleware), + TypedMiddleware(testAction1Middleware), + TypedMiddleware(testAction2Middleware), ], ); - store.dispatch(new TestAction1()); + store.dispatch(TestAction1()); expect(store.state, 'testAction1Middleware called'); - store.dispatch(new TestAction2()); + store.dispatch(TestAction2()); expect(store.state, 'testAction2Middleware called'); }); @@ -122,35 +122,35 @@ void main() { 'are not invoked if they do not handle the Action and call the next piece of middleware in the chain', () { final initialState = 'hello'; - final store = new Store( + final store = Store( stringReducer, initialState: initialState, middleware: [ - new TypedMiddleware(testAction1Middleware), + TypedMiddleware(testAction1Middleware), ], ); expect(store.state, initialState); - store.dispatch(new TestAction2()); + store.dispatch(TestAction2()); expect(store.state, notFound); }); test('works with a function that has a dynamic action', () { final initialState = 'hello'; - final incrementMiddleware = new IncrementMiddleware(); - final store = new Store( + final incrementMiddleware = IncrementMiddleware(); + final store = Store( stringReducer, initialState: initialState, middleware: [ - new TypedMiddleware(incrementMiddleware), - new TypedMiddleware(incrementMiddleware), + TypedMiddleware(incrementMiddleware), + TypedMiddleware(incrementMiddleware), ], ); - store.dispatch(new TestAction1()); - store.dispatch(new TestAction2()); + store.dispatch(TestAction1()); + store.dispatch(TestAction2()); expect(incrementMiddleware.counter, 2); }); From 20e1f3383d862625b76b2a7525edad7b14d90e61 Mon Sep 17 00:00:00 2001 From: Brian Egan Date: Thu, 7 Nov 2019 17:42:20 +0100 Subject: [PATCH 2/3] format store --- lib/src/store.dart | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/src/store.dart b/lib/src/store.dart index 93b49cd..25b7b6d 100644 --- a/lib/src/store.dart +++ b/lib/src/store.dart @@ -190,8 +190,7 @@ class Store { /// Under the hood, it will use the `==` method from your State class to /// determine whether or not the two States are equal. bool distinct = false, - }) - : _changeController = StreamController.broadcast(sync: syncStream) { + }) : _changeController = StreamController.broadcast(sync: syncStream) { _state = initialState; _dispatchers = _createDispatchers( middleware, From 1162d0cfa9846458a5cde9a59676d4b4c6b12f62 Mon Sep 17 00:00:00 2001 From: Brian Egan Date: Thu, 7 Nov 2019 17:50:10 +0100 Subject: [PATCH 3/3] Add coverage badge to README --- CHANGELOG.md | 1 + README.md | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 181de42..763d0c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,6 +5,7 @@ * Enforce Pedantic package lint rules * Enforce `public_member_api_docs` lint rule and add docs to missing parts * Update `travis.yml` with support for dartfmt, analysis, and code coverage + * Add coverage badge to root README * Pub.dev updates * Longer description * https for package url diff --git a/README.md b/README.md index a576482..a3f0f02 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,7 @@ # redux.dart [![Build Status](https://api.travis-ci.org/johnpryan/redux.dart.svg?branch=master)](https://travis-ci.org/johnpryan/redux.dart) +[![codecov](https://codecov.io/gh/johnpryan/redux.dart/branch/master/graph/badge.svg)](https://codecov.io/gh/johnpryan/redux.dart) [Redux](http://redux.js.org/) for Dart using generics for typed State. It includes a rich ecosystem of [Docs](#docs), [Middleware](#middleware), [Dev Tools](#dev-tools) and can be combined with Flutter using the [flutter_redux](https://pub.dartlang.org/packages/flutter_redux) package.