test(e2e): test login page
This commit is contained in:
parent
5f8a9d0f59
commit
3a1b81c4be
11 changed files with 92 additions and 6 deletions
4
.env.test
Normal file
4
.env.test
Normal file
|
@ -0,0 +1,4 @@
|
||||||
|
AUTH_SECRET="auth_secret"
|
||||||
|
AUTH_URL="http://localhost:3000"
|
||||||
|
DATABASE_URL="file:./dev.db"
|
||||||
|
AUTH_AUTHENTIK_ISSUER="auth_issuer"
|
2
.github/workflows/tests.yml
vendored
2
.github/workflows/tests.yml
vendored
|
@ -23,5 +23,5 @@ jobs:
|
||||||
uses: https://github.com/cypress-io/github-action@v6
|
uses: https://github.com/cypress-io/github-action@v6
|
||||||
with:
|
with:
|
||||||
build: yarn run build
|
build: yarn run build
|
||||||
start: yarn start
|
start: yarn cypress:start_server
|
||||||
component: true
|
component: true
|
||||||
|
|
1
.gitignore
vendored
1
.gitignore
vendored
|
@ -33,6 +33,7 @@ yarn-error.log*
|
||||||
# env files (can opt-in for committing if needed)
|
# env files (can opt-in for committing if needed)
|
||||||
.env*
|
.env*
|
||||||
!.env.example
|
!.env.example
|
||||||
|
!.env.test
|
||||||
|
|
||||||
# vercel
|
# vercel
|
||||||
.vercel
|
.vercel
|
||||||
|
|
|
@ -7,4 +7,10 @@ export default defineConfig({
|
||||||
bundler: 'webpack',
|
bundler: 'webpack',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
|
||||||
|
e2e: {
|
||||||
|
setupNodeEvents(on, config) {
|
||||||
|
// implement node event listeners here
|
||||||
|
},
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
31
cypress/e2e/login.cy.ts
Normal file
31
cypress/e2e/login.cy.ts
Normal file
|
@ -0,0 +1,31 @@
|
||||||
|
describe('login', () => {
|
||||||
|
it('loads', () => {
|
||||||
|
cy.visit('http://localhost:3000/');
|
||||||
|
|
||||||
|
cy.getBySel('login-header').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows login form', () => {
|
||||||
|
cy.visit('http://localhost:3000/');
|
||||||
|
|
||||||
|
cy.getBySel('login-form').should('exist');
|
||||||
|
cy.getBySel('email-input').should('exist');
|
||||||
|
cy.getBySel('password-input').should('exist');
|
||||||
|
cy.getBySel('login-button').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('shows sso button', () => {
|
||||||
|
cy.visit('http://localhost:3000/');
|
||||||
|
|
||||||
|
cy.getBySel('sso-login-button_authentik').should('exist');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('allows login', () => {
|
||||||
|
cy.visit('http://localhost:3000/');
|
||||||
|
|
||||||
|
cy.getBySel('email-input').type('test@example.com');
|
||||||
|
cy.getBySel('password-input').type('password');
|
||||||
|
cy.getBySel('login-button').click();
|
||||||
|
cy.url().should('include', '/home');
|
||||||
|
});
|
||||||
|
});
|
|
@ -8,6 +8,7 @@
|
||||||
"start": "node .next/standalone/server.js",
|
"start": "node .next/standalone/server.js",
|
||||||
"lint": "next lint",
|
"lint": "next lint",
|
||||||
"format": "prettier --write .",
|
"format": "prettier --write .",
|
||||||
|
"cypress:start_server": "cp .next/static/ .next/standalone/.next/ -r && dotenv -e .env.test -- node .next/standalone/server.js",
|
||||||
"cypress:open": "cypress open",
|
"cypress:open": "cypress open",
|
||||||
"cypress:run": "cypress run"
|
"cypress:run": "cypress run"
|
||||||
},
|
},
|
||||||
|
@ -43,6 +44,7 @@
|
||||||
"@types/react": "19.1.3",
|
"@types/react": "19.1.3",
|
||||||
"@types/react-dom": "19.1.4",
|
"@types/react-dom": "19.1.4",
|
||||||
"cypress": "14.3.3",
|
"cypress": "14.3.3",
|
||||||
|
"dotenv-cli": "^8.0.0",
|
||||||
"eslint": "9.26.0",
|
"eslint": "9.26.0",
|
||||||
"eslint-config-next": "15.3.2",
|
"eslint-config-next": "15.3.2",
|
||||||
"eslint-config-prettier": "10.1.5",
|
"eslint-config-prettier": "10.1.5",
|
||||||
|
|
|
@ -30,7 +30,9 @@ export default async function LoginPage() {
|
||||||
<div>
|
<div>
|
||||||
<Card className='w-[350px] max-w-screen'>
|
<Card className='w-[350px] max-w-screen'>
|
||||||
<CardHeader>
|
<CardHeader>
|
||||||
<CardTitle className='text-lg text-center'>Login</CardTitle>
|
<CardTitle className='text-lg text-center' data-cy='login-header'>
|
||||||
|
Login
|
||||||
|
</CardTitle>
|
||||||
</CardHeader>
|
</CardHeader>
|
||||||
<CardContent className='gap-6 flex flex-col'>
|
<CardContent className='gap-6 flex flex-col'>
|
||||||
<LoginForm />
|
<LoginForm />
|
||||||
|
@ -38,7 +40,11 @@ export default async function LoginPage() {
|
||||||
<hr />
|
<hr />
|
||||||
|
|
||||||
{process.env.AUTH_AUTHENTIK_ISSUER && (
|
{process.env.AUTH_AUTHENTIK_ISSUER && (
|
||||||
<SSOLogin provider='authentik' providerDisplayName='SSO' />
|
<SSOLogin
|
||||||
|
provider='authentik'
|
||||||
|
providerDisplayName='SSO'
|
||||||
|
data-cy='sso-login-button_authentik'
|
||||||
|
/>
|
||||||
)}
|
)}
|
||||||
</CardContent>
|
</CardContent>
|
||||||
</Card>
|
</Card>
|
||||||
|
|
|
@ -6,12 +6,13 @@ export default function LabeledInput({
|
||||||
label,
|
label,
|
||||||
placeholder,
|
placeholder,
|
||||||
value,
|
value,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
type: 'text' | 'email' | 'password';
|
type: 'text' | 'email' | 'password';
|
||||||
label: string;
|
label: string;
|
||||||
placeholder?: string;
|
placeholder?: string;
|
||||||
value?: string;
|
value?: string;
|
||||||
}) {
|
} & React.InputHTMLAttributes<HTMLInputElement>) {
|
||||||
const elementId = Math.random().toString(36).substring(2, 15);
|
const elementId = Math.random().toString(36).substring(2, 15);
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
@ -23,6 +24,7 @@ export default function LabeledInput({
|
||||||
placeholder={placeholder}
|
placeholder={placeholder}
|
||||||
defaultValue={value}
|
defaultValue={value}
|
||||||
id={elementId}
|
id={elementId}
|
||||||
|
{...props}
|
||||||
/>
|
/>
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
|
|
|
@ -3,21 +3,24 @@ import { Button } from '@/components/ui/button';
|
||||||
|
|
||||||
export default function LoginForm() {
|
export default function LoginForm() {
|
||||||
return (
|
return (
|
||||||
<form className='flex flex-col gap-5 w-full'>
|
<form className='flex flex-col gap-5 w-full' data-cy='login-form'>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='email'
|
type='email'
|
||||||
label='E-Mail'
|
label='E-Mail'
|
||||||
placeholder='Enter your E-Mail'
|
placeholder='Enter your E-Mail'
|
||||||
|
data-cy='email-input'
|
||||||
/>
|
/>
|
||||||
<LabeledInput
|
<LabeledInput
|
||||||
type='password'
|
type='password'
|
||||||
label='Password'
|
label='Password'
|
||||||
placeholder='Enter your Password'
|
placeholder='Enter your Password'
|
||||||
|
data-cy='password-input'
|
||||||
/>
|
/>
|
||||||
<Button
|
<Button
|
||||||
className='hover:bg-blue-600 hover:text-white'
|
className='hover:bg-blue-600 hover:text-white'
|
||||||
type='submit'
|
type='submit'
|
||||||
variant='secondary'
|
variant='secondary'
|
||||||
|
data-cy='login-button'
|
||||||
>
|
>
|
||||||
Login
|
Login
|
||||||
</Button>
|
</Button>
|
||||||
|
|
|
@ -5,10 +5,11 @@ import { faOpenid } from '@fortawesome/free-brands-svg-icons';
|
||||||
export default function SSOLogin({
|
export default function SSOLogin({
|
||||||
provider,
|
provider,
|
||||||
providerDisplayName,
|
providerDisplayName,
|
||||||
|
...props
|
||||||
}: {
|
}: {
|
||||||
provider: string;
|
provider: string;
|
||||||
providerDisplayName: string;
|
providerDisplayName: string;
|
||||||
}) {
|
} & React.HTMLProps<HTMLFormElement>) {
|
||||||
return (
|
return (
|
||||||
<form
|
<form
|
||||||
className='flex flex-col items-center gap-4 w-full'
|
className='flex flex-col items-center gap-4 w-full'
|
||||||
|
@ -16,6 +17,7 @@ export default function SSOLogin({
|
||||||
'use server';
|
'use server';
|
||||||
await signIn(provider);
|
await signIn(provider);
|
||||||
}}
|
}}
|
||||||
|
{...props}
|
||||||
>
|
>
|
||||||
<IconButton
|
<IconButton
|
||||||
className='w-full'
|
className='w-full'
|
||||||
|
|
29
yarn.lock
29
yarn.lock
|
@ -3133,6 +3133,34 @@ __metadata:
|
||||||
languageName: node
|
languageName: node
|
||||||
linkType: hard
|
linkType: hard
|
||||||
|
|
||||||
|
"dotenv-cli@npm:^8.0.0":
|
||||||
|
version: 8.0.0
|
||||||
|
resolution: "dotenv-cli@npm:8.0.0"
|
||||||
|
dependencies:
|
||||||
|
cross-spawn: "npm:^7.0.6"
|
||||||
|
dotenv: "npm:^16.3.0"
|
||||||
|
dotenv-expand: "npm:^10.0.0"
|
||||||
|
minimist: "npm:^1.2.6"
|
||||||
|
bin:
|
||||||
|
dotenv: cli.js
|
||||||
|
checksum: 10c0/000469632758b7b44aaaa80cbbbd7f0c94dc170ec02e51aa8d8280341a0108fb7407954c23054257b77235b064033efdb8745836633eb6fd1586924953cf0528
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"dotenv-expand@npm:^10.0.0":
|
||||||
|
version: 10.0.0
|
||||||
|
resolution: "dotenv-expand@npm:10.0.0"
|
||||||
|
checksum: 10c0/298f5018e29cfdcb0b5f463ba8e8627749103fbcf6cf81c561119115754ed582deee37b49dfc7253028aaba875ab7aea5fa90e5dac88e511d009ab0e6677924e
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
|
"dotenv@npm:^16.3.0":
|
||||||
|
version: 16.5.0
|
||||||
|
resolution: "dotenv@npm:16.5.0"
|
||||||
|
checksum: 10c0/5bc94c919fbd955bf0ba44d33922a1e93d1078e64a1db5c30faeded1d996e7a83c55332cb8ea4fae5a9ca4d0be44cbceb95c5811e70f9f095298df09d1997dd9
|
||||||
|
languageName: node
|
||||||
|
linkType: hard
|
||||||
|
|
||||||
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
|
"dunder-proto@npm:^1.0.0, dunder-proto@npm:^1.0.1":
|
||||||
version: 1.0.1
|
version: 1.0.1
|
||||||
resolution: "dunder-proto@npm:1.0.1"
|
resolution: "dunder-proto@npm:1.0.1"
|
||||||
|
@ -5372,6 +5400,7 @@ __metadata:
|
||||||
class-variance-authority: "npm:^0.7.1"
|
class-variance-authority: "npm:^0.7.1"
|
||||||
clsx: "npm:^2.1.1"
|
clsx: "npm:^2.1.1"
|
||||||
cypress: "npm:14.3.3"
|
cypress: "npm:14.3.3"
|
||||||
|
dotenv-cli: "npm:^8.0.0"
|
||||||
eslint: "npm:9.26.0"
|
eslint: "npm:9.26.0"
|
||||||
eslint-config-next: "npm:15.3.2"
|
eslint-config-next: "npm:15.3.2"
|
||||||
eslint-config-prettier: "npm:10.1.5"
|
eslint-config-prettier: "npm:10.1.5"
|
||||||
|
|
Loading…
Add table
Add a link
Reference in a new issue