Skip to content

Commit

Permalink
feat(clerk-js,types,nextjs,remix): Terse path parameters (#1957)
Browse files Browse the repository at this point in the history
* feat(clerk-js,types,nextjs,remix): Terse paths parameters

* chore(repo): Warning instead of erroring for no unused variables rule

* chore(repo): Remove changes from NextJS playground

* feat(clerk-js,clerk-react,types): Terse params for OrganizationSwitcher and UserButton

* fix(clerk-js): Use only userProfileUrl prop to check what userProfileMode should be used

* test(clerk-js): Added tests for normalizeRoutingOptions utility function

* chore(repo): Added Changeset

* feat(clerk-js): Omit routing prop from Modal components

* chore(types): Fix typo

* fix(clerk-js): Check all falsy values instead of just undefined

* test(clerk-js): Fix tests for normalizeRoutingOptions

* feat(types): Added WithoutRouting type helper

* fix(remix): Revert SignIn and SIgnUp components

* chore(clerk-js): Remove uneeded import

* fix(remix): Revert changes

* fix(remix,nextjs,types): Components with routing

* test(clerk-react): Added types tests for SignIn component

* test(clerk-react): Added types tests for SignUp component

* test(clerk-react): Added types tests for UserButton component

* test(clerk-react): Added types tests for OrganizationSwitcher component

* test(clerk-react): Added type tests for UserProfile and OrganizationProfile components

* feat(clerk-js): Apply changes to ui.retheme

* refactor(clerk-js): Make normalizeRoutingOptions more verbose

* chore(clerk-js): Remove unedeed type

* refactor(clerk-js,types): Add types for modal components

* refactor(nextjs,remix): Refactor SignIn/SignUp components for Next and Remix

* tests(repo): Add integration tests

* chore(repo): Fix formatting

* test(repo): Update navigation integration tests

* test(repo): Update navigation integration tests

* test(repo): Add more test cases for navigation integration tests
  • Loading branch information
octoper authored and desiprisg committed Nov 23, 2023
1 parent 25f4cdd commit 101ba6b
Show file tree
Hide file tree
Showing 41 changed files with 836 additions and 339 deletions.
11 changes: 11 additions & 0 deletions .changeset/dry-feet-join.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
---
'@clerk/clerk-js': major
'@clerk/nextjs': minor
'@clerk/clerk-react': minor
'@clerk/remix': minor
'@clerk/types': patch
---

- By default, all the components with routing will have the `routing` prop assigned as `'path'` by default when the `path` prop is filled.
- The `<UserButton />` component will set the default value of the `userProfileMode` prop to `'navigation'` if the `userProfileUrl` prop is provided.
- The `<OrganizationSwitcher />` component will have the `organizationProfileMode` and `createOrganizationMode` props assigned with `'navigation'` by default if the `organizationProfileUrl` and `createOrganizationUrl` props are filled accordingly.
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { UserProfile } from '@clerk/nextjs';

export default function Page() {
return (
<div>
<UserProfile path={'/user'} />
</div>
);
}
2 changes: 1 addition & 1 deletion integration/templates/next-app-router/src/middleware.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { authMiddleware } from '@clerk/nextjs/server';

export default authMiddleware({
publicRoutes: ['/'],
publicRoutes: ['/', '/hash/sign-in', '/hash/sign-up'],
});

export const config = {
Expand Down
5 changes: 5 additions & 0 deletions integration/templates/react-vite/src/main.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import App from './App.tsx';
import Protected from './protected';
import SignIn from './sign-in';
import SignUp from './sign-up';
import UserProfile from './user';

const Root = () => {
const navigate = useNavigate();
Expand Down Expand Up @@ -39,6 +40,10 @@ const router = createBrowserRouter([
path: '/sign-up/*',
element: <SignUp />,
},
{
path: '/user/*',
element: <UserProfile />,
},
{
path: '/protected',
element: <Protected />,
Expand Down
1 change: 0 additions & 1 deletion integration/templates/react-vite/src/sign-in/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ export default function Page() {
return (
<div>
<SignIn
routing={'path'}
path={'/sign-in'}
signUpUrl={'/sign-up'}
/>
Expand Down
1 change: 0 additions & 1 deletion integration/templates/react-vite/src/sign-up/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ export default function Page() {
<div>
<SignUp
path={'/sign-up'}
routing={'path'}
signInUrl={'/sign-in'}
/>
</div>
Expand Down
9 changes: 9 additions & 0 deletions integration/templates/react-vite/src/user/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
import { UserProfile } from '@clerk/clerk-react';

export default function Page() {
return (
<div>
<UserProfile path={'/user'} />
</div>
);
}
2 changes: 2 additions & 0 deletions integration/testUtils/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { createEmailService } from './emailService';
import type { EnchancedPage, TestArgs } from './signInPageObject';
import { createSignInComponentPageObject } from './signInPageObject';
import { createSignUpComponentPageObject } from './signUpPageObject';
import { createUserProfileComponentPageObject } from './userProfilePageObject';
import type { FakeUser } from './usersService';
import { createUserService } from './usersService';

Expand Down Expand Up @@ -64,6 +65,7 @@ export const createTestUtils = <
const pageObjects = {
signUp: createSignUpComponentPageObject(testArgs),
signIn: createSignInComponentPageObject(testArgs),
userProfile: createUserProfileComponentPageObject(testArgs),
expect: createExpectPageObject(testArgs),
};

Expand Down
20 changes: 20 additions & 0 deletions integration/testUtils/userProfilePageObject.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { Browser, BrowserContext } from '@playwright/test';

import type { createAppPageObject } from './appPageObject';

export type EnchancedPage = ReturnType<typeof createAppPageObject>;
export type TestArgs = { page: EnchancedPage; context: BrowserContext; browser: Browser };

export const createUserProfileComponentPageObject = (testArgs: TestArgs) => {
const { page } = testArgs;
const self = {
goTo: async (opts?: { searchParams: URLSearchParams }) => {
await page.goToRelative('/user', opts);
return self.waitForMounted();
},
waitForMounted: () => {
return page.waitForSelector('.cl-userProfile-root', { state: 'attached' });
},
};
return self;
};
200 changes: 200 additions & 0 deletions integration/tests/navigation.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,200 @@
import { expect, test } from '@playwright/test';

import type { Application } from '../models/application';
import { appConfigs } from '../presets';
import type { FakeUser } from '../testUtils';
import { createTestUtils } from '../testUtils';

test.describe('navigation modes @generic', () => {
test.describe.configure({ mode: 'serial' });
let app: Application;
let fakeUser: FakeUser;

test.beforeAll(async () => {
app = await appConfigs.next.appRouter
.clone()
.addFile(
'src/app/provider.tsx',
() => `'use client'
import { ClerkProvider } from "@clerk/nextjs"
export function Provider({ children }: { children: any }) {
return (
<ClerkProvider>
{children}
</ClerkProvider>
)
}`,
)
.addFile(
'src/app/layout.tsx',
() => `import './globals.css';
import { Inter } from 'next/font/google';
import { Provider } from './provider';
const inter = Inter({ subsets: ['latin'] });
export const metadata = {
title: 'Create Next App',
description: 'Generated by create next app',
};
export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<Provider>
<html lang='en'>
<body className={inter.className}>{children}</body>
</html>
</Provider>
);
}`,
)
.addFile(
'src/app/hash/user/[[...catchall]]/page.tsx',
() => `
import { UserProfile, UserButton } from '@clerk/nextjs';
export default function Page() {
return (
<div>
<UserButton />
<UserProfile routing="hash" />
</div>
);
}`,
)
.addFile(
'src/app/hash/sign-in/[[...catchall]]/page.tsx',
() => `
import { SignIn } from '@clerk/nextjs';
export default function Page() {
return (
<SignIn routing="hash" />
);
}`,
)
.commit();
await app.setup();
await app.withEnv(appConfigs.envs.withEmailCodes);
await app.build();

const m = createTestUtils({ app });
fakeUser = m.services.users.createFakeUser();
await m.services.users.createBapiUser(fakeUser);

await app.serve();
});

test.afterAll(async () => {
await fakeUser.deleteIfExists();
await app.teardown();
});

test('user profile with path routing', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.waitForMounted();
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
await u.po.expect.toBeSignedIn();

await u.po.userProfile.goTo();
await u.po.userProfile.waitForMounted();

await u.page.getByText(/Set username/i).click();

await u.page.waitForURL(`${app.serverUrl}/user/username`);

await u.page.getByText(/Cancel/i).click();

await u.page.waitForURL(`${app.serverUrl}/user`);

await u.page.getByText(/Add an email address/i).click();

await u.page.waitForURL(`${app.serverUrl}/user/email-address`);

await u.page.getByText(/Cancel/i).click();

await u.page.waitForURL(`${app.serverUrl}/user`);
});

test('user profile with hash routing', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.waitForMounted();
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
await u.po.expect.toBeSignedIn();

await u.page.goToRelative('/hash/user');
await u.po.userProfile.waitForMounted();

await u.page.getByText(/Set username/i).click();

expect(u.page.url()).toBe(`${app.serverUrl}/hash/user#/username`);

await u.page.getByText(/Cancel/i).click();

expect(u.page.url()).toBe(`${app.serverUrl}/hash/user#`);

await u.page.getByText(/Add an email address/i).click();

expect(u.page.url()).toBe(`${app.serverUrl}/hash/user#/email-address`);

await u.page.getByText(/Cancel/i).click();

expect(u.page.url()).toBe(`${app.serverUrl}/hash/user#`);
});

test('sign in with path routing', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.waitForMounted();

await u.po.signIn.setIdentifier(fakeUser.email);
await u.po.signIn.continue();
await u.page.waitForURL(`${app.serverUrl}/sign-in/factor-one`);

await u.po.signIn.setPassword(fakeUser.password);
await u.po.signIn.continue();

await u.po.expect.toBeSignedIn();
});

test('sign in with hash routing', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.page.goToRelative('/hash/sign-in');
await u.po.signIn.waitForMounted();

await u.po.signIn.setIdentifier(fakeUser.email);
await u.po.signIn.continue();
await u.page.waitForURL(`${app.serverUrl}/hash/sign-in#/factor-one`);

await u.po.signIn.setPassword(fakeUser.password);
await u.po.signIn.continue();

await u.po.expect.toBeSignedIn();
});

test('user profile from user button navigates correctly', async ({ page, context }) => {
const u = createTestUtils({ app, page, context });
await u.po.signIn.goTo();
await u.po.signIn.waitForMounted();
await u.po.signIn.signInWithEmailAndInstantPassword({ email: fakeUser.email, password: fakeUser.password });
await u.po.expect.toBeSignedIn();

await u.page.goToRelative('/');
await u.page.waitForClerkComponentMounted();

await u.page.getByRole('button', { name: 'Open user button' }).click();

await u.page.getByText(/Manage account/).click();

await u.page.waitForSelector('.cl-modalContent > .cl-userProfile-root', { state: 'attached' });

await u.page.getByText(/Set username/i).click();
await u.page.getByText(/Cancel/i).click();

await u.page.getByText(/Add an email address/i).click();
await u.page.getByText(/Cancel/i).click();
});
});
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { CreateOrganizationProps } from '@clerk/types';
import type { CreateOrganizationModalProps, CreateOrganizationProps } from '@clerk/types';

import { withOrganizationsEnabledGuard } from '../../common';
import { ComponentContext, withCoreUserGuard } from '../../contexts';
Expand Down Expand Up @@ -38,7 +38,7 @@ export const CreateOrganization = withOrganizationsEnabledGuard(
{ mode: 'redirect' },
);

export const CreateOrganizationModal = (props: CreateOrganizationProps): JSX.Element => {
export const CreateOrganizationModal = (props: CreateOrganizationModalProps): JSX.Element => {
const createOrganizationProps: CreateOrganizationCtx = {
...props,
routing: 'virtual',
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { OrganizationProfileProps } from '@clerk/types';
import type { OrganizationProfileModalProps, OrganizationProfileProps } from '@clerk/types';
import React from 'react';

import { withOrganizationsEnabledGuard, withRedirectToHomeOrganizationGuard } from '../../common';
Expand Down Expand Up @@ -48,7 +48,7 @@ export const OrganizationProfile = withRedirectToHomeOrganizationGuard(
}),
);

export const OrganizationProfileModal = (props: OrganizationProfileProps): JSX.Element => {
export const OrganizationProfileModal = (props: OrganizationProfileModalProps): JSX.Element => {
const organizationProfileProps: OrganizationProfileCtx = {
...props,
routing: 'virtual',
Expand Down
4 changes: 2 additions & 2 deletions packages/clerk-js/src/ui.retheme/components/SignIn/SignIn.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignInProps } from '@clerk/types';
import type { SignInModalProps, SignInProps } from '@clerk/types';
import React from 'react';

import { SignInEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
Expand Down Expand Up @@ -74,7 +74,7 @@ SignInRoutes.displayName = 'SignIn';

export const SignIn: React.ComponentType<SignInProps> = withCoreSessionSwitchGuard(SignInRoutes);

export const SignInModal = (props: SignInProps): JSX.Element => {
export const SignInModal = (props: SignInModalProps): JSX.Element => {
const signInProps = {
signUpUrl: `/${VIRTUAL_ROUTER_BASE_PATH}/sign-up`,
...props,
Expand Down
6 changes: 3 additions & 3 deletions packages/clerk-js/src/ui.retheme/components/SignUp/SignUp.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { SignUpProps } from '@clerk/types';
import type { SignUpModalProps, SignUpProps } from '@clerk/types';
import React from 'react';

import { SignUpEmailLinkFlowComplete } from '../../common/EmailLinkCompleteFlowCard';
Expand Down Expand Up @@ -87,8 +87,8 @@ SignUpRoutes.displayName = 'SignUp';

export const SignUp: React.ComponentType<SignUpProps> = withCoreSessionSwitchGuard(SignUpRoutes);

export const SignUpModal = (props: SignUpProps): JSX.Element => {
const signUpProps: SignUpProps = {
export const SignUpModal = (props: SignUpModalProps): JSX.Element => {
const signUpProps = {
signInUrl: `/${VIRTUAL_ROUTER_BASE_PATH}/sign-in`,
...props,
};
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import type { UserProfileProps } from '@clerk/types';
import type { UserProfileModalProps, UserProfileProps } from '@clerk/types';
import React from 'react';

import { withRedirectToHomeUserGuard } from '../../common';
Expand Down Expand Up @@ -42,7 +42,7 @@ const AuthenticatedRoutes = withCoreUserGuard(() => {

export const UserProfile = withRedirectToHomeUserGuard(withCardStateProvider(_UserProfile));

export const UserProfileModal = (props: UserProfileProps): JSX.Element => {
export const UserProfileModal = (props: UserProfileModalProps): JSX.Element => {
const userProfileProps: UserProfileCtx = {
...props,
routing: 'virtual',
Expand Down
Loading

0 comments on commit 101ba6b

Please sign in to comment.