Skip to content

Commit 4c7c841

Browse files
committed
Automatically remove 'past' and 'future' history from redux-undo
1 parent 0ab4e52 commit 4c7c841

File tree

2 files changed

+128
-4
lines changed

2 files changed

+128
-4
lines changed

index.js

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,45 @@
11
const identity = x => x;
22
const getUndefined = () => {};
33
const filter = () => true;
4+
// Include a heuristic to remove redux-undo history (https://github.com/omnidan/redux-undo)
5+
// 'past' and 'future' are arrays that can include a large number of copies of the state.
6+
const removeHistoryFromObject = obj =>
7+
Object.assign({}, obj, {
8+
past: `redux-undo history was automatically removed. (Entries: ${
9+
obj.past.length
10+
})`,
11+
future: `redux-undo history was automatically removed. (Entries: ${
12+
obj.future.length
13+
})`
14+
});
15+
const isReduxUndoState = state =>
16+
state &&
17+
state.past &&
18+
state.present &&
19+
state.future &&
20+
typeof state.index === "number" &&
21+
typeof state.limit === "number";
22+
const removeReduxUndoHistoryFromState = state => {
23+
if (!state || typeof state !== "object") return state;
24+
if (isReduxUndoState(state)) {
25+
return removeHistoryFromObject(state);
26+
}
27+
let newState = null;
28+
Object.entries(state).forEach(([key, store]) => {
29+
if (isReduxUndoState(store)) {
30+
if (!newState) newState = Object.assign({}, state);
31+
newState[key] = removeHistoryFromObject(store);
32+
}
33+
});
34+
return newState || state;
35+
};
36+
437
function createRavenMiddleware(Raven, options = {}) {
538
// TODO: Validate options.
639
const {
740
breadcrumbDataFromAction = getUndefined,
841
actionTransformer = identity,
9-
stateTransformer = identity,
42+
stateTransformer = removeReduxUndoHistoryFromState,
1043
breadcrumbCategory = "redux-action",
1144
filterBreadcrumbActions = filter,
1245
getUserContext,

index.test.js

Lines changed: 94 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ Raven.config("https://[email protected]/146969", {
66
allowDuplicates: true
77
}).install();
88

9-
const reducer = (previousState = { value: 0 }, action) => {
9+
const defaultState = { value: 0 };
10+
const context = {
11+
initialState: defaultState
12+
};
13+
14+
const reducer = (previousState = context.initialState, action) => {
1015
switch (action.type) {
1116
case "THROW":
1217
// Raven does not seem to be able to capture global exceptions in Jest tests.
@@ -23,11 +28,10 @@ const reducer = (previousState = { value: 0 }, action) => {
2328
}
2429
};
2530

26-
const context = {};
27-
2831
describe("raven-for-redux", () => {
2932
beforeEach(() => {
3033
context.mockTransport = jest.fn();
34+
context.initialState = defaultState;
3135
Raven.setTransport(context.mockTransport);
3236
Raven.setDataCallback(undefined);
3337
Raven.setBreadcrumbCallback(undefined);
@@ -186,6 +190,93 @@ describe("raven-for-redux", () => {
186190
userData
187191
);
188192
});
193+
describe("with redux-undo history as top-level state", () => {
194+
beforeEach(() => {
195+
context.initialState = {
196+
past: [{ value: 2 }, { value: 1 }],
197+
present: { value: 0 },
198+
future: [],
199+
index: 2,
200+
limit: 2
201+
};
202+
context.store = createStore(
203+
reducer,
204+
applyMiddleware(context.middleware)
205+
);
206+
});
207+
it("replaces the past and future arrays in the state", () => {
208+
expect(() => {
209+
context.store.dispatch({ type: "THROW" });
210+
}).toThrow();
211+
212+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
213+
const { extra } = context.mockTransport.mock.calls[0][0].data;
214+
expect(extra.state).toEqual({
215+
past: "redux-undo history was automatically removed. (Entries: 2)",
216+
present: { value: 0 },
217+
future: "redux-undo history was automatically removed. (Entries: 0)",
218+
index: 2,
219+
limit: 2
220+
});
221+
});
222+
});
223+
describe("with redux-undo history as nested stores", () => {
224+
beforeEach(() => {
225+
context.initialState = {
226+
fooStore: {
227+
past: [{ value: 2 }, { value: 1 }],
228+
present: { value: 0 },
229+
future: [],
230+
index: 2,
231+
limit: 2
232+
},
233+
barStore: {
234+
value: 2
235+
}
236+
};
237+
context.store = createStore(
238+
reducer,
239+
applyMiddleware(context.middleware)
240+
);
241+
});
242+
it("replaces past and future arrays in any nested stores that use redux-undo", () => {
243+
expect(() => {
244+
context.store.dispatch({ type: "THROW" });
245+
}).toThrow();
246+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
247+
const { extra } = context.mockTransport.mock.calls[0][0].data;
248+
expect(extra.state).toEqual({
249+
fooStore: {
250+
past: "redux-undo history was automatically removed. (Entries: 2)",
251+
present: { value: 0 },
252+
future:
253+
"redux-undo history was automatically removed. (Entries: 0)",
254+
index: 2,
255+
limit: 2
256+
},
257+
barStore: {
258+
value: 2
259+
}
260+
});
261+
});
262+
});
263+
describe("with state that is not an object", () => {
264+
beforeEach(() => {
265+
context.initialState = 42;
266+
context.store = createStore(
267+
reducer,
268+
applyMiddleware(context.middleware)
269+
);
270+
});
271+
it("does not affect the state", () => {
272+
expect(() => {
273+
context.store.dispatch({ type: "THROW" });
274+
}).toThrow();
275+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
276+
const { extra } = context.mockTransport.mock.calls[0][0].data;
277+
expect(extra.state).toEqual(42);
278+
});
279+
});
189280
});
190281
describe("with all the options enabled", () => {
191282
beforeEach(() => {

0 commit comments

Comments
 (0)