Skip to content

Commit c2d4108

Browse files
authored
make auto action prefixing work with sagas
1 parent 93214e8 commit c2d4108

File tree

8 files changed

+299
-108
lines changed

8 files changed

+299
-108
lines changed

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@lastui/rocker",
3-
"version": "0.19.27",
3+
"version": "0.19.28",
44
"license": "Apache-2.0",
55
"author": "[email protected]",
66
"homepage": "https://github.com/lastui/rocker#readme",

platform/src/kernel/middleware/__tests__/saga.test.js

+85-4
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import configureStore from "redux-mock-store";
2-
import { put } from "redux-saga/effects";
2+
import { put, take, all } from "redux-saga/effects";
33

44
import createSagaMiddleware from "../saga";
55

@@ -17,13 +17,94 @@ describe("saga middleware ", () => {
1717
const { runSaga, sagaMiddleware } = createSagaMiddleware();
1818

1919
const store = configureStore([sagaMiddleware])({});
20-
const action = { type: "probe " };
20+
store.wrap = (type) => `test_${type}`;
2121

2222
runSaga(store, function* () {
23-
yield put(action);
23+
yield put({ type: "probe" });
2424
});
2525

26-
expect(store.getActions()).toEqual([action]);
26+
expect(store.getActions()).toEqual([{ type: "test_probe" }]);
27+
});
28+
29+
describe("intercepts effects", () => {
30+
const { runSaga, sagaMiddleware } = createSagaMiddleware();
31+
32+
const store = configureStore([sagaMiddleware])({});
33+
34+
store.wrap = (type) => (!type || type.startsWith("@@") ? type : `test_${type}`);
35+
36+
beforeEach(() => {
37+
store.clearActions();
38+
});
39+
40+
it("all([])", () => {
41+
runSaga(store, function* () {
42+
yield all([]);
43+
});
44+
});
45+
46+
it("put({})", () => {
47+
runSaga(store, function* () {
48+
yield put({ type: null });
49+
});
50+
expect(store.getActions()).toEqual([{ type: null }]);
51+
});
52+
53+
it('put({ type: "@@BROADCAST/THING" })', () => {
54+
runSaga(store, function* () {
55+
yield put({ type: "@@BROADCAST/THING" });
56+
});
57+
expect(store.getActions()).toEqual([{ type: "@@BROADCAST/THING" }]);
58+
});
59+
60+
it('put({ type: "THING" })', () => {
61+
runSaga(store, function* () {
62+
yield put({ type: "THING" });
63+
});
64+
expect(store.getActions()).toEqual([{ type: "test_THING" }]);
65+
});
66+
67+
it('take("*")', () => {
68+
const sniff = [];
69+
runSaga(store, function* () {
70+
sniff.push(yield take());
71+
sniff.push(yield take("*"));
72+
});
73+
74+
store.dispatch({ type: "probe-1" });
75+
store.dispatch({ type: "probe-2" });
76+
expect(sniff).toEqual([{ type: "probe-1" }, { type: "probe-2" }]);
77+
});
78+
79+
it('take("REQUEST")', () => {
80+
const sniff = [];
81+
runSaga(store, function* () {
82+
sniff.push(yield take("REQUEST"));
83+
});
84+
85+
store.dispatch({ type: "test_REQUEST" });
86+
expect(sniff).toEqual([{ type: "test_REQUEST" }]);
87+
});
88+
89+
it('take(["REQUEST"])', () => {
90+
const sniff = [];
91+
runSaga(store, function* () {
92+
sniff.push(yield take(["REQUEST"]));
93+
});
94+
95+
store.dispatch({ type: "test_REQUEST" });
96+
expect(sniff).toEqual([{ type: "test_REQUEST" }]);
97+
});
98+
99+
it("take(() => true)", () => {
100+
const sniff = [];
101+
runSaga(store, function* () {
102+
sniff.push(yield take(() => true));
103+
});
104+
105+
store.dispatch({ type: "test_REQUEST" });
106+
expect(sniff).toEqual([]);
107+
});
27108
});
28109
});
29110
});

platform/src/kernel/middleware/saga.js

+67
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,7 @@
1+
import { IO, SAGA_ACTION } from "@redux-saga/symbols";
12
import { runSaga as runSagaInternal, stdChannel } from "redux-saga";
23

4+
import { warning } from "../../utils";
35
import { setSagaRunner } from "../registry/saga";
46

57
const createSagaMiddleware = (options = {}) => {
@@ -12,6 +14,71 @@ const createSagaMiddleware = (options = {}) => {
1214
channel,
1315
dispatch: store.dispatch,
1416
getState: store.getState,
17+
effectMiddlewares: [
18+
(next) => (effect) => {
19+
switch (effect.type) {
20+
case "TAKE": {
21+
if (!effect.payload.pattern || effect.payload.pattern === "*") {
22+
return next(effect);
23+
}
24+
if (typeof effect.payload.pattern === "string") {
25+
return next({
26+
[IO]: effect[IO],
27+
[SAGA_ACTION]: effect[SAGA_ACTION],
28+
combinator: effect.combinator,
29+
payload: {
30+
channel: effect.payload.channel,
31+
pattern: store.wrap(effect.payload.pattern),
32+
},
33+
type: effect.type,
34+
});
35+
}
36+
if (Array.isArray(effect.payload.pattern)) {
37+
const pattern = new Array(effect.payload.pattern.length);
38+
for (let i = 0; i < effect.payload.pattern.length; i++) {
39+
pattern[i] = store.wrap(effect.payload.pattern[i]);
40+
}
41+
return next({
42+
[IO]: effect[IO],
43+
combinator: effect.combinator,
44+
payload: {
45+
channel: effect.payload.channel,
46+
pattern,
47+
},
48+
type: effect.type,
49+
});
50+
}
51+
/* istanbul ignore next */
52+
if (process.env.NODE_ENV === "development") {
53+
warning("Saga TAKE pattern function is not supported", effect);
54+
}
55+
return;
56+
}
57+
case "PUT": {
58+
if (!effect.payload.action.type) {
59+
return next(effect);
60+
}
61+
return next({
62+
[IO]: effect[IO],
63+
[SAGA_ACTION]: effect[SAGA_ACTION],
64+
combinator: effect.combinator,
65+
payload: {
66+
action: {
67+
...effect.payload.action,
68+
type: store.wrap(effect.payload.action.type),
69+
},
70+
channel: effect.payload.channel,
71+
},
72+
type: effect.type,
73+
resolve: effect.resolve,
74+
});
75+
}
76+
default: {
77+
return next(effect);
78+
}
79+
}
80+
},
81+
],
1582
},
1683
saga,
1784
);

platform/src/kernel/reducer/modules.js

+4-3
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
import * as constants from "../../constants";
22
import { warning } from "../../utils";
33

4+
const RUNE = "$";
45
const BROADCAST_ACTION_PREFIX = "@@";
56

67
const PROBE_ACTION = {
@@ -39,9 +40,9 @@ const handler = {
3940
const index = obj.keys.indexOf(prop);
4041
if (index === -1) {
4142
obj.keys.push(prop);
42-
obj.values.push([prop, `$${prop}$`, value]);
43+
obj.values.push([prop, `${RUNE}${prop}${RUNE}`, value]);
4344
} else {
44-
obj.values[index] = [prop, `$${prop}$`, value];
45+
obj.values[index] = [prop, `${RUNE}${prop}${RUNE}`, value];
4546
}
4647
return true;
4748
},
@@ -110,7 +111,7 @@ function createModulesReducer() {
110111
} else if (action.type.startsWith(prefix)) {
111112
copy = {
112113
...action,
113-
type: action.type.replace(prefix, ""),
114+
type: action.type.slice(prefix.length),
114115
};
115116
} else {
116117
copy = PROBE_ACTION;

platform/src/kernel/registry/__tests__/store.test.js

+28-14
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,6 @@ describe("store registry", () => {
2222
errorSpy.mockRestore();
2323
});
2424

25-
it("setStore", () => {
26-
// TODO implement tests for setter
27-
//if
28-
});
29-
3025
describe("getStore", () => {
3126
it("setter interception", () => {
3227
const store = getStore();
@@ -72,6 +67,15 @@ describe("store registry", () => {
7267

7368
expect(errorSpy).not.toHaveBeenCalledWith("Redux store is not provided!");
7469
});
70+
71+
it(".wrap", () => {
72+
const store = getStore();
73+
74+
expect(typeof store.wrap).toEqual("function");
75+
store.wrap();
76+
77+
expect(errorSpy).not.toHaveBeenCalledWith("Redux store is not provided!");
78+
});
7579
});
7680

7781
describe("namespace", () => {
@@ -215,7 +219,6 @@ describe("store registry", () => {
215219
setStore(storeRef);
216220

217221
const store = getStore().namespace("my-feature");
218-
219222
expect(typeof store.dispatch).toEqual("function");
220223

221224
store.dispatch({ type: "foo" });
@@ -255,32 +258,43 @@ describe("store registry", () => {
255258
]);
256259
storeRef.clearActions();
257260

258-
store.dispatch({
259-
type: undefined,
260-
});
261+
store.dispatch({ type: undefined });
261262
expect(storeRef.getActions()).toEqual([]);
262263
storeRef.clearActions();
263264

264-
store.dispatch({
265-
type: "$injection$ACTION",
266-
});
265+
store.dispatch({ type: "$injection$ACTION" });
267266
expect(storeRef.getActions()).toEqual([]);
268267
storeRef.clearActions();
268+
269+
store.dispatch({ type: "$my-feature$foo" });
270+
expect(storeRef.getActions()).toEqual([{ type: "$my-feature$foo" }]);
271+
storeRef.clearActions();
269272
});
270273

271274
it(".subscribe", () => {
272275
const store = getStore().namespace("my-feature");
273-
274276
expect(typeof store.subscribe).toEqual("function");
275277
store.subscribe();
276278
});
277279

278280
it(".replaceReducer", () => {
279281
const store = getStore().namespace("my-feature");
280-
281282
expect(typeof store.replaceReducer).toEqual("function");
282283
store.replaceReducer();
283284
});
284285
});
286+
287+
it(".wrap", () => {
288+
const storeRef = configureStore([])({});
289+
setStore(storeRef);
290+
291+
const store = getStore().namespace("my-feature");
292+
expect(typeof store.wrap).toEqual("function");
293+
294+
expect(store.wrap()).toEqual();
295+
expect(store.wrap("$other-feature$THING")).toEqual("$other-feature$THING");
296+
expect(store.wrap("@@agenda/THING")).toEqual("@@agenda/THING");
297+
expect(store.wrap("THING")).toEqual("$my-feature$THING");
298+
});
285299
});
286300
});

platform/src/kernel/registry/saga.js

+5-1
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,11 @@ function removeSaga(name, preferentialStore) {
3232
}
3333

3434
async function addSaga(name, preferentialStore, saga) {
35-
if (sagas[name]) {
35+
const dangling = sagas[name];
36+
if (dangling) {
37+
sagaRunner(preferentialStore, function* () {
38+
yield cancel(dangling);
39+
});
3640
delete sagas[name];
3741
console.debug(`module ${name} replacing saga`);
3842
} else {

0 commit comments

Comments
 (0)