mirror of
https://github.com/logto-io/logto.git
synced 2025-03-31 22:51:25 -05:00
fix(console): fix sign-in method can not removed bug (#7175)
* fix(console): fix sign-in method can't removed bug fix sign-in methods can't removed bug. Also add new integration tests * chore(console): clean up legacy code clean up legacy code
This commit is contained in:
parent
cdc1acb238
commit
fd9b03ea08
5 changed files with 564 additions and 56 deletions
|
@ -1,5 +1,6 @@
|
|||
import { SignInIdentifier } from '@logto/schemas';
|
||||
import { AlternativeSignUpIdentifier, SignInIdentifier } from '@logto/schemas';
|
||||
import { conditional } from '@silverhand/essentials';
|
||||
import { useCallback } from 'react';
|
||||
import { Controller, useFieldArray, useFormContext } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -48,22 +49,49 @@ function SignInMethodEditBox() {
|
|||
|
||||
const {
|
||||
identifier: signUpIdentifier,
|
||||
identifiers: signUpIdentifiers,
|
||||
identifiers,
|
||||
password: isSignUpPasswordRequired,
|
||||
verify: isSignUpVerificationRequired,
|
||||
} = signUp;
|
||||
|
||||
const requiredSignInIdentifiers = signUpIdentifiersMapping[signUpIdentifier];
|
||||
const signUpIdentifiers = identifiers.map(({ identifier }) => identifier);
|
||||
|
||||
// TODO: Remove this dev feature guard when multi sign-up identifiers are launched
|
||||
const ignoredWarningConnectors = isDevFeaturesEnabled
|
||||
? getSignUpIdentifiersRequiredConnectors(signUpIdentifiers.map(({ identifier }) => identifier))
|
||||
? getSignUpIdentifiersRequiredConnectors(signUpIdentifiers)
|
||||
: getSignUpRequiredConnectorTypes(signUpIdentifier);
|
||||
|
||||
const signInIdentifierOptions = signInIdentifiers.filter((candidateIdentifier) =>
|
||||
fields.every(({ identifier }) => identifier !== candidateIdentifier)
|
||||
);
|
||||
|
||||
const isVerificationCodeCheckable = useCallback(
|
||||
(identifier: SignInIdentifier) => {
|
||||
if (identifier === SignInIdentifier.Username) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (isSignUpPasswordRequired) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (!isDevFeaturesEnabled) {
|
||||
return !isSignUpVerificationRequired;
|
||||
}
|
||||
|
||||
// If the sign-in identifier is also enabled for sign-up.
|
||||
const signUpVerificationRequired = signUpIdentifiers.some(
|
||||
(signUpIdentifier) =>
|
||||
signUpIdentifier === identifier ||
|
||||
signUpIdentifier === AlternativeSignUpIdentifier.EmailOrPhone
|
||||
);
|
||||
|
||||
return !signUpVerificationRequired;
|
||||
},
|
||||
[isSignUpPasswordRequired, isSignUpVerificationRequired, signUpIdentifiers]
|
||||
);
|
||||
|
||||
return (
|
||||
<div>
|
||||
<DragDropProvider>
|
||||
|
@ -116,9 +144,7 @@ function SignInMethodEditBox() {
|
|||
identifier !== SignInIdentifier.Username &&
|
||||
(isDevFeaturesEnabled || !isSignUpPasswordRequired)
|
||||
}
|
||||
isVerificationCodeCheckable={
|
||||
!(isSignUpVerificationRequired && !isSignUpPasswordRequired)
|
||||
}
|
||||
isVerificationCodeCheckable={isVerificationCodeCheckable(value.identifier)}
|
||||
isDeletable={
|
||||
isDevFeaturesEnabled || !requiredSignInIdentifiers.includes(identifier)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { AlternativeSignUpIdentifier, SignInIdentifier } from '@logto/schemas';
|
||||
import { useEffect, useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Controller, useFormContext, useWatch } from 'react-hook-form';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
|
@ -22,16 +22,9 @@ function SignUpForm() {
|
|||
setValue,
|
||||
getValues,
|
||||
trigger,
|
||||
formState: { submitCount, dirtyFields },
|
||||
formState: { submitCount },
|
||||
} = useFormContext<SignInExperienceForm>();
|
||||
|
||||
// Note: `useWatch` is a hook that returns the updated value on every render.
|
||||
// Unlike `watch`, it doesn't require a re-render to get the updated value (alway return the current ref).
|
||||
const signUp = useWatch({
|
||||
control,
|
||||
name: 'signUp',
|
||||
});
|
||||
|
||||
const signUpIdentifiers = useWatch({
|
||||
control,
|
||||
name: 'signUp.identifiers',
|
||||
|
@ -44,40 +37,11 @@ function SignUpForm() {
|
|||
};
|
||||
}, [signUpIdentifiers]);
|
||||
|
||||
// Should sync the sign-up identifier auth settings when the sign-up identifiers changed
|
||||
// TODO: need to check with designer
|
||||
useEffect(() => {
|
||||
// Only trigger the effect when the identifiers field is dirty
|
||||
const isIdentifiersDirty = dirtyFields.signUp?.identifiers;
|
||||
if (!isIdentifiersDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const identifiers = signUpIdentifiers.map(({ identifier }) => identifier);
|
||||
if (identifiers.length === 0) {
|
||||
setValue('signUp.password', false);
|
||||
setValue('signUp.verify', false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (identifiers.includes(SignInIdentifier.Username)) {
|
||||
setValue('signUp.password', true);
|
||||
}
|
||||
|
||||
// Disable verification when the primary identifier is username,
|
||||
// otherwise enable it for the rest of the identifiers (email, phone, emailOrPhone)
|
||||
setValue('signUp.verify', identifiers[0] !== SignInIdentifier.Username);
|
||||
}, [dirtyFields.signUp?.identifiers, setValue, signUpIdentifiers]);
|
||||
|
||||
// Sync sign-in methods when sign-up methods change
|
||||
useEffect(() => {
|
||||
// Only trigger the effect when the sign-up field is dirty
|
||||
const isIdentifiersDirty = dirtyFields.signUp;
|
||||
if (!isIdentifiersDirty) {
|
||||
return;
|
||||
}
|
||||
|
||||
const syncSignInMethods = useCallback(() => {
|
||||
const signInMethods = getValues('signIn.methods');
|
||||
const signUp = getValues('signUp');
|
||||
|
||||
const { password, identifiers } = signUp;
|
||||
|
||||
const enabledSignUpIdentifiers = identifiers.reduce<SignInIdentifier[]>(
|
||||
|
@ -129,7 +93,7 @@ function SignUpForm() {
|
|||
void trigger('signIn.methods');
|
||||
}, 0);
|
||||
}
|
||||
}, [dirtyFields.signUp, getValues, setValue, signUp, submitCount, trigger]);
|
||||
}, [getValues, setValue, submitCount, trigger]);
|
||||
|
||||
return (
|
||||
<Card>
|
||||
|
@ -138,7 +102,7 @@ function SignUpForm() {
|
|||
<FormFieldDescription>
|
||||
{t('sign_in_exp.sign_up_and_sign_in.sign_up.identifier_description')}
|
||||
</FormFieldDescription>
|
||||
<SignUpIdentifiersEditBox />
|
||||
<SignUpIdentifiersEditBox syncSignInMethods={syncSignInMethods} />
|
||||
</FormField>
|
||||
{shouldShowAuthenticationFields && (
|
||||
<FormField title="sign_in_exp.sign_up_and_sign_in.sign_up.sign_up_authentication">
|
||||
|
@ -153,7 +117,10 @@ function SignUpForm() {
|
|||
<Checkbox
|
||||
label={t('sign_in_exp.sign_up_and_sign_in.sign_up.set_a_password_option')}
|
||||
checked={value}
|
||||
onChange={onChange}
|
||||
onChange={(value) => {
|
||||
onChange(value);
|
||||
syncSignInMethods();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
/>
|
||||
|
|
|
@ -4,7 +4,7 @@ import {
|
|||
type SignUpIdentifier,
|
||||
} from '@logto/schemas';
|
||||
import { t } from 'i18next';
|
||||
import { useMemo } from 'react';
|
||||
import { useCallback, useMemo } from 'react';
|
||||
import { Controller, useFieldArray, useFormContext, useWatch } from 'react-hook-form';
|
||||
|
||||
import { DragDropProvider, DraggableItem } from '@/ds-components/DragDrop';
|
||||
|
@ -29,8 +29,15 @@ const emailOrPhoneOption = {
|
|||
|
||||
const signUpIdentifierOptions = [...signInIdentifierOptions, emailOrPhoneOption];
|
||||
|
||||
function SignUpIdentifiersEditBox() {
|
||||
const { control } = useFormContext<SignInExperienceForm>();
|
||||
type Props = {
|
||||
/**
|
||||
* Sync the sign-in methods when the sign-up settings change.
|
||||
*/
|
||||
readonly syncSignInMethods: () => void;
|
||||
};
|
||||
|
||||
function SignUpIdentifiersEditBox({ syncSignInMethods }: Props) {
|
||||
const { control, getValues, setValue } = useFormContext<SignInExperienceForm>();
|
||||
|
||||
const signUpIdentifiers = useWatch({ control, name: 'signUp.identifiers' });
|
||||
|
||||
|
@ -41,6 +48,37 @@ function SignUpIdentifiersEditBox() {
|
|||
name: 'signUp.identifiers',
|
||||
});
|
||||
|
||||
// Revalidate the primary identifier authentication fields when the identifiers change
|
||||
const onSignUpIdentifiersChange = useCallback(() => {
|
||||
const identifiers = getValues('signUp.identifiers').map(({ identifier }) => identifier);
|
||||
setValue('signUp.verify', identifiers[0] !== SignInIdentifier.Username);
|
||||
syncSignInMethods();
|
||||
}, [getValues, setValue, syncSignInMethods]);
|
||||
|
||||
const onDeleteSignUpIdentifier = useCallback(() => {
|
||||
const identifiers = getValues('signUp.identifiers').map(({ identifier }) => identifier);
|
||||
|
||||
if (identifiers.length === 0) {
|
||||
setValue('signUp.password', false);
|
||||
setValue('signUp.verify', false);
|
||||
// Password changed need to sync sign-in methods
|
||||
syncSignInMethods();
|
||||
return;
|
||||
}
|
||||
|
||||
onSignUpIdentifiersChange();
|
||||
}, [getValues, onSignUpIdentifiersChange, setValue, syncSignInMethods]);
|
||||
|
||||
const onAppendSignUpIdentifier = useCallback(
|
||||
(identifier: SignUpIdentifier) => {
|
||||
if (identifier === SignInIdentifier.Username) {
|
||||
setValue('signUp.password', true);
|
||||
}
|
||||
onSignUpIdentifiersChange();
|
||||
},
|
||||
[onSignUpIdentifiersChange, setValue]
|
||||
);
|
||||
|
||||
const options = useMemo<
|
||||
Array<{
|
||||
value: SignUpIdentifier;
|
||||
|
@ -89,7 +127,10 @@ function SignUpIdentifiersEditBox() {
|
|||
key={id}
|
||||
id={id}
|
||||
sortIndex={index}
|
||||
moveItem={swap}
|
||||
moveItem={(dragIndex, hoverIndex) => {
|
||||
swap(dragIndex, hoverIndex);
|
||||
onSignUpIdentifiersChange();
|
||||
}}
|
||||
className={styles.draggleItemContainer}
|
||||
>
|
||||
<Controller
|
||||
|
@ -121,6 +162,7 @@ function SignUpIdentifiersEditBox() {
|
|||
errorMessage={error?.message}
|
||||
onDelete={() => {
|
||||
remove(index);
|
||||
onDeleteSignUpIdentifier();
|
||||
}}
|
||||
/>
|
||||
)}
|
||||
|
@ -135,6 +177,7 @@ function SignUpIdentifiersEditBox() {
|
|||
hasSelectedIdentifiers={signUpIdentifiers.length > 0}
|
||||
onSelected={(identifier) => {
|
||||
append({ identifier });
|
||||
onAppendSignUpIdentifier(identifier);
|
||||
}}
|
||||
/>
|
||||
</div>
|
||||
|
|
|
@ -1,3 +1,4 @@
|
|||
/* eslint-disable max-lines */
|
||||
import { logtoConsoleUrl as logtoConsoleUrlString } from '#src/constants.js';
|
||||
import {
|
||||
expectModalWithTitle,
|
||||
|
@ -7,7 +8,13 @@ import {
|
|||
goToAdminConsole,
|
||||
waitForToast,
|
||||
} from '#src/ui-helpers/index.js';
|
||||
import { expectNavigation, appendPathname, devFeatureDisabledTest } from '#src/utils.js';
|
||||
import {
|
||||
expectNavigation,
|
||||
appendPathname,
|
||||
devFeatureDisabledTest,
|
||||
devFeatureTest,
|
||||
waitFor,
|
||||
} from '#src/utils.js';
|
||||
|
||||
import { expectToSaveSignInExperience, waitForFormCard } from '../helpers.js';
|
||||
|
||||
|
@ -21,7 +28,9 @@ import {
|
|||
expectToDeleteSocialConnector,
|
||||
} from './connector-setup-helpers.js';
|
||||
import {
|
||||
cleanUpSignInAndSignUpIdentifiers,
|
||||
expectToAddSignInMethod,
|
||||
expectToAddSignUpMethod,
|
||||
expectToAddSocialSignInConnector,
|
||||
expectToClickSignInMethodAuthnOption,
|
||||
expectToClickSignUpAuthnOption,
|
||||
|
@ -30,6 +39,7 @@ import {
|
|||
expectToResetSignUpAndSignInConfig,
|
||||
expectToSelectSignUpIdentifier,
|
||||
expectToSwapSignInMethodAuthnOption,
|
||||
resetSignUpAndSignInConfigToUsernamePassword,
|
||||
} from './helpers.js';
|
||||
|
||||
await page.setViewport({ width: 1920, height: 1080 });
|
||||
|
@ -75,7 +85,7 @@ describe('sign-in experience(happy path): sign-up and sign-in', () => {
|
|||
await expectToResetSignUpAndSignInConfig(page);
|
||||
});
|
||||
|
||||
it('select email as sign-in method and disable password settings for sign-up', async () => {
|
||||
it('select email as sign-up method and disable password settings for sign-up', async () => {
|
||||
await expectToSelectSignUpIdentifier(page, 'Email address');
|
||||
// Disable password settings for sign-up
|
||||
await expectToClickSignUpAuthnOption(page, 'Create your password');
|
||||
|
@ -594,6 +604,391 @@ describe('sign-in experience(happy path): sign-up and sign-in', () => {
|
|||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('email as sign-up identifier', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select email as sign-up identifier', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Email address', false);
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update email sign-in method', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Toggle off password
|
||||
* - Email address: verification code
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Toggle on password
|
||||
* - Email address: verification code + password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('add username sign-in method', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToAddSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('add & update phone number sign-in method', async () => {
|
||||
await expectToAddSignInMethod(page, 'Phone number');
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Username: password
|
||||
* - Phone number: password + verification code
|
||||
*/
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Username: password
|
||||
* - Phone number: verification code
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Phone number: verification code
|
||||
*/
|
||||
await expectToRemoveSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Phone number: password + verification code
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('email as sign-up identifier (password and verify)', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select email as sign-up identifier and enable password settings for sign-up', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Email address', false);
|
||||
// Enable password settings for sign-up
|
||||
await expectToClickSignUpAuthnOption(page, 'Create your password');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update email sign-in method', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
*/
|
||||
// Sign-in method: Email address + verification code + password
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await expectToSaveSignInExperience(page);
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('add phone number & username as sign-in method', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password + verification code
|
||||
*/
|
||||
await expectToAddSignInMethod(page, 'Phone number');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToAddSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password + verification code
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('email or phone as sign-up identifier (verify only', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select email or phone as sign-up identifier and disable password settings for sign-up', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Email address or phone number', false);
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password + verification code
|
||||
* - Phone number: password + verification code
|
||||
*/
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update sign-in method configs', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Phone number: verification code + password
|
||||
*/
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await waitFor(100);
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
|
||||
await expectToSaveSignInExperience(page);
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code
|
||||
* - Phone number: verification code + password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code
|
||||
* - Phone number: verification code
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Password',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code
|
||||
* - Phone number: verification code
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToAddSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('email or phone as sign-up identifier (password & verify)', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select email or phone as sign-up identifier and enable password settings for sign-up', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Email address or phone number', false);
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password + verification code
|
||||
* - Phone number: password + verification code
|
||||
*/
|
||||
await expectToClickSignUpAuthnOption(page, 'Create your password');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update sign-in method configs', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
*/
|
||||
// Sign-in method: Email address + verification code + password
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await waitFor(100);
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
|
||||
await expectToSaveSignInExperience(page);
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await waitFor(100);
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code
|
||||
* - Phone number: verification code
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToAddSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('username and email as sign-up identifier', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select username and email as sign-up identifier', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Email address', false);
|
||||
await expectToAddSignUpMethod(page, 'Username');
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password + verification code
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update sign-in method configs', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await expectToSaveSignInExperience(page);
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
*/
|
||||
await expectToRemoveSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
devFeatureTest.describe('username and email or phone as sign-up identifier', () => {
|
||||
beforeAll(async () => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page);
|
||||
});
|
||||
afterAll(async () => {
|
||||
await resetSignUpAndSignInConfigToUsernamePassword(page);
|
||||
});
|
||||
|
||||
it('select username and email or phone as sign-up identifier', async () => {
|
||||
await expectToAddSignUpMethod(page, 'Username', false);
|
||||
await expectToAddSignUpMethod(page, 'Email address or phone number');
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password + verification code
|
||||
* - Phone number: password + verification code
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
|
||||
it('update sign-in method configs', async () => {
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: verification code + password
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Email address');
|
||||
await waitFor(100);
|
||||
await expectToSwapSignInMethodAuthnOption(page, 'Phone number');
|
||||
await expectToSaveSignInExperience(page);
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password
|
||||
* - Username: password
|
||||
*/
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Email address',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await waitFor(100);
|
||||
await expectToClickSignInMethodAuthnOption(page, {
|
||||
method: 'Phone number',
|
||||
option: 'Verification code',
|
||||
});
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
|
||||
/**
|
||||
* Sign-in method
|
||||
* - Email address: password
|
||||
* - Phone number: password
|
||||
*/
|
||||
await expectToRemoveSignInMethod(page, 'Username');
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
});
|
||||
});
|
||||
|
||||
it('can disable user registration', async () => {
|
||||
const switchSelector = 'label[class$=switch]:has(input[name=createAccountEnabled])';
|
||||
await expect(page).toClick(switchSelector);
|
||||
|
@ -604,3 +999,4 @@ describe('sign-in experience(happy path): sign-up and sign-in', () => {
|
|||
await expectToSaveSignInExperience(page);
|
||||
});
|
||||
});
|
||||
/* eslint-enable max-lines */
|
||||
|
|
|
@ -226,3 +226,79 @@ export const expectErrorsOnNavTab = async (
|
|||
text: error,
|
||||
});
|
||||
};
|
||||
|
||||
/* eslint-disable no-await-in-loop */
|
||||
|
||||
const cleanUpAllSignUpMethods = async (page: Page) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
||||
while (true) {
|
||||
const signUpItems = await page.$$('div[class$=signUpMethodItem]');
|
||||
if (signUpItems.length === 0) {
|
||||
break;
|
||||
}
|
||||
await expect(signUpItems[0]).toClick('button:last-of-type');
|
||||
await waitFor(100);
|
||||
}
|
||||
};
|
||||
|
||||
const cleanUpAllSignInMethods = async (page: Page) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition, no-constant-condition
|
||||
while (true) {
|
||||
const signInItems = await page.$$('div[class$=signInMethodItem]');
|
||||
if (signInItems.length === 0) {
|
||||
break;
|
||||
}
|
||||
await expect(signInItems[0]).toClick('div[class$=anchor] button:last-of-type');
|
||||
await waitFor(100);
|
||||
}
|
||||
};
|
||||
|
||||
export const cleanUpSignInAndSignUpIdentifiers = async (page: Page, needSave = true) => {
|
||||
const signUpItems = await page.$$('div[class$=signUpMethodItem]');
|
||||
const signInItems = await page.$$('div[class$=signInMethodItem]');
|
||||
|
||||
// Directly return if there is no sign-up or sign-in method
|
||||
if (signUpItems.length === 0 && signInItems.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
await cleanUpAllSignUpMethods(page);
|
||||
await cleanUpAllSignInMethods(page);
|
||||
|
||||
if (needSave) {
|
||||
await waitFor(100);
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
}
|
||||
};
|
||||
|
||||
export const resetSignUpAndSignInConfigToUsernamePassword = async (page: Page) => {
|
||||
await cleanUpSignInAndSignUpIdentifiers(page, false);
|
||||
await expectToAddSignUpMethod(page, 'Username', false);
|
||||
await waitFor(100);
|
||||
await expectToSaveSignInExperience(page, { needToConfirmChanges: true });
|
||||
};
|
||||
|
||||
export const expectToAddSignUpMethod = async (page: Page, method: string, isAddAnother = true) => {
|
||||
const signUpMethodsField = await expect(page).toMatchElement(
|
||||
'div[class$=field]:has(div[class$=headline] > div[class$=title])',
|
||||
{
|
||||
text: 'Sign-up identifiers',
|
||||
}
|
||||
);
|
||||
|
||||
// Click Add another
|
||||
await expect(signUpMethodsField).toClick('button span', {
|
||||
text: isAddAnother ? 'Add another' : 'Add sign-up method',
|
||||
});
|
||||
|
||||
// Wait for the dropdown to be rendered in the correct position
|
||||
await waitFor(100);
|
||||
await expect(page).toClick('.ReactModalPortal div[class$=dropdownContainer] div[role=menuitem]', {
|
||||
text: method,
|
||||
});
|
||||
|
||||
await page.waitForSelector('.ReactModalPortal div[class$=dropdownContainer]', {
|
||||
hidden: true,
|
||||
});
|
||||
};
|
||||
/* eslint-enable no-await-in-loop */
|
||||
|
|
Loading…
Add table
Reference in a new issue