mirror of https://github.com/buster-so/buster.git
Add more unit tests
This commit is contained in:
parent
75b3eb65e7
commit
9212903e9e
|
@ -3,6 +3,7 @@ import { useAppLayout } from './AppLayoutProvider';
|
||||||
import { useRouter, usePathname, useParams } from 'next/navigation';
|
import { useRouter, usePathname, useParams } from 'next/navigation';
|
||||||
import { BusterRoutesWithArgsRoute } from '@/routes/busterRoutes';
|
import { BusterRoutesWithArgsRoute } from '@/routes/busterRoutes';
|
||||||
import { BusterAppRoutes } from '@/routes/busterRoutes/busterAppRoutes';
|
import { BusterAppRoutes } from '@/routes/busterRoutes/busterAppRoutes';
|
||||||
|
import { DashboardSecondaryRecord } from '@/layouts/ChatLayout/FileContainer/FileContainerSecondary/secondaryPanelsConfig/dashboardPanels';
|
||||||
|
|
||||||
// Mock next/navigation
|
// Mock next/navigation
|
||||||
jest.mock('next/navigation', () => ({
|
jest.mock('next/navigation', () => ({
|
||||||
|
@ -12,9 +13,11 @@ jest.mock('next/navigation', () => ({
|
||||||
}));
|
}));
|
||||||
|
|
||||||
describe('useAppLayout - onChangePage', () => {
|
describe('useAppLayout - onChangePage', () => {
|
||||||
// Mock window.location
|
// Mock window.location and history
|
||||||
const mockPush = jest.fn();
|
const mockPush = jest.fn();
|
||||||
|
const mockPushState = jest.fn();
|
||||||
let originalLocation: Location;
|
let originalLocation: Location;
|
||||||
|
let originalHistory: History;
|
||||||
|
|
||||||
beforeEach(() => {
|
beforeEach(() => {
|
||||||
// Setup router mock
|
// Setup router mock
|
||||||
|
@ -22,8 +25,11 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
(usePathname as jest.Mock).mockReturnValue('/');
|
(usePathname as jest.Mock).mockReturnValue('/');
|
||||||
(useParams as jest.Mock).mockReturnValue({});
|
(useParams as jest.Mock).mockReturnValue({});
|
||||||
|
|
||||||
// Setup window.location mock
|
// Store original window.location and history
|
||||||
originalLocation = window.location;
|
originalLocation = window.location;
|
||||||
|
originalHistory = window.history;
|
||||||
|
|
||||||
|
// Mock window.location
|
||||||
delete (window as any).location;
|
delete (window as any).location;
|
||||||
window.location = {
|
window.location = {
|
||||||
...originalLocation,
|
...originalLocation,
|
||||||
|
@ -34,12 +40,17 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
} as Location;
|
} as Location;
|
||||||
|
|
||||||
// Mock window.history
|
// Mock window.history
|
||||||
window.history.pushState = jest.fn();
|
delete (window as any).history;
|
||||||
|
window.history = {
|
||||||
|
...originalHistory,
|
||||||
|
pushState: mockPushState
|
||||||
|
} as History;
|
||||||
});
|
});
|
||||||
|
|
||||||
afterEach(() => {
|
afterEach(() => {
|
||||||
jest.clearAllMocks();
|
jest.clearAllMocks();
|
||||||
window.location = originalLocation;
|
window.location = originalLocation;
|
||||||
|
window.history = originalHistory;
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should not navigate when target URL is identical to current URL', async () => {
|
it('should not navigate when target URL is identical to current URL', async () => {
|
||||||
|
@ -52,7 +63,7 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
await result.current.onChangePage('/dashboard');
|
await result.current.onChangePage('/dashboard');
|
||||||
|
|
||||||
expect(mockPush).not.toHaveBeenCalled();
|
expect(mockPush).not.toHaveBeenCalled();
|
||||||
expect(window.history.pushState).not.toHaveBeenCalled();
|
expect(mockPushState).not.toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle shallow routing when only query params change', async () => {
|
it('should handle shallow routing when only query params change', async () => {
|
||||||
|
@ -65,11 +76,10 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
await result.current.onChangePage('/dashboard?filter=active', { shallow: true });
|
await result.current.onChangePage('/dashboard?filter=active', { shallow: true });
|
||||||
|
|
||||||
expect(mockPush).not.toHaveBeenCalled();
|
expect(mockPush).not.toHaveBeenCalled();
|
||||||
expect(window.history.pushState).toHaveBeenCalledWith(
|
expect(mockPushState).toHaveBeenCalled();
|
||||||
{},
|
const url = new URL(window.location.href);
|
||||||
'',
|
url.searchParams.set('filter', 'active');
|
||||||
expect.stringContaining('/dashboard?filter=active')
|
expect(window.history.pushState).toHaveBeenCalledWith({}, '', url);
|
||||||
);
|
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should navigate to new route when pathname changes', async () => {
|
it('should navigate to new route when pathname changes', async () => {
|
||||||
|
@ -107,7 +117,7 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
|
|
||||||
await result.current.onChangePage('/dashboard');
|
await result.current.onChangePage('/dashboard');
|
||||||
|
|
||||||
expect(window.history.pushState).toHaveBeenCalledWith({}, '', '/dashboard');
|
expect(mockPushState).toHaveBeenCalledWith({}, '', expect.any(String));
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle route with dynamic parameters', async () => {
|
it('should handle route with dynamic parameters', async () => {
|
||||||
|
@ -126,4 +136,196 @@ describe('useAppLayout - onChangePage', () => {
|
||||||
|
|
||||||
expect(mockPush).toHaveBeenCalled();
|
expect(mockPush).toHaveBeenCalled();
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle multiple query parameters', async () => {
|
||||||
|
// Set initial URL
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage('/dashboard?filter=active&sort=date&view=list');
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalledWith('/dashboard?filter=active&sort=date&view=list');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should support shallow navigation with existing query params', async () => {
|
||||||
|
// Set initial URL with existing query params
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard?page=1';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
window.location.search = '?page=1';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage('/dashboard?page=1&filter=active', { shallow: true });
|
||||||
|
|
||||||
|
expect(mockPush).not.toHaveBeenCalled();
|
||||||
|
expect(mockPushState).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle navigating to relative paths', async () => {
|
||||||
|
// Set initial URL
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard/settings';
|
||||||
|
window.location.pathname = '/dashboard/settings';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage('../profile');
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalledWith('../profile');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should not trigger navigation when passing the same URL with different casing', async () => {
|
||||||
|
// Set initial URL
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage('/DashBoard');
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalled();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle adding a remove a param', async () => {
|
||||||
|
// Set initial URL
|
||||||
|
window.location.href =
|
||||||
|
'http://localhost:3000/app/dashboard/123?dashboard_version_number=1&secondary_view=version-history';
|
||||||
|
window.location.pathname = '/app/dashboard/123';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage({
|
||||||
|
route: BusterAppRoutes.APP_DASHBOARD_ID,
|
||||||
|
dashboardId: '123'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalled();
|
||||||
|
expect(mockPush).toHaveBeenCalledWith('/app/dashboards/123');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle adding adding two params', async () => {
|
||||||
|
// Set initial URL
|
||||||
|
window.location.href = 'http://localhost:3000/app/dashboard/123';
|
||||||
|
window.location.pathname = '/app/dashboard/123';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
await result.current.onChangePage({
|
||||||
|
route: BusterAppRoutes.APP_DASHBOARD_ID_VERSION_NUMBER,
|
||||||
|
dashboardId: '123',
|
||||||
|
versionNumber: 2,
|
||||||
|
secondaryView: 'version-history'
|
||||||
|
});
|
||||||
|
|
||||||
|
expect(mockPush).toHaveBeenCalled();
|
||||||
|
expect(mockPush).toHaveBeenCalledWith(
|
||||||
|
'/app/dashboards/123?dashboard_version_number=2&secondary_view=version-history'
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
describe('useAppLayout - onChangeQueryParams', () => {
|
||||||
|
// Mock window.location and history
|
||||||
|
const mockPushState = jest.fn();
|
||||||
|
let originalLocation: Location;
|
||||||
|
let originalHistory: History;
|
||||||
|
|
||||||
|
beforeEach(() => {
|
||||||
|
// Setup router mock
|
||||||
|
(useRouter as jest.Mock).mockReturnValue({ push: jest.fn() });
|
||||||
|
(usePathname as jest.Mock).mockReturnValue('/');
|
||||||
|
(useParams as jest.Mock).mockReturnValue({});
|
||||||
|
|
||||||
|
// Store original window.location and history
|
||||||
|
originalLocation = window.location;
|
||||||
|
originalHistory = window.history;
|
||||||
|
|
||||||
|
// Mock window.location
|
||||||
|
delete (window as any).location;
|
||||||
|
window.location = {
|
||||||
|
...originalLocation,
|
||||||
|
href: 'http://localhost:3000',
|
||||||
|
origin: 'http://localhost:3000',
|
||||||
|
pathname: '/',
|
||||||
|
search: ''
|
||||||
|
} as Location;
|
||||||
|
|
||||||
|
// Mock window.history
|
||||||
|
delete (window as any).history;
|
||||||
|
window.history = {
|
||||||
|
...originalHistory,
|
||||||
|
pushState: mockPushState
|
||||||
|
} as History;
|
||||||
|
});
|
||||||
|
|
||||||
|
afterEach(() => {
|
||||||
|
jest.clearAllMocks();
|
||||||
|
window.location = originalLocation;
|
||||||
|
window.history = originalHistory;
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should add new query parameters while preserving existing ones', () => {
|
||||||
|
// Set initial URL with existing query params
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard?page=1';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
window.location.search = '?page=1';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
result.current.onChangeQueryParams({ filter: 'active' }, true);
|
||||||
|
|
||||||
|
expect(mockPushState).toHaveBeenCalled();
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
url.searchParams.set('filter', 'active');
|
||||||
|
// Should preserve existing params
|
||||||
|
expect(url.searchParams.get('page')).toBe('1');
|
||||||
|
expect(url.searchParams.get('filter')).toBe('active');
|
||||||
|
expect(mockPushState).toHaveBeenCalledWith({}, '', url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should replace all existing query parameters when preserveExisting is false', () => {
|
||||||
|
// Set initial URL with existing query params
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard?page=1&sort=date';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
window.location.search = '?page=1&sort=date';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
result.current.onChangeQueryParams({ filter: 'active' }, false);
|
||||||
|
|
||||||
|
expect(mockPushState).toHaveBeenCalled();
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
// Clear existing params
|
||||||
|
url.search = '';
|
||||||
|
// Add new params
|
||||||
|
url.searchParams.set('filter', 'active');
|
||||||
|
// Should not contain old params
|
||||||
|
expect(url.searchParams.has('page')).toBe(false);
|
||||||
|
expect(url.searchParams.has('sort')).toBe(false);
|
||||||
|
expect(url.searchParams.get('filter')).toBe('active');
|
||||||
|
expect(mockPushState).toHaveBeenCalledWith({}, '', url);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should remove query parameters when value is null', () => {
|
||||||
|
// Set initial URL with existing query params
|
||||||
|
window.location.href = 'http://localhost:3000/dashboard?page=1&filter=active&sort=date';
|
||||||
|
window.location.pathname = '/dashboard';
|
||||||
|
window.location.search = '?page=1&filter=active&sort=date';
|
||||||
|
|
||||||
|
const { result } = renderHook(() => useAppLayout());
|
||||||
|
|
||||||
|
result.current.onChangeQueryParams({ filter: null, sort: null }, true);
|
||||||
|
|
||||||
|
expect(mockPushState).toHaveBeenCalled();
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
// Should remove specified params
|
||||||
|
url.searchParams.delete('filter');
|
||||||
|
url.searchParams.delete('sort');
|
||||||
|
// Should preserve other params
|
||||||
|
expect(url.searchParams.get('page')).toBe('1');
|
||||||
|
expect(url.searchParams.has('filter')).toBe(false);
|
||||||
|
expect(url.searchParams.has('sort')).toBe(false);
|
||||||
|
expect(mockPushState).toHaveBeenCalledWith({}, '', url);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
|
@ -37,6 +37,7 @@ export const useAppLayout = () => {
|
||||||
|
|
||||||
// Handle shallow routing (only updating query params)
|
// Handle shallow routing (only updating query params)
|
||||||
if (options?.shallow && targetPathname === currentPathname) {
|
if (options?.shallow && targetPathname === currentPathname) {
|
||||||
|
console.log('shallow routing', targetPathname, currentPathname);
|
||||||
return new Promise((resolve) => {
|
return new Promise((resolve) => {
|
||||||
const params = getQueryParamsFromPath(targetPath);
|
const params = getQueryParamsFromPath(targetPath);
|
||||||
onChangeQueryParams(params, false);
|
onChangeQueryParams(params, false);
|
||||||
|
@ -76,36 +77,15 @@ export const useAppLayout = () => {
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
const createQueryParams = useMemoizedFn(
|
|
||||||
(params: Record<string, string | null>, preserveExisting: boolean) => {
|
|
||||||
const url = new URL(window.location.href);
|
|
||||||
|
|
||||||
if (!preserveExisting) {
|
|
||||||
// Clear all existing search parameters
|
|
||||||
url.search = '';
|
|
||||||
}
|
|
||||||
|
|
||||||
// Add new parameters
|
|
||||||
Object.entries(params).forEach(([key, value]) => {
|
|
||||||
if (value) {
|
|
||||||
url.searchParams.set(key, value);
|
|
||||||
} else {
|
|
||||||
url.searchParams.delete(key);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
return url;
|
|
||||||
}
|
|
||||||
);
|
|
||||||
|
|
||||||
//TODO: make this typesafe...
|
//TODO: make this typesafe...
|
||||||
const onChangeQueryParams = useMemoizedFn(
|
const onChangeQueryParams = useMemoizedFn(
|
||||||
(params: Record<string, string | null>, preserveExisting: boolean) => {
|
(params: Record<string, string | null>, preserveExisting: boolean) => {
|
||||||
const isRemovingANonExistentParam = Object.keys(params).every(
|
const isRemovingANonExistentParam = Object.entries(params).every(([key, value]) => {
|
||||||
(key) => !window.location.href.includes(key)
|
return !value ? !window.location.href.includes(key) : false;
|
||||||
);
|
});
|
||||||
if (isRemovingANonExistentParam) return; //we don't need to do anything if we're removing a non-existent param
|
if (isRemovingANonExistentParam) return; //we don't need to do anything if we're removing a non-existent param
|
||||||
const url = createQueryParams(params, preserveExisting);
|
const url = createQueryParams(params, preserveExisting);
|
||||||
|
|
||||||
if (url) window.history.pushState({}, '', url); //we used window.history instead of replace for true shallow routing
|
if (url) window.history.pushState({}, '', url); //we used window.history instead of replace for true shallow routing
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
@ -114,11 +94,30 @@ export const useAppLayout = () => {
|
||||||
currentRoute,
|
currentRoute,
|
||||||
onChangePage,
|
onChangePage,
|
||||||
currentParentRoute,
|
currentParentRoute,
|
||||||
onChangeQueryParams,
|
onChangeQueryParams
|
||||||
createQueryParams
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const createQueryParams = (params: Record<string, string | null>, preserveExisting: boolean) => {
|
||||||
|
const url = new URL(window.location.href);
|
||||||
|
|
||||||
|
if (!preserveExisting) {
|
||||||
|
// Clear all existing search parameters
|
||||||
|
url.search = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
// Add new parameters
|
||||||
|
Object.entries(params).forEach(([key, value]) => {
|
||||||
|
if (value) {
|
||||||
|
url.searchParams.set(key, value);
|
||||||
|
} else {
|
||||||
|
url.searchParams.delete(key);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return url;
|
||||||
|
};
|
||||||
|
|
||||||
const getQueryParamsFromPath = (path: string): Record<string, string> => {
|
const getQueryParamsFromPath = (path: string): Record<string, string> => {
|
||||||
const url = new URL(path, window.location.origin);
|
const url = new URL(path, window.location.origin);
|
||||||
const params: Record<string, string> = {};
|
const params: Record<string, string> = {};
|
||||||
|
|
Loading…
Reference in New Issue