Skip to content

Commit

Permalink
feat: Add progress to RemotePending
Browse files Browse the repository at this point in the history
closes #9
  • Loading branch information
raveclassic committed Sep 21, 2018
1 parent 68e5de5 commit c75c8a6
Show file tree
Hide file tree
Showing 2 changed files with 98 additions and 1 deletion.
48 changes: 48 additions & 0 deletions src/__tests__/remote-data.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,8 @@ import {
fromOption,
fromEither,
fromPredicate,
progress,
fromProgressEvent,
} from '../remote-data';
import { identity, compose } from 'fp-ts/lib/function';
import { sequence, traverse } from 'fp-ts/lib/Traversable';
Expand Down Expand Up @@ -378,6 +380,41 @@ describe('RemoteData', () => {
expect(combine.apply(null, values)).toEqual(failure('bar'));
expect(combine.apply(null, values.reverse())).toEqual(failure('bar'));
});
describe('progress', () => {
it('should combine pendings without progress', () => {
const values = [pending, pending];
expect(combine.apply(null, values)).toBe(pending);
expect(combine.apply(null, values.reverse())).toBe(pending);
});
it('should combine pending and progress', () => {
const withProgress = progress({ loaded: 1, total: none });
const values = [pending, withProgress];
expect(combine.apply(null, values)).toBe(withProgress);
expect(combine.apply(null, values.reverse())).toBe(withProgress);
});
it('should combine progress without total', () => {
const withProgress = progress({ loaded: 1, total: none });
const values = [withProgress, withProgress];
expect(combine.apply(null, values)).toEqual(progress({ loaded: 2, total: none }));
expect(combine.apply(null, values.reverse())).toEqual(progress({ loaded: 2, total: none }));
});
it('should combine progress without total and progress with total', () => {
const withProgress = progress({ loaded: 1, total: none });
const withProgressAndTotal = progress({ loaded: 1, total: some(2) });
const values = [withProgress, withProgressAndTotal];
expect(combine.apply(null, values)).toEqual(progress({ loaded: 2, total: none }));
expect(combine.apply(null, values.reverse())).toEqual(progress({ loaded: 2, total: none }));
});
it('should combine progresses with total', () => {
const values = [progress({ loaded: 2, total: some(10) }), progress({ loaded: 2, total: some(30) })];
const expected = progress({
loaded: (2 * 10 + 2 * 30) / (40 * 40),
total: some(10 + 30),
});
expect(combine.apply(null, values)).toEqual(expected);
expect(combine.apply(null, values.reverse())).toEqual(expected);
});
});
});
describe('fromOption', () => {
const error = new Error('foo');
Expand Down Expand Up @@ -405,6 +442,17 @@ describe('RemoteData', () => {
expect(factory(true)).toEqual(success(true));
});
});
describe('fromProgressEvent', () => {
const e = new ProgressEvent('test');
it('lengthComputable === false', () => {
expect(fromProgressEvent({ ...e, loaded: 123 })).toEqual(progress({ loaded: 123, total: none }));
});
it('lengthComputable === true', () => {
expect(fromProgressEvent({ ...e, loaded: 123, lengthComputable: true, total: 1000 })).toEqual(
progress({ loaded: 123, total: some(1000) }),
);
});
});
});
describe('instance methods', () => {
describe('getOrElse', () => {
Expand Down
51 changes: 50 additions & 1 deletion src/remote-data.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,40 @@ declare module 'fp-ts/lib/HKT' {
}
}

export type RemoteProgress = {
loaded: number;
total: Option<number>;
};
const concatPendings = <L, A>(a: RemotePending<L, A>, b: RemotePending<L, A>): RemotePending<L, A> => {
const noA = a.progress.isNone();
const noB = b.progress.isNone();
if (a.progress.isSome() && b.progress.isSome()) {
const progressA = a.progress.value;
const progressB = b.progress.value;
if (progressA.total.isNone() || progressB.total.isNone()) {
return progress({
loaded: progressA.loaded + progressB.loaded,
total: none,
});
}
const totalA = progressA.total.value;
const totalB = progressB.total.value;
const total = totalA + totalB;
const loaded = (progressA.loaded * totalA + progressB.loaded * totalB) / (total * total);
return progress({
loaded,
total: some(total),
});
}
if (noA && !noB) {
return b;
}
if (!noA && noB) {
return a;
}
return pending;
};

export class RemoteInitial<L, A> {
readonly _tag: 'RemoteInitial' = 'RemoteInitial';
// prettier-ignore
Expand Down Expand Up @@ -892,6 +926,8 @@ export class RemotePending<L, A> {
// prettier-ignore
readonly '_L': L;

constructor(readonly progress: Option<RemoteProgress> = none) {}

/**
* `alt` short for alternative, takes another `RemoteData`.
* If `this` `RemoteData` is a `RemoteSuccess` type then it will be returned.
Expand Down Expand Up @@ -934,7 +970,12 @@ export class RemotePending<L, A> {
* `failure(new Error('err text')).ap(initial) will return initial.`
*/
ap<B>(fab: RemoteData<L, Function1<A, B>>): RemoteData<L, B> {
return fab.fold(initial, pending as any, () => pending, () => pending); //tslint:disable-line no-use-before-declare
return fab.fold(
initial,
fab.isPending() ? (concatPendings(this, fab as any) as any) : this,
() => this,
() => this,
); //tslint:disable-line no-use-before-declare
}

/**
Expand Down Expand Up @@ -1222,6 +1263,7 @@ const extend = <L, A, B>(fla: RemoteData<L, A>, f: Function1<RemoteData<L, A>, B
export const failure = <L, A>(error: L): RemoteFailure<L, A> => new RemoteFailure(error);
export const success: <L, A>(value: A) => RemoteSuccess<L, A> = of;
export const pending: RemotePending<never, never> = new RemotePending<never, never>();
export const progress = <L, A>(progress: RemoteProgress): RemotePending<L, A> => new RemotePending(some(progress));
export const initial: RemoteInitial<never, never> = new RemoteInitial<never, never>();

//Alternative
Expand Down Expand Up @@ -1312,6 +1354,13 @@ export function fromPredicate<L, A>(
return a => (predicate(a) ? success(a) : failure(whenFalse(a)));
}

export function fromProgressEvent<L, A>(event: ProgressEvent): RemotePending<L, A> {
return progress({
loaded: event.loaded,
total: event.lengthComputable ? some(event.total) : none,
});
}

//instance
export const remoteData: Monad2<URI> &
Foldable2<URI> &
Expand Down

0 comments on commit c75c8a6

Please sign in to comment.