-
Notifications
You must be signed in to change notification settings - Fork 8
Expand file tree
/
Copy pathProgram.test.tsx
More file actions
125 lines (115 loc) · 3.68 KB
/
Program.test.tsx
File metadata and controls
125 lines (115 loc) · 3.68 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
import { describe, expect, test } from 'vitest';
import { Cmd, Dispatcher, just, Maybe, noCmd, nothing, Result, Sub, Task } from 'tea-cup-fp';
import { Program } from './Program';
import { updateUntilCondition, updateUntilIdle } from './Testing';
import { render, RenderResult } from '@testing-library/react';
import * as React from 'react';
interface Model {
readonly id: Maybe<string>;
readonly other: boolean;
}
type Msg = { tag: 'got-load'; id: Result<Error, string> } | { tag: 'got-other' };
function init(): [Model, Cmd<Msg>] {
const cmd: Cmd<Msg> = Task.attempt(
Task.fromPromise(
() => new Promise<string>((res) => setTimeout(() => res('myid'), 1000)),
),
(r) => ({ tag: 'got-load', id: r }),
);
return [{ id: nothing, other: false }, cmd];
}
function view(model: Model) {
const id = model.id.withDefault('tmpid');
return (
<div id={id} style={{ backgroundColor: 'red', height: '30px', width: '200px' }}>
{id},{model.other ? 'true' : 'false'}
</div>
);
}
function update(msg: Msg, model: Model): [Model, Cmd<Msg>] {
switch (msg.tag) {
case 'got-load': {
const newModel: Model = {
...model,
id: msg.id.toMaybe(),
};
const t: Maybe<Task<Error, HTMLElement>> = msg.id.toMaybe().map((id) =>
Task.fromLambda(() => {
const n = document.getElementById(id);
if (!n) {
throw new Error('node not found');
}
return n;
}),
);
const t2: Maybe<Task<Error, boolean>> = t.map((task) =>
task.map((e) => {
return true;
}),
);
const msgOther: Msg = { tag: 'got-other' };
const cmd: Cmd<Msg> = t2.map((task) => Task.attempt(task, () => msgOther)).withDefaultSupply(() => Cmd.none());
return [newModel, cmd];
}
case 'got-other': {
const newModel: Model = {
...model,
other: true,
};
return noCmd(newModel);
}
}
}
function subscriptions(): Sub<Msg> {
return Sub.none();
}
describe('program test', () => {
test('view should be called after each update', () =>
new Promise<void>((done) => {
let initCount = 0;
let viewCount = 0;
let updateCount = 0;
let subsCount = 0;
const myInit = () => {
initCount++;
return init();
};
const myView = (_d: Dispatcher<Msg>, m: Model) => {
viewCount++;
return view(m);
};
const myUpdate = (msg: Msg, model: Model) => {
updateCount++;
return update(msg, model);
};
const mySubs = (_model: Model) => {
subsCount++;
return subscriptions();
};
const p = <Program init={myInit} view={myView} update={myUpdate} subscriptions={mySubs} />;
const { container } = render(p);
expect(container.querySelector('#tmpid')?.textContent).toEqual('tmpid,false');
expect(initCount).toBe(1);
expect(viewCount).toBe(1);
expect(updateCount).toBe(0);
expect(subsCount).toBe(1);
setTimeout(() => {
expect(container.querySelector('#myid')?.textContent).toEqual('myid,true');
expect(initCount).toBe(1);
expect(viewCount).toBe(3);
expect(updateCount).toBe(2);
expect(subsCount).toBe(3);
done();
}, 3000);
}));
test('update until condition', () => {
return updateUntilCondition(
{ init: init, view: (_d, model) => view(model), update: update, subscriptions: subscriptions },
(node) => render(node),
(model) => model.other,
).then(([model, { container }]) => {
expect(model).toEqual({ id: just('myid'), other: true });
expect(container.querySelector('#myid')?.textContent).toEqual('myid,true');
});
});
});