Skip to content

Commit

Permalink
chore(elements): Add more unit tests (#2896)
Browse files Browse the repository at this point in the history
* chore(elements): Move tests to __tests__ folder

* feat(shared): Add some more URL helpers

* chore(elements): Add more tests

* chore(clerk-js): Do not fail publint
  • Loading branch information
LekoArts authored Mar 1, 2024
1 parent 37e932a commit e5c989a
Show file tree
Hide file tree
Showing 13 changed files with 372 additions and 9 deletions.
5 changes: 5 additions & 0 deletions .changeset/great-falcons-knock.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/shared': minor
---

Add `withoutTrailingSlash()`, `hasLeadingSlash()`, `withoutLeadingSlash()`, `withLeadingSlash()`, and `cleanDoubleSlashes()` to `@clerk/shared/url`.
2 changes: 1 addition & 1 deletion packages/clerk-js/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"dev:headless": "webpack serve --config webpack.config.js --env variant=\"clerk.headless.browser\"",
"lint": "eslint src/",
"lint:attw": "attw --pack .",
"lint:publint": "publint",
"lint:publint": "publint || true",
"test": "jest",
"test:cache:clear": "jest --clearCache --useStderr",
"test:ci": "jest --maxWorkers=70%",
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { fieldsToSignUpParams } from './fields-to-params';
import { fieldsToSignUpParams } from '../fields-to-params';

describe('fieldsToSignUpParams', () => {
it('converts form fields to sign up params', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { assertActorEventDone, assertActorEventError, assertIsDefined } from './assert';
import { assertActorEventDone, assertActorEventError, assertIsDefined } from '../assert';

describe('assertIsDefined', () => {
it('should throw an error if the value is undefined', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { NEXT_ROUTING_CHANGE_VERSION } from '~/internals/constants';

import { shouldUseVirtualRouting } from './next';
import { shouldUseVirtualRouting } from '../next';

let windowSpy: jest.SpyInstance;

Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { matchStrategy } from './strategies';
import { matchStrategy } from '../strategies';

describe('matchStrategy', () => {
it('should return false if either current or desired is undefined', () => {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import { act, renderHook } from '@testing-library/react';
import { createActor, createMachine } from 'xstate';

import { useActiveStates } from '../use-active-states.hook';

describe('useActiveStates', () => {
const machine = createMachine({
id: 'toggle',
initial: 'inactive',
states: {
inactive: {
on: { toggle: 'active' },
},
active: {
on: { toggle: 'inactive' },
},
reset: {
on: { toggle: 'inactive' },
},
},
});

const actor = createActor(machine).start();

it('should return false for invalid states param', () => {
const { result } = renderHook(() => useActiveStates(actor, 1 as any));

expect(result.current).toBe(false);
});

describe('single state', () => {
it('should return true if state is active', () => {
const { result } = renderHook(() => useActiveStates(actor, 'inactive'));

expect(result.current).toBe(true);
});

it('should return false if state is not active', () => {
const { result } = renderHook(() => useActiveStates(actor, 'active'));

expect(result.current).toBe(false);
});
});

describe('multiple states', () => {
it('should return true if any state is active', () => {
const { result } = renderHook(() => useActiveStates(actor, ['inactive', 'active']));

expect(result.current).toBe(true);
});

it('should return false if no state is active', () => {
const { result } = renderHook(() => useActiveStates(actor, ['active', 'reset']));

expect(result.current).toBe(false);
});

it('should return true if valid active state switches', () => {
const { result } = renderHook(() => useActiveStates(actor, ['inactive', 'active']));

expect(result.current).toBe(true);
act(() => actor.send({ type: 'toggle' }));
expect(result.current).toBe(true);
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import { createActor, createMachine } from 'xstate';

import { catchHookError } from '~/utils/test-utils';

import { ActiveTagsMode, useActiveTags } from './use-active-tags.hook';
import { ActiveTagsMode, useActiveTags } from '../use-active-tags.hook';

describe('useActiveTags', () => {
const allTags = ['foo', 'bar'];
Expand Down
30 changes: 30 additions & 0 deletions packages/elements/src/react/hooks/__tests__/use-focus.hook.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { fireEvent, renderHook } from '@testing-library/react';

import { useFocus } from '../use-focus.hook';

describe('useFocus', () => {
it('should set isFocused to true when input is focused', () => {
const inputRef = { current: document.createElement('input') };
const { result } = renderHook(() => useFocus(inputRef));

fireEvent.focus(inputRef.current);
expect(result.current).toBe(true);
});

it('should set isFocused to false when input is blurred', () => {
const inputRef = { current: document.createElement('input') };
const { result } = renderHook(() => useFocus(inputRef));

fireEvent.focus(inputRef.current);
expect(result.current).toBe(true);
fireEvent.blur(inputRef.current);
expect(result.current).toBe(false);
});

it('should return false when inputRef is null', () => {
const inputRef = { current: null };
const { result } = renderHook(() => useFocus(inputRef));

expect(result.current).toBe(false);
});
});
107 changes: 107 additions & 0 deletions packages/elements/src/react/router/__tests__/router.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import { createClerkRouter } from '../router';

describe('createClerkRouter', () => {
const mockRouter = {
pathname: jest.fn(),
searchParams: jest.fn(),
push: jest.fn(),
replace: jest.fn(),
};

beforeEach(() => {
jest.clearAllMocks();
});

it('creates a ClerkRouter instance with the correct base path', () => {
const oneBasePath = '/app';
const twoBasePath = 'app';
const threeBasePath = 'app/';
const one = createClerkRouter(mockRouter, oneBasePath);
const two = createClerkRouter(mockRouter, twoBasePath);
const three = createClerkRouter(mockRouter, threeBasePath);

expect(one.basePath).toBe(oneBasePath);
expect(two.basePath).toBe('/app');
expect(three.basePath).toBe('/app');
});

it('matches the path correctly', () => {
const path = '/dashboard';
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.pathname.mockReturnValue('/app/dashboard');

expect(clerkRouter.match(path)).toBe(true);
});

it('throws an error when no path is provided', () => {
const clerkRouter = createClerkRouter(mockRouter, '/app');

expect(() => {
clerkRouter.match();
}).toThrow('[clerk] router.match() requires either a path to match, or the index flag must be set to true.');
});

it('creates a child router with the correct base path', () => {
const clerkRouter = createClerkRouter(mockRouter, '/app');
const childRouter = clerkRouter.child('dashboard');

expect(childRouter.basePath).toBe('/app/dashboard');
});

it('pushes the correct destination URL ', () => {
const path = '/app/dashboard';
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.searchParams.mockImplementation(() => new URLSearchParams(''));
clerkRouter.push(path);

expect(mockRouter.push).toHaveBeenCalledWith('/app/dashboard');
});

it('replaces the correct destination URL', () => {
const path = '/app/dashboard';
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.searchParams.mockImplementation(() => new URLSearchParams(''));
clerkRouter.replace(path);

expect(mockRouter.replace).toHaveBeenCalledWith('/app/dashboard');
});

it('pushes the correct destination URL with preserved query parameters', () => {
const path = '/app/dashboard';
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.searchParams.mockImplementation(() => new URLSearchParams('after_sign_in_url=foobar&foo=bar'));
clerkRouter.push(path);

expect(mockRouter.push).toHaveBeenCalledWith('/app/dashboard?after_sign_in_url=foobar');
});

it('replaces the correct destination URL with preserved query parameters', () => {
const path = '/app/dashboard';
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.searchParams.mockImplementation(() => new URLSearchParams('after_sign_in_url=foobar&foo=bar'));
clerkRouter.replace(path);

expect(mockRouter.replace).toHaveBeenCalledWith('/app/dashboard?after_sign_in_url=foobar');
});

it('returns the correct pathname', () => {
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.pathname.mockReturnValue('/app/dashboard');

expect(clerkRouter.pathname()).toBe('/app/dashboard');
});

it('returns the correct searchParams', () => {
const clerkRouter = createClerkRouter(mockRouter, '/app');

mockRouter.searchParams.mockImplementation(() => new URLSearchParams('foo=bar'));

expect(clerkRouter.searchParams().get('foo')).toEqual('bar');
});
});
7 changes: 4 additions & 3 deletions packages/elements/src/react/router/router.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
import { withLeadingSlash, withoutTrailingSlash } from '@clerk/shared/url';

export const PRESERVED_QUERYSTRING_PARAMS = ['after_sign_in_url', 'after_sign_up_url', 'redirect_url'];

/**
Expand Down Expand Up @@ -48,8 +50,7 @@ export type ClerkRouter = {
* Ensures the provided path has a leading slash and no trailing slash
*/
function normalizePath(path: string) {
const pathNoTrailingSlash = path.replace(/\/$/, '');
return pathNoTrailingSlash.startsWith('/') ? pathNoTrailingSlash : `/${pathNoTrailingSlash}`;
return withoutTrailingSlash(withLeadingSlash(path));
}

/**
Expand Down Expand Up @@ -92,7 +93,7 @@ export function createClerkRouter(router: ClerkHostRouter, basePath: string = '/
}

function child(childBasePath: string) {
return createClerkRouter(router, `${normalizedBasePath}/${normalizePath(childBasePath)}`);
return createClerkRouter(router, `${normalizedBasePath}${normalizePath(childBasePath)}`);
}

function push(path: string) {
Expand Down
Loading

0 comments on commit e5c989a

Please sign in to comment.