Skip to content

Commit

Permalink
📱fix: set initial nav visibility for small screens (danny-avila#3208)
Browse files Browse the repository at this point in the history
* fix: hide nav on small screens by default

* test: add spec for Nav component
  • Loading branch information
arthurian authored Jun 27, 2024
1 parent 81292bb commit b8f2bee
Show file tree
Hide file tree
Showing 4 changed files with 136 additions and 0 deletions.
102 changes: 102 additions & 0 deletions client/src/components/Nav/Nav.spec.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
import 'test/resizeObserver.mock';
import 'test/matchMedia.mock';
import 'test/localStorage.mock';

import React from 'react';
import { BrowserRouter } from 'react-router-dom';
import { RecoilRoot } from 'recoil';
import { QueryClient, QueryClientProvider } from '@tanstack/react-query';
import { render } from '@testing-library/react';
import '@testing-library/jest-dom/extend-expect';

import { AuthContextProvider } from '~/hooks/AuthContext';
import { SearchContext } from '~/Providers';
import Nav from './Nav';

const renderNav = ({ search, navVisible, setNavVisible }) => {
const queryClient = new QueryClient({
defaultOptions: {
queries: {
retry: false,
},
},
});

return render(
<RecoilRoot>
<BrowserRouter>
<QueryClientProvider client={queryClient}>
<AuthContextProvider>
<SearchContext.Provider value={search}>
<Nav navVisible={navVisible} setNavVisible={setNavVisible} />
</SearchContext.Provider>
</AuthContextProvider>
</QueryClientProvider>
</BrowserRouter>
</RecoilRoot>,
);
};

const mockMatchMedia = (mediaQueryList?: string[]) => {
mediaQueryList = mediaQueryList || [];

Object.defineProperty(window, 'matchMedia', {
writable: true,
value: jest.fn().mockImplementation((query) => ({
matches: mediaQueryList.includes(query),
media: query,
onchange: null,
addEventListener: jest.fn(),
removeEventListener: jest.fn(),
dispatchEvent: jest.fn(),
})),
});
};

describe('Nav', () => {
beforeEach(() => {
mockMatchMedia();
});

it('renders visible', () => {
const { getByTestId } = renderNav({
search: { data: [], pageNumber: 1 },
navVisible: true,
setNavVisible: jest.fn(),
});

expect(getByTestId('nav')).toBeVisible();
});

it('renders hidden', async () => {
const { getByTestId } = renderNav({
search: { data: [], pageNumber: 1 },
navVisible: false,
setNavVisible: jest.fn(),
});

expect(getByTestId('nav')).not.toBeVisible();
});

it('renders hidden when small screen is detected', async () => {
mockMatchMedia(['(max-width: 768px)']);

const navVisible = true;
const mockSetNavVisible = jest.fn();

const { getByTestId } = renderNav({
search: { data: [], pageNumber: 1 },
navVisible: navVisible,
setNavVisible: mockSetNavVisible,
});

// nav is initially visible
expect(getByTestId('nav')).toBeVisible();

// when small screen is detected, the nav is hidden
expect(mockSetNavVisible.mock.calls).toHaveLength(1);
const updatedNavVisible = mockSetNavVisible.mock.calls[0][0](navVisible);
expect(updatedNavVisible).not.toEqual(navVisible);
expect(updatedNavVisible).toBeFalsy();
});
});
5 changes: 5 additions & 0 deletions client/src/components/Nav/Nav.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,10 @@ const Nav = ({ navVisible, setNavVisible }) => {

useEffect(() => {
if (isSmallScreen) {
const savedNavVisible = localStorage.getItem('navVisible');
if (savedNavVisible === null) {
toggleNavVisible();
}
setNavWidth('320px');
} else {
setNavWidth('260px');
Expand Down Expand Up @@ -102,6 +106,7 @@ const Nav = ({ navVisible, setNavVisible }) => {
<TooltipProvider delayDuration={250}>
<Tooltip>
<div
data-testid="nav"
className={
'nav active max-w-[320px] flex-shrink-0 overflow-x-hidden bg-gray-50 dark:bg-gray-850 md:max-w-[260px]'
}
Expand Down
21 changes: 21 additions & 0 deletions client/test/localStorage.mock
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
let store = {};
Object.defineProperty(window, 'localStorage', {
writable: true,
value: {
getItem: jest.fn().mockImplementation((key) => {
if(key in store) {
return store[key];
}
return null;
}),
setItem: jest.fn().mockImplementation((key, value) => {
store[key] = value.toString();
}),
clear: jest.fn().mockImplementation(() => {
store = {};
}),
removeItem: jest.fn().mockImplementation(() => {
delete store[key];
}),
},
});
8 changes: 8 additions & 0 deletions client/test/resizeObserver.mock
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
Object.defineProperty(window, 'ResizeObserver', {
writable: true,
value: jest.fn().mockImplementation(() => ({
disconnect: jest.fn(),
observe: jest.fn(),
unobserve: jest.fn(),
}))
});

0 comments on commit b8f2bee

Please sign in to comment.