Skip to content

Commit 136043e

Browse files
committed
test(chat): add unit tests
1 parent a91058b commit 136043e

File tree

10 files changed

+671
-3
lines changed

10 files changed

+671
-3
lines changed

package.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -111,6 +111,7 @@
111111
"react-dom": "^18.0.0",
112112
"react-test-renderer": "^18.2.0",
113113
"standard-version": "^9.5.0",
114+
"resize-observer-polyfill": "^1.5.1",
114115
"stylelint": "^14.9.1",
115116
"ts-jest": "^29.0.3",
116117
"typescript": "~4.5.2"

pnpm-lock.yaml

Lines changed: 2 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

src/chat/__tests__/__snapshots__/icon.test.tsx.snap

Lines changed: 325 additions & 0 deletions
Large diffs are not rendered by default.
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Test Chat Input Match snapshot 1`] = `
4+
<DocumentFragment>
5+
<textarea
6+
class="ant-input aigc-textarea"
7+
/>
8+
</DocumentFragment>
9+
`;
Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Jest Snapshot v1, https://goo.gl/fbAQLP
2+
3+
exports[`Test Chat Loading Match Snapshots: loading 1`] = `
4+
{
5+
"asFragment": [Function],
6+
"baseElement": <body>
7+
<div>
8+
<div
9+
class="dtc__aigc__loading"
10+
>
11+
<div />
12+
<div />
13+
<div />
14+
</div>
15+
</div>
16+
</body>,
17+
"container": <div>
18+
<div
19+
class="dtc__aigc__loading"
20+
>
21+
<div />
22+
<div />
23+
<div />
24+
</div>
25+
</div>,
26+
"debug": [Function],
27+
"findAllByAltText": [Function],
28+
"findAllByDisplayValue": [Function],
29+
"findAllByLabelText": [Function],
30+
"findAllByPlaceholderText": [Function],
31+
"findAllByRole": [Function],
32+
"findAllByTestId": [Function],
33+
"findAllByText": [Function],
34+
"findAllByTitle": [Function],
35+
"findByAltText": [Function],
36+
"findByDisplayValue": [Function],
37+
"findByLabelText": [Function],
38+
"findByPlaceholderText": [Function],
39+
"findByRole": [Function],
40+
"findByTestId": [Function],
41+
"findByText": [Function],
42+
"findByTitle": [Function],
43+
"getAllByAltText": [Function],
44+
"getAllByDisplayValue": [Function],
45+
"getAllByLabelText": [Function],
46+
"getAllByPlaceholderText": [Function],
47+
"getAllByRole": [Function],
48+
"getAllByTestId": [Function],
49+
"getAllByText": [Function],
50+
"getAllByTitle": [Function],
51+
"getByAltText": [Function],
52+
"getByDisplayValue": [Function],
53+
"getByLabelText": [Function],
54+
"getByPlaceholderText": [Function],
55+
"getByRole": [Function],
56+
"getByTestId": [Function],
57+
"getByText": [Function],
58+
"getByTitle": [Function],
59+
"queryAllByAltText": [Function],
60+
"queryAllByDisplayValue": [Function],
61+
"queryAllByLabelText": [Function],
62+
"queryAllByPlaceholderText": [Function],
63+
"queryAllByRole": [Function],
64+
"queryAllByTestId": [Function],
65+
"queryAllByText": [Function],
66+
"queryAllByTitle": [Function],
67+
"queryByAltText": [Function],
68+
"queryByDisplayValue": [Function],
69+
"queryByLabelText": [Function],
70+
"queryByPlaceholderText": [Function],
71+
"queryByRole": [Function],
72+
"queryByTestId": [Function],
73+
"queryByText": [Function],
74+
"queryByTitle": [Function],
75+
"rerender": [Function],
76+
"unmount": [Function],
77+
}
78+
`;
79+
80+
exports[`Test Chat Loading Match Snapshots: not loading 1`] = `
81+
{
82+
"asFragment": [Function],
83+
"baseElement": <body>
84+
<div>
85+
<div
86+
class="dtc__aigc__loading"
87+
>
88+
<div />
89+
<div />
90+
<div />
91+
</div>
92+
</div>
93+
<div>
94+
test
95+
</div>
96+
</body>,
97+
"container": <div>
98+
test
99+
</div>,
100+
"debug": [Function],
101+
"findAllByAltText": [Function],
102+
"findAllByDisplayValue": [Function],
103+
"findAllByLabelText": [Function],
104+
"findAllByPlaceholderText": [Function],
105+
"findAllByRole": [Function],
106+
"findAllByTestId": [Function],
107+
"findAllByText": [Function],
108+
"findAllByTitle": [Function],
109+
"findByAltText": [Function],
110+
"findByDisplayValue": [Function],
111+
"findByLabelText": [Function],
112+
"findByPlaceholderText": [Function],
113+
"findByRole": [Function],
114+
"findByTestId": [Function],
115+
"findByText": [Function],
116+
"findByTitle": [Function],
117+
"getAllByAltText": [Function],
118+
"getAllByDisplayValue": [Function],
119+
"getAllByLabelText": [Function],
120+
"getAllByPlaceholderText": [Function],
121+
"getAllByRole": [Function],
122+
"getAllByTestId": [Function],
123+
"getAllByText": [Function],
124+
"getAllByTitle": [Function],
125+
"getByAltText": [Function],
126+
"getByDisplayValue": [Function],
127+
"getByLabelText": [Function],
128+
"getByPlaceholderText": [Function],
129+
"getByRole": [Function],
130+
"getByTestId": [Function],
131+
"getByText": [Function],
132+
"getByTitle": [Function],
133+
"queryAllByAltText": [Function],
134+
"queryAllByDisplayValue": [Function],
135+
"queryAllByLabelText": [Function],
136+
"queryAllByPlaceholderText": [Function],
137+
"queryAllByRole": [Function],
138+
"queryAllByTestId": [Function],
139+
"queryAllByText": [Function],
140+
"queryAllByTitle": [Function],
141+
"queryByAltText": [Function],
142+
"queryByDisplayValue": [Function],
143+
"queryByLabelText": [Function],
144+
"queryByPlaceholderText": [Function],
145+
"queryByRole": [Function],
146+
"queryByTestId": [Function],
147+
"queryByText": [Function],
148+
"queryByTitle": [Function],
149+
"rerender": [Function],
150+
"unmount": [Function],
151+
}
152+
`;

src/chat/__tests__/content.test.tsx

Lines changed: 78 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
import React from 'react';
2-
import { act, cleanup, render } from '@testing-library/react';
2+
import { act, cleanup, fireEvent, render } from '@testing-library/react';
3+
import { produce } from 'immer';
34
import '@testing-library/jest-dom/extend-expect';
45

5-
import Content from '../content';
6+
import Content, { type IContentRef } from '../content';
67
import { Message, Prompt } from '../entity';
78

89
class BasePrompt extends Prompt {}
@@ -51,7 +52,25 @@ function generatePrompt() {
5152
return prompt;
5253
}
5354

54-
describe('Test Content', () => {
55+
function generatePromptWithGeneratingMessage() {
56+
const prompt = new BasePrompt({
57+
id: '1',
58+
title: 'What is your name?',
59+
messages: [],
60+
createdAt: 1736479532239,
61+
});
62+
prompt.messages.push(
63+
new BaseMessage({
64+
id: '1',
65+
content: 'My Name is dt-react-component',
66+
createdAt: 1736479532239,
67+
status: 1,
68+
})
69+
);
70+
return prompt;
71+
}
72+
73+
describe('Test Chat Content', () => {
5574
let originalRAF: typeof window.requestAnimationFrame;
5675
beforeEach(() => {
5776
jest.useFakeTimers();
@@ -123,5 +142,61 @@ describe('Test Content', () => {
123142
});
124143

125144
expect(fn).toBeCalledTimes(1);
145+
expect(fn).toBeCalledWith(100);
146+
});
147+
148+
it('Should always scroll to bottom', () => {
149+
let data = generatePromptWithGeneratingMessage();
150+
const { container, rerender } = render(
151+
<Content data={[data]} placeholder={<div>placeholder</div>} />
152+
);
153+
154+
const ele = container.querySelector('.dtc__aigc__content__container')!;
155+
jest.spyOn(ele, 'scrollHeight', 'get')
156+
.mockReturnValueOnce(100)
157+
.mockReturnValueOnce(400)
158+
.mockReturnValue(200);
159+
jest.spyOn(ele, 'clientHeight', 'get').mockReturnValue(200);
160+
161+
const fn = jest.spyOn(ele, 'scrollTop', 'set');
162+
expect(fn).not.toBeCalled();
163+
164+
data = produce(data, (draft) => {
165+
draft.messages[0].content += ', I am a developer';
166+
});
167+
rerender(<Content data={[data]} placeholder={<div>placeholder</div>} />);
168+
169+
expect(fn).toBeCalledTimes(1);
170+
expect(fn).toBeCalledWith(100);
171+
172+
// Simulate user scroll to top and will not scroll to bottom
173+
fireEvent(ele, new Event('scroll'));
174+
expect(fn).toBeCalledTimes(1);
175+
176+
// Simulate user scroll to bottom and will keep sticky at bottom
177+
fireEvent(ele, new Event('scroll'));
178+
expect(fn).toBeCalledTimes(2);
179+
expect(fn).lastCalledWith(200);
180+
});
181+
182+
it('Should get ref', () => {
183+
const ref = React.createRef<IContentRef>();
184+
const { container } = render(
185+
<Content ref={ref} data={[generatePrompt()]} placeholder={<div>placeholder</div>} />
186+
);
187+
188+
const ele = container.querySelector('.dtc__aigc__content__container')!;
189+
expect(ref.current?.nativeElement).toBe(ele);
190+
191+
jest.spyOn(ele, 'scrollHeight', 'get').mockReturnValue(200);
192+
const fn = jest.fn();
193+
ele.scrollTo = fn;
194+
195+
act(() => {
196+
ref.current?.scrollToBottom();
197+
});
198+
199+
expect(fn).toBeCalledTimes(1);
200+
expect(fn).lastCalledWith({ top: 200, left: 0, behavior: 'instant' });
126201
});
127202
});

src/chat/__tests__/icon.test.tsx

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
import React from 'react';
2+
import { cleanup, render } from '@testing-library/react';
3+
4+
import {
5+
AssistantAvatarIcon,
6+
CopyIcon,
7+
GradientDotIcon,
8+
PauseIcon,
9+
ReloadIcon,
10+
RobotIcon,
11+
SendIcon,
12+
ShiningIcon,
13+
} from '../icon';
14+
15+
describe('Test Chat Icon', () => {
16+
beforeEach(cleanup);
17+
18+
it('Match Snapshots', () => {
19+
expect(render(<AssistantAvatarIcon />).asFragment()).toMatchSnapshot('AssistantAvatarIcon');
20+
expect(render(<ReloadIcon />).asFragment()).toMatchSnapshot('ReloadIcon');
21+
expect(render(<PauseIcon />).asFragment()).toMatchSnapshot('PauseIcon');
22+
expect(render(<RobotIcon />).asFragment()).toMatchSnapshot('RobotIcon');
23+
expect(render(<SendIcon />).asFragment()).toMatchSnapshot('SendIcon');
24+
expect(render(<SendIcon gradient />).asFragment()).toMatchSnapshot(
25+
'SendIcon with gradient'
26+
);
27+
expect(render(<CopyIcon />).asFragment()).toMatchSnapshot('CopyIcon');
28+
expect(render(<GradientDotIcon />).asFragment()).toMatchSnapshot('GradientDotIcon');
29+
expect(render(<ShiningIcon />).asFragment()).toMatchSnapshot('ShiningIcon');
30+
});
31+
});

src/chat/__tests__/input.test.tsx

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
import React from 'react';
2+
import { act, cleanup, fireEvent, render } from '@testing-library/react';
3+
import { input } from 'ant-design-testing';
4+
5+
import Input from '../input';
6+
7+
describe('Test Chat Input', () => {
8+
beforeAll(() => {
9+
window.ResizeObserver = jest.fn().mockImplementation(() => ({
10+
disconnect: jest.fn(),
11+
observe: jest.fn(),
12+
unobserve: jest.fn(),
13+
}));
14+
});
15+
16+
beforeEach(cleanup);
17+
it('Match snapshot', () => {
18+
expect(render(<Input />).asFragment()).toMatchSnapshot();
19+
});
20+
21+
it('Should call onChange', () => {
22+
const onChange = jest.fn();
23+
const { container } = render(<Input onChange={onChange} />);
24+
act(() => {
25+
input.textarea.fireChange(container, 'test');
26+
});
27+
expect(onChange).toBeCalledWith('test');
28+
});
29+
30+
it('Should call specify function', () => {
31+
const onPressShiftEnter = jest.fn();
32+
const onChange = jest.fn();
33+
const onPressEnter = jest.fn();
34+
const { container } = render(
35+
<Input
36+
value="test"
37+
onPressShiftEnter={onPressShiftEnter}
38+
onChange={onChange}
39+
onPressEnter={onPressEnter}
40+
/>
41+
);
42+
// Simulate press shift + enter
43+
// FIXME: ant-design-testing 不支持触发 shift 的 onPressEnter 事件
44+
const ele = container.querySelector<HTMLTextAreaElement>('textarea')!;
45+
fireEvent.keyDown(ele, { keyCode: 13, shiftKey: true });
46+
47+
expect(onPressShiftEnter).toBeCalled();
48+
expect(onChange).lastCalledWith('test\n');
49+
expect(onPressEnter).not.toBeCalled();
50+
51+
onChange.mockClear();
52+
onPressShiftEnter.mockClear();
53+
// Simulate press enter
54+
input.textarea.firePressEnter(container);
55+
expect(onPressShiftEnter).not.toBeCalled();
56+
expect(onChange).not.toBeCalled();
57+
expect(onPressEnter).toBeCalledTimes(1);
58+
});
59+
});
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
import React from 'react';
2+
import { render } from '@testing-library/react';
3+
4+
import Loading from '../loading';
5+
6+
describe('Test Chat Loading', () => {
7+
it('Match Snapshots', () => {
8+
expect(render(<Loading loading>test</Loading>)).toMatchSnapshot('loading');
9+
expect(render(<Loading loading={false}>test</Loading>)).toMatchSnapshot('not loading');
10+
});
11+
});

tests/setupTests.js

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,7 @@
11
const { TextDecoder, TextEncoder } = require('node:util');
2+
const ResizeObserver = require('resize-observer-polyfill');
3+
4+
global.ResizeObserver = ResizeObserver;
25

36
Object.defineProperty(window, 'matchMedia', {
47
writable: true,

0 commit comments

Comments
 (0)