Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(test): Add Clerk Elements E2E #3394

Merged
merged 20 commits into from
May 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/healthy-turtles-compete.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/elements': patch
---

Update the TypeScript type of `<Input />` to allow the `validatePassword` prop also on `type="text"` (in addition to `type="password"`)
5 changes: 3 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ jobs:

strategy:
matrix:
test-name: ['generic', 'nextjs', 'express', 'quickstart', 'ap-flows']
test-name: ['generic', 'nextjs', 'express', 'quickstart', 'ap-flows', 'elements']
test-project: ['chrome']

steps:
Expand All @@ -152,7 +152,7 @@ jobs:
uses: ./.github/actions/verdaccio
with:
publish-cmd: |
if [ "$(npm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else npx turbo build $TURBO_ARGS --filter=!elements --only && npx changeset publish --no-git-tag; fi
if [ "$(npm config get registry)" = "https://registry.npmjs.org/" ]; then echo 'Error: Using default registry' && exit 1; else npx turbo build $TURBO_ARGS --only && npx changeset publish --no-git-tag; fi

- name: Install @clerk/backend in /integration
working-directory: ./integration
Expand All @@ -163,6 +163,7 @@ jobs:
run: mkdir clerk-js && cd clerk-js && npm init -y && npm install @clerk/clerk-js

- name: Run Integration Tests
id: integration-tests
run: npx turbo test:integration:${{ matrix.test-name }} $TURBO_ARGS --only -- --project=${{ matrix.test-project }}
env:
E2E_APP_CLERK_JS_DIR: ${{runner.temp}}
Expand Down
3 changes: 3 additions & 0 deletions integration/models/applicationConfig.ts
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ export const applicationConfig = () => {
scripts[name] = cmd;
return self;
},
/**
* Adds a dependency to the template's `package.json` file. If the version is undefined, the dependency is not added. If the dependency already exists, the version is overwritten.
*/
addDependency: (name: string, version: string | undefined) => {
if (version) {
dependencies.set(name, version);
Expand Down
24 changes: 24 additions & 0 deletions integration/presets/elements.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
import { constants } from '../constants';
import { applicationConfig } from '../models/applicationConfig.js';
import { templates } from '../templates/index.js';

const clerkNextjsLocal = `file:${process.cwd()}/packages/nextjs`;
const clerkElementsLocal = `file:${process.cwd()}/packages/elements`;

const nextAppRouter = applicationConfig()
.setName('elements-next')
.useTemplate(templates['elements-next'])
.setEnvFormatter('public', key => `NEXT_PUBLIC_${key}`)
.addScript('setup', 'npm i')
.addScript('dev', 'npm run dev')
.addScript('build', 'npm run build')
.addScript('serve', 'npm run start')
.addDependency('next', constants.E2E_NEXTJS_VERSION)
.addDependency('react', constants.E2E_REACT_VERSION)
.addDependency('react-dom', constants.E2E_REACT_DOM_VERSION)
.addDependency('@clerk/nextjs', constants.E2E_CLERK_VERSION || clerkNextjsLocal)
.addDependency('@clerk/elements', constants.E2E_CLERK_VERSION || clerkElementsLocal);

export const elements = {
nextAppRouter,
} as const;
2 changes: 2 additions & 0 deletions integration/presets/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import { elements } from './elements';
import { envs } from './envs';
import { express } from './express';
import { createLongRunningApps } from './longRunningApps';
Expand All @@ -12,4 +13,5 @@ export const appConfigs = {
next,
react,
remix,
elements,
} as const;
2 changes: 2 additions & 0 deletions integration/presets/longRunningApps.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import type { LongRunningApplication } from '../models/longRunningApplication';
import { longRunningApplication } from '../models/longRunningApplication';
import { elements } from './elements';
import { envs } from './envs';
import { express } from './express';
import { next } from './next';
Expand All @@ -20,6 +21,7 @@ export const createLongRunningApps = () => {
{ id: 'next.appRouter.withEmailCodes', config: next.appRouter, env: envs.withEmailCodes },
{ id: 'next.appRouter.withCustomRoles', config: next.appRouter, env: envs.withCustomRoles },
{ id: 'quickstart.next.appRouter', config: next.appRouterQuickstart, env: envs.withEmailCodesQuickstart },
{ id: 'elements.next.appRouter', config: elements.nextAppRouter, env: envs.withEmailCodes },
] as const;

const apps = configs.map(longRunningApplication);
Expand Down
3 changes: 3 additions & 0 deletions integration/templates/elements-next/.eslintrc.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
module.exports = {
root: true,
};
37 changes: 37 additions & 0 deletions integration/templates/elements-next/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.

# dependencies
/node_modules
/.pnp
.pnp.js

# testing
/coverage

# next.js
/.next/
/out/

# production
/build

# misc
.DS_Store
*.pem

# debug
npm-debug.log*
yarn-debug.log*
yarn-error.log*

# local env files
.env*.local

# vercel
.vercel

# typescript
*.tsbuildinfo
next-env.d.ts

package-lock.json
34 changes: 34 additions & 0 deletions integration/templates/elements-next/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app).

## Getting Started

First, run the development server:

```bash
npm run dev
# or
yarn dev
# or
pnpm dev
```

Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.

You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file.

This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font.

## Learn More

To learn more about Next.js, take a look at the following resources:

- [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API.
- [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial.

You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome!

## Deploy on Vercel

The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js.

Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details.
8 changes: 8 additions & 0 deletions integration/templates/elements-next/next.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
/** @type {import('next').NextConfig} */
const nextConfig = {
eslint: {
ignoreDuringBuilds: true,
},
};

module.exports = nextConfig;
30 changes: 30 additions & 0 deletions integration/templates/elements-next/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"name": "elements-next",
"version": "0.1.0",
"private": true,
"scripts": {
"build": "next build",
"dev": "next dev",
"lint": "next lint",
"start": "next start"
},
"dependencies": {
"@clerk/elements": "file:../../../packages/elements",
"@clerk/nextjs": "file:../../../packages/nextjs",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

☁️ I think we should drop the 2 lines above. We are not passing the deps added in presets in the package.json of the template in the other integration tests.
cc: @nikosdouvlis

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This way you can run the template locally just fine without needing to install them first. With .addDependency these entries are overwritten

"@types/node": "^18.17.0",
"@types/react": "^18.3.1",
"@types/react-dom": "^18.3.0",
"next": "^14.2.3",
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ shouldn't we use the 13.5.4 since it's the minimum supported version defined in elements peer dependencies?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This can be handled with something like #3431

"react": "^18.3.1",
"react-dom": "^18.3.1",
"typescript": "^5.4.5"
},
"devDependencies": {
"autoprefixer": "^10.4.19",
"postcss": "^8.4.38",
"tailwindcss": "^3.4.3"
},
"engines": {
"node": ">=18.17.0"
}
}
6 changes: 6 additions & 0 deletions integration/templates/elements-next/postcss.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
module.exports = {
plugins: {
tailwindcss: {},
autoprefixer: {},
},
};
Binary file not shown.
37 changes: 37 additions & 0 deletions integration/templates/elements-next/src/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
@tailwind base;
@tailwind components;
@tailwind utilities;

:root {
--foreground-rgb: 0, 0, 0;
--background-start-rgb: 214, 219, 220;
--background-end-rgb: 255, 255, 255;
}

* {
box-sizing: border-box;
padding: 0;
margin: 0;
}

html,
body {
max-width: 100vw;
overflow-x: hidden;
}

body {
color: rgb(var(--foreground-rgb));
background: linear-gradient(to bottom, transparent, rgb(var(--background-end-rgb))) rgb(var(--background-start-rgb));
font-family: -apple-system, BlinkMacSystemFont, avenir next, avenir, segoe ui, helvetica neue, helvetica, Cantarell,
Ubuntu, roboto, noto, arial, sans-serif;
}

main {
display: flex;
flex-direction: column;
justify-content: center;
align-items: center;
padding: 6rem;
min-height: 100vh;
}
19 changes: 19 additions & 0 deletions integration/templates/elements-next/src/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
import './globals.css';

import { ClerkProvider } from '@clerk/nextjs';
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'Clerk Elements - Next.js E2E',
description: 'Clerk Elements - Next.js E2E',
};

export default function RootLayout({ children }: { children: React.ReactNode }) {
return (
<html lang='en'>
<ClerkProvider clerkJSVariant='headless'>
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

❓ Why headless? Is this the suggested approach when using the elements package?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Right now the integration tests don't need the AIO components and eventually people should use headless, yes.

<body>{children}</body>
</ClerkProvider>
</html>
);
}
118 changes: 118 additions & 0 deletions integration/templates/elements-next/src/app/otp/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,118 @@
'use client';

import * as Clerk from '@clerk/elements/common';
import * as SignIn from '@clerk/elements/sign-in';

function clsx(...args: (string | undefined | Record<string, boolean>)[]): string {
const classes: string[] = [];

for (const arg of args) {
switch (typeof arg) {
case 'string':
classes.push(arg);
break;
case 'object':
for (const key in arg) {
if (arg[key]) {
classes.push(key);
}
}
break;
}
}

return classes.join(' ');
}

export default function OTP() {
return (
<main>
<SignIn.Root path='/otp'>
<SignIn.Step
name='start'
className='w-full space-y-6 rounded-2xl px-4 py-10 sm:w-96 sm:px-8 bg-white text-center shadow-md'
>
<header className='text-center'>
<h1 className='text-xl font-medium tracking-tight text-neutral-950'>OTP Playground</h1>
</header>
<Clerk.Field
name='simple-otp'
className='space-y-2'
>
<Clerk.Label className='text-sm font-medium text-zinc-950'>Simple OTP Input</Clerk.Label>
<Clerk.Input
type='otp'
className='w-full rounded-md bg-white px-3.5 py-2 text-sm outline-none ring-1 ring-inset ring-zinc-300 hover:ring-zinc-400 focus:ring-[1.5px] focus:ring-zinc-950 data-[invalid]:ring-red-400'
data-testid='simple-otp'
/>
</Clerk.Field>
<Clerk.Field
name='segmented-otp'
className='space-y-2'
>
<Clerk.Label className='text-sm font-medium text-zinc-950'>Segmented OTP Input</Clerk.Label>
<Clerk.Input
className='flex justify-center has-[:disabled]:opacity-50 segmented-otp-wrapper'
type='otp'
data-testid='segmented-otp'
render={({ value, status, index }) => {
return (
<div
data-status={status}
data-testid={`segmented-otp-${index}`}
className={clsx(
'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
{
'z-10 ring-2 ring-black ring-offset-black': status === 'cursor' || status === 'selected',
},
)}
>
{value}
{status === 'cursor' && (
<div className='pointer-events-none absolute inset-0 flex items-center justify-center'>
<div className='animate-caret-blink h-4 w-px bg-black duration-1000' />
</div>
)}
</div>
);
}}
/>
</Clerk.Field>
<Clerk.Field
name='segmented-otp-with-props'
className='space-y-2'
>
<Clerk.Label className='text-sm font-medium text-zinc-950'>Segmented OTP Input (with props)</Clerk.Label>
<Clerk.Input
className='flex justify-center has-[:disabled]:opacity-50 segmented-otp-with-props-wrapper'
type='otp'
data-testid='segmented-otp-with-props'
passwordManagerOffset={4}
length={4}
render={({ value, status }) => {
return (
<div
data-status={status}
className={clsx(
'relative flex size-10 items-center justify-center border-y border-r border-input text-sm transition-all first:rounded-l-md first:border-l last:rounded-r-md',
{
'z-10 ring-2 ring-black ring-offset-black': status === 'cursor' || status === 'selected',
},
)}
>
{value}
{status === 'cursor' && (
<div className='pointer-events-none absolute inset-0 flex items-center justify-center'>
<div className='animate-caret-blink h-4 w-px bg-black duration-1000' />
</div>
)}
</div>
);
}}
/>
</Clerk.Field>
</SignIn.Step>
</SignIn.Root>
</main>
);
}
Loading
Loading