Skip to content

Commit 52a3ef6

Browse files
committed
Add images unit tests
1 parent ac8981a commit 52a3ef6

File tree

4 files changed

+471
-6
lines changed

4 files changed

+471
-6
lines changed
Lines changed: 151 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,151 @@
1+
import {render, waitFor, screen, within, fireEvent} from '@testing-library/react'
2+
import userEvent from '@testing-library/user-event'
3+
import {I18nextProvider} from 'react-i18next'
4+
import {QueryClient, QueryClientProvider} from 'react-query'
5+
import {Provider} from 'react-redux'
6+
import {BrowserRouter} from 'react-router-dom'
7+
import i18n from '../../../../i18n'
8+
import {BuildImage, ListCustomImages, DescribeCustomImage} from '../../../../model'
9+
import CustomImages from '../CustomImages'
10+
import {ImageBuildStatus, ImageInfoSummary} from '../../../../types/images'
11+
import {Ec2AmiState} from '../../../../types/images'
12+
import {CloudFormationStackStatus} from "../../../../types/base"
13+
import {createStore} from 'redux'
14+
import {setState, getState} from '../../../../store'
15+
import {act} from "react-dom/test-utils";
16+
import {Store} from "@reduxjs/toolkit";
17+
import {mock} from "jest-mock-extended";
18+
19+
const queryClient = new QueryClient()
20+
const mockImages: ImageInfoSummary[] = [
21+
{
22+
imageId: 'test-image',
23+
imageBuildStatus: ImageBuildStatus.BuildComplete,
24+
region: 'us-east-1',
25+
version: '3.12.0',
26+
ec2AmiInfo: {
27+
tags: [],
28+
amiName: 'test',
29+
architecture: 'x86_64',
30+
description: "test ami",
31+
amiId: 'ami-12345',
32+
state: Ec2AmiState.Available,
33+
},
34+
cloudformationStackArn: "example-arn",
35+
cloudformationStackStatus: CloudFormationStackStatus.CreateComplete,
36+
},
37+
]
38+
39+
const mockInitialState = {
40+
customImages: {
41+
list: mockImages
42+
},
43+
app: {
44+
version: {
45+
full: ['3.12.0', '3.11.0', '3.10.0']
46+
},
47+
customImages: {
48+
selectedImageStatus: 'AVAILABLE',
49+
imageBuild: {
50+
ImageId: 'test-build-image',
51+
dialog: true,
52+
imageId: '',
53+
config: ''
54+
}
55+
}
56+
}
57+
}
58+
59+
const mockStore = mock<Store>()
60+
const wrapper = (props: any) => (
61+
<Provider store={mockStore}>{props.children}</Provider>
62+
)
63+
64+
65+
const MockProviders = ({children}: {children: React.ReactNode}) => (
66+
<QueryClientProvider client={queryClient}>
67+
<I18nextProvider i18n={i18n}>
68+
<Provider store={mockStore}>
69+
<BrowserRouter>{children}</BrowserRouter>
70+
</Provider>
71+
</I18nextProvider>
72+
</QueryClientProvider>
73+
)
74+
75+
jest.mock('../../../../model', () => ({
76+
ListCustomImages: jest.fn(),
77+
DescribeCustomImage: jest.fn(),
78+
BuildImage: jest.fn(),
79+
}))
80+
81+
describe('CustomImages', () => {
82+
beforeEach(() => {
83+
jest.clearAllMocks()
84+
mockStore.getState.mockReturnValue(mockInitialState)
85+
;(ListCustomImages as jest.Mock).mockImplementation(() => {
86+
mockStore.dispatch({ type: 'SET_IMAGES', payload: mockImages })
87+
return Promise.resolve(mockImages)
88+
})
89+
;(DescribeCustomImage as jest.Mock).mockResolvedValue(mockImages[0])
90+
})
91+
92+
describe('CustomImagesList', () => {
93+
it('should render the images list', async () => {
94+
const {container} = render(
95+
<MockProviders>
96+
<CustomImages />
97+
</MockProviders>,
98+
)
99+
100+
await waitFor(() => {
101+
expect(ListCustomImages).toHaveBeenCalled()
102+
})
103+
104+
105+
await waitFor(() => {
106+
const tableElement = container.querySelector('table')
107+
expect(tableElement).toBeTruthy()
108+
109+
const cellContent = container.textContent
110+
expect(cellContent).toContain('test-image')
111+
expect(cellContent).toContain('ami-12345')
112+
expect(cellContent).toContain('us-east-1')
113+
expect(cellContent).toContain('3.12.0')
114+
})
115+
})
116+
117+
it('should handle image selection', async () => {
118+
const {container} = render(
119+
<MockProviders>
120+
<CustomImages />
121+
</MockProviders>,
122+
)
123+
124+
await waitFor(() => {
125+
expect(container.textContent).toContain('test-image')
126+
})
127+
128+
const radio = container.querySelector('input[type="radio"]')
129+
if (radio) {
130+
await userEvent.click(radio)
131+
expect(DescribeCustomImage).toHaveBeenCalledWith('test-image')
132+
}
133+
})
134+
135+
it('should handle status filter changes', async () => {
136+
render(
137+
<MockProviders>
138+
<CustomImages />
139+
</MockProviders>,
140+
)
141+
142+
const statusSelect = screen.getByRole('button', {name: /available/i})
143+
await userEvent.click(statusSelect)
144+
145+
const pendingOption = await screen.findByText('Pending')
146+
await userEvent.click(pendingOption)
147+
148+
expect(ListCustomImages).toHaveBeenCalledWith(Ec2AmiState.Pending)
149+
})
150+
})
151+
})
Lines changed: 155 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,155 @@
1+
import React from 'react';
2+
import {render, screen, fireEvent, waitFor, within} from '@testing-library/react';
3+
import userEvent from '@testing-library/user-event';
4+
import { I18nextProvider } from 'react-i18next';
5+
import i18n from '../../../../i18n';
6+
import ImageBuildDialog from '../ImageBuildDialog';
7+
import { BuildImage } from '../../../../model';
8+
import {act} from "react-dom/test-utils";
9+
10+
jest.mock('../../../../model', () => ({
11+
BuildImage: jest.fn(),
12+
}));
13+
14+
15+
const mockUseState = jest.fn()
16+
const mockSetState = jest.fn()
17+
const mockGetState = jest.fn()
18+
const mockClearState = jest.fn()
19+
20+
21+
jest.mock('../../../../store', () => ({
22+
...(jest.requireActual('../../../../store') as any),
23+
setState: (...args: unknown[]) => mockSetState(...args),
24+
useState: (...args: unknown[]) => mockUseState(...args),
25+
getState: (...args: unknown[]) => mockGetState(...args),
26+
clearState: (...args: unknown[]) => mockClearState(...args),
27+
28+
}))
29+
30+
31+
jest.mock('../../../../components/FileChooser', () => ({
32+
__esModule: true,
33+
default: () => <button>Upload File</button>,
34+
}));
35+
36+
describe('ImageBuildDialog', () => {
37+
beforeEach(() => {
38+
mockUseState.mockReturnValue(['3.12.0', '3.6.0', '3.7.0'])
39+
40+
});
41+
42+
it('renders correctly when open', () => {
43+
render(
44+
<I18nextProvider i18n={i18n}>
45+
<ImageBuildDialog />
46+
</I18nextProvider>
47+
);
48+
49+
expect(screen.getByText('Build image')).toBeTruthy();
50+
expect(screen.getByPlaceholderText('Enter image AMI ID')).toBeTruthy();
51+
expect(screen.getByText('Upload File')).toBeTruthy();
52+
});
53+
54+
it('handles image ID input', async () => {
55+
render(
56+
<I18nextProvider i18n={i18n}>
57+
<ImageBuildDialog />
58+
</I18nextProvider>
59+
);
60+
61+
const input = screen.getByPlaceholderText('Enter image AMI ID');
62+
63+
await act(async () => {
64+
fireEvent.change(input, {
65+
target: { value: 'test-image-id' },
66+
detail: { value: 'test-image-id' },
67+
bubbles: true,
68+
});
69+
});
70+
71+
await waitFor(() => {
72+
expect(mockSetState).toHaveBeenCalledWith(
73+
['app', 'customImages', 'imageBuild', 'imageId'],
74+
'test-image-id'
75+
);
76+
});
77+
});
78+
79+
it('handles version selection', async () => {
80+
render(
81+
<I18nextProvider i18n={i18n}>
82+
<ImageBuildDialog />
83+
</I18nextProvider>
84+
);
85+
86+
const versionSelect = screen.getByLabelText('Version');
87+
await userEvent.click(versionSelect);
88+
89+
const version = await screen.findByText('3.7.0');
90+
await userEvent.click(version);
91+
92+
expect(screen.getByText('3.7.0')).toBeTruthy();
93+
94+
await userEvent.click(versionSelect);
95+
96+
const version2 = await screen.findByText('3.12.0');
97+
await userEvent.click(version2);
98+
99+
expect(screen.getByText('3.12.0')).toBeTruthy();
100+
101+
});
102+
103+
it('handles build button click', async () => {
104+
(mockUseState as jest.Mock).mockImplementation((path) => {
105+
if (path.join('.') === 'app.version.full') return ['3.12.0', '3.6.0', '3.7.0'];
106+
if (path.join('.') === 'app.customImages.imageBuild.imageId') return 'test-image-id'
107+
if (path.join('.') === 'app.customImages.imageBuild.config') return 'test-config';
108+
109+
return null;
110+
});
111+
112+
mockGetState.mockImplementation((path: string[]) => {
113+
if (Array.isArray(path)) {
114+
switch (path.join('.')) {
115+
case 'app.customImages.imageBuild.imageId':
116+
return 'test-image-id';
117+
case 'app.customImages.imageBuild.config':
118+
return 'test-config';
119+
}
120+
}
121+
return undefined;
122+
});
123+
124+
render(
125+
<I18nextProvider i18n={i18n}>
126+
<ImageBuildDialog />
127+
</I18nextProvider>
128+
);
129+
130+
const buildButton = screen.getByText('Build image');
131+
expect(buildButton).toBeTruthy();
132+
133+
await act(async () => {
134+
await userEvent.click(buildButton);
135+
});
136+
137+
await waitFor(() => {
138+
expect(BuildImage).toHaveBeenCalledWith('test-image-id', 'test-config', '3.12.0');
139+
});
140+
});
141+
142+
it('closes the dialog', async () => {
143+
render(
144+
<I18nextProvider i18n={i18n}>
145+
<ImageBuildDialog />
146+
</I18nextProvider>
147+
);
148+
149+
const cancelButton = screen.getByText('Cancel');
150+
await userEvent.click(cancelButton);
151+
152+
expect(mockSetState).toHaveBeenCalledWith(['app', 'customImages', 'imageBuild', 'dialog'], false);
153+
expect(mockClearState).toHaveBeenCalledWith(['app', 'customImages', 'imageBuild', 'errors']);
154+
});
155+
});

0 commit comments

Comments
 (0)