Skip to content

Commit 0614ec7

Browse files
committed
test: migrate dialog tests to RTL
1 parent 6fe83b1 commit 0614ec7

16 files changed

+1223
-2907
lines changed
Lines changed: 200 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,200 @@
1+
import { fireEvent, screen, waitFor } from '@testing-library/react';
2+
import { beforeEach, describe, expect, it, vi } from 'vitest';
3+
4+
import { AddThemeDialog } from '../../src/renderer/components/dialog-add-theme';
5+
import { AppState } from '../../src/renderer/state';
6+
import { LoadedFiddleTheme, defaultLight } from '../../src/themes-defaults';
7+
import { overrideRendererPlatform } from '../../tests/utils';
8+
import { renderClassComponentWithInstanceRef } from '../test-utils/renderClassComponentWithInstanceRef';
9+
10+
class FileMock extends Blob {
11+
public lastModified: number;
12+
public webkitRelativePath: string;
13+
14+
constructor(
15+
private bits: string[],
16+
public name: string,
17+
public path: string,
18+
type: string,
19+
) {
20+
super(bits, { type });
21+
this.lastModified = Date.now();
22+
this.webkitRelativePath = '';
23+
}
24+
25+
async text() {
26+
return this.bits.join('');
27+
}
28+
}
29+
30+
describe('AddThemeDialog component', () => {
31+
let store: AppState;
32+
33+
beforeEach(() => {
34+
overrideRendererPlatform('darwin');
35+
({ state: store } = window.app);
36+
store.isThemeDialogShowing = true;
37+
});
38+
39+
function renderAddThemeDialog() {
40+
return renderClassComponentWithInstanceRef(AddThemeDialog, {
41+
appState: store,
42+
});
43+
}
44+
45+
it('renders the dialog when open', () => {
46+
renderAddThemeDialog();
47+
48+
expect(screen.getByText('Add theme')).toBeInTheDocument();
49+
expect(screen.getByText('Select the Monaco file...')).toBeInTheDocument();
50+
});
51+
52+
it('displays Add button as disabled when no file selected', () => {
53+
renderAddThemeDialog();
54+
55+
const addButton = screen.getByRole('button', { name: /add/i });
56+
expect(addButton).toBeDisabled();
57+
});
58+
59+
it('displays Cancel button that closes dialog', () => {
60+
renderAddThemeDialog();
61+
62+
const cancelButton = screen.getByRole('button', { name: /cancel/i });
63+
expect(cancelButton).toBeInTheDocument();
64+
65+
fireEvent.click(cancelButton);
66+
expect(store.isThemeDialogShowing).toBe(false);
67+
});
68+
69+
describe('createNewThemeFromMonaco()', () => {
70+
it('handles invalid input', async () => {
71+
const { instance } = renderAddThemeDialog();
72+
73+
await expect(
74+
instance.createNewThemeFromMonaco('', {} as LoadedFiddleTheme),
75+
).rejects.toThrow('Filename not found');
76+
77+
expect(window.ElectronFiddle.createThemeFile).toHaveBeenCalledTimes(0);
78+
expect(store.setTheme).toHaveBeenCalledTimes(0);
79+
});
80+
81+
it('handles valid input', async () => {
82+
const { instance } = renderAddThemeDialog();
83+
84+
const themePath = '~/.electron-fiddle/themes/testingLight';
85+
vi.mocked(window.ElectronFiddle.createThemeFile).mockResolvedValue({
86+
file: themePath,
87+
} as LoadedFiddleTheme);
88+
89+
await instance.createNewThemeFromMonaco('testingLight', defaultLight);
90+
91+
expect(window.ElectronFiddle.createThemeFile).toHaveBeenCalledWith(
92+
expect.objectContaining({
93+
name: 'Fiddle (Light)',
94+
common: expect.anything(),
95+
file: 'defaultLight',
96+
}),
97+
'testingLight',
98+
);
99+
expect(store.setTheme).toHaveBeenCalledWith(themePath);
100+
});
101+
});
102+
103+
describe('onSubmit()', () => {
104+
it('does nothing if there is no file currently set', async () => {
105+
const { instance } = renderAddThemeDialog();
106+
107+
instance.createNewThemeFromMonaco = vi.fn();
108+
const onCloseSpy = vi.spyOn(instance, 'onClose');
109+
110+
await instance.onSubmit();
111+
112+
expect(instance.createNewThemeFromMonaco).toHaveBeenCalledTimes(0);
113+
expect(onCloseSpy).toHaveBeenCalledTimes(0);
114+
});
115+
116+
it('loads a theme if a file is currently set', async () => {
117+
const { instance } = renderAddThemeDialog();
118+
119+
const file = new FileMock(
120+
[JSON.stringify(defaultLight.editor)],
121+
'file.json',
122+
'/test/file.json',
123+
'application/json',
124+
);
125+
const spy = vi.spyOn(file, 'text');
126+
127+
instance.setState({ file: file as unknown as File });
128+
129+
instance.createNewThemeFromMonaco = vi.fn();
130+
const onCloseSpy = vi.spyOn(instance, 'onClose');
131+
132+
await instance.onSubmit();
133+
134+
expect(spy).toHaveBeenCalledTimes(1);
135+
expect(instance.createNewThemeFromMonaco).toHaveBeenCalledTimes(1);
136+
expect(onCloseSpy).toHaveBeenCalledTimes(1);
137+
});
138+
139+
it('shows an error dialog for a malformed theme', async () => {
140+
store.showErrorDialog = vi.fn().mockResolvedValueOnce(true);
141+
const { instance } = renderAddThemeDialog();
142+
143+
const file = new FileMock(
144+
[JSON.stringify(defaultLight.editor)],
145+
'file.json',
146+
'/test/file.json',
147+
'application/json',
148+
);
149+
vi.spyOn(file, 'text').mockResolvedValue('{}');
150+
151+
instance.setState({ file: file as unknown as File });
152+
153+
const onCloseSpy = vi.spyOn(instance, 'onClose');
154+
155+
await instance.onSubmit();
156+
157+
expect(store.showErrorDialog).toHaveBeenCalledWith(
158+
expect.stringMatching(/file does not match specifications/i),
159+
);
160+
expect(onCloseSpy).not.toHaveBeenCalled();
161+
});
162+
});
163+
164+
describe('onChangeFile()', () => {
165+
it('handles valid input', async () => {
166+
const { instance } = renderAddThemeDialog();
167+
168+
const files = ['one', 'two'];
169+
await instance.onChangeFile({
170+
target: { files } as unknown as EventTarget,
171+
} as React.FormEvent<HTMLInputElement>);
172+
173+
await waitFor(() => {
174+
expect(instance.state.file).toBe(files[0]);
175+
});
176+
});
177+
178+
it('handles no input', async () => {
179+
const { instance } = renderAddThemeDialog();
180+
181+
await instance.onChangeFile({
182+
target: { files: null } as unknown as EventTarget,
183+
} as React.FormEvent<HTMLInputElement>);
184+
185+
expect(instance.state.file).toBeUndefined();
186+
});
187+
});
188+
189+
describe('onClose()', () => {
190+
it('resets state and hides dialog', () => {
191+
const { instance } = renderAddThemeDialog();
192+
193+
instance.setState({ file: {} as File });
194+
instance.onClose();
195+
196+
expect(instance.state.file).toBeUndefined();
197+
expect(store.isThemeDialogShowing).toBe(false);
198+
});
199+
});
200+
});

0 commit comments

Comments
 (0)