Skip to content
Permalink
Browse files

feat(devtools): basic dispatcher support

with this you can now dispatch registered actions (via actionCreators option to the devToolsOptions) and have the store execute those remote dispatches.
By default the argument for state is ignored.
  • Loading branch information
zewa666 committed Mar 2, 2020
1 parent ed5c137 commit ca97e85f59aef93e17da1b6cf7b04cd294fed636
Showing with 142 additions and 1 deletion.
  1. +19 −1 src/store.ts
  2. +123 −0 test/unit/redux-devtools.spec.ts
@@ -328,6 +328,24 @@ export class Store<T> {
this.devTools.subscribe((message: any) => {
this.logger[getLogType(this.options, "devToolsStatus", LogLevel.debug)](`DevTools sent change ${message.type}`);

if (message.type === "ACTION" && message.payload) {
const byName = Array.from(this.actions).find(function ([reducer]) {
return reducer.name === message.payload.name;
});
const action = this.lookupAction(message.payload.name) || byName && byName[0];

if (!action) {
throw new Error("Tried to remotely dispatch an unregistered action");
}

if (!message.payload.args || message.payload.args.length < 1) {
throw new Error("No action arguments provided");
}

this.dispatch(action, ...message.payload.args.slice(1).map((arg: string) => JSON.parse(arg)));
return;
}

if (message.type === "DISPATCH" && message.payload) {
switch (message.payload.type) {
case "JUMP_TO_STATE":
@@ -344,7 +362,7 @@ export class Store<T> {
case "ROLLBACK":
const parsedState = JSON.parse(message.state);

this.resetToState(parsedState);
this.resetToState(parsedState);
this.devTools.init(parsedState);
return;
}
@@ -225,4 +225,127 @@ describe("redux devtools", () => {
done();
});
});

describe("dispatching actions", () => {
it("should react to the ACTION type and execute the intended action", (done) => {
const devToolsValue = "dispatched value by devtools";

createDevToolsMock();

const fakeAction = (currentState: testState, newValue: string) => {
return Object.assign({}, currentState, { foo: newValue });
};

const store = new Store<testState>({ foo: "bar " }, {
devToolsOptions: {
actionCreators: { "FakeAction": fakeAction }
}
});

store.registerAction("FakeAction", fakeAction);

const devtools = ((store as any).devTools as DevToolsMock);

expect(devtools.subscriptions.length).toBe(1);

devtools.subscriptions[0]({
type: "ACTION",
state: null,
payload: { name: "FakeAction", args: [null, devToolsValue].map((arg) => JSON.stringify(arg)) }
});

store.state.pipe(skip(1)).subscribe((state) => {
expect(state.foo).toBe(devToolsValue);
done();
});
});

it("should detect action by function name if not found via registered type", (done) => {
const devToolsValue = "dispatched value by devtools";

createDevToolsMock();

const fakeAction = (currentState: testState, newValue: string) => {
return Object.assign({}, currentState, { foo: newValue });
};

const store = new Store<testState>({ foo: "bar " }, {
devToolsOptions: {
actionCreators: { "Foobert": fakeAction }
}
});

store.registerAction("FakeAction", fakeAction);

const devtools = ((store as any).devTools as DevToolsMock);

expect(devtools.subscriptions.length).toBe(1);

devtools.subscriptions[0]({
type: "ACTION",
state: null,
payload: { name: "fakeAction", args: [null, devToolsValue].map((arg) => JSON.stringify(arg)) }
});

store.state.pipe(skip(1)).subscribe((state) => {
expect(state.foo).toBe(devToolsValue);
done();
});
});

it("should throw when dispatching an unregistered action", async () => {
createDevToolsMock();

const fakeAction = (currentState: testState, newValue: string) => {
return Object.assign({}, currentState, { foo: newValue });
};

const store = new Store<testState>({ foo: "bar " }, {
devToolsOptions: {
actionCreators: { "FakeAction": fakeAction }
}
});


const devtools = ((store as any).devTools as DevToolsMock);

expect(devtools.subscriptions.length).toBe(1);

expect(() => {
devtools.subscriptions[0]({
type: "ACTION",
state: null,
payload: { name: "FakeAction", args: [null, "foobar"].map((arg) => JSON.stringify(arg)) }
});
}).toThrowError(expect.objectContaining({ message: expect.stringContaining("unregistered")}));
});

it("should throw when no arguments are provided", async () => {
createDevToolsMock();

const fakeAction = (currentState: testState, newValue: string) => {
return Object.assign({}, currentState, { foo: newValue });
};

const store = new Store<testState>({ foo: "bar " }, {
devToolsOptions: {
actionCreators: { "FakeAction": fakeAction }
}
});

store.registerAction("FakeAction", fakeAction);

const devtools = ((store as any).devTools as DevToolsMock);

expect(devtools.subscriptions.length).toBe(1);

expect(() => {
devtools.subscriptions[0]({
type: "ACTION",
state: null,
payload: { name: "FakeAction", args: [] }
});
}).toThrowError(expect.objectContaining({ message: expect.stringContaining("arguments")}));
});
});
});

0 comments on commit ca97e85

Please sign in to comment.
You can’t perform that action at this time.