diff --git a/web/src/components/__tests__/Menu.test.jsx b/web/src/components/__tests__/Menu.test.jsx
new file mode 100644
index 000000000..208adc997
--- /dev/null
+++ b/web/src/components/__tests__/Menu.test.jsx
@@ -0,0 +1,52 @@
+import { h } from 'preact';
+import Menu, { MenuItem } from '../Menu';
+import { fireEvent, render, screen } from '@testing-library/preact';
+import { useRef } from 'preact/hooks';
+
+describe('Menu', () => {
+ test('renders a dialog', async () => {
+ function Test() {
+ const relativeRef = useRef();
+ return (
+
+ );
+ }
+
+ render();
+ expect(screen.queryByRole('listbox')).toBeInTheDocument();
+ });
+});
+
+describe('MenuItem', () => {
+ test('renders a menu item', async () => {
+ render();
+ expect(screen.queryByRole('option')).toHaveTextContent('Tacos');
+ });
+
+ test('calls onSelect when clicked', async () => {
+ const handleSelect = jest.fn();
+ render();
+ fireEvent.click(screen.queryByRole('option'));
+ expect(handleSelect).toHaveBeenCalledWith('tacos-value', 'Tacos');
+ });
+
+ test('renders and icon when passed', async () => {
+ function Icon() {
+ return ;
+ }
+ render();
+ expect(screen.queryByTestId('icon')).toBeInTheDocument();
+ });
+
+ test('applies different styles when focused', async () => {
+ const { rerender } = render();
+ const classes = Array.from(screen.queryByRole('option').classList);
+ rerender();
+ const focusClasses = Array.from(screen.queryByRole('option').classList);
+
+ expect(focusClasses.length).toBeGreaterThan(classes.length);
+ });
+});