Skip to content

Instantly share code, notes, and snippets.

@evertbouw
Last active May 17, 2019 13:59

Revisions

  1. evertbouw revised this gist Dec 20, 2018. 1 changed file with 2 additions and 4 deletions.
    6 changes: 2 additions & 4 deletions reducer.ts
    Original file line number Diff line number Diff line change
    @@ -20,10 +20,8 @@ export const setMessage = (message: string) => ({
    payload: message
    });

    export type AllActions =
    | ReturnType<typeof ping>
    | ReturnType<typeof pong>
    | ReturnType<typeof setMessage>;
    export type AllActions = ReturnType<typeof ping | typeof pong | typeof setMessage>;

    export type MyReducer<S> = Reducer<S, AllActions>;

    export const messageReducer: MyReducer<string> = (state = "Hello", action) => {
  2. evertbouw revised this gist Dec 20, 2018. 1 changed file with 21 additions and 31 deletions.
    52 changes: 21 additions & 31 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -3,41 +3,31 @@ import { ActionsObservable, Epic, StateObservable } from "redux-observable";
    import { TestScheduler } from "rxjs/testing";

    const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    expect(actual).toEqual(expected);
    };

    export const marbleTest = <
    T extends Action,
    O extends T = T,
    S = void,
    D = any
    >({
    epic,
    actions,
    states = "",
    expected,
    values,
    dependencies
    export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>({
    epic,
    actions,
    states = "",
    expected,
    values,
    dependencies,
    }: {
    epic: Epic<T, O, S, D | undefined>;
    actions: string;
    states?: string;
    expected: string;
    values: Record<string, T | O | S>;
    dependencies?: D;
    epic: Epic<T, O, S, D>;
    actions: string;
    states?: string;
    expected: string;
    values: { [marble: string]: T | O | S };
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable(
    hot<S>(states, <Record<string, S>>values),
    <S>values.s // use s as the initial state
    );
    const action$ = new ActionsObservable(
    hot<T>(actions, <Record<string, T>>values)
    );
    const output$ = epic(action$, state$, dependencies);
    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable<S>(hot<S>(states, <{ [marble: string]: S }>values), <S>values.s);
    const action$ = new ActionsObservable<T>(hot<T>(actions, <{ [marble: string]: T }>values));
    const output$ = epic(action$, state$, <D>dependencies);

    expectObservable(output$).toBe(expected, values);
    });
    expectObservable(output$).toBe(expected, values);
    });
    };
  3. evertbouw revised this gist Dec 20, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -31,8 +31,8 @@ export const marbleTest = <
    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable(
    hot<S>(states, <Record<string, S>>values),
    <S>values.s
    ); // use s as the initial state
    <S>values.s // use s as the initial state
    );
    const action$ = new ActionsObservable(
    hot<T>(actions, <Record<string, T>>values)
    );
  4. evertbouw revised this gist Dec 20, 2018. 1 changed file with 16 additions and 6 deletions.
    22 changes: 16 additions & 6 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -6,26 +6,36 @@ const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>({
    export const marbleTest = <
    T extends Action,
    O extends T = T,
    S = void,
    D = any
    >({
    epic,
    actions,
    states = "",
    expected,
    values,
    dependencies,
    dependencies
    }: {
    epic: Epic<T, O, S, D>;
    epic: Epic<T, O, S, D | undefined>;
    actions: string;
    states?: string;
    expected: string;
    values: Record<string, T | O | S>,
    values: Record<string, T | O | S>;
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable(hot<S>(states, <Record<string, S>>values), <S>values.s); // use s as the initial state
    const action$ = new ActionsObservable(hot<T>(actions, <Record<string, T>>values));
    const state$ = new StateObservable(
    hot<S>(states, <Record<string, S>>values),
    <S>values.s
    ); // use s as the initial state
    const action$ = new ActionsObservable(
    hot<T>(actions, <Record<string, T>>values)
    );
    const output$ = epic(action$, state$, dependencies);

    expectObservable(output$).toBe(expected, values);
  5. evertbouw revised this gist Dec 20, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>
    actions: string;
    states?: string;
    expected: string;
    values: <Record<string, T | O | S>>,
    values: Record<string, T | O | S>,
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);
  6. evertbouw revised this gist Dec 20, 2018. 1 changed file with 3 additions and 5 deletions.
    8 changes: 3 additions & 5 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -18,16 +18,14 @@ export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>
    actions: string;
    states?: string;
    expected: string;
    values: {
    [marble: string]: T | D | S
    },
    values: <Record<string, T | O | S>>,
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable(hot<S>(states, values), values.s); // use s as the initial state
    const action$ = new ActionsObservable(hot<T>(actions, values));
    const state$ = new StateObservable(hot<S>(states, <Record<string, S>>values), <S>values.s); // use s as the initial state
    const action$ = new ActionsObservable(hot<T>(actions, <Record<string, T>>values));
    const output$ = epic(action$, state$, dependencies);

    expectObservable(output$).toBe(expected, values);
  7. evertbouw revised this gist Oct 31, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -1,4 +1,4 @@
    import { marbleTest } from "../../src/testHelper";
    import { marbleTest } from "./epicMarbleTest";
    import { rootReducer, pingEpic, ping, pong, setMessage } from "./reducer";

    test("ping epic", () => {
  8. evertbouw revised this gist Jun 12, 2018. 2 changed files with 2 additions and 2 deletions.
    2 changes: 1 addition & 1 deletion epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -26,7 +26,7 @@ export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const state$ = new StateObservable(hot<S>(states, values), values.s);
    const state$ = new StateObservable(hot<S>(states, values), values.s); // use s as the initial state
    const action$ = new ActionsObservable(hot<T>(actions, values));
    const output$ = epic(action$, state$, dependencies);

    2 changes: 1 addition & 1 deletion pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -12,7 +12,7 @@ test("ping epic", () => {
    expected: "5s 1 -- 2",
    values: {
    a: ping(),
    s,
    s, // s is used as the initial state, doesn't need to go in the marble
    t,
    1: pong(s.message),
    2: pong(t.message)
  9. evertbouw revised this gist Jun 12, 2018. 5 changed files with 61 additions and 60 deletions.
    3 changes: 0 additions & 3 deletions actionTypes.ts
    Original file line number Diff line number Diff line change
    @@ -1,3 +0,0 @@
    export const PING: "PING" = "PING";
    export const PONG: "PONG" = "PONG";
    export const SET_MESSAGE: "SET_MESSAGE" = "SET_MESSAGE";
    17 changes: 0 additions & 17 deletions actions.ts
    Original file line number Diff line number Diff line change
    @@ -1,17 +0,0 @@
    import { PING, PONG, SET_MESSAGE } from "./actionTypes";

    export const ping = () => ({
    type: PING
    });

    export const pong = (message: string) => ({
    type: PONG,
    payload: message
    });

    export const setMessage = (message: string) => ({
    type: SET_MESSAGE,
    payload: message,
    });

    export type AllActions = ReturnType<typeof ping> | ReturnType<typeof pong> | ReturnType<typeof setMessage>;
    16 changes: 0 additions & 16 deletions epic.ts
    Original file line number Diff line number Diff line change
    @@ -1,16 +0,0 @@
    import { Epic, ofType } from "redux-observable";
    import { mergeMap, take, map, delay } from "rxjs/operators";
    import { MyState } from "./reducer";

    export type MyEpic<O extends AllActions> = Epic<AllActions, O, MyState>;

    export const pingEpic: MyEpic<ReturnType<typeof pong>> = (action$, state$) =>
    action$.pipe(
    ofType(PING),
    mergeMap(() => state$.pipe(
    take(1),
    map(state => state.message),
    delay(5000),
    map(pong),
    )),
    );
    36 changes: 18 additions & 18 deletions pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -1,21 +1,21 @@
    import { marbleTest } from "./epicMarbleTest";
    import { rootReducer, pingEpic, ping, pong, setMessage } from "./duck";

    const initialState = rootReducer(undefined, undefined);
    const newMessage = "world";
    import { marbleTest } from "../../src/testHelper";
    import { rootReducer, pingEpic, ping, pong, setMessage } from "./reducer";

    test("ping epic", () => {
    marbleTest({
    epic: pingEpic,
    actions: "a -- a",
    states: " -- t",
    expected: "5s 1 -- 2",
    values: {
    a: ping(),
    s: initialState,
    t: rootReducer(initialState, setMessage(newMessage)),
    1: pong(initialState.message),
    2: pong(newMessage),
    },
    });
    const s = rootReducer(undefined, { type: "" });
    const t = rootReducer(undefined, setMessage("world"));

    marbleTest({
    epic: pingEpic,
    actions: "a -- a",
    states: " -- t",
    expected: "5s 1 -- 2",
    values: {
    a: ping(),
    s,
    t,
    1: pong(s.message),
    2: pong(t.message)
    }
    });
    });
    49 changes: 43 additions & 6 deletions reducer.ts
    Original file line number Diff line number Diff line change
    @@ -1,18 +1,55 @@
    import { combineReducers, Reducer } from "redux";
    import { SET_MESSAGE } from "./actionTypes";
    import { Epic, ofType } from "redux-observable";
    import { mergeMap, take, map, delay } from "rxjs/operators";

    export const PING: "PING" = "PING";
    export const PONG: "PONG" = "PONG";
    export const SET_MESSAGE: "SET_MESSAGE" = "SET_MESSAGE";

    export const ping = () => ({
    type: PING
    });

    export const pong = (message: string) => ({
    type: PONG,
    payload: message
    });

    export const setMessage = (message: string) => ({
    type: SET_MESSAGE,
    payload: message
    });

    export type AllActions =
    | ReturnType<typeof ping>
    | ReturnType<typeof pong>
    | ReturnType<typeof setMessage>;
    export type MyReducer<S> = Reducer<S, AllActions>;

    export const messageReducer: MyReducer<string> = (state = "Hello", action) => {
    if (action.type === SET_MESSAGE) {
    return action.payload;
    }
    if (action.type === SET_MESSAGE) {
    return action.payload;
    }

    return state;
    return state;
    };

    export const rootReducer = combineReducers({
    message: messageReducer,
    message: messageReducer
    });

    export type MyState = ReturnType<typeof rootReducer>;
    export type MyEpic<O extends AllActions> = Epic<AllActions, O, MyState>;

    export const pingEpic: MyEpic<ReturnType<typeof pong>> = (action$, state$) =>
    action$.pipe(
    ofType(PING),
    mergeMap(() =>
    state$.pipe(
    take(1),
    map(state => state.message),
    delay(5000),
    map(pong)
    )
    )
    );
  10. evertbouw revised this gist Jun 12, 2018. 5 changed files with 54 additions and 50 deletions.
    3 changes: 3 additions & 0 deletions actionTypes.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,3 @@
    export const PING: "PING" = "PING";
    export const PONG: "PONG" = "PONG";
    export const SET_MESSAGE: "SET_MESSAGE" = "SET_MESSAGE";
    17 changes: 17 additions & 0 deletions actions.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,17 @@
    import { PING, PONG, SET_MESSAGE } from "./actionTypes";

    export const ping = () => ({
    type: PING
    });

    export const pong = (message: string) => ({
    type: PONG,
    payload: message
    });

    export const setMessage = (message: string) => ({
    type: SET_MESSAGE,
    payload: message,
    });

    export type AllActions = ReturnType<typeof ping> | ReturnType<typeof pong> | ReturnType<typeof setMessage>;
    50 changes: 0 additions & 50 deletions duck.ts
    Original file line number Diff line number Diff line change
    @@ -1,50 +0,0 @@
    import { combineReducers, Reducer } from "redux";
    import { Epic, ofType } from "redux-observable";
    import { mergeMap, take, map, delay } from "rxjs/operators";

    export const PING: "PING" = "PING";
    export const PONG: "PONG" = "PONG";
    export const SET_MESSAGE: "SET_MESSAGE" = "SET_MESSAGE";

    export const ping = () => ({
    type: PING
    });

    export const pong = (message: string) => ({
    type: PONG,
    payload: message
    });

    export const setMessage = (message: string) => ({
    type: SET_MESSAGE,
    payload: message,
    });

    export type AllActions = ReturnType<typeof ping> | ReturnType<typeof pong> | ReturnType<typeof setMessage>;
    export type MyReducer<S> = Reducer<S, AllActions>;

    export const messageReducer: MyReducer<string> = (state = "Hello", action) => {
    if (action.type === SET_MESSAGE) {
    return action.payload;
    }

    return state;
    };

    export const rootReducer = combineReducers({
    message: messageReducer,
    });

    export type MyState = ReturnType<typeof rootReducer>;
    export type MyEpic<O extends AllActions> = Epic<AllActions, O, MyState>;

    export const pingEpic: MyEpic<ReturnType<typeof pong>> = (action$, state$) =>
    action$.pipe(
    ofType(PING),
    mergeMap(() => state$.pipe(
    take(1),
    map(state => state.message),
    delay(5000),
    map(pong),
    )),
    );
    16 changes: 16 additions & 0 deletions epic.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,16 @@
    import { Epic, ofType } from "redux-observable";
    import { mergeMap, take, map, delay } from "rxjs/operators";
    import { MyState } from "./reducer";

    export type MyEpic<O extends AllActions> = Epic<AllActions, O, MyState>;

    export const pingEpic: MyEpic<ReturnType<typeof pong>> = (action$, state$) =>
    action$.pipe(
    ofType(PING),
    mergeMap(() => state$.pipe(
    take(1),
    map(state => state.message),
    delay(5000),
    map(pong),
    )),
    );
    18 changes: 18 additions & 0 deletions reducer.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,18 @@
    import { combineReducers, Reducer } from "redux";
    import { SET_MESSAGE } from "./actionTypes";

    export type MyReducer<S> = Reducer<S, AllActions>;

    export const messageReducer: MyReducer<string> = (state = "Hello", action) => {
    if (action.type === SET_MESSAGE) {
    return action.payload;
    }

    return state;
    };

    export const rootReducer = combineReducers({
    message: messageReducer,
    });

    export type MyState = ReturnType<typeof rootReducer>;
  11. evertbouw revised this gist Jun 11, 2018. 4 changed files with 67 additions and 27 deletions.
    50 changes: 50 additions & 0 deletions duck.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,50 @@
    import { combineReducers, Reducer } from "redux";
    import { Epic, ofType } from "redux-observable";
    import { mergeMap, take, map, delay } from "rxjs/operators";

    export const PING: "PING" = "PING";
    export const PONG: "PONG" = "PONG";
    export const SET_MESSAGE: "SET_MESSAGE" = "SET_MESSAGE";

    export const ping = () => ({
    type: PING
    });

    export const pong = (message: string) => ({
    type: PONG,
    payload: message
    });

    export const setMessage = (message: string) => ({
    type: SET_MESSAGE,
    payload: message,
    });

    export type AllActions = ReturnType<typeof ping> | ReturnType<typeof pong> | ReturnType<typeof setMessage>;
    export type MyReducer<S> = Reducer<S, AllActions>;

    export const messageReducer: MyReducer<string> = (state = "Hello", action) => {
    if (action.type === SET_MESSAGE) {
    return action.payload;
    }

    return state;
    };

    export const rootReducer = combineReducers({
    message: messageReducer,
    });

    export type MyState = ReturnType<typeof rootReducer>;
    export type MyEpic<O extends AllActions> = Epic<AllActions, O, MyState>;

    export const pingEpic: MyEpic<ReturnType<typeof pong>> = (action$, state$) =>
    action$.pipe(
    ofType(PING),
    mergeMap(() => state$.pipe(
    take(1),
    map(state => state.message),
    delay(5000),
    map(pong),
    )),
    );
    12 changes: 5 additions & 7 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -1,20 +1,20 @@
    import { AnyAction } from "redux";
    import { Action } from "redux";
    import { ActionsObservable, Epic, StateObservable } from "redux-observable";
    import { TestScheduler } from "rxjs/testing";

    const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    export const marbleTest = <T extends Action, O extends T = T, S = void, D = any>({
    epic,
    actions,
    states,
    states = "",
    expected,
    values,
    dependencies,
    }: {
    epic: Epic<T, S, D, O>;
    epic: Epic<T, O, S, D>;
    actions: string;
    states?: string;
    expected: string;
    @@ -26,9 +26,7 @@ export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    // Second arg for StateObservable should be a store, but is only used for dispatch.
    // Since that is deprecated I don't want to support it in here
    const state$ = states && new StateObservable(hot<S>(states, values), undefined);
    const state$ = new StateObservable(hot<S>(states, values), values.s);
    const action$ = new ActionsObservable(hot<T>(actions, values));
    const output$ = epic(action$, state$, dependencies);

    21 changes: 12 additions & 9 deletions pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -1,18 +1,21 @@
    import { marbleTest } from "./helper";
    import { pingEpic } from "./pingEpic";
    import { marbleTest } from "./epicMarbleTest";
    import { rootReducer, pingEpic, ping, pong, setMessage } from "./duck";

    const initialState = rootReducer(undefined, undefined);
    const newMessage = "world";

    test("ping epic", () => {
    marbleTest({
    epic: pingEpic,
    actions: "a -- a",
    states: "s -- t",
    expected: "5s z -- y",
    states: " -- t",
    expected: "5s 1 -- 2",
    values: {
    a: { type: "ping" },
    s: "Hello",
    t: "world",
    z: { type: "pong", payload: "Hello" },
    y: { type: "pong", payload: "world" },
    a: ping(),
    s: initialState,
    t: rootReducer(initialState, setMessage(newMessage)),
    1: pong(initialState.message),
    2: pong(newMessage),
    },
    });
    });
    11 changes: 0 additions & 11 deletions pingEpic.ts
    Original file line number Diff line number Diff line change
    @@ -1,11 +0,0 @@
    import { ofType, Epic } from "redux-observable";
    import { delay, map, withLatestFrom } from "rxjs/operators";
    import { AnyAction } from "redux";

    export const pingEpic: Epic<AnyAction, string> = (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    withLatestFrom(state$),
    delay(5000),
    map(([, state]) => ({ type: "pong", payload: state }))
    );
  12. evertbouw revised this gist May 31, 2018. 3 changed files with 22 additions and 33 deletions.
    25 changes: 12 additions & 13 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -6,33 +6,32 @@ const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    export interface MarblesAndValues<V> {
    marbles: string;
    values: { [marble: string]: V };
    }

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    epic,
    actions,
    states,
    expected,
    dependencies
    values,
    dependencies,
    }: {
    epic: Epic<T, O, S, D>;
    actions: MarblesAndValues<T>;
    states?: MarblesAndValues<S>;
    expected: MarblesAndValues<O>;
    epic: Epic<T, S, D, O>;
    actions: string;
    states?: string;
    expected: string;
    values: {
    [marble: string]: T | D | S
    },
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    // Second arg for StateObservable should be a store, but is only used for dispatch.
    // Since that is deprecated I don't want to support it in here
    const state$ = states && new StateObservable(hot<S>(states.marbles, states.values), undefined);
    const action$ = new ActionsObservable(hot<T>(actions.marbles, actions.values));
    const state$ = states && new StateObservable(hot<S>(states, values), undefined);
    const action$ = new ActionsObservable(hot<T>(actions, values));
    const output$ = epic(action$, state$, dependencies);

    expectObservable(output$).toBe(expected.marbles, expected.values);
    expectObservable(output$).toBe(expected, values);
    });
    };
    28 changes: 9 additions & 19 deletions pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -4,25 +4,15 @@ import { pingEpic } from "./pingEpic";
    test("ping epic", () => {
    marbleTest({
    epic: pingEpic,
    actions: {
    marbles: " a -- a ",
    values: {
    a: { type: "ping" },
    },
    actions: "a -- a",
    states: "s -- t",
    expected: "5s z -- y",
    values: {
    a: { type: "ping" },
    s: "Hello",
    t: "world",
    z: { type: "pong", payload: "Hello" },
    y: { type: "pong", payload: "world" },
    },
    states: {
    marbles: " a -- b ",
    states: {
    a: "Hello",
    b: "world"
    },
    },
    expected: {
    marbles: " 5ms a -- b ",
    values: {
    a: { type: "pong", payload: "Hello" },
    b: { type: "pong", payload: "world" },
    },
    }
    });
    });
    2 changes: 1 addition & 1 deletion pingEpic.ts
    Original file line number Diff line number Diff line change
    @@ -6,6 +6,6 @@ export const pingEpic: Epic<AnyAction, string> = (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    withLatestFrom(state$),
    delay(5),
    delay(5000),
    map(([, state]) => ({ type: "pong", payload: state }))
    );
  13. evertbouw revised this gist May 25, 2018. 1 changed file with 1 addition and 1 deletion.
    2 changes: 1 addition & 1 deletion epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -18,7 +18,7 @@ export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    expected,
    dependencies
    }: {
    epic: Epic<T, S, D, O>;
    epic: Epic<T, O, S, D>;
    actions: MarblesAndValues<T>;
    states?: MarblesAndValues<S>;
    expected: MarblesAndValues<O>;
  14. evertbouw revised this gist May 8, 2018. No changes.
  15. evertbouw revised this gist May 8, 2018. 4 changed files with 53 additions and 44 deletions.
    29 changes: 14 additions & 15 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -6,34 +6,33 @@ const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    export interface MarblesAndValues<V> {
    marbles: string;
    values: { [marble: string]: V };
    }

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    epic,
    inputActions,
    inputMarble,
    stateMarble,
    actions,
    states,
    expectedActions,
    expectedMarble,
    expected,
    dependencies
    }: {
    epic: Epic<T, S, D, O>;
    inputActions: { [letter: string]: T };
    inputMarble: string;
    stateMarble?: string;
    states?: { [letter: string]: S }
    expectedActions: { [letter: string]: O };
    expectedMarble: string;
    actions: MarblesAndValues<T>;
    states?: MarblesAndValues<S>;
    expected: MarblesAndValues<O>;
    dependencies?: D;
    }) => {
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    // Second arg for StateObservable should be a store, but is only used for dispatch.
    // Since that is deprecated I don't want to support it in here
    const state$ = (stateMarble && states) ? new StateObservable(hot<S>(stateMarble, states), undefined as any) : (undefined as any);
    const action$ = new ActionsObservable(hot<T>(inputMarble, inputActions));
    const output$ = epic(action$, state$, dependencies as any);
    const state$ = states && new StateObservable(hot<S>(states.marbles, states.values), undefined);
    const action$ = new ActionsObservable(hot<T>(actions.marbles, actions.values));
    const output$ = epic(action$, state$, dependencies);

    expectObservable(output$).toBe(expectedMarble, expectedActions);
    expectObservable(output$).toBe(expected.marbles, expected.values);
    });
    };
    28 changes: 28 additions & 0 deletions pingEpic.test.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,28 @@
    import { marbleTest } from "./helper";
    import { pingEpic } from "./pingEpic";

    test("ping epic", () => {
    marbleTest({
    epic: pingEpic,
    actions: {
    marbles: " a -- a ",
    values: {
    a: { type: "ping" },
    },
    },
    states: {
    marbles: " a -- b ",
    states: {
    a: "Hello",
    b: "world"
    },
    },
    expected: {
    marbles: " 5ms a -- b ",
    values: {
    a: { type: "pong", payload: "Hello" },
    b: { type: "pong", payload: "world" },
    },
    }
    });
    });
    11 changes: 11 additions & 0 deletions pingEpic.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,11 @@
    import { ofType, Epic } from "redux-observable";
    import { delay, map, withLatestFrom } from "rxjs/operators";
    import { AnyAction } from "redux";

    export const pingEpic: Epic<AnyAction, string> = (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    withLatestFrom(state$),
    delay(5),
    map(([, state]) => ({ type: "pong", payload: state }))
    );
    29 changes: 0 additions & 29 deletions pingTest.ts
    Original file line number Diff line number Diff line change
    @@ -1,29 +0,0 @@
    import { ofType } from "redux-observable";
    import { delay, map, withLatestFrom } from "rxjs/operators";
    import { marbleTest } from "./helper";

    test("ping epic", () => {
    marbleTest({
    epic: (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    withLatestFrom(state$),
    delay(5),
    map(([, state]) => ({ type: "pong", payload: state })),
    ),
    inputMarble: " a -- a ",
    inputActions: {
    a: { type: "ping" },
    },
    stateMarble: " a -- b ",
    states: {
    a: "Hello",
    b: "world"
    },
    expectedMarble: " 5ms a -- b ",
    expectedActions: {
    a: { type: "pong", payload: "Hello" },
    b: { type: "pong", payload: "world" },
    },
    });
    });
  16. evertbouw revised this gist May 2, 2018. 2 changed files with 38 additions and 35 deletions.
    54 changes: 26 additions & 28 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -1,41 +1,39 @@
    import { AnyAction } from "redux";
    import { ActionsObservable, Epic, StateObservable } from "redux-observable";
    import { Subject } from "rxjs";
    import { TestScheduler } from "rxjs/testing";

    const stateInput$ = new Subject();

    const state$ = new StateObservable<any>(stateInput$, undefined);

    const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    expect(actual).toEqual(expected);
    };

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    epic,
    inputActions,
    inputMarble,
    expectedActions,
    expectedMarble,
    state = {},
    dependencies,
    epic,
    inputActions,
    inputMarble,
    stateMarble,
    states,
    expectedActions,
    expectedMarble,
    dependencies
    }: {
    epic: Epic<T, S, D, O>;
    inputActions: { [letter: string]: T };
    inputMarble: string;
    expectedActions: { [letter: string]: O };
    expectedMarble: string;
    state?: S;
    dependencies?: D;
    epic: Epic<T, S, D, O>;
    inputActions: { [letter: string]: T };
    inputMarble: string;
    stateMarble?: string;
    states?: { [letter: string]: S }
    expectedActions: { [letter: string]: O };
    expectedMarble: string;
    dependencies?: D;
    }) => {
    stateInput$.next(state);

    const testScheduler = new TestScheduler(assertDeepEquals);
    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const action$ = new ActionsObservable(hot<T>(inputMarble, inputActions));
    const output$ = epic(action$, state$, dependencies);
    testScheduler.run(({ hot, expectObservable }) => {
    // Second arg for StateObservable should be a store, but is only used for dispatch.
    // Since that is deprecated I don't want to support it in here
    const state$ = (stateMarble && states) ? new StateObservable(hot<S>(stateMarble, states), undefined as any) : (undefined as any);
    const action$ = new ActionsObservable(hot<T>(inputMarble, inputActions));
    const output$ = epic(action$, state$, dependencies as any);

    expectObservable(output$).toBe(expectedMarble, expectedActions);
    });
    expectObservable(output$).toBe(expectedMarble, expectedActions);
    });
    };
    19 changes: 12 additions & 7 deletions pingTest.ts
    Original file line number Diff line number Diff line change
    @@ -1,24 +1,29 @@
    import { ofType } from "redux-observable";
    import { delay, map, withLatestFrom } from "rxjs/operators";
    import { marbleTest } from "./epicMarbleTest";
    import { marbleTest } from "./helper";

    it("should do stuff", () => {
    test("ping epic", () => {
    marbleTest({
    epic: (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    delay(5),
    withLatestFrom(state$),
    delay(5),
    map(([, state]) => ({ type: "pong", payload: state })),
    ),
    inputMarble: "---a--------",
    inputMarble: " a -- a ",
    inputActions: {
    a: { type: "ping" },
    },
    expectedMarble: "--------a---",
    stateMarble: " a -- b ",
    states: {
    a: "Hello",
    b: "world"
    },
    expectedMarble: " 5ms a -- b ",
    expectedActions: {
    a: { type: "pong", payload: "Hello world" },
    a: { type: "pong", payload: "Hello" },
    b: { type: "pong", payload: "world" },
    },
    state: "Hello world",
    });
    });
  17. evertbouw revised this gist Apr 25, 2018. 2 changed files with 25 additions and 1 deletion.
    2 changes: 1 addition & 1 deletion epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -38,4 +38,4 @@ export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({

    expectObservable(output$).toBe(expectedMarble, expectedActions);
    });
    };
    };
    24 changes: 24 additions & 0 deletions pingTest.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,24 @@
    import { ofType } from "redux-observable";
    import { delay, map, withLatestFrom } from "rxjs/operators";
    import { marbleTest } from "./epicMarbleTest";

    it("should do stuff", () => {
    marbleTest({
    epic: (action$, state$) =>
    action$.pipe(
    ofType("ping"),
    delay(5),
    withLatestFrom(state$),
    map(([, state]) => ({ type: "pong", payload: state })),
    ),
    inputMarble: "---a--------",
    inputActions: {
    a: { type: "ping" },
    },
    expectedMarble: "--------a---",
    expectedActions: {
    a: { type: "pong", payload: "Hello world" },
    },
    state: "Hello world",
    });
    });
  18. evertbouw revised this gist Apr 25, 2018. 1 changed file with 2 additions and 2 deletions.
    4 changes: 2 additions & 2 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -11,8 +11,6 @@ const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    const testScheduler = new TestScheduler(assertDeepEquals);

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    epic,
    inputActions,
    @@ -32,6 +30,8 @@ export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    }) => {
    stateInput$.next(state);

    const testScheduler = new TestScheduler(assertDeepEquals);

    testScheduler.run(({ hot, expectObservable }) => {
    const action$ = new ActionsObservable(hot<T>(inputMarble, inputActions));
    const output$ = epic(action$, state$, dependencies);
  19. evertbouw created this gist Apr 25, 2018.
    41 changes: 41 additions & 0 deletions epicMarbleTest.ts
    Original file line number Diff line number Diff line change
    @@ -0,0 +1,41 @@
    import { AnyAction } from "redux";
    import { ActionsObservable, Epic, StateObservable } from "redux-observable";
    import { Subject } from "rxjs";
    import { TestScheduler } from "rxjs/testing";

    const stateInput$ = new Subject();

    const state$ = new StateObservable<any>(stateInput$, undefined);

    const assertDeepEquals = (actual: any, expected: any) => {
    expect(actual).toEqual(expected);
    };

    const testScheduler = new TestScheduler(assertDeepEquals);

    export const marbleTest = <T extends AnyAction, S, D = any, O extends T = T>({
    epic,
    inputActions,
    inputMarble,
    expectedActions,
    expectedMarble,
    state = {},
    dependencies,
    }: {
    epic: Epic<T, S, D, O>;
    inputActions: { [letter: string]: T };
    inputMarble: string;
    expectedActions: { [letter: string]: O };
    expectedMarble: string;
    state?: S;
    dependencies?: D;
    }) => {
    stateInput$.next(state);

    testScheduler.run(({ hot, expectObservable }) => {
    const action$ = new ActionsObservable(hot<T>(inputMarble, inputActions));
    const output$ = epic(action$, state$, dependencies);

    expectObservable(output$).toBe(expectedMarble, expectedActions);
    });
    };