Skip to content

Commit f9321eb

Browse files
committed
Add jest setup and initial tests
1 parent 52feda3 commit f9321eb

File tree

6 files changed

+9849
-4177
lines changed

6 files changed

+9849
-4177
lines changed

.github/workflows/ci.yml

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -35,7 +35,7 @@ jobs:
3535
build-lambda:
3636
uses: ./.github/workflows/build-link-index-updater-lambda.yml
3737

38-
lint:
38+
npm:
3939
runs-on: ubuntu-latest
4040
defaults:
4141
run:
@@ -57,6 +57,12 @@ jobs:
5757

5858
- name: Format
5959
run: npm run fmt:check
60+
61+
- name: Build
62+
run: npm run build
63+
64+
- name: Test
65+
run: npm run test
6066

6167

6268
build:
Lines changed: 222 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,222 @@
1+
import { AskAiAnswer } from './AskAiAnswer'
2+
import { render, screen, act } from '@testing-library/react'
3+
import userEvent from '@testing-library/user-event'
4+
import * as React from 'react'
5+
6+
// Mock the dependencies
7+
const mockSendQuestion = jest.fn(() => Promise.resolve())
8+
const mockRetry = jest.fn()
9+
10+
jest.mock('./search.store', () => ({
11+
useAskAiTerm: jest.fn(() => 'What is Elasticsearch?'),
12+
}))
13+
14+
jest.mock('./useLlmGateway', () => ({
15+
useLlmGateway: jest.fn(() => ({
16+
messages: [],
17+
error: null,
18+
retry: mockRetry,
19+
sendQuestion: mockSendQuestion,
20+
})),
21+
}))
22+
23+
// Mock uuid
24+
jest.mock('uuid', () => ({
25+
v4: jest.fn(() => 'mock-uuid-123'),
26+
}))
27+
28+
describe('AskAiAnswer Component', () => {
29+
const {
30+
useLlmGateway,
31+
} = require('./useLlmGateway')
32+
33+
beforeEach(() => {
34+
jest.clearAllMocks()
35+
})
36+
37+
describe('Initial Loading State', () => {
38+
test('should show loading spinner when no messages are present', () => {
39+
// Arrange
40+
useLlmGateway.mockReturnValue({
41+
messages: [],
42+
error: null,
43+
retry: mockRetry,
44+
sendQuestion: mockSendQuestion,
45+
})
46+
47+
// Act
48+
render(<AskAiAnswer />)
49+
50+
// Assert
51+
const loadingSpinner = screen.getByRole('progressbar')
52+
expect(loadingSpinner).toBeInTheDocument()
53+
expect(screen.getByText('Generating...')).toBeInTheDocument()
54+
})
55+
})
56+
57+
describe('Message Display', () => {
58+
test('should display AI message content correctly', () => {
59+
// Arrange
60+
const mockMessages = [
61+
{
62+
type: 'ai_message',
63+
data: {
64+
content:
65+
'Elasticsearch is a distributed search engine...',
66+
},
67+
},
68+
{
69+
type: 'ai_message_chunk',
70+
data: {
71+
content: ' It provides real-time search capabilities.',
72+
},
73+
},
74+
]
75+
76+
useLlmGateway.mockReturnValue({
77+
messages: mockMessages,
78+
error: null,
79+
retry: mockRetry,
80+
sendQuestion: mockSendQuestion,
81+
})
82+
83+
// Act
84+
render(<AskAiAnswer />)
85+
86+
// Assert
87+
const expectedContent =
88+
'Elasticsearch is a distributed search engine... It provides real-time search capabilities.'
89+
expect(screen.getByText(expectedContent)).toBeInTheDocument()
90+
})
91+
})
92+
93+
describe('Error State', () => {
94+
test('should display error message when there is an error', () => {
95+
// Arrange
96+
useLlmGateway.mockReturnValue({
97+
messages: [],
98+
error: new Error('Network error'),
99+
retry: mockRetry,
100+
sendQuestion: mockSendQuestion,
101+
})
102+
103+
// Act
104+
render(<AskAiAnswer />)
105+
106+
// Assert
107+
expect(
108+
screen.getByText('Sorry, there was an error')
109+
).toBeInTheDocument()
110+
expect(
111+
screen.getByText(
112+
'The Elastic Docs AI Assistant encountered an error. Please try again.'
113+
)
114+
).toBeInTheDocument()
115+
})
116+
})
117+
118+
describe('Finished State with Feedback Buttons', () => {
119+
test('should show feedback buttons when answer is finished', () => {
120+
// Arrange
121+
let onMessageCallback: (message: any) => void = () => {}
122+
123+
const mockMessages = [
124+
{
125+
type: 'ai_message',
126+
data: {
127+
content: 'Here is your answer about Elasticsearch.',
128+
},
129+
},
130+
]
131+
132+
useLlmGateway.mockImplementation(({ onMessage }) => {
133+
onMessageCallback = onMessage
134+
return {
135+
messages: mockMessages,
136+
error: null,
137+
retry: mockRetry,
138+
sendQuestion: mockSendQuestion,
139+
}
140+
})
141+
142+
// Act
143+
render(<AskAiAnswer />)
144+
145+
// Simulate the component receiving an 'agent_end' message to finish loading
146+
act(() => {
147+
onMessageCallback({ type: 'agent_end' })
148+
})
149+
150+
// Assert
151+
expect(
152+
screen.getByLabelText('This answer was helpful')
153+
).toBeInTheDocument()
154+
expect(
155+
screen.getByLabelText('This answer was not helpful')
156+
).toBeInTheDocument()
157+
expect(
158+
screen.getByLabelText('Request a new answer')
159+
).toBeInTheDocument()
160+
})
161+
162+
test('should call retry function when refresh button is clicked', async () => {
163+
// Arrange
164+
const user = userEvent.setup()
165+
let onMessageCallback: (message: any) => void = () => {}
166+
167+
const mockMessages = [
168+
{
169+
type: 'ai_message',
170+
data: { content: 'Here is your answer.' },
171+
},
172+
]
173+
174+
useLlmGateway.mockImplementation(({ onMessage }) => {
175+
onMessageCallback = onMessage
176+
return {
177+
messages: mockMessages,
178+
error: null,
179+
retry: mockRetry,
180+
sendQuestion: mockSendQuestion,
181+
}
182+
})
183+
184+
render(<AskAiAnswer />)
185+
186+
// Simulate finished state
187+
act(() => {
188+
onMessageCallback({ type: 'agent_end' })
189+
})
190+
191+
// Act
192+
const refreshButton = screen.getByLabelText('Request a new answer')
193+
194+
await act(async () => {
195+
await user.click(refreshButton)
196+
})
197+
198+
// Assert
199+
expect(mockRetry).toHaveBeenCalledTimes(1)
200+
})
201+
})
202+
203+
describe('Question Sending', () => {
204+
test('should send question on component mount', () => {
205+
// Arrange
206+
useLlmGateway.mockReturnValue({
207+
messages: [],
208+
error: null,
209+
retry: mockRetry,
210+
sendQuestion: mockSendQuestion,
211+
})
212+
213+
// Act
214+
render(<AskAiAnswer />)
215+
216+
// Assert
217+
expect(mockSendQuestion).toHaveBeenCalledWith(
218+
'What is Elasticsearch?'
219+
)
220+
})
221+
})
222+
})
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
module.exports = {
2+
testEnvironment: 'jsdom',
3+
setupFilesAfterEnv: ['<rootDir>/setupTests.ts'],
4+
transform: {
5+
'^.+\\.(ts|tsx)$': [
6+
'babel-jest',
7+
{
8+
presets: [
9+
['@babel/preset-env', { targets: { node: 'current' } }],
10+
['@babel/preset-react', { runtime: 'automatic' }],
11+
'@babel/preset-typescript',
12+
],
13+
},
14+
],
15+
'^.+\\.(js|jsx)$': [
16+
'babel-jest',
17+
{
18+
presets: [
19+
['@babel/preset-env', { targets: { node: 'current' } }],
20+
['@babel/preset-react', { runtime: 'automatic' }],
21+
],
22+
},
23+
],
24+
},
25+
transformIgnorePatterns: [],
26+
}

0 commit comments

Comments
 (0)