mirror of
https://github.com/zitadel/zitadel.git
synced 2025-12-12 07:24:51 +00:00
test: add table driven unit tests
This commit is contained in:
@@ -1,110 +0,0 @@
|
|||||||
import RootLayout from '#/app/layout';
|
|
||||||
import Page from './page'
|
|
||||||
import { RenderResult, render, screen, waitFor, renderHook } from '@testing-library/react';
|
|
||||||
import { act } from 'react-dom/test-utils';
|
|
||||||
|
|
||||||
describe('/login/verify', () => {
|
|
||||||
|
|
||||||
const noUserIDError = "No userId provided!"
|
|
||||||
const noCodeError = "No code provided!"
|
|
||||||
const codeLabelText = "Code"
|
|
||||||
|
|
||||||
describe.each`
|
|
||||||
userId | code | directSubmit | expectRenderedUserIDError | expectRenderedCodePrefilled | expectRenderedCodeError
|
|
||||||
| ${"123"} | ${"xyz"} | ${true} | ${false} | ${"" /*TODO: We should expect "xyz"*/} | ${false}
|
|
||||||
| ${"123"} | ${""} | ${true} | ${false} | ${""} | ${true}
|
|
||||||
| ${"123"} | ${undefined} | ${true} | ${false} | ${""} | ${true}
|
|
||||||
| ${"123"} | ${"xyz"} | ${false} | ${false} | ${"xyz"} | ${false}
|
|
||||||
| ${"123"} | ${""} | ${false} | ${false} | ${""} | ${false}
|
|
||||||
| ${"123"} | ${undefined} | ${false} | ${false} | ${""} | ${false}
|
|
||||||
| ${""} | ${"xyz"} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${""} | ${""} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${""} | ${undefined} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${""} | ${"xyz"} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
| ${""} | ${""} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
| ${""} | ${undefined} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${"xyz"} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${""} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${undefined} | ${true} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${"xyz"} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${""} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
| ${undefined} | ${undefined} | ${false} | ${true} | ${false} | ${false}
|
|
||||||
`(`With code=$code, submit=$submit and userId=$userId`, ({ userId, code, directSubmit, expectRenderedUserIDError, expectRenderedCodePrefilled, expectRenderedCodeError }) => {
|
|
||||||
|
|
||||||
let renderResult: RenderResult;
|
|
||||||
beforeEach(async () => {
|
|
||||||
await act(async () => {
|
|
||||||
renderResult = render(await RootLayout({
|
|
||||||
children: await Page({
|
|
||||||
searchParams: {
|
|
||||||
code: code,
|
|
||||||
submit: directSubmit,
|
|
||||||
userID: userId,
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}))
|
|
||||||
// TODO: Replace the above syntax for awaiting the JSX.Element once https://github.com/DefinitelyTyped/DefinitelyTyped/pull/65135 is released
|
|
||||||
// renderResult = render(<Page searchParams={{
|
|
||||||
// code: code,
|
|
||||||
// submit: submit,
|
|
||||||
// userID: userId,
|
|
||||||
// }} />)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
it(`should have rendered`, () => {
|
|
||||||
expect(renderResult.container.firstChild).toBeDefined();
|
|
||||||
})
|
|
||||||
describe(`With expectRenderedUserIDError=${expectRenderedUserIDError}`, () => {
|
|
||||||
if (expectRenderedUserIDError) {
|
|
||||||
it(`should show the error "${noUserIDError}"`, () => {
|
|
||||||
const error = screen.getByText(noUserIDError)
|
|
||||||
expect(error).toBeInTheDocument()
|
|
||||||
expect(error).toBeVisible()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
it(`should not show the error "${noUserIDError}`, () => {
|
|
||||||
const error = screen.queryByText(noUserIDError)
|
|
||||||
expect(error).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe(`With expectRenderedCodePrefilled=${expectRenderedCodePrefilled}`, () => {
|
|
||||||
if (typeof expectRenderedCodePrefilled == 'string') {
|
|
||||||
it(`should show the ${codeLabelText} input with the value "${expectRenderedCodePrefilled}" prefilled`, async () => {
|
|
||||||
await waitFor(() => {
|
|
||||||
const codeInput = screen.getByLabelText(codeLabelText)
|
|
||||||
expect(codeInput).toHaveTextContent(expectRenderedCodePrefilled)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
it(`should not show the ${codeLabelText} input`, () => {
|
|
||||||
const codeInput = screen.queryByText(codeLabelText)
|
|
||||||
expect(codeInput).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
describe(`With expectRenderedCodeError=${expectRenderedCodeError}`, () => {
|
|
||||||
if (expectRenderedCodeError) {
|
|
||||||
it(`should show the error "${noCodeError}"`, () => {
|
|
||||||
const error = screen.getByText(noCodeError)
|
|
||||||
expect(error).toBeInTheDocument()
|
|
||||||
expect(error).toBeVisible()
|
|
||||||
})
|
|
||||||
} else {
|
|
||||||
it(`should not show the error "${expectRenderedCodeError}`, () => {
|
|
||||||
const error = screen.queryByText(noCodeError)
|
|
||||||
expect(error).not.toBeInTheDocument()
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
if (!directSubmit) {
|
|
||||||
describe(`With click on submit`, () => {
|
|
||||||
|
|
||||||
})
|
|
||||||
}
|
|
||||||
})
|
|
||||||
});
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
// Polyfill "window.fetch" used in the React component.
|
|
||||||
import 'whatwg-fetch'
|
|
||||||
|
|
||||||
import * as mockRouter from 'next-router-mock';
|
|
||||||
import { mockLoginBackend } from './mock-login-backend'
|
|
||||||
|
|
||||||
// Inspired by https://github.com/scottrippey/next-router-mock/issues/67#issuecomment-1564906960
|
|
||||||
const useRouter = mockRouter.useRouter;
|
|
||||||
|
|
||||||
const MockNextNavigation = {
|
|
||||||
...mockRouter,
|
|
||||||
usePathname: () => {
|
|
||||||
const router = useRouter();
|
|
||||||
return router.pathname;
|
|
||||||
},
|
|
||||||
useSearchParams: () => {
|
|
||||||
const router = useRouter();
|
|
||||||
const path = router.asPath.split('?')?.[1] ?? '';
|
|
||||||
return new URLSearchParams(path);
|
|
||||||
},
|
|
||||||
};
|
|
||||||
|
|
||||||
// jest.mock('next/navigation', () => MockNextNavigation);
|
|
||||||
// jest.mock('next/router', () => mockRouter)
|
|
||||||
|
|
||||||
beforeAll(() => {
|
|
||||||
mockLoginBackend.listen()
|
|
||||||
})
|
|
||||||
beforeEach(() => mockLoginBackend.resetHandlers())
|
|
||||||
afterAll(() => mockLoginBackend.close())
|
|
||||||
@@ -8,12 +8,10 @@ export default async (): Promise<Config.InitialOptions> => {
|
|||||||
},
|
},
|
||||||
setupFilesAfterEnv: [
|
setupFilesAfterEnv: [
|
||||||
'@testing-library/jest-dom/extend-expect',
|
'@testing-library/jest-dom/extend-expect',
|
||||||
'<rootDir>/jest-setup.ts',
|
|
||||||
],
|
],
|
||||||
moduleNameMapper: {
|
moduleNameMapper: {
|
||||||
'^#/(.*)$': '<rootDir>/$1',
|
'^#/(.*)$': '<rootDir>/$1',
|
||||||
},
|
},
|
||||||
testEnvironment: 'jsdom',
|
testEnvironment: 'jsdom',
|
||||||
clearMocks: true,
|
|
||||||
};
|
};
|
||||||
};
|
};
|
||||||
@@ -60,7 +60,7 @@
|
|||||||
"@types/jest": "^29.5.1",
|
"@types/jest": "^29.5.1",
|
||||||
"@types/ms": "0.7.31",
|
"@types/ms": "0.7.31",
|
||||||
"@types/node": "18.11.9",
|
"@types/node": "18.11.9",
|
||||||
"@types/react": "18.0.25",
|
"@types/react": "18.2.8",
|
||||||
"@types/react-dom": "18.0.9",
|
"@types/react-dom": "18.0.9",
|
||||||
"@types/testing-library__jest-dom": "^5.14.6",
|
"@types/testing-library__jest-dom": "^5.14.6",
|
||||||
"@types/tinycolor2": "1.4.3",
|
"@types/tinycolor2": "1.4.3",
|
||||||
|
|||||||
43
apps/login/ui/PasswordComplexity.test.tsx
Normal file
43
apps/login/ui/PasswordComplexity.test.tsx
Normal file
@@ -0,0 +1,43 @@
|
|||||||
|
import { render, screen, waitFor, within } from '@testing-library/react';
|
||||||
|
import PasswordComplexity from './PasswordComplexity';
|
||||||
|
// TODO: Why does this not compile?
|
||||||
|
// import { ResourceOwnerType } from '@zitadel/server';
|
||||||
|
|
||||||
|
const matchesTitle = `Matches`
|
||||||
|
const doesntMatchTitle = `Doesn't match`
|
||||||
|
|
||||||
|
describe('<PasswordComplexity/>', () => {
|
||||||
|
describe.each`
|
||||||
|
settingsMinLength | password | expectSVGTitle
|
||||||
|
${5} | ${'Password1!'} | ${matchesTitle}
|
||||||
|
${30} | ${'Password1!'} | ${doesntMatchTitle}
|
||||||
|
${0} | ${'Password1!'} | ${matchesTitle}
|
||||||
|
${undefined} | ${'Password1!'} | ${false}
|
||||||
|
`(`With settingsMinLength=$settingsMinLength, password=$password, expectSVGTitle=$expectSVGTitle`, ({ settingsMinLength, password, expectSVGTitle }) => {
|
||||||
|
const feedbackElementLabel = /password length/i
|
||||||
|
beforeEach(() => {
|
||||||
|
render(<PasswordComplexity password={password} equals passwordComplexitySettings={{
|
||||||
|
minLength: settingsMinLength,
|
||||||
|
requiresLowercase: false,
|
||||||
|
requiresUppercase: false,
|
||||||
|
requiresNumber: false,
|
||||||
|
requiresSymbol: false,
|
||||||
|
resourceOwnerType: 0 // ResourceOwnerType.RESOURCE_OWNER_TYPE_UNSPECIFIED,
|
||||||
|
}} />)
|
||||||
|
})
|
||||||
|
if (expectSVGTitle === false) {
|
||||||
|
it(`should not render the feedback element`, async () => {
|
||||||
|
await waitFor(() => {
|
||||||
|
expect(screen.queryByText(feedbackElementLabel)).not.toBeInTheDocument()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
it(`Should show one SVG with title ${expectSVGTitle}`, async () => {
|
||||||
|
await waitFor(async () => {
|
||||||
|
const svg = within(screen.getByText(feedbackElementLabel).parentElement as HTMLElement).findByRole('img')
|
||||||
|
expect(await svg).toHaveTextContent(expectSVGTitle)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
})
|
||||||
|
});
|
||||||
@@ -20,7 +20,9 @@ const check = (
|
|||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg"
|
className="w-6 h-6 las la-check text-green-500 dark:text-green-500 mr-2 text-lg"
|
||||||
|
role="img"
|
||||||
>
|
>
|
||||||
|
<title>Matches</title>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -36,7 +38,9 @@ const cross = (
|
|||||||
viewBox="0 0 24 24"
|
viewBox="0 0 24 24"
|
||||||
strokeWidth={1.5}
|
strokeWidth={1.5}
|
||||||
stroke="currentColor"
|
stroke="currentColor"
|
||||||
|
role="img"
|
||||||
>
|
>
|
||||||
|
<title>Doesn't match</title>
|
||||||
<path
|
<path
|
||||||
strokeLinecap="round"
|
strokeLinecap="round"
|
||||||
strokeLinejoin="round"
|
strokeLinejoin="round"
|
||||||
@@ -60,12 +64,14 @@ export default function PasswordComplexity({
|
|||||||
|
|
||||||
return (
|
return (
|
||||||
<div className="mb-4 grid grid-cols-2 gap-x-8 gap-y-2">
|
<div className="mb-4 grid grid-cols-2 gap-x-8 gap-y-2">
|
||||||
<div className="flex flex-row items-center">
|
{passwordComplexitySettings.minLength != undefined ? (
|
||||||
{hasMinLength ? check : cross}
|
<div className="flex flex-row items-center">
|
||||||
<span className={desc}>
|
{hasMinLength ? check : cross}
|
||||||
Password length {passwordComplexitySettings.minLength}
|
<span className={desc}>
|
||||||
</span>
|
Password length {passwordComplexitySettings.minLength}
|
||||||
</div>
|
</span>
|
||||||
|
</div>
|
||||||
|
) : <span />}
|
||||||
<div className="flex flex-row items-center">
|
<div className="flex flex-row items-center">
|
||||||
{hasSymbol ? check : cross}
|
{hasSymbol ? check : cross}
|
||||||
<span className={desc}>has Symbol</span>
|
<span className={desc}>has Symbol</span>
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ export {
|
|||||||
|
|
||||||
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
export { type LegalAndSupportSettings } from "./proto/server/zitadel/settings/v2alpha/legal_settings";
|
||||||
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
export { type PasswordComplexitySettings } from "./proto/server/zitadel/settings/v2alpha/password_settings";
|
||||||
|
export { type ResourceOwnerType } from "./proto/server/zitadel/settings/v2alpha/settings";
|
||||||
|
|
||||||
import {
|
import {
|
||||||
getServers,
|
getServers,
|
||||||
|
|||||||
10
pnpm-lock.yaml
generated
10
pnpm-lock.yaml
generated
@@ -139,8 +139,8 @@ importers:
|
|||||||
specifier: 18.11.9
|
specifier: 18.11.9
|
||||||
version: 18.11.9
|
version: 18.11.9
|
||||||
'@types/react':
|
'@types/react':
|
||||||
specifier: 18.0.25
|
specifier: 18.2.8
|
||||||
version: 18.0.25
|
version: 18.2.8
|
||||||
'@types/react-dom':
|
'@types/react-dom':
|
||||||
specifier: 18.0.9
|
specifier: 18.0.9
|
||||||
version: 18.0.9
|
version: 18.0.9
|
||||||
@@ -5886,7 +5886,7 @@ packages:
|
|||||||
/@types/react-dom@18.0.9:
|
/@types/react-dom@18.0.9:
|
||||||
resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==}
|
resolution: {integrity: sha512-qnVvHxASt/H7i+XG1U1xMiY5t+IHcPGUK7TDMDzom08xa7e86eCeKOiLZezwCKVxJn6NEiiy2ekgX8aQssjIKg==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/react': 18.0.25
|
'@types/react': 17.0.52
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react@17.0.52:
|
/@types/react@17.0.52:
|
||||||
@@ -5897,8 +5897,8 @@ packages:
|
|||||||
csstype: 3.1.1
|
csstype: 3.1.1
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/@types/react@18.0.25:
|
/@types/react@18.2.8:
|
||||||
resolution: {integrity: sha512-xD6c0KDT4m7n9uD4ZHi02lzskaiqcBxf4zi+tXZY98a04wvc0hi/TcCPC2FOESZi51Nd7tlUeOJY8RofL799/g==}
|
resolution: {integrity: sha512-lTyWUNrd8ntVkqycEEplasWy2OxNlShj3zqS0LuB1ENUGis5HodmhM7DtCoUGbxj3VW/WsGA0DUhpG6XrM7gPA==}
|
||||||
dependencies:
|
dependencies:
|
||||||
'@types/prop-types': 15.7.5
|
'@types/prop-types': 15.7.5
|
||||||
'@types/scheduler': 0.16.2
|
'@types/scheduler': 0.16.2
|
||||||
|
|||||||
Reference in New Issue
Block a user