Skip to content
This repository was archived by the owner on Apr 8, 2020. It is now read-only.

Commit 6cf4a69

Browse files
committed
Use timeout as a last resort
1 parent 30684c9 commit 6cf4a69

File tree

5 files changed

+174
-1
lines changed

5 files changed

+174
-1
lines changed

src/composables/withTimeout.spec.tsx

+100
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,100 @@
1+
import * as React from "react";
2+
import { assert } from "chai";
3+
import { mount } from "enzyme";
4+
import { spy } from "sinon";
5+
import { assemble } from "reassemble";
6+
7+
import { Component } from "../../test/component";
8+
import { withTimeout, timeoutMultiplier } from "./withTimeout";
9+
10+
describe("withTimeout", () => {
11+
const composable = withTimeout;
12+
const Assembly = assemble<any, any>(composable)(Component);
13+
14+
it("shouldn't timeout without a transition", (done) => {
15+
const props = {
16+
transitionState: {
17+
inTransition: false,
18+
},
19+
transitionInfo: {
20+
totalDuration: 20,
21+
},
22+
timeout: spy(),
23+
};
24+
const timeoutIn = props.transitionInfo.totalDuration * timeoutMultiplier;
25+
mount(<Assembly {...props} />);
26+
setTimeout(() => {
27+
assert.isFalse(props.timeout.called);
28+
done();
29+
}, timeoutIn);
30+
});
31+
32+
it("shouldn't timeout when transition ends before", (done) => {
33+
const props = {
34+
transitionState: {
35+
inTransition: false,
36+
},
37+
transitionInfo: {
38+
totalDuration: 20,
39+
},
40+
timeout: spy(),
41+
};
42+
const timeoutIn = props.transitionInfo.totalDuration * timeoutMultiplier;
43+
const wrapper = mount(<Assembly {...props} />);
44+
wrapper.setProps({ transitionState: { inTransition: true } });
45+
setTimeout(() => {
46+
assert.isFalse(props.timeout.called);
47+
wrapper.setProps({ transitionState: { inTransition: false } });
48+
setTimeout(() => {
49+
assert.isFalse(props.timeout.called);
50+
done();
51+
}, timeoutIn - props.transitionInfo.totalDuration);
52+
}, props.transitionInfo.totalDuration);
53+
});
54+
55+
it("should timeout if transition passes time", (done) => {
56+
const props = {
57+
transitionState: {
58+
inTransition: false,
59+
},
60+
transitionInfo: {
61+
totalDuration: 20,
62+
},
63+
timeout: spy(),
64+
};
65+
const timeoutIn = props.transitionInfo.totalDuration * timeoutMultiplier;
66+
const wrapper = mount(<Assembly {...props} />);
67+
wrapper.setProps({ transitionState: { inTransition: true } });
68+
setTimeout(() => {
69+
assert.isTrue(props.timeout.called);
70+
done();
71+
}, timeoutIn);
72+
});
73+
74+
it("should reset timer if transition was interrupted", (done) => {
75+
const props = {
76+
transitionState: {
77+
inTransition: false,
78+
},
79+
transitionInfo: {
80+
totalDuration: 20,
81+
},
82+
timeout: spy(),
83+
};
84+
const halfPeriod = props.transitionInfo.totalDuration * timeoutMultiplier / 2;
85+
const wrapper = mount(<Assembly {...props} />);
86+
wrapper.setProps({ transitionState: { inTransition: true } });
87+
setTimeout(() => {
88+
assert.isFalse(props.timeout.called);
89+
wrapper.setProps({ active: true });
90+
91+
setTimeout(() => {
92+
assert.isFalse(props.timeout.called);
93+
setTimeout(() => {
94+
assert.isTrue(props.timeout.called);
95+
done();
96+
}, halfPeriod);
97+
}, halfPeriod);
98+
}, halfPeriod);
99+
});
100+
});

src/composables/withTimeout.ts

+48
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,48 @@
1+
import { withHandlers, isolate, onWillReceiveProps, debug } from "reassemble";
2+
3+
import { CSSTransitionProps } from "../csstransition";
4+
import { WithTransitionStateProps } from "./withTransitionState";
5+
import { WithTransitionInfoProps } from "./withTransitionInfo";
6+
7+
export const timeoutMultiplier = 3;
8+
9+
type PropsOut = {
10+
cancel: () => void,
11+
timeoutIn: (ms: number) => void,
12+
};
13+
14+
type PropsUnion =
15+
CSSTransitionProps
16+
& WithTransitionStateProps
17+
& WithTransitionInfoProps
18+
& PropsOut;
19+
20+
export const withTimeout =
21+
isolate(
22+
withHandlers<PropsUnion, PropsOut>(
23+
() => {
24+
let timeoutID: any;
25+
return {
26+
timeoutIn: ({timeout}) => (ms: number) => timeoutID = setTimeout(timeout, ms),
27+
cancel: () => () => clearTimeout(timeoutID),
28+
};
29+
}),
30+
onWillReceiveProps<PropsUnion>(
31+
(
32+
{transitionState: {inTransition}, active},
33+
{
34+
transitionState: {inTransition: nextInTransition},
35+
transitionInfo: {totalDuration},
36+
cancel, timeoutIn, active: nextActive,
37+
},
38+
) => {
39+
const newTransition = inTransition !== nextInTransition;
40+
const interrupted = nextInTransition && active !== nextActive;
41+
if (newTransition || interrupted) {
42+
cancel();
43+
if (nextInTransition) {
44+
timeoutIn(totalDuration * timeoutMultiplier);
45+
}
46+
}
47+
}),
48+
);

src/composables/withTransitionState.spec.tsx

+20
Original file line numberDiff line numberDiff line change
@@ -128,6 +128,26 @@ describe("withTransitionState.tsx", () => {
128128
});
129129
});
130130

131+
describe("timeout", () => {
132+
const initialState = { id: 1, style: {} };
133+
const state = { id: 2, style: { top: 0 }, className: "foo" };
134+
let reducer: SinonSpy;
135+
before(() => {
136+
reducer = spy(createReducer({ 0: { state: initialState }, 1: { state } }));
137+
wrapper = getWrapper({ children: <span /> }, reducer);
138+
reducer.reset();
139+
wrapper.props().timeout();
140+
});
141+
142+
it("should dispatch ActionID.Timeout", () => {
143+
assert.isTrue(reducer.calledWith(initialState.id, { kind: ActionID.Timeout, props: {} }));
144+
});
145+
146+
it("should return transitionState", () => {
147+
assert.deepEqual(wrapper.props().transitionState, pick(state, "style", "className"));
148+
});
149+
});
150+
131151
describe("pending action", () => {
132152
const initialState = { id: 1, style: {} };
133153
const pendingState = { id: 2, style: { top: 0 }, className: "foo" };

src/composables/withTransitionState.tsx

+3-1
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ export type TransitionState = {
1919

2020
export type WithTransitionStateProps = {
2121
transitionState?: TransitionState,
22+
timeout?: () => void,
2223
};
2324

2425
type PropsOut =
@@ -89,6 +90,7 @@ export const withTransitionState = (reduce: Reducer) => combine(
8990
withHandlers<PropsUnion, PropsOut>({
9091
onTransitionBegin: ({dispatch}) => () => dispatch(ActionID.TransitionStart),
9192
onTransitionComplete: ({dispatch}) => () => dispatch(ActionID.TransitionComplete),
93+
timeout: ({dispatch}) => () => dispatch(ActionID.Timeout),
9294
}),
9395
onDidMount<PropsUnion>(
9496
({dispatch}) => {
@@ -104,7 +106,7 @@ export const withTransitionState = (reduce: Reducer) => combine(
104106
dispatch(ActionID.TransitionTrigger);
105107
}),
106108
integrate<keyof PropsUnion>(
107-
"transitionState", "onTransitionBegin", "onTransitionComplete",
109+
"timeout", "transitionState", "onTransitionBegin", "onTransitionComplete",
108110
),
109111
),
110112
);

src/csstransition.tsx

+3
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { reducer } from "./reducer";
66
import { withTransitionState, WithTransitionStateProps } from "./composables/withTransitionState";
77
import { mergeWithBaseStyle } from "./composables/mergeWithBaseStyle";
88
import { withTransitionInfo, WithTransitionInfoProps } from "./composables/withTransitionInfo";
9+
import { withTimeout } from "./composables/withTimeout";
910
import { withTransitionObserver, WithTransitionObserverProps } from "./composables/withTransitionObserver";
1011
import { withWorkaround } from "./composables/withWorkaround";
1112
import { withDOMNodeCallback, WithDOMNodeCallbackProps } from "./composables/withDOMNodeCallback";
@@ -86,6 +87,7 @@ const mapPropsToInner = omitProps<keyof PropsUnion>(
8687
"onTransitionBegin",
8788
"transitionInfo",
8889
"transitionState",
90+
"timeout",
8991
"getDOMNode",
9092
);
9193

@@ -96,6 +98,7 @@ const enhance = assemble<CSSTransitionInnerProps, CSSTransitionProps>(
9698
withTransitionState(reducer),
9799
mergeWithBaseStyle,
98100
withTransitionInfo,
101+
withTimeout,
99102
withTransitionObserver,
100103
withWorkaround,
101104
// TODO: needs more investigation and probably a different way to do it.

0 commit comments

Comments
 (0)