Skip to content

Commit aa03bf4

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

File tree

2 files changed

+113
-4
lines changed

2 files changed

+113
-4
lines changed

index.js

Lines changed: 27 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,38 @@
11
const identity = x => x;
22
const getUndefined = () => {};
33
const filter = () => true;
4+
// Includes 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 removeReduxUndoHistoryFromState = state => {
7+
if (!state || typeof state !== "object") return state;
8+
const removeHistoryFromObject = obj =>
9+
Object.assign({}, obj, {
10+
past: `redux-undo history was automatically removed. (Entries: ${
11+
obj.past.length
12+
})`,
13+
future: `redux-undo history was automatically removed. (Entries: ${
14+
obj.future.length
15+
})`
16+
});
17+
if (state.past && state.present && state.future) {
18+
return removeHistoryFromObject(state);
19+
}
20+
let newState = null;
21+
Object.entries(state).forEach(([key, store]) => {
22+
if (store && store.past && store.present && store.future) {
23+
if (!newState) newState = Object.assign({}, state);
24+
newState[key] = removeHistoryFromObject(store);
25+
}
26+
});
27+
return newState || state;
28+
};
29+
430
function createRavenMiddleware(Raven, options = {}) {
531
// TODO: Validate options.
632
const {
733
breadcrumbDataFromAction = getUndefined,
834
actionTransformer = identity,
9-
stateTransformer = identity,
35+
stateTransformer = removeReduxUndoHistoryFromState,
1036
breadcrumbCategory = "redux-action",
1137
filterBreadcrumbActions = filter,
1238
getUserContext,

index.test.js

Lines changed: 86 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,12 @@ Raven.config("https://5d5bf17b1bed4afc9103b5a09634775e@sentry.io/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,85 @@ describe("raven-for-redux", () => {
186190
userData
187191
);
188192
});
193+
194+
describe("with redux-undo history as top-level state", () => {
195+
beforeEach(() => {
196+
context.initialState = {
197+
past: [{ value: 2 }, { value: 1 }],
198+
present: { value: 0 },
199+
future: []
200+
};
201+
context.store = createStore(
202+
reducer,
203+
applyMiddleware(context.middleware)
204+
);
205+
});
206+
it("replaces the past and future arrays in the state", () => {
207+
expect(() => {
208+
context.store.dispatch({ type: "THROW" });
209+
}).toThrow();
210+
211+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
212+
const { extra } = context.mockTransport.mock.calls[0][0].data;
213+
expect(extra.state).toEqual({
214+
past: "redux-undo history was automatically removed. (Entries: 2)",
215+
present: { value: 0 },
216+
future: "redux-undo history was automatically removed. (Entries: 0)"
217+
});
218+
});
219+
});
220+
describe("with redux-undo history as nested stores", () => {
221+
beforeEach(() => {
222+
context.initialState = {
223+
fooStore: {
224+
past: [{ value: 2 }, { value: 1 }],
225+
present: { value: 0 },
226+
future: []
227+
},
228+
barStore: {
229+
value: 2
230+
}
231+
};
232+
context.store = createStore(
233+
reducer,
234+
applyMiddleware(context.middleware)
235+
);
236+
});
237+
it("replaces past and future arrays in any nested stores that use redux-undo", () => {
238+
expect(() => {
239+
context.store.dispatch({ type: "THROW" });
240+
}).toThrow();
241+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
242+
const { extra } = context.mockTransport.mock.calls[0][0].data;
243+
expect(extra.state).toEqual({
244+
fooStore: {
245+
past: "redux-undo history was automatically removed. (Entries: 2)",
246+
present: { value: 0 },
247+
future: "redux-undo history was automatically removed. (Entries: 0)"
248+
},
249+
barStore: {
250+
value: 2
251+
}
252+
});
253+
});
254+
});
255+
describe("with state that is not an object", () => {
256+
beforeEach(() => {
257+
context.initialState = 42;
258+
context.store = createStore(
259+
reducer,
260+
applyMiddleware(context.middleware)
261+
);
262+
});
263+
it("does not affect the state", () => {
264+
expect(() => {
265+
context.store.dispatch({ type: "THROW" });
266+
}).toThrow();
267+
expect(context.mockTransport).toHaveBeenCalledTimes(1);
268+
const { extra } = context.mockTransport.mock.calls[0][0].data;
269+
expect(extra.state).toEqual(42);
270+
});
271+
});
189272
});
190273
describe("with all the options enabled", () => {
191274
beforeEach(() => {

0 commit comments

Comments
 (0)