Skip to content

Commit 850f824

Browse files
committed
chore: rewrite LanguageSelector using radix/react-select
1 parent 8ac23d4 commit 850f824

File tree

2 files changed

+177
-194
lines changed

2 files changed

+177
-194
lines changed
Lines changed: 77 additions & 66 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,10 @@
11
import React from 'react';
22
import { useLocation } from '@reach/router';
3-
import { render, screen, fireEvent } from '@testing-library/react';
3+
import { render, screen, fireEvent, waitFor } from '@testing-library/react';
44
import '@testing-library/jest-dom/extend-expect';
55
import { LanguageSelector } from './LanguageSelector';
66
import { useLayoutContext } from 'src/contexts/layout-context';
7+
import { navigate } from '../Link';
78

89
jest.mock('src/contexts/layout-context', () => ({
910
useLayoutContext: jest.fn(),
@@ -19,6 +20,10 @@ jest.mock('@ably/ui/core/Badge', () => ({
1920
default: ({ children }: { children: React.ReactNode }) => <div>{children}</div>,
2021
}));
2122

23+
jest.mock('../Link', () => ({
24+
navigate: jest.fn(),
25+
}));
26+
2227
const mockUseLayoutContext = useLayoutContext as jest.Mock;
2328

2429
jest.mock('@reach/router', () => ({
@@ -28,19 +33,20 @@ jest.mock('@reach/router', () => ({
2833

2934
const mockUseLocation = useLocation as jest.Mock;
3035

31-
const mockLanguageData = {
32-
pubsub: {
33-
javascript: 1.0,
34-
python: 1.0,
35-
ruby: 1.0,
36+
jest.mock('src/data/languages', () => ({
37+
languageData: {
38+
pubsub: {
39+
javascript: '1.0',
40+
python: '1.0',
41+
ruby: '1.0',
42+
},
3643
},
37-
};
38-
39-
const mockLanguageInfo = {
40-
javascript: { label: 'JavaScript' },
41-
python: { label: 'Python' },
42-
ruby: { label: 'Ruby' },
43-
};
44+
languageInfo: {
45+
javascript: { label: 'JavaScript' },
46+
python: { label: 'Python' },
47+
ruby: { label: 'Ruby' },
48+
},
49+
}));
4450

4551
describe('LanguageSelector', () => {
4652
beforeEach(() => {
@@ -55,26 +61,10 @@ describe('LanguageSelector', () => {
5561
activePage: {
5662
tree: [0],
5763
languages: ['javascript', 'python'],
64+
language: 'javascript',
5865
},
5966
products: [['pubsub']],
6067
});
61-
62-
Object.defineProperty(window, 'location', {
63-
writable: true,
64-
value: { search: '' },
65-
});
66-
67-
jest.spyOn(window, 'URLSearchParams').mockImplementation(
68-
() =>
69-
({
70-
get: jest.fn().mockReturnValue(null),
71-
}) as unknown as URLSearchParams,
72-
);
73-
74-
jest.mock('src/data/languages', () => ({
75-
languageData: mockLanguageData,
76-
languageInfo: mockLanguageInfo,
77-
}));
7868
});
7969

8070
afterEach(() => {
@@ -85,44 +75,60 @@ describe('LanguageSelector', () => {
8575
render(<LanguageSelector />);
8676
expect(screen.getByText('icon-gui-chevron-down-micro')).toBeInTheDocument();
8777
expect(screen.getByText('icon-tech-javascript')).toBeInTheDocument();
78+
expect(screen.getByText('JavaScript')).toBeInTheDocument();
8879
});
8980

90-
it('opens the dropdown menu on click', () => {
81+
it('opens the dropdown menu on click', async () => {
9182
render(<LanguageSelector />);
92-
fireEvent.click(screen.getByText('icon-gui-chevron-down-micro'));
93-
expect(screen.getByText('Code Language')).toBeInTheDocument();
83+
const trigger = screen.getByRole('combobox', { name: /select code language/i });
84+
fireEvent.click(trigger);
85+
86+
await waitFor(() => {
87+
expect(screen.getByText('Code Language')).toBeInTheDocument();
88+
});
9489
});
9590

96-
it('renders language options', () => {
91+
it('renders language options', async () => {
9792
render(<LanguageSelector />);
98-
fireEvent.click(screen.getByText('icon-gui-chevron-down-micro'));
99-
expect(screen.getByText('JavaScript')).toBeInTheDocument();
100-
expect(screen.getByText('Python')).toBeInTheDocument();
93+
const trigger = screen.getByRole('combobox', { name: /select code language/i });
94+
fireEvent.click(trigger);
95+
96+
await waitFor(() => {
97+
const items = screen.getAllByText('JavaScript');
98+
// One in trigger, one in dropdown
99+
expect(items.length).toBeGreaterThanOrEqual(1);
100+
expect(screen.getByText('Python')).toBeInTheDocument();
101+
});
101102
});
102103

103-
it('closes the dropdown menu on outside click', () => {
104+
it('closes the dropdown menu on escape key', async () => {
104105
render(<LanguageSelector />);
105-
fireEvent.click(screen.getByText('icon-gui-chevron-down-micro'));
106-
fireEvent.mouseDown(document);
107-
expect(screen.queryByText('Code Language')).not.toBeInTheDocument();
106+
const trigger = screen.getByRole('combobox', { name: /select code language/i });
107+
fireEvent.click(trigger);
108+
109+
await waitFor(() => {
110+
expect(screen.getByText('Code Language')).toBeInTheDocument();
111+
});
112+
113+
fireEvent.keyDown(trigger, { key: 'Escape', code: 'Escape' });
114+
115+
await waitFor(() => {
116+
expect(screen.queryByText('Code Language')).not.toBeInTheDocument();
117+
});
108118
});
109119

110-
it('filters options based on activePage.languages', () => {
120+
it('filters options based on activePage.languages', async () => {
111121
render(<LanguageSelector />);
112-
fireEvent.click(screen.getByText('icon-gui-chevron-down-micro'));
113-
expect(screen.getByText('JavaScript')).toBeInTheDocument();
114-
expect(screen.getByText('Python')).toBeInTheDocument();
115-
expect(screen.queryByText('Ruby')).not.toBeInTheDocument();
116-
});
122+
const trigger = screen.getByRole('combobox', { name: /select code language/i });
123+
fireEvent.click(trigger);
117124

118-
it('sets the default option to Python when ?lang=python is in the URL', () => {
119-
mockUseLocation.mockReturnValue({
120-
pathname: '/some-path',
121-
search: '?lang=python',
122-
hash: '',
123-
state: null,
125+
await waitFor(() => {
126+
expect(screen.getByText('Python')).toBeInTheDocument();
127+
expect(screen.queryByText('Ruby')).not.toBeInTheDocument();
124128
});
129+
});
125130

131+
it('sets the default option to Python when language is python', () => {
126132
mockUseLayoutContext.mockReturnValue({
127133
activePage: {
128134
tree: [0],
@@ -132,20 +138,25 @@ describe('LanguageSelector', () => {
132138
products: [['pubsub']],
133139
});
134140

135-
jest.spyOn(window, 'URLSearchParams').mockImplementation(
136-
() =>
137-
({
138-
get: jest.fn((key) => {
139-
if (key === 'lang') {
140-
return 'python';
141-
}
142-
return null;
143-
}),
144-
}) as unknown as URLSearchParams,
145-
);
146-
147141
render(<LanguageSelector />);
148142
expect(screen.getByText('icon-tech-python')).toBeInTheDocument();
149-
expect(screen.queryByText('icon-tech-javascript')).not.toBeInTheDocument();
143+
expect(screen.getByText('Python')).toBeInTheDocument();
144+
});
145+
146+
it('navigates when a language option is selected', async () => {
147+
render(<LanguageSelector />);
148+
const trigger = screen.getByRole('combobox', { name: /select code language/i });
149+
fireEvent.click(trigger);
150+
151+
await waitFor(() => {
152+
expect(screen.getByText('Python')).toBeInTheDocument();
153+
});
154+
155+
const pythonOption = screen.getByRole('option', { name: /python/i });
156+
fireEvent.click(pythonOption);
157+
158+
await waitFor(() => {
159+
expect(navigate).toHaveBeenCalledWith('/some-path?lang=python');
160+
});
150161
});
151162
});

0 commit comments

Comments
 (0)