Skip to content

Commit

Permalink
[added] motionObservable._flattenIterables();
Browse files Browse the repository at this point in the history
Summary: Closes #210

Reviewers: O2 Material Motion, O3 Material JavaScript platform reviewers, #material_motion, featherless

Reviewed By: O2 Material Motion, #material_motion, featherless

Tags: #material_motion

Differential Revision: http://codereview.cc/D3119
  • Loading branch information
appsforartists committed Apr 27, 2017
1 parent dd5219e commit b820142
Show file tree
Hide file tree
Showing 5 changed files with 153 additions and 2 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import { expect } from 'chai';

import {
beforeEach,
describe,
it,
} from 'mocha-sugar-free';

import {
stub,
} from 'sinon';

declare function require(name: string);

// chai really doesn't like being imported as an ES2015 module; will be fixed in v4
require('chai').use(
require('sinon-chai')
);

import {
createMockObserver,
} from 'material-motion-testing-utils';

import {
MotionObservable,
} from '../../../observables/';

describe(`motionObservable._flattenIterables`,
() => {
let stream;
let mockObserver;
let listener;

beforeEach(
() => {
mockObserver = createMockObserver();
stream = new MotionObservable(mockObserver.connect);
listener = stub();
}
);

it(`should dispatch each value independently if it receives an Array`,
() => {
stream._flattenIterables().subscribe(listener);

mockObserver.next([1, 2]);

expect(listener)
.to.have.been.calledTwice.and.to.have.been.calledWith(1)
.and.to.have.been.calledWith(2);
}
);

it(`should passthrough values that aren't iterable`,
() => {
stream._flattenIterables().subscribe(listener);

mockObserver.next(1);

expect(listener).to.have.been.calledOnce.and.to.have.been.calledWith(1);
}
);
}
);

53 changes: 53 additions & 0 deletions packages/core/src/operators/foundation/_flattenIterables.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
/** @license
* Copyright 2016 - present The Material Motion Authors. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License"); you may not
* use this file except in compliance with the License. You may obtain a copy
* of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
* WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
* License for the specific language governing permissions and limitations
* under the License.
*/

import {
Constructor,
MotionNextOperable,
NextChannel,
Observable,
ObservableWithMotionOperators,
} from '../../types';

import {
isIterable,
} from '../../typeGuards';

export interface MotionFlattenable<T> extends Observable<Iterable<T>> {
_flattenIterables(): ObservableWithMotionOperators<T>;
}

export function withFlattenIterables<T, S extends Constructor<MotionNextOperable<Iterable<T>>>>(superclass: S): S & Constructor<MotionFlattenable<T>> {
return class extends superclass implements MotionFlattenable<T> {
/**
* Iterables over every value it receives from upstream and dispatches each
* individually.
*/
_flattenIterables(): ObservableWithMotionOperators<T> {
return this._nextOperator(
(values: Iterable<T>, dispatch: NextChannel<T>) => {
if (isIterable(values)) {
for (const value of values) {
dispatch(value);
}
} else {
dispatch(values);
}
}
);
}
};
}
13 changes: 11 additions & 2 deletions packages/core/src/operators/foundation/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,11 @@ import {
withFilter,
} from './_filter';

import {
MotionFlattenable,
withFlattenIterables,
} from './_flattenIterables';

import {
MotionMappable,
withMap,
Expand All @@ -50,7 +55,8 @@ import {
} from './_read';

export type ObservableWithFoundationalMotionOperators<T> = MotionNextOperable<T> & MotionMappable<T>
& MotionFilterable<T> & MotionMemorable<T> & MotionDebounceable<T> & MotionReadable<T>;
& MotionFilterable<T> & MotionMemorable<T> & MotionDebounceable<T> & MotionReadable<T>
& MotionFlattenable<T>;

export function withFoundationalMotionOperators<T, S extends Constructor<Observable<T>>>(superclass: S): S
& Constructor<ObservableWithFoundationalMotionOperators<T>> {
Expand All @@ -60,11 +66,14 @@ export function withFoundationalMotionOperators<T, S extends Constructor<Observa
// Constructor<MotionNextOperable<T>>'.
// Type 'Constructor<MotionNextOperable<T>> &
// Constructor<MotionMappable<T>>' is not assignable to type 'S'.
return withRead(withDebounce(withRemember(withFilter(withMap(withNextOperator<T, S>(superclass))))));
return withRead(withDebounce(withRemember(withFilter(withMap(
withFlattenIterables(withNextOperator<T, S>(superclass))
)))));
}

export * from './_debounce';
export * from './_filter';
export * from './_flattenIterables';
export * from './_map';
export * from './_nextOperator';
export * from './_remember';
Expand Down
7 changes: 7 additions & 0 deletions packages/core/src/typeGuards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,3 +39,10 @@ export function isPoint2D(value: any): value is Point2D {
export function isPointerEvent(value: any): value is PointerEvent {
return value instanceof Event && value.type.startsWith('pointer');
}

/**
* Checks if value is iterable; that is, usable in a `for of` loop.
*/
export function isIterable(value: any): value is Iterable<any> {
return value[Symbol.iterator] !== undefined;
}
1 change: 1 addition & 0 deletions packages/core/src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export {
MotionDelayable,
MotionDeduplicable,
MotionFilterable,
MotionFlattenable,
MotionIgnorable,
MotionInvertible,
MotionLoggable,
Expand Down

0 comments on commit b820142

Please sign in to comment.