Skip to content

Commit ff94618

Browse files
authored
Merge pull request #87 from vankeisb/feature/support-unmount
support unmount
2 parents 4723f50 + 0ed6fa7 commit ff94618

3 files changed

Lines changed: 91 additions & 31 deletions

File tree

Lines changed: 76 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,29 +1,29 @@
11
import { mount } from 'enzyme';
2-
import { Cmd, Dispatcher, Sub, Task } from 'tea-cup-core';
3-
import { extendJest, ProgramProps, updateUntilIdle } from 'react-tea-cup';
2+
import { Cmd, Dispatcher, noCmd, Port, Sub, Task } from 'tea-cup-core';
3+
import { DevTools, extendJest, Program, ProgramProps, updateUntilIdle } from 'react-tea-cup';
44
import React from 'react';
55

66
extendJest(expect);
77

8-
const init1: () => [number, Cmd<string>] = () => {
9-
return [0, toCmd('go')];
10-
};
8+
// const toCmd = (msg: string) => Task.perform(Time.in(0), () => msg);
9+
const toCmd = (msg: string) => Task.perform(Task.succeed(0), () => msg);
1110

12-
const view1: (dispatch: Dispatcher<string>, model: number) => React.ReactNode = (
13-
dispatch: Dispatcher<string>,
14-
model: number,
15-
) => {
16-
return <div className={'count'}>{model}</div>;
17-
};
11+
describe('Test Program using updateUntilIdle()', () => {
12+
const init1: () => [number, Cmd<string>] = () => {
13+
return [0, toCmd('go')];
14+
};
1815

19-
const update1: (msg: string, model: number) => [number, Cmd<string>] = (msg: string, model: number) => {
20-
return [model + 1, model < 5 ? toCmd('go') : Cmd.none()];
21-
};
16+
const view1: (dispatch: Dispatcher<string>, model: number) => React.ReactNode = (
17+
dispatch: Dispatcher<string>,
18+
model: number,
19+
) => {
20+
return <div className={'count'}>{model}</div>;
21+
};
2222

23-
// const toCmd = (msg: string) => Task.perform(Time.in(0), () => msg);
24-
const toCmd = (msg: string) => Task.perform(Task.succeed(0), () => msg);
23+
const update1: (msg: string, model: number) => [number, Cmd<string>] = (msg: string, model: number) => {
24+
return [model + 1, model < 5 ? toCmd('go') : Cmd.none()];
25+
};
2526

26-
describe('Test Program', () => {
2727
it('expect when program is idle', () => {
2828
const props: ProgramProps<number, string> = {
2929
init: init1,
@@ -38,3 +38,62 @@ describe('Test Program', () => {
3838
});
3939
});
4040
});
41+
42+
describe('Test Program using DevTools', () => {
43+
const init1: () => [ReadonlyArray<string>, Cmd<string>] = () => {
44+
return noCmd([]);
45+
};
46+
47+
const view1: (dispatch: Dispatcher<string>, model: ReadonlyArray<string>) => React.ReactNode = (
48+
dispatch: Dispatcher<string>,
49+
model: ReadonlyArray<string>,
50+
) => {
51+
return <div className={'history'}>{model.join(' ')}</div>;
52+
};
53+
54+
const update1: (msg: string, model: ReadonlyArray<string>) => [ReadonlyArray<string>, Cmd<string>] = (
55+
msg: string,
56+
model: ReadonlyArray<string>,
57+
) => {
58+
return noCmd(model.concat([msg]));
59+
};
60+
61+
const port1: Port<string> = new Port<string>();
62+
63+
it('stop dispatching when unmounted', () => {
64+
const devTools = DevTools.init(window);
65+
const props: ProgramProps<number, ReadonlyArray<string>> = {
66+
init: init1,
67+
view: view1,
68+
update: update1,
69+
subscriptions: () => port1.subscribe((msg) => msg),
70+
devTools,
71+
};
72+
73+
const wrapper = mount(<Program {...props} />);
74+
75+
expect(devTools.lastEvent().tag).toEqual('init');
76+
expect(devTools.lastEvent().model).toEqual([]);
77+
expect(devTools.lastModel()).toEqual([]);
78+
79+
port1.send('first');
80+
81+
expect(devTools.lastEvent().tag).toEqual('updated');
82+
expect(devTools.lastEvent().msg).toEqual('first');
83+
expect(devTools.lastModel()).toEqual(['first']);
84+
85+
port1.send('second');
86+
87+
expect(devTools.lastEvent().tag).toEqual('updated');
88+
expect(devTools.lastEvent().msg).toEqual('second');
89+
expect(devTools.lastModel()).toEqual(['first', 'second']);
90+
91+
wrapper.unmount();
92+
93+
port1.send('too-late');
94+
95+
expect(devTools.lastEvent().tag).toEqual('updated');
96+
expect(devTools.lastEvent().msg).toEqual('second');
97+
expect(devTools.lastModel()).toEqual(['first', 'second']);
98+
});
99+
});

tea-cup/src/TeaCup/Program.ts

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,9 @@ export class Program<Model, Msg> extends Component<ProgramProps<Model, Msg>, nev
6262
}
6363

6464
dispatch(msg: Msg) {
65+
if (this.currentSub === undefined) {
66+
return;
67+
}
6568
if (this.props.devTools && this.props.devTools.isPaused()) {
6669
// do not process messages if we are paused
6770
return;
@@ -89,7 +92,7 @@ export class Program<Model, Msg> extends Component<ProgramProps<Model, Msg>, nev
8992
const d = this.dispatch.bind(this);
9093

9194
newSub.init(d);
92-
prevSub && prevSub.release();
95+
prevSub?.release();
9396

9497
// perform commands in a separate timout, to
9598
// make sure that this dispatch is done
@@ -115,6 +118,11 @@ export class Program<Model, Msg> extends Component<ProgramProps<Model, Msg>, nev
115118
}
116119
}
117120

121+
componentWillUnmount() {
122+
this.currentSub?.release();
123+
this.currentSub = undefined;
124+
}
125+
118126
componentDidMount() {
119127
const { devTools } = this.props;
120128
if (devTools) {

tea-cup/src/TeaCup/Testing.tsx

Lines changed: 6 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,20 @@ export function updateUntilIdle<Model, Msg, T>(
8888
fun: Trigger<Model, Msg, T>,
8989
): Promise<[Model, T]> {
9090
return new Promise((resolve) => {
91-
fun(<Program {...testableProps(resolve, props, fun)} />);
91+
let wrapper = fun(<Program {...testableProps(resolve, props, () => wrapper)} />);
9292
});
9393
}
9494

9595
function testableProps<Model, Msg, T>(
9696
resolve: ResolveType<Model, T>,
9797
props: ProgramProps<Model, Msg>,
98-
fun: Trigger<Model, Msg, T>,
98+
getWrapper: () => T,
9999
) {
100100
const tprops: ProgramProps<TestableModel<Model, Msg, T>, Msg> = {
101101
init: initTestable(resolve, props.init),
102102
view: viewTestable(props.view),
103103
update: updateTestable(props.update),
104-
subscriptions: suscriptionsTestable(props, fun),
104+
subscriptions: subscriptionsTestable(props, getWrapper),
105105
};
106106
return tprops;
107107
}
@@ -150,21 +150,14 @@ function updateTestable<Model, Msg, T>(
150150
};
151151
}
152152

153-
function suscriptionsTestable<Model, Msg, T>(
153+
function subscriptionsTestable<Model, Msg, T>(
154154
props: ProgramProps<Model, Msg>,
155-
fun: Trigger<Model, Msg, T>,
155+
getWrapper: () => T,
156156
): ProgramProps<TestableModel<Model, Msg, T>, Msg>['subscriptions'] {
157157
return (model: TestableModel<Model, Msg, T>) => {
158158
const subs = props.subscriptions(model.model);
159159
if (model.cmds.length === 0) {
160-
const result = fun(
161-
<Program
162-
init={() => [model.model, Cmd.none()]}
163-
update={(msg, model) => [model, Cmd.none()]}
164-
view={(d, m) => props.view(d, m)}
165-
subscriptions={(d) => Sub.none()}
166-
/>,
167-
);
160+
const result = getWrapper();
168161
model.resolve([model.model, result]);
169162
return subs;
170163
}

0 commit comments

Comments
 (0)