From a2f1d7ed10e4bed046dd2da295b153e967ee67ce Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 11:33:22 +0530 Subject: [PATCH 01/53] Add inputComponent to exposed types --- lib/ts/types.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 8399585b5..1aa48a340 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -13,6 +13,7 @@ * under the License. */ +import type { InputProps } from "./recipe/emailpassword/components/library/input"; import type { BaseRecipeModule } from "./recipe/recipeModule/baseRecipeModule"; import type { NormalisedConfig as NormalisedRecipeModuleConfig } from "./recipe/recipeModule/types"; import type { TranslationFunc, TranslationStore } from "./translation/translationHelpers"; @@ -234,6 +235,11 @@ export type FormField = FormFieldBaseConfig & { * Whether the field is optional or not. */ optional?: boolean; + + /* + * Ability to add custom components + */ + inputComponent?: React.FC; }; export type APIFormField = { From d4ab51fe3eae86a86f6c59cd49f73160816a750d Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 12:22:03 +0530 Subject: [PATCH 02/53] Add inputComponent to normalised fields --- lib/ts/types.ts | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 1aa48a340..5785bdf77 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -289,6 +289,11 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; + + /* + * Ability to add custom components + */ + inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From 2e8c67dce4f9370b4418ebb603a0f7ba54374e7a Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 13:08:52 +0530 Subject: [PATCH 03/53] For testing only - use custom type definition for inputComponent --- lib/ts/types.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 5785bdf77..df8a4d112 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -239,7 +239,7 @@ export type FormField = FormFieldBaseConfig & { /* * Ability to add custom components */ - inputComponent?: React.FC; + inputComponent?: (props: InputProps) => JSX.Element; }; export type APIFormField = { From 578815a31a0c61273881b5e14c39b574c77d1c92 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 13:20:16 +0530 Subject: [PATCH 04/53] Input component already present in FormFieldThemeProps --- lib/ts/types.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/lib/ts/types.ts b/lib/ts/types.ts index df8a4d112..f651e96b8 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -289,11 +289,6 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; - - /* - * Ability to add custom components - */ - inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From fcf51e19f9e446281e8fff1330c766149e349668 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 13:28:47 +0530 Subject: [PATCH 05/53] Testing if git package is getting installed correctly --- .../components/themes/signInAndUp/signInHeader.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx index 822d6d2c3..caa7250b6 100644 --- a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx +++ b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx @@ -21,13 +21,15 @@ export const SignInHeader = withOverride( "EmailPasswordSignInHeader", function EmailPasswordSignInHeader({ onClick }: { onClick: (() => void) | undefined }): JSX.Element { const t = useTranslation(); + const testing = "TESTING"; return (

{t("EMAIL_PASSWORD_SIGN_IN_HEADER_TITLE")}
- {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START")} + {/* {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START")} */} + {testing} {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_SIGN_UP_LINK")} From 1b747f59407dc76fd078d4ef873d53e9305e4371 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 14:53:20 +0530 Subject: [PATCH 06/53] Run build for previous commits --- lib/build/emailpassword-shared6.js | 3 ++- lib/build/types.d.ts | 3 +++ lib/ts/types.ts | 7 ++++++- 3 files changed, 11 insertions(+), 2 deletions(-) diff --git a/lib/build/emailpassword-shared6.js b/lib/build/emailpassword-shared6.js index 86d336eb2..478c00869 100644 --- a/lib/build/emailpassword-shared6.js +++ b/lib/build/emailpassword-shared6.js @@ -809,6 +809,7 @@ var SignInForm = uiEntry.withOverride("EmailPasswordSignInForm", function EmailP var SignInHeader = uiEntry.withOverride("EmailPasswordSignInHeader", function EmailPasswordSignInHeader(_a) { var onClick = _a.onClick; var t = translationContext.useTranslation(); + var testing = "TESTING"; return jsxRuntime.jsxs(React.Fragment, { children: [ jsxRuntime.jsx( @@ -829,7 +830,7 @@ var SignInHeader = uiEntry.withOverride("EmailPasswordSignInHeader", function Em { "data-supertokens": "secondaryText" }, { children: [ - t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START"), + testing, jsxRuntime.jsx( "span", genericComponentOverrideContext.__assign( diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 1aa7f2568..223b152bf 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -1,3 +1,4 @@ +import type { InputProps } from "./recipe/emailpassword/components/library/input"; import type { BaseRecipeModule } from "./recipe/recipeModule/baseRecipeModule"; import type { NormalisedConfig as NormalisedRecipeModuleConfig } from "./recipe/recipeModule/types"; import type { TranslationFunc, TranslationStore } from "./translation/translationHelpers"; @@ -93,6 +94,7 @@ export declare type FormFieldBaseConfig = { export declare type FormField = FormFieldBaseConfig & { validate?: (value: any) => Promise; optional?: boolean; + inputComponent?: React.FC; }; export declare type APIFormField = { id: string; @@ -106,6 +108,7 @@ export declare type NormalisedFormField = { optional: boolean; autoComplete?: string; autofocus?: boolean; + inputComponent?: React.FC; }; export declare type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); export declare type FeatureBaseConfig = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index f651e96b8..5785bdf77 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -239,7 +239,7 @@ export type FormField = FormFieldBaseConfig & { /* * Ability to add custom components */ - inputComponent?: (props: InputProps) => JSX.Element; + inputComponent?: React.FC; }; export type APIFormField = { @@ -289,6 +289,11 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; + + /* + * Ability to add custom components + */ + inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From e2b95de92dcdb07a88dff965b4a97b45ddaef27d Mon Sep 17 00:00:00 2001 From: amitbadala Date: Fri, 20 Oct 2023 15:03:53 +0530 Subject: [PATCH 07/53] Remove inputComp from NormalizedFormField --- lib/build/types.d.ts | 1 - lib/ts/types.ts | 5 ----- 2 files changed, 6 deletions(-) diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 223b152bf..8e2086686 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -108,7 +108,6 @@ export declare type NormalisedFormField = { optional: boolean; autoComplete?: string; autofocus?: boolean; - inputComponent?: React.FC; }; export declare type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); export declare type FeatureBaseConfig = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 5785bdf77..1aa48a340 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -289,11 +289,6 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; - - /* - * Ability to add custom components - */ - inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From 52e7dab599ec2ac1a0abaee3dc9d7bcd1d89a02b Mon Sep 17 00:00:00 2001 From: amitbadala Date: Mon, 23 Oct 2023 18:39:56 +0530 Subject: [PATCH 08/53] Add tests for custom fields --- examples/for-tests/src/App.js | 49 +++++++++++- lib/build/types.d.ts | 1 + lib/ts/types.ts | 5 ++ test/end-to-end/signup.test.js | 132 +++++++++++++++++++++++++++++++++ test/helpers.js | 5 ++ 5 files changed, 191 insertions(+), 1 deletion(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index f65dac002..89e683e3a 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -168,6 +168,52 @@ const formFields = [ }, ]; +const customFields = [ + { + id: "ratings", + label: "Ratings", + inputComponent: ({ value, name, ...rest }) => ( + + ), + optional: true, + }, + { + id: "terms", + showLabels: false, + optional: false, + inputComponent: ({ value, name, ...rest }) => ( +

+ rest.onChange({ id: name, value: e.target.checked })}> + I agree to the terms and conditions +
+ ), + validate: async (value) => { + if (value) return undefined; + return "Please check Terms and conditions"; + }, + }, +]; + const testContext = getTestContext(); let recipeList = [ @@ -637,7 +683,8 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { style: theme, privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - formFields, + formFields: + localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES" ? formFields.concat(customFields) : formFields, }, }, }); diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 8e2086686..223b152bf 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -108,6 +108,7 @@ export declare type NormalisedFormField = { optional: boolean; autoComplete?: string; autofocus?: boolean; + inputComponent?: React.FC; }; export declare type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); export declare type FeatureBaseConfig = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 1aa48a340..5785bdf77 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -289,6 +289,11 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; + + /* + * Ability to add custom components + */ + inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 857bfbc42..f96aa2790 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -39,6 +39,7 @@ import { getGeneralError, waitForSTElement, backendBeforeEach, + getCustomComponents, } from "../helpers"; import { @@ -414,3 +415,134 @@ describe("SuperTokens SignUp => Server Error", function () { await waitForSTElement(page, "[data-supertokens~=generalError]", true); }); }); + +// CUSTOM FIELDS TEST + +describe("Signup custom fields test", function () { + let browser; + let page; + let consoleLogs; + + before(async function () { + await backendBeforeEach(); + + await fetch(`${TEST_SERVER_BASE_URL}/startst`, { + method: "POST", + }).catch(console.error); + + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: false, + }); + page = await browser.newPage(); + }); + + // after(async function () { + // await browser.close(); + // }); + + afterEach(function () { + return screenshotOnFailure(this, browser); + }); + + beforeEach(async function () { + consoleLogs = []; + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); + + let elem = await getLoginWithRedirectToSignUp(page); + await page.evaluate((e) => e.click(), elem); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + it("Check if the custom fields are loaded", async function () { + let text = await getAuthPageHeaderText(page); + assert.deepStrictEqual(text, "Sign Up"); + + // check if select dropdown is loaded + const selectDropdownExists = await getCustomComponents(page, "select"); + assert.ok(selectDropdownExists, "Select dropdown exists"); + + // check if checbox is loaded + const checkboxExists = await getCustomComponents(page, 'input[type="checkbox"]'); + assert.ok(checkboxExists, "Checkbox exists"); + }); + + it("Should show error messages, based on the validation", async function () { + await submitForm(page); + let formFieldErrors = await getFieldErrors(page); + + // 4 regular form field errors + + // 1 required custom field => terms checkbox + assert.deepStrictEqual(formFieldErrors, [ + "Field is not optional", + "Field is not optional", + "Field is not optional", + "Field is not optional", + "Field is not optional", + ]); + + // supply values for regular-required fields only + await setInputValues(page, [ + { name: "email", value: "jack.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@ssw0rd" }, + { name: "name", value: "John Doe" }, + { name: "age", value: "20" }, + ]); + await submitForm(page); + formFieldErrors = await getFieldErrors(page); + assert.deepStrictEqual(formFieldErrors, ["Field is not optional"]); + + // check terms and condition checkbox + let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + await page.evaluate((e) => e.click(), termsCheckbox); + await submitForm(page); + formFieldErrors = await getFieldErrors(page); + assert.deepStrictEqual(formFieldErrors, []); + }); + + it.only("Check if custom values are part of the signup payload", async function () { + const customFieldNames = ["terms", "ratings"]; + const requestHandler = (request) => { + if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { + assert.ok( + customFieldNames.every((key) => request.data.hasOwnProperty(key)), + "Custom field are part of the payload" + ); + } + return request.continue(); + }; + + try { + await page.setRequestInterception(true); + page.on("request", requestHandler); + + // Fill the entire form + await setInputValues(page, [ + { name: "email", value: "john.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@assw0rd" }, + { name: "name", value: "Supertokens" }, + { name: "age", value: "20" }, + ]); + + // Select value from dropdown (ratings) + let dropdownEle = await getCustomComponents(page, '[name="ratings"]'); + const dropdownValue = 3; + await page.select(dropdownEle, dropdownValue); + + // Check terms and condition checkbox + let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + await page.evaluate((e) => e.click(), termsCheckbox); + + await submitForm(page); + } finally { + page.off("request", requestHandler); + await page.setRequestInterception(false); + } + }); +}); diff --git a/test/helpers.js b/test/helpers.js index bc7138534..56f2fa81a 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -28,6 +28,7 @@ import { import path from "path"; import assert from "assert"; import mkdirp from "mkdirp"; +import { async } from "regenerator-runtime"; const SESSION_STORAGE_STATE_KEY = "supertokens-oauth-state"; @@ -233,6 +234,10 @@ export async function getInputNames(page) { ); } +export async function getCustomComponents(page, element) { + return waitForSTElement(page, element); +} + export async function getInputAdornmentsSuccess(page) { await waitForSTElement(page); return await page.evaluate( From eb36b369f21bac996f247df28d4a88c02b36f6b9 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 11:32:50 +0530 Subject: [PATCH 09/53] Remove testing ele --- .../components/themes/signInAndUp/signInHeader.tsx | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx index caa7250b6..822d6d2c3 100644 --- a/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx +++ b/lib/ts/recipe/emailpassword/components/themes/signInAndUp/signInHeader.tsx @@ -21,15 +21,13 @@ export const SignInHeader = withOverride( "EmailPasswordSignInHeader", function EmailPasswordSignInHeader({ onClick }: { onClick: (() => void) | undefined }): JSX.Element { const t = useTranslation(); - const testing = "TESTING"; return (

{t("EMAIL_PASSWORD_SIGN_IN_HEADER_TITLE")}
- {/* {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START")} */} - {testing} + {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START")} {t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_SIGN_UP_LINK")} From e6fc94207537856d2310acfb029e338932ad9bc7 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 11:50:20 +0530 Subject: [PATCH 10/53] Move the custom fields tests into existing describe --- test/end-to-end/signup.test.js | 258 ++++++++++++++++----------------- 1 file changed, 127 insertions(+), 131 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index f96aa2790..d2a306e23 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -343,6 +343,133 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(emailError, "This email already exists. Please sign in instead."); }); }); + + // CUSTOM FIELDS TEST + + describe("Signup custom fields test", function () { + before(async function () { + await backendBeforeEach(); + + await fetch(`${TEST_SERVER_BASE_URL}/startst`, { + method: "POST", + }).catch(console.error); + + browser = await puppeteer.launch({ + args: ["--no-sandbox", "--disable-setuid-sandbox"], + headless: false, + }); + page = await browser.newPage(); + }); + + // after(async function () { + // await browser.close(); + // }); + + afterEach(function () { + return screenshotOnFailure(this, browser); + }); + + beforeEach(async function () { + consoleLogs = []; + consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); + await Promise.all([ + page.goto(`${TEST_CLIENT_BASE_URL}`), + page.waitForNavigation({ waitUntil: "networkidle0" }), + ]); + + await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); + + let elem = await getLoginWithRedirectToSignUp(page); + await page.evaluate((e) => e.click(), elem); + await page.waitForNavigation({ waitUntil: "networkidle0" }); + }); + + it.only("Check if the custom fields are loaded", async function () { + let text = await getAuthPageHeaderText(page); + assert.deepStrictEqual(text, "Sign Up"); + + // check if select dropdown is loaded + const selectDropdownExists = await getCustomComponents(page, "select"); + assert.ok(selectDropdownExists, "Select dropdown exists"); + + // check if checbox is loaded + const checkboxExists = await getCustomComponents(page, 'input[type="checkbox"]'); + assert.ok(checkboxExists, "Checkbox exists"); + }); + + it.only("Should show error messages, based on the validation", async function () { + await submitForm(page); + let formFieldErrors = await getFieldErrors(page); + + // 4 regular form field errors + + // 1 required custom field => terms checkbox + assert.deepStrictEqual(formFieldErrors, [ + "Field is not optional", + "Field is not optional", + "Field is not optional", + "Field is not optional", + "Field is not optional", + ]); + + // supply values for regular-required fields only + await setInputValues(page, [ + { name: "email", value: "jack.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@ssw0rd" }, + { name: "name", value: "John Doe" }, + { name: "age", value: "20" }, + ]); + await submitForm(page); + formFieldErrors = await getFieldErrors(page); + assert.deepStrictEqual(formFieldErrors, ["Field is not optional"]); + + // check terms and condition checkbox + let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + await page.evaluate((e) => e.click(), termsCheckbox); + await submitForm(page); + formFieldErrors = await getFieldErrors(page); + assert.deepStrictEqual(formFieldErrors, []); + }); + + it.only("Check if custom values are part of the signup payload", async function () { + const customFieldNames = ["terms", "ratings"]; + const requestHandler = (request) => { + if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { + assert.ok( + customFieldNames.every((key) => request.data.hasOwnProperty(key)), + "Custom field are part of the payload" + ); + } + return request.continue(); + }; + + try { + await page.setRequestInterception(true); + page.on("request", requestHandler); + + // Fill the entire form + await setInputValues(page, [ + { name: "email", value: "john.doe@supertokens.io" }, + { name: "password", value: "Str0ngP@assw0rd" }, + { name: "name", value: "Supertokens" }, + { name: "age", value: "20" }, + ]); + + // Select value from dropdown (ratings) + let dropdownEle = await getCustomComponents(page, '[name="ratings"]'); + const dropdownValue = 3; + await page.select(dropdownEle, dropdownValue); + + // Check terms and condition checkbox + let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + await page.evaluate((e) => e.click(), termsCheckbox); + + await submitForm(page); + } finally { + page.off("request", requestHandler); + await page.setRequestInterception(false); + } + }); + }); }); describe("SuperTokens SignUp => Server Error", function () { @@ -415,134 +542,3 @@ describe("SuperTokens SignUp => Server Error", function () { await waitForSTElement(page, "[data-supertokens~=generalError]", true); }); }); - -// CUSTOM FIELDS TEST - -describe("Signup custom fields test", function () { - let browser; - let page; - let consoleLogs; - - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await puppeteer.launch({ - args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: false, - }); - page = await browser.newPage(); - }); - - // after(async function () { - // await browser.close(); - // }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - - beforeEach(async function () { - consoleLogs = []; - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - - await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); - - let elem = await getLoginWithRedirectToSignUp(page); - await page.evaluate((e) => e.click(), elem); - await page.waitForNavigation({ waitUntil: "networkidle0" }); - }); - - it("Check if the custom fields are loaded", async function () { - let text = await getAuthPageHeaderText(page); - assert.deepStrictEqual(text, "Sign Up"); - - // check if select dropdown is loaded - const selectDropdownExists = await getCustomComponents(page, "select"); - assert.ok(selectDropdownExists, "Select dropdown exists"); - - // check if checbox is loaded - const checkboxExists = await getCustomComponents(page, 'input[type="checkbox"]'); - assert.ok(checkboxExists, "Checkbox exists"); - }); - - it("Should show error messages, based on the validation", async function () { - await submitForm(page); - let formFieldErrors = await getFieldErrors(page); - - // 4 regular form field errors + - // 1 required custom field => terms checkbox - assert.deepStrictEqual(formFieldErrors, [ - "Field is not optional", - "Field is not optional", - "Field is not optional", - "Field is not optional", - "Field is not optional", - ]); - - // supply values for regular-required fields only - await setInputValues(page, [ - { name: "email", value: "jack.doe@supertokens.io" }, - { name: "password", value: "Str0ngP@ssw0rd" }, - { name: "name", value: "John Doe" }, - { name: "age", value: "20" }, - ]); - await submitForm(page); - formFieldErrors = await getFieldErrors(page); - assert.deepStrictEqual(formFieldErrors, ["Field is not optional"]); - - // check terms and condition checkbox - let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); - await page.evaluate((e) => e.click(), termsCheckbox); - await submitForm(page); - formFieldErrors = await getFieldErrors(page); - assert.deepStrictEqual(formFieldErrors, []); - }); - - it.only("Check if custom values are part of the signup payload", async function () { - const customFieldNames = ["terms", "ratings"]; - const requestHandler = (request) => { - if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { - assert.ok( - customFieldNames.every((key) => request.data.hasOwnProperty(key)), - "Custom field are part of the payload" - ); - } - return request.continue(); - }; - - try { - await page.setRequestInterception(true); - page.on("request", requestHandler); - - // Fill the entire form - await setInputValues(page, [ - { name: "email", value: "john.doe@supertokens.io" }, - { name: "password", value: "Str0ngP@assw0rd" }, - { name: "name", value: "Supertokens" }, - { name: "age", value: "20" }, - ]); - - // Select value from dropdown (ratings) - let dropdownEle = await getCustomComponents(page, '[name="ratings"]'); - const dropdownValue = 3; - await page.select(dropdownEle, dropdownValue); - - // Check terms and condition checkbox - let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); - await page.evaluate((e) => e.click(), termsCheckbox); - - await submitForm(page); - } finally { - page.off("request", requestHandler); - await page.setRequestInterception(false); - } - }); -}); From 8514575b82a27379c876febf84749f04d1e67034 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 13:52:34 +0530 Subject: [PATCH 11/53] Update dropdown values to avoid confusion --- examples/for-tests/src/App.js | 6 +++--- lib/build/emailpassword-shared6.js | 3 +-- 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 89e683e3a..84ce183d3 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -181,9 +181,9 @@ const customFields = [ - - - + + + ), optional: true, diff --git a/lib/build/emailpassword-shared6.js b/lib/build/emailpassword-shared6.js index 478c00869..86d336eb2 100644 --- a/lib/build/emailpassword-shared6.js +++ b/lib/build/emailpassword-shared6.js @@ -809,7 +809,6 @@ var SignInForm = uiEntry.withOverride("EmailPasswordSignInForm", function EmailP var SignInHeader = uiEntry.withOverride("EmailPasswordSignInHeader", function EmailPasswordSignInHeader(_a) { var onClick = _a.onClick; var t = translationContext.useTranslation(); - var testing = "TESTING"; return jsxRuntime.jsxs(React.Fragment, { children: [ jsxRuntime.jsx( @@ -830,7 +829,7 @@ var SignInHeader = uiEntry.withOverride("EmailPasswordSignInHeader", function Em { "data-supertokens": "secondaryText" }, { children: [ - testing, + t("EMAIL_PASSWORD_SIGN_IN_HEADER_SUBTITLE_START"), jsxRuntime.jsx( "span", genericComponentOverrideContext.__assign( From ccd46cca1f635e14834c1a8b5b188f30c8c9c514 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 13:53:41 +0530 Subject: [PATCH 12/53] Add helper func to set dropdown, better test title, use existing describe hooks --- test/end-to-end/signup.test.js | 68 ++++++++++------------------------ test/helpers.js | 15 ++++++++ 2 files changed, 35 insertions(+), 48 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index d2a306e23..c0ae01fc6 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -40,6 +40,7 @@ import { waitForSTElement, backendBeforeEach, getCustomComponents, + setSelectDropdownValue, } from "../helpers"; import { @@ -347,44 +348,17 @@ describe("SuperTokens SignUp", function () { // CUSTOM FIELDS TEST describe("Signup custom fields test", function () { - before(async function () { - await backendBeforeEach(); - - await fetch(`${TEST_SERVER_BASE_URL}/startst`, { - method: "POST", - }).catch(console.error); - - browser = await puppeteer.launch({ - args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: false, - }); - page = await browser.newPage(); - }); - - // after(async function () { - // await browser.close(); - // }); - - afterEach(function () { - return screenshotOnFailure(this, browser); - }); - beforeEach(async function () { - consoleLogs = []; - consoleLogs = await clearBrowserCookiesWithoutAffectingConsole(page, consoleLogs); - await Promise.all([ - page.goto(`${TEST_CLIENT_BASE_URL}`), - page.waitForNavigation({ waitUntil: "networkidle0" }), - ]); - + // set cookie and reload which loads the form with custom field await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); - let elem = await getLoginWithRedirectToSignUp(page); - await page.evaluate((e) => e.click(), elem); - await page.waitForNavigation({ waitUntil: "networkidle0" }); + await page.reload({ + waitUntil: "domcontentloaded", + }); + await toggleSignInSignUp(page); }); - it.only("Check if the custom fields are loaded", async function () { + it("Check if the custom fields are loaded", async function () { let text = await getAuthPageHeaderText(page); assert.deepStrictEqual(text, "Sign Up"); @@ -397,7 +371,7 @@ describe("SuperTokens SignUp", function () { assert.ok(checkboxExists, "Checkbox exists"); }); - it.only("Should show error messages, based on the validation", async function () { + it("Should show error messages, based on optional flag", async function () { await submitForm(page); let formFieldErrors = await getFieldErrors(page); @@ -430,23 +404,24 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(formFieldErrors, []); }); - it.only("Check if custom values are part of the signup payload", async function () { - const customFieldNames = ["terms", "ratings"]; + it("Check if custom values are part of the signup payload", async function () { + const customFields = { + terms: true, // checked + ratings: "best", + }; const requestHandler = (request) => { if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { - assert.ok( - customFieldNames.every((key) => request.data.hasOwnProperty(key)), - "Custom field are part of the payload" - ); + Object.keys(customFields).every((key) => { + assert.equal(data[key], customFields[key]); + }); } return request.continue(); }; + await page.setRequestInterception(true); + page.on("request", requestHandler); + // Fill and submit the form with custom fields try { - await page.setRequestInterception(true); - page.on("request", requestHandler); - - // Fill the entire form await setInputValues(page, [ { name: "email", value: "john.doe@supertokens.io" }, { name: "password", value: "Str0ngP@assw0rd" }, @@ -454,10 +429,7 @@ describe("SuperTokens SignUp", function () { { name: "age", value: "20" }, ]); - // Select value from dropdown (ratings) - let dropdownEle = await getCustomComponents(page, '[name="ratings"]'); - const dropdownValue = 3; - await page.select(dropdownEle, dropdownValue); + await setSelectDropdownValue(page, 'select[name="ratings"]', customFields["ratings"]); // Check terms and condition checkbox let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); diff --git a/test/helpers.js b/test/helpers.js index 56f2fa81a..01ab1aa69 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -422,6 +422,21 @@ export async function setInputValues(page, fields) { return await new Promise((r) => setTimeout(r, 300)); } +export async function setSelectDropdownValue(page, selector, optionValue) { + return await page.evaluate( + (selector, optionValue, ST_ROOT_SELECTOR) => { + const dropdownElement = document.querySelector(ST_ROOT_SELECTOR).shadowRoot.querySelector(selector); + if (dropdownElement) { + dropdownElement.value = optionValue; + dropdownElement.dispatchEvent(new Event("change", { bubbles: false })); + } + }, + selector, + optionValue, + ST_ROOT_SELECTOR + ); +} + export async function clearBrowserCookiesWithoutAffectingConsole(page, console) { let toReturn = [...console]; const client = await page.target().createCDPSession(); From 16b38e5de97ef305d006332685a1202b9e5fb51d Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 14:00:09 +0530 Subject: [PATCH 13/53] Use strict equal --- test/end-to-end/signup.test.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index c0ae01fc6..795e2eeaf 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -347,7 +347,7 @@ describe("SuperTokens SignUp", function () { // CUSTOM FIELDS TEST - describe("Signup custom fields test", function () { + describe.only("Signup custom fields test", function () { beforeEach(async function () { // set cookie and reload which loads the form with custom field await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); @@ -412,7 +412,7 @@ describe("SuperTokens SignUp", function () { const requestHandler = (request) => { if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { Object.keys(customFields).every((key) => { - assert.equal(data[key], customFields[key]); + assert.strictEqual(data[key], customFields[key]); }); } return request.continue(); From 9136be9b5c27dc08a119a81a76778c716dfa7433 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 14:23:44 +0530 Subject: [PATCH 14/53] Update request --- test/end-to-end/signup.test.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 795e2eeaf..049b95de2 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -347,7 +347,7 @@ describe("SuperTokens SignUp", function () { // CUSTOM FIELDS TEST - describe.only("Signup custom fields test", function () { + describe("Signup custom fields test", function () { beforeEach(async function () { // set cookie and reload which loads the form with custom field await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS", "YES")); @@ -414,6 +414,8 @@ describe("SuperTokens SignUp", function () { Object.keys(customFields).every((key) => { assert.strictEqual(data[key], customFields[key]); }); + // remove it after updating backend to handle the custom fields + return request.abort(); } return request.continue(); }; From e365a13943ef7cdd3a472f8a852eab2a1a59511a Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 14:32:15 +0530 Subject: [PATCH 15/53] A seperate func to fetch custom comp not required --- test/end-to-end/signup.test.js | 9 ++++----- test/helpers.js | 4 ---- 2 files changed, 4 insertions(+), 9 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 049b95de2..96e5f396e 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -39,7 +39,6 @@ import { getGeneralError, waitForSTElement, backendBeforeEach, - getCustomComponents, setSelectDropdownValue, } from "../helpers"; @@ -363,11 +362,11 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(text, "Sign Up"); // check if select dropdown is loaded - const selectDropdownExists = await getCustomComponents(page, "select"); + const selectDropdownExists = await waitForSTElement(page, "select"); assert.ok(selectDropdownExists, "Select dropdown exists"); // check if checbox is loaded - const checkboxExists = await getCustomComponents(page, 'input[type="checkbox"]'); + const checkboxExists = await waitForSTElement(page, 'input[type="checkbox"]'); assert.ok(checkboxExists, "Checkbox exists"); }); @@ -397,7 +396,7 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(formFieldErrors, ["Field is not optional"]); // check terms and condition checkbox - let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + let termsCheckbox = await waitForSTElement(page, '[name="terms"]'); await page.evaluate((e) => e.click(), termsCheckbox); await submitForm(page); formFieldErrors = await getFieldErrors(page); @@ -434,7 +433,7 @@ describe("SuperTokens SignUp", function () { await setSelectDropdownValue(page, 'select[name="ratings"]', customFields["ratings"]); // Check terms and condition checkbox - let termsCheckbox = await getCustomComponents(page, '[name="terms"]'); + let termsCheckbox = await waitForSTElement(page, '[name="terms"]'); await page.evaluate((e) => e.click(), termsCheckbox); await submitForm(page); diff --git a/test/helpers.js b/test/helpers.js index 01ab1aa69..bd893efb8 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -234,10 +234,6 @@ export async function getInputNames(page) { ); } -export async function getCustomComponents(page, element) { - return waitForSTElement(page, element); -} - export async function getInputAdornmentsSuccess(page) { await waitForSTElement(page); return await page.evaluate( From c8b31dbbfb821597380ff62db3988fa005efbb97 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 18:33:22 +0530 Subject: [PATCH 16/53] Move inputComponent to signup types --- lib/build/recipe/emailpassword/types.d.ts | 8 ++++++-- lib/build/types.d.ts | 1 - lib/ts/recipe/emailpassword/types.ts | 13 +++++++------ lib/ts/types.ts | 5 ----- 4 files changed, 13 insertions(+), 14 deletions(-) diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index d819f58c8..1c9da91cf 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -94,7 +94,9 @@ export declare type NormalisedSignInFormFeatureConfig = NormalisedBaseConfig & { formFields: NormalisedFormField[]; }; export declare type FormFieldSignInConfig = FormFieldBaseConfig; -export declare type FormFieldSignUpConfig = FormField; +export declare type FormFieldSignUpConfig = FormField & { + inputComponent?: React.FC; +}; export declare type ResetPasswordUsingTokenUserInput = { disableDefaultUI?: boolean; submitNewPasswordForm?: FeatureBaseConfig; @@ -131,6 +133,9 @@ export declare type SignUpThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; + formFields: (FormFieldThemeProps & { + inputComponent?: React.FC; + })[]; }; export declare type SignInAndUpThemeProps = { signInForm: SignInThemeProps; @@ -144,7 +149,6 @@ export declare type SignInAndUpThemeProps = { }; export declare type FormFieldThemeProps = NormalisedFormField & { labelComponent?: JSX.Element; - inputComponent?: React.FC; showIsRequired?: boolean; clearOnSubmit?: boolean; }; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 223b152bf..3c70442b9 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -94,7 +94,6 @@ export declare type FormFieldBaseConfig = { export declare type FormField = FormFieldBaseConfig & { validate?: (value: any) => Promise; optional?: boolean; - inputComponent?: React.FC; }; export declare type APIFormField = { id: string; diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 253329077..274fa30b2 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -181,7 +181,12 @@ export type NormalisedSignInFormFeatureConfig = NormalisedBaseConfig & { export type FormFieldSignInConfig = FormFieldBaseConfig; -export type FormFieldSignUpConfig = FormField; +export type FormFieldSignUpConfig = FormField & { + /* + * Ability to add custom components + */ + inputComponent?: React.FC; +}; export type ResetPasswordUsingTokenUserInput = { /* @@ -255,6 +260,7 @@ export type SignUpThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; + formFields: (FormFieldThemeProps & { inputComponent?: React.FC })[]; }; export type SignInAndUpThemeProps = { @@ -274,11 +280,6 @@ export type FormFieldThemeProps = NormalisedFormField & { */ labelComponent?: JSX.Element; - /* - * Custom component that replaces the standard input component - */ - inputComponent?: React.FC; - /* * Show Is required (*) next to label */ diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 5785bdf77..595b24315 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -235,11 +235,6 @@ export type FormField = FormFieldBaseConfig & { * Whether the field is optional or not. */ optional?: boolean; - - /* - * Ability to add custom components - */ - inputComponent?: React.FC; }; export type APIFormField = { From 31d6e29f7d7df451de137ef97bcb08cac65d04c2 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 24 Oct 2023 18:38:13 +0530 Subject: [PATCH 17/53] Cleanup unwanted imports --- test/helpers.js | 1 - 1 file changed, 1 deletion(-) diff --git a/test/helpers.js b/test/helpers.js index bd893efb8..20ab9b6b4 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -28,7 +28,6 @@ import { import path from "path"; import assert from "assert"; import mkdirp from "mkdirp"; -import { async } from "regenerator-runtime"; const SESSION_STORAGE_STATE_KEY = "supertokens-oauth-state"; From 8d60068b288f4798bb0704682c22ce01721e6afc Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 11:42:27 +0530 Subject: [PATCH 18/53] Move inputComponent to signup types --- .../components/themes/signInAndUp/signIn.d.ts | 2 +- .../themes/signInAndUp/signInForm.d.ts | 2 +- lib/build/recipe/emailpassword/types.d.ts | 10 ++++++---- lib/build/types.d.ts | 2 -- lib/ts/recipe/emailpassword/types.ts | 19 ++++++++++++++++--- lib/ts/types.ts | 6 ------ 6 files changed, 24 insertions(+), 17 deletions(-) diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts index 2b40c960b..64639bb96 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signIn.d.ts @@ -1,7 +1,7 @@ /// export declare const SignIn: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; + formFields: Omit[]; error: string | undefined; } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts index 7dbc1321c..f57175ca5 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signInForm.d.ts @@ -1,7 +1,7 @@ /// export declare const SignInForm: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; + formFields: Omit[]; error: string | undefined; } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 1c9da91cf..458fe4cfc 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -117,7 +117,11 @@ declare type FormThemeBaseProps = ThemeBaseProps & { formFields: FormFieldThemeProps[]; error: string | undefined; }; -export declare type SignInThemeProps = FormThemeBaseProps & { +declare type SignInFormThemeBaseProps = ThemeBaseProps & { + formFields: Omit[]; + error: string | undefined; +}; +export declare type SignInThemeProps = SignInFormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -133,9 +137,6 @@ export declare type SignUpThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; - formFields: (FormFieldThemeProps & { - inputComponent?: React.FC; - })[]; }; export declare type SignInAndUpThemeProps = { signInForm: SignInThemeProps; @@ -151,6 +152,7 @@ export declare type FormFieldThemeProps = NormalisedFormField & { labelComponent?: JSX.Element; showIsRequired?: boolean; clearOnSubmit?: boolean; + inputComponent?: React.FC; }; export declare type FormFieldError = { id: string; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 3c70442b9..1aa7f2568 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -1,4 +1,3 @@ -import type { InputProps } from "./recipe/emailpassword/components/library/input"; import type { BaseRecipeModule } from "./recipe/recipeModule/baseRecipeModule"; import type { NormalisedConfig as NormalisedRecipeModuleConfig } from "./recipe/recipeModule/types"; import type { TranslationFunc, TranslationStore } from "./translation/translationHelpers"; @@ -107,7 +106,6 @@ export declare type NormalisedFormField = { optional: boolean; autoComplete?: string; autofocus?: boolean; - inputComponent?: React.FC; }; export declare type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); export declare type FeatureBaseConfig = { diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 274fa30b2..432008bb7 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -236,14 +236,23 @@ export type NormalisedEnterEmailForm = FeatureBaseConfig & { type FormThemeBaseProps = ThemeBaseProps & { /* - * Form fields to use in the signin form. + * Form fields to use in the signup form. */ formFields: FormFieldThemeProps[]; error: string | undefined; }; -export type SignInThemeProps = FormThemeBaseProps & { +type SignInFormThemeBaseProps = ThemeBaseProps & { + /* + * Form fields to use in the signin form. exclude custom component + */ + formFields: Omit[]; + + error: string | undefined; +}; + +export type SignInThemeProps = SignInFormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -260,7 +269,6 @@ export type SignUpThemeProps = FormThemeBaseProps & { config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; - formFields: (FormFieldThemeProps & { inputComponent?: React.FC })[]; }; export type SignInAndUpThemeProps = { @@ -289,6 +297,11 @@ export type FormFieldThemeProps = NormalisedFormField & { * Clears the field after calling the API. */ clearOnSubmit?: boolean; + + /* + * Ability to add custom components + */ + inputComponent?: React.FC; }; export type FormFieldError = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 595b24315..8399585b5 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -13,7 +13,6 @@ * under the License. */ -import type { InputProps } from "./recipe/emailpassword/components/library/input"; import type { BaseRecipeModule } from "./recipe/recipeModule/baseRecipeModule"; import type { NormalisedConfig as NormalisedRecipeModuleConfig } from "./recipe/recipeModule/types"; import type { TranslationFunc, TranslationStore } from "./translation/translationHelpers"; @@ -284,11 +283,6 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; - - /* - * Ability to add custom components - */ - inputComponent?: React.FC; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From 809d396734d896e295badfaa1cfb3c6b794330ed Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 13:54:32 +0530 Subject: [PATCH 19/53] Clean types --- lib/ts/recipe/emailpassword/types.ts | 21 +++++---------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 432008bb7..a34397de0 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -152,7 +152,7 @@ export type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { /* * Normalised form fields for SignUp. */ - formFields: NormalisedFormField[]; + formFields: (NormalisedFormField & { inputComponent?: React.FC })[]; /* * Privacy policy link for sign up form. @@ -230,29 +230,16 @@ export type NormalisedEnterEmailForm = FeatureBaseConfig & { formFields: NormalisedFormField[]; }; -/* - * Props Types. - */ - type FormThemeBaseProps = ThemeBaseProps & { /* * Form fields to use in the signup form. */ - formFields: FormFieldThemeProps[]; - - error: string | undefined; -}; - -type SignInFormThemeBaseProps = ThemeBaseProps & { - /* - * Form fields to use in the signin form. exclude custom component - */ formFields: Omit[]; error: string | undefined; }; -export type SignInThemeProps = SignInFormThemeBaseProps & { +export type SignInThemeProps = FormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -262,13 +249,15 @@ export type SignInThemeProps = SignInFormThemeBaseProps & { onSuccess: (result: { user: User }) => void; }; -export type SignUpThemeProps = FormThemeBaseProps & { +export type SignUpThemeProps = ThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; + formFields: FormFieldThemeProps[]; + error: string | undefined; }; export type SignInAndUpThemeProps = { From b3af734c45a2b457b7fffdc1a58a7e48711c9809 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 13:58:19 +0530 Subject: [PATCH 20/53] Update build files --- lib/build/recipe/emailpassword/types.d.ts | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 458fe4cfc..c1e4cb6f6 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -83,7 +83,9 @@ export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { termsOfServiceLink?: string; }; export declare type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { - formFields: NormalisedFormField[]; + formFields: (NormalisedFormField & { + inputComponent?: React.FC; + })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; }; @@ -114,14 +116,10 @@ export declare type NormalisedEnterEmailForm = FeatureBaseConfig & { formFields: NormalisedFormField[]; }; declare type FormThemeBaseProps = ThemeBaseProps & { - formFields: FormFieldThemeProps[]; - error: string | undefined; -}; -declare type SignInFormThemeBaseProps = ThemeBaseProps & { formFields: Omit[]; error: string | undefined; }; -export declare type SignInThemeProps = SignInFormThemeBaseProps & { +export declare type SignInThemeProps = FormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -130,13 +128,15 @@ export declare type SignInThemeProps = SignInFormThemeBaseProps & { forgotPasswordClick: () => void; onSuccess: (result: { user: User }) => void; }; -export declare type SignUpThemeProps = FormThemeBaseProps & { +export declare type SignUpThemeProps = ThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; config: NormalisedConfig; signInClicked?: () => void; onSuccess: (result: { user: User }) => void; + formFields: FormFieldThemeProps[]; + error: string | undefined; }; export declare type SignInAndUpThemeProps = { signInForm: SignInThemeProps; From fb99461df39dc4f6f3c0cb8e0b973d2e36709e18 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 14:05:36 +0530 Subject: [PATCH 21/53] Use explicit values in validate func --- examples/for-tests/src/App.js | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 84ce183d3..e7d7adf4c 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -208,7 +208,9 @@ const customFields = [

), validate: async (value) => { - if (value) return undefined; + if (value === true) { + return undefined; + } return "Please check Terms and conditions"; }, }, From 42cba3f43ef6cc9890bea34f917143ba74f301cc Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 14:31:50 +0530 Subject: [PATCH 22/53] Minor cleanup of types --- lib/build/recipe/emailpassword/types.d.ts | 7 +++---- lib/ts/recipe/emailpassword/types.ts | 9 +-------- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index c1e4cb6f6..83d942b19 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -78,7 +78,9 @@ export declare type NormalisedSignInAndUpFeatureConfig = { signInForm: NormalisedSignInFormFeatureConfig; }; export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { - formFields?: FormFieldSignUpConfig[]; + formFields?: (FormField & { + inputComponent?: React.FC; + })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; }; @@ -96,9 +98,6 @@ export declare type NormalisedSignInFormFeatureConfig = NormalisedBaseConfig & { formFields: NormalisedFormField[]; }; export declare type FormFieldSignInConfig = FormFieldBaseConfig; -export declare type FormFieldSignUpConfig = FormField & { - inputComponent?: React.FC; -}; export declare type ResetPasswordUsingTokenUserInput = { disableDefaultUI?: boolean; submitNewPasswordForm?: FeatureBaseConfig; diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index a34397de0..27ce38dc4 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -135,7 +135,7 @@ export type SignUpFormFeatureUserInput = FeatureBaseConfig & { /* * Form fields for SignUp. */ - formFields?: FormFieldSignUpConfig[]; + formFields?: (FormField & { inputComponent?: React.FC })[]; /* * Privacy policy link for sign up form. @@ -181,13 +181,6 @@ export type NormalisedSignInFormFeatureConfig = NormalisedBaseConfig & { export type FormFieldSignInConfig = FormFieldBaseConfig; -export type FormFieldSignUpConfig = FormField & { - /* - * Ability to add custom components - */ - inputComponent?: React.FC; -}; - export type ResetPasswordUsingTokenUserInput = { /* * Disable default implementation with default routes. From 8bae27369758084fe5632d477088961bbb0578aa Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 16:16:03 +0530 Subject: [PATCH 23/53] Better type names --- lib/build/recipe/emailpassword/types.d.ts | 8 ++++---- lib/ts/recipe/emailpassword/types.ts | 10 +++++----- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 83d942b19..e0dde85d6 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -114,11 +114,11 @@ export declare type NormalisedSubmitNewPasswordForm = FeatureBaseConfig & { export declare type NormalisedEnterEmailForm = FeatureBaseConfig & { formFields: NormalisedFormField[]; }; -declare type FormThemeBaseProps = ThemeBaseProps & { +declare type NonSignUpFormThemeBaseProps = ThemeBaseProps & { formFields: Omit[]; error: string | undefined; }; -export declare type SignInThemeProps = FormThemeBaseProps & { +export declare type SignInThemeProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -197,7 +197,7 @@ export declare type ResetPasswordUsingTokenThemeProps = { config: NormalisedConfig; userContext?: any; }; -export declare type EnterEmailProps = FormThemeBaseProps & { +export declare type EnterEmailProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; error: string | undefined; clearError: () => void; @@ -205,7 +205,7 @@ export declare type EnterEmailProps = FormThemeBaseProps & { config: NormalisedConfig; onBackButtonClicked: () => void; }; -export declare type SubmitNewPasswordProps = FormThemeBaseProps & { +export declare type SubmitNewPasswordProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; error: string | undefined; clearError: () => void; diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 27ce38dc4..e31d6ebc1 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -223,16 +223,16 @@ export type NormalisedEnterEmailForm = FeatureBaseConfig & { formFields: NormalisedFormField[]; }; -type FormThemeBaseProps = ThemeBaseProps & { +type NonSignUpFormThemeBaseProps = ThemeBaseProps & { /* - * Form fields to use in the signup form. + * Omit since, custom inputComponent only part of signup */ formFields: Omit[]; error: string | undefined; }; -export type SignInThemeProps = FormThemeBaseProps & { +export type SignInThemeProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; clearError: () => void; onError: (error: string) => void; @@ -363,7 +363,7 @@ export type ResetPasswordUsingTokenThemeProps = { userContext?: any; }; -export type EnterEmailProps = FormThemeBaseProps & { +export type EnterEmailProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; error: string | undefined; clearError: () => void; @@ -372,7 +372,7 @@ export type EnterEmailProps = FormThemeBaseProps & { onBackButtonClicked: () => void; }; -export type SubmitNewPasswordProps = FormThemeBaseProps & { +export type SubmitNewPasswordProps = NonSignUpFormThemeBaseProps & { recipeImplementation: RecipeInterface; error: string | undefined; clearError: () => void; From 2023dce24454d51c4e81c19db753a0125f9dcd27 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 25 Oct 2023 18:38:17 +0530 Subject: [PATCH 24/53] Props suggestions working for inputComponent --- .../themes/resetPasswordUsingToken/resetPasswordEmail.d.ts | 2 +- .../themes/resetPasswordUsingToken/submitNewPassword.d.ts | 2 +- .../components/themes/signInAndUp/signUp.d.ts | 5 ++--- .../components/themes/signInAndUp/signUpForm.d.ts | 5 ++--- lib/build/recipe/emailpassword/types.d.ts | 7 +++---- lib/ts/recipe/emailpassword/types.ts | 7 +++---- 6 files changed, 12 insertions(+), 16 deletions(-) diff --git a/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.d.ts b/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.d.ts index 8ff8a9b05..bcc6e2b05 100644 --- a/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/resetPasswordEmail.d.ts @@ -1,7 +1,7 @@ /// export declare const ResetPasswordEmail: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; + formFields: Omit[]; error: string | undefined; } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; diff --git a/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/submitNewPassword.d.ts b/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/submitNewPassword.d.ts index b8ec36fd7..9c795dce2 100644 --- a/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/submitNewPassword.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/resetPasswordUsingToken/submitNewPassword.d.ts @@ -1,7 +1,7 @@ /// export declare const SubmitNewPassword: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; + formFields: Omit[]; error: string | undefined; } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts index 380a6e333..32bc386b1 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUp.d.ts @@ -1,14 +1,13 @@ /// export declare const SignUp: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; - error: string | undefined; - } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; clearError: () => void; onError: (error: string) => void; config: import("../../../types").NormalisedConfig; signInClicked?: (() => void) | undefined; onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; + formFields: import("../../../types").FormFieldThemeProps[]; + error: string | undefined; } >; diff --git a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts index bbe4a2eb2..85b7e4c8d 100644 --- a/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts +++ b/lib/build/recipe/emailpassword/components/themes/signInAndUp/signUpForm.d.ts @@ -1,15 +1,14 @@ /// export declare const SignUpForm: import("react").ComponentType< import("../../../../../types").ThemeBaseProps & { - formFields: import("../../../types").FormFieldThemeProps[]; - error: string | undefined; - } & { recipeImplementation: import("supertokens-web-js/recipe/emailpassword").RecipeInterface; clearError: () => void; onError: (error: string) => void; config: import("../../../types").NormalisedConfig; signInClicked?: (() => void) | undefined; onSuccess: (result: { user: import("supertokens-web-js/types").User }) => void; + formFields: import("../../../types").FormFieldThemeProps[]; + error: string | undefined; } & { header?: JSX.Element | undefined; footer?: JSX.Element | undefined; diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index e0dde85d6..d1cb6e9ea 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -26,7 +26,6 @@ import type { NormalisedConfig as NormalisedAuthRecipeModuleConfig, UserInput as AuthRecipeModuleUserInput, } from "../authRecipe/types"; -import type React from "react"; import type { Dispatch } from "react"; import type { OverrideableBuilder } from "supertokens-js-override"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; @@ -79,14 +78,14 @@ export declare type NormalisedSignInAndUpFeatureConfig = { }; export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { formFields?: (FormField & { - inputComponent?: React.FC; + inputComponent?: (props: InputProps) => JSX.Element; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; }; export declare type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { formFields: (NormalisedFormField & { - inputComponent?: React.FC; + inputComponent?: (props: InputProps) => JSX.Element; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; @@ -151,7 +150,7 @@ export declare type FormFieldThemeProps = NormalisedFormField & { labelComponent?: JSX.Element; showIsRequired?: boolean; clearOnSubmit?: boolean; - inputComponent?: React.FC; + inputComponent?: (props: InputProps) => JSX.Element; }; export declare type FormFieldError = { id: string; diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index e31d6ebc1..aa65b2dcd 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -41,7 +41,6 @@ import type { NormalisedConfig as NormalisedAuthRecipeModuleConfig, UserInput as AuthRecipeModuleUserInput, } from "../authRecipe/types"; -import type React from "react"; import type { Dispatch } from "react"; import type { OverrideableBuilder } from "supertokens-js-override"; import type { RecipeInterface } from "supertokens-web-js/recipe/emailpassword"; @@ -135,7 +134,7 @@ export type SignUpFormFeatureUserInput = FeatureBaseConfig & { /* * Form fields for SignUp. */ - formFields?: (FormField & { inputComponent?: React.FC })[]; + formFields?: (FormField & { inputComponent?: (props: InputProps) => JSX.Element })[]; /* * Privacy policy link for sign up form. @@ -152,7 +151,7 @@ export type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { /* * Normalised form fields for SignUp. */ - formFields: (NormalisedFormField & { inputComponent?: React.FC })[]; + formFields: (NormalisedFormField & { inputComponent?: (props: InputProps) => JSX.Element })[]; /* * Privacy policy link for sign up form. @@ -283,7 +282,7 @@ export type FormFieldThemeProps = NormalisedFormField & { /* * Ability to add custom components */ - inputComponent?: React.FC; + inputComponent?: (props: InputProps) => JSX.Element; }; export type FormFieldError = { From 7dc66b8a8226d147ed22c094c8d3d0c57e16d12b Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 14:43:28 +0530 Subject: [PATCH 25/53] Enforce strict string check on form values, now onChange function for fields only needs value, no need to supply name or id --- lib/build/emailpassword-shared7.js | 45 ++++++++++++++++--- .../components/library/input.d.ts | 2 +- .../components/library/formBase.tsx | 17 ++++++- .../components/library/input.tsx | 7 +-- 4 files changed, 56 insertions(+), 15 deletions(-) diff --git a/lib/build/emailpassword-shared7.js b/lib/build/emailpassword-shared7.js index 6415dc169..fffbcdf39 100644 --- a/lib/build/emailpassword-shared7.js +++ b/lib/build/emailpassword-shared7.js @@ -336,10 +336,7 @@ var Input = function (_a) { } function handleChange(event) { if (onChange) { - onChange({ - id: name, - value: event.target.value, - }); + onChange(event.target.value); } } if (autoComplete === undefined) { @@ -549,7 +546,18 @@ var FormBase = function (props) { var onFormSubmit = React.useCallback( function (e) { return genericComponentOverrideContext.__awaiter(void 0, void 0, void 0, function () { - var apiFields, fieldUpdates, result, generalError, e_1, _loop_1, _i, formFields_1, field, errorFields_1; + var apiFields, + fieldsWithIncorrectValues, + errorFields, + fieldUpdates, + result, + generalError, + e_1, + _loop_1, + _i, + formFields_1, + field, + errorFields_1; return genericComponentOverrideContext.__generator(this, function (_a) { switch (_a.label) { case 0: @@ -574,6 +582,25 @@ var FormBase = function (props) { value: fieldState === undefined ? "" : fieldState.value, }; }); + // field.value must be a string + try { + fieldsWithIncorrectValues = apiFields.filter(function (field) { + return typeof field.value !== "string"; + }); + if (fieldsWithIncorrectValues.length > 0) { + errorFields = fieldsWithIncorrectValues + .map(function (_a) { + var id = _a.id; + return id; + }) + .join(", "); + throw new Error("".concat(errorFields, " value must be a string")); + } + } catch (error) { + console.error(error); + setIsLoading(false); + return [2 /*return*/, props.onError("SOMETHING_WENT_WRONG_ERROR")]; + } fieldUpdates = []; _a.label = 1; case 1: @@ -721,7 +748,9 @@ var FormBase = function (props) { autofocus: field.autofocus, onInputFocus: onInputFocus, onInputBlur: onInputBlur, - onChange: onInputChange, + onChange: function (value) { + return onInputChange({ id: field.id, value: value }); + }, hasError: fstate.error !== undefined, }) : jsxRuntime.jsx(Input, { @@ -733,7 +762,9 @@ var FormBase = function (props) { autoComplete: field.autoComplete, onInputFocus: onInputFocus, onInputBlur: onInputBlur, - onChange: onInputChange, + onChange: function (value) { + return onInputChange({ id: field.id, value: value }); + }, autofocus: field.autofocus, hasError: fstate.error !== undefined, }), diff --git a/lib/build/recipe/emailpassword/components/library/input.d.ts b/lib/build/recipe/emailpassword/components/library/input.d.ts index 44893e14a..a9d8890e2 100644 --- a/lib/build/recipe/emailpassword/components/library/input.d.ts +++ b/lib/build/recipe/emailpassword/components/library/input.d.ts @@ -11,7 +11,7 @@ export declare type InputProps = { value: string; onInputBlur?: (field: APIFormField) => void; onInputFocus?: (field: APIFormField) => void; - onChange?: (field: APIFormField) => void; + onChange?: (value: string) => void; }; declare const Input: React.FC; export default Input; diff --git a/lib/ts/recipe/emailpassword/components/library/formBase.tsx b/lib/ts/recipe/emailpassword/components/library/formBase.tsx index 1dabbd92a..d1368c435 100644 --- a/lib/ts/recipe/emailpassword/components/library/formBase.tsx +++ b/lib/ts/recipe/emailpassword/components/library/formBase.tsx @@ -120,6 +120,19 @@ export const FormBase: React.FC> = (props) => { }; }); + // field.value must be a string + try { + const fieldsWithIncorrectValues = apiFields.filter((field) => typeof field.value !== "string"); + if (fieldsWithIncorrectValues.length > 0) { + const errorFields = fieldsWithIncorrectValues.map(({ id }) => id).join(", "); + throw new Error(`${errorFields} value must be a string`); + } + } catch (error) { + console.error(error); + setIsLoading(false); + return props.onError("SOMETHING_WENT_WRONG_ERROR"); + } + const fieldUpdates: FieldState[] = []; // Call API. try { @@ -219,7 +232,7 @@ export const FormBase: React.FC> = (props) => { autofocus={field.autofocus} onInputFocus={onInputFocus} onInputBlur={onInputBlur} - onChange={onInputChange} + onChange={(value) => onInputChange({ id: field.id, value: value })} hasError={fstate.error !== undefined} /> ) : ( @@ -232,7 +245,7 @@ export const FormBase: React.FC> = (props) => { autoComplete={field.autoComplete} onInputFocus={onInputFocus} onInputBlur={onInputBlur} - onChange={onInputChange} + onChange={(value) => onInputChange({ id: field.id, value: value })} autofocus={field.autofocus} hasError={fstate.error !== undefined} /> diff --git a/lib/ts/recipe/emailpassword/components/library/input.tsx b/lib/ts/recipe/emailpassword/components/library/input.tsx index 70f34f331..a72cea67d 100644 --- a/lib/ts/recipe/emailpassword/components/library/input.tsx +++ b/lib/ts/recipe/emailpassword/components/library/input.tsx @@ -34,7 +34,7 @@ export type InputProps = { value: string; onInputBlur?: (field: APIFormField) => void; onInputFocus?: (field: APIFormField) => void; - onChange?: (field: APIFormField) => void; + onChange?: (value: string) => void; }; const Input: React.FC = ({ @@ -77,10 +77,7 @@ const Input: React.FC = ({ function handleChange(event: ChangeEvent) { if (onChange) { - onChange({ - id: name, - value: event.target.value, - }); + onChange(event.target.value); } } From 3cc4ec8bc51f7963a8312ddb2bb8df46f883f427 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 14:50:16 +0530 Subject: [PATCH 26/53] Update based on the new onChange func --- examples/for-tests/src/App.js | 20 ++++++------------- lib/build/passwordless-shared3.js | 10 ++-------- .../themes/signInUp/phoneNumberInput.tsx | 10 ++-------- 3 files changed, 10 insertions(+), 30 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index e7d7adf4c..f888f4bac 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -172,12 +172,8 @@ const customFields = [ { id: "ratings", label: "Ratings", - inputComponent: ({ value, name, ...rest }) => ( - onChange(e.target.value)} placeholder="Add Ratings"> @@ -190,25 +186,21 @@ const customFields = [ }, { id: "terms", - showLabels: false, + label: "", optional: false, - inputComponent: ({ value, name, ...rest }) => ( + inputComponent: ({ name, onChange }) => (
- rest.onChange({ id: name, value: e.target.checked })}> + onChange(e.target.checked.toString())}> I agree to the terms and conditions
), validate: async (value) => { - if (value === true) { + if (value === "true") { return undefined; } return "Please check Terms and conditions"; diff --git a/lib/build/passwordless-shared3.js b/lib/build/passwordless-shared3.js index 54b23e5a7..926983c06 100644 --- a/lib/build/passwordless-shared3.js +++ b/lib/build/passwordless-shared3.js @@ -2596,10 +2596,7 @@ function PhoneNumberInput(_a) { var handleChange = React.useCallback( function (newValue) { if (onChangeRef.current !== undefined) { - onChangeRef.current({ - id: name, - value: newValue, - }); + onChangeRef.current(newValue); } }, [onChangeRef] @@ -2607,10 +2604,7 @@ function PhoneNumberInput(_a) { var handleCountryChange = React.useCallback( function (ev) { if (onChangeRef.current !== undefined && phoneInputInstance !== undefined) { - onChangeRef.current({ - id: name, - value: ev.target.value, - }); + onChangeRef.current(ev.target.value); } }, [onChangeRef] diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx index 584c230dd..79f7eb314 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx @@ -71,10 +71,7 @@ function PhoneNumberInput({ const handleChange = useCallback( (newValue: string) => { if (onChangeRef.current !== undefined) { - onChangeRef.current({ - id: name, - value: newValue, - }); + onChangeRef.current(newValue); } }, [onChangeRef] @@ -83,10 +80,7 @@ function PhoneNumberInput({ const handleCountryChange = useCallback( (ev) => { if (onChangeRef.current !== undefined && phoneInputInstance !== undefined) { - onChangeRef.current({ - id: name, - value: ev.target.value, - }); + onChangeRef.current(ev.target.value); } }, [onChangeRef] From a13ddc1a7f05c87059367214fa79cb7634239426 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 15:49:52 +0530 Subject: [PATCH 27/53] Ability to add default value with getDefaultValue prop --- lib/build/emailpassword-shared7.js | 8 +++++++- lib/build/recipe/emailpassword/types.d.ts | 3 +++ .../emailpassword/components/library/formBase.tsx | 11 +++++++++-- lib/ts/recipe/emailpassword/types.ts | 15 +++++++++++++-- 4 files changed, 32 insertions(+), 5 deletions(-) diff --git a/lib/build/emailpassword-shared7.js b/lib/build/emailpassword-shared7.js index fffbcdf39..2430a8d61 100644 --- a/lib/build/emailpassword-shared7.js +++ b/lib/build/emailpassword-shared7.js @@ -453,9 +453,15 @@ var FormBase = function (props) { }, [unmounting] ); + var fetchDefaultValue = function (field) { + if (field && field.getDefaultValue) { + return field.getDefaultValue(); + } + return ""; + }; var _a = React.useState( props.formFields.map(function (f) { - return { id: f.id, value: "" }; + return { id: f.id, value: fetchDefaultValue(f) }; }) ), fieldStates = _a[0], diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index d1cb6e9ea..70c7ffcfd 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -79,6 +79,7 @@ export declare type NormalisedSignInAndUpFeatureConfig = { export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { formFields?: (FormField & { inputComponent?: (props: InputProps) => JSX.Element; + getDefaultValue?: () => string; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; @@ -86,6 +87,7 @@ export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { export declare type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { formFields: (NormalisedFormField & { inputComponent?: (props: InputProps) => JSX.Element; + getDefaultValue?: () => string; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; @@ -151,6 +153,7 @@ export declare type FormFieldThemeProps = NormalisedFormField & { showIsRequired?: boolean; clearOnSubmit?: boolean; inputComponent?: (props: InputProps) => JSX.Element; + getDefaultValue?: () => string; }; export declare type FormFieldError = { id: string; diff --git a/lib/ts/recipe/emailpassword/components/library/formBase.tsx b/lib/ts/recipe/emailpassword/components/library/formBase.tsx index d1368c435..228a785e9 100644 --- a/lib/ts/recipe/emailpassword/components/library/formBase.tsx +++ b/lib/ts/recipe/emailpassword/components/library/formBase.tsx @@ -25,7 +25,7 @@ import STGeneralError from "supertokens-web-js/utils/error"; import { MANDATORY_FORM_FIELDS_ID_ARRAY } from "../../constants"; import type { APIFormField } from "../../../../types"; -import type { FormBaseProps } from "../../types"; +import type { FormBaseProps, FormFieldThemeProps } from "../../types"; import type { FormEvent } from "react"; import { Button, FormRow, Input, InputError, Label } from "."; @@ -49,8 +49,15 @@ export const FormBase: React.FC> = (props) => { }; }, [unmounting]); + const fetchDefaultValue = (field: FormFieldThemeProps) => { + if (field && field.getDefaultValue) { + return field.getDefaultValue(); + } + return ""; + }; + const [fieldStates, setFieldStates] = useState( - props.formFields.map((f) => ({ id: f.id, value: "" })) + props.formFields.map((f) => ({ id: f.id, value: fetchDefaultValue(f) })) ); const [isLoading, setIsLoading] = useState(false); diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index aa65b2dcd..8618f6d1e 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -134,7 +134,10 @@ export type SignUpFormFeatureUserInput = FeatureBaseConfig & { /* * Form fields for SignUp. */ - formFields?: (FormField & { inputComponent?: (props: InputProps) => JSX.Element })[]; + formFields?: (FormField & { + inputComponent?: (props: InputProps) => JSX.Element; + getDefaultValue?: () => string; + })[]; /* * Privacy policy link for sign up form. @@ -151,7 +154,10 @@ export type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { /* * Normalised form fields for SignUp. */ - formFields: (NormalisedFormField & { inputComponent?: (props: InputProps) => JSX.Element })[]; + formFields: (NormalisedFormField & { + inputComponent?: (props: InputProps) => JSX.Element; + getDefaultValue?: () => string; + })[]; /* * Privacy policy link for sign up form. @@ -283,6 +289,11 @@ export type FormFieldThemeProps = NormalisedFormField & { * Ability to add custom components */ inputComponent?: (props: InputProps) => JSX.Element; + + /* + * Ability to add custom components + */ + getDefaultValue?: () => string; }; export type FormFieldError = { From 2be7cfae89d5c40d78ec71c87fc8a303ce86e5f0 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 16:47:23 +0530 Subject: [PATCH 28/53] Handle if getDefaultValue is not a function --- lib/build/emailpassword-shared7.js | 39 +++++++++++++------ .../components/library/formBase.tsx | 38 +++++++++++++----- 2 files changed, 56 insertions(+), 21 deletions(-) diff --git a/lib/build/emailpassword-shared7.js b/lib/build/emailpassword-shared7.js index 2430a8d61..eb4e5bd38 100644 --- a/lib/build/emailpassword-shared7.js +++ b/lib/build/emailpassword-shared7.js @@ -436,6 +436,24 @@ function Label(_a) { ); } +var fetchDefaultValue = function (field) { + if (field && field.getDefaultValue) { + try { + if (typeof field.getDefaultValue !== "function") { + throw new Error("getDefaultValue for ".concat(field.id, " must be a function")); + } + var defaultValue = field.getDefaultValue(); + if (typeof defaultValue !== "string") { + throw new Error("getDefaultValue for ".concat(field.id, " must return a string")); + } else { + return defaultValue; + } + } catch (error) { + console.error(error); + } + } + return ""; +}; var FormBase = function (props) { var footer = props.footer, buttonLabel = props.buttonLabel, @@ -453,22 +471,21 @@ var FormBase = function (props) { }, [unmounting] ); - var fetchDefaultValue = function (field) { - if (field && field.getDefaultValue) { - return field.getDefaultValue(); - } - return ""; - }; - var _a = React.useState( - props.formFields.map(function (f) { - return { id: f.id, value: fetchDefaultValue(f) }; - }) - ), + var _a = React.useState([]), fieldStates = _a[0], setFieldStates = _a[1]; var _b = React.useState(false), isLoading = _b[0], setIsLoading = _b[1]; + React.useEffect(function () { + var initialValues = props.formFields.map(function (f) { + return { + id: f.id, + value: fetchDefaultValue(f), + }; + }); + setFieldStates(initialValues); + }, []); var updateFieldState = React.useCallback( function (id, update) { setFieldStates(function (os) { diff --git a/lib/ts/recipe/emailpassword/components/library/formBase.tsx b/lib/ts/recipe/emailpassword/components/library/formBase.tsx index 228a785e9..5576f0483 100644 --- a/lib/ts/recipe/emailpassword/components/library/formBase.tsx +++ b/lib/ts/recipe/emailpassword/components/library/formBase.tsx @@ -37,6 +37,25 @@ type FieldState = { value: string; }; +const fetchDefaultValue = (field: FormFieldThemeProps): string => { + if (field && field.getDefaultValue) { + try { + if (typeof field.getDefaultValue !== "function") { + throw new Error(`getDefaultValue for ${field.id} must be a function`); + } + const defaultValue = field.getDefaultValue(); + if (typeof defaultValue !== "string") { + throw new Error(`getDefaultValue for ${field.id} must return a string`); + } else { + return defaultValue; + } + } catch (error) { + console.error(error); + } + } + return ""; +}; + export const FormBase: React.FC> = (props) => { const { footer, buttonLabel, showLabels, validateOnBlur, formFields } = props; @@ -49,18 +68,17 @@ export const FormBase: React.FC> = (props) => { }; }, [unmounting]); - const fetchDefaultValue = (field: FormFieldThemeProps) => { - if (field && field.getDefaultValue) { - return field.getDefaultValue(); - } - return ""; - }; - - const [fieldStates, setFieldStates] = useState( - props.formFields.map((f) => ({ id: f.id, value: fetchDefaultValue(f) })) - ); + const [fieldStates, setFieldStates] = useState([]); const [isLoading, setIsLoading] = useState(false); + useEffect(() => { + const initialValues = props.formFields.map((f) => ({ + id: f.id, + value: fetchDefaultValue(f), + })); + setFieldStates(initialValues); + }, []); + const updateFieldState = useCallback( (id: string, update: (os: FieldState) => FieldState) => { setFieldStates((os) => { From 2480ebde0708950e598bdbed56207de7c700afd0 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 18:34:09 +0530 Subject: [PATCH 29/53] instead of form submit apply type test within onChange function itself --- lib/build/emailpassword-shared7.js | 40 +++++-------------- .../components/library/formBase.tsx | 21 ++++------ 2 files changed, 17 insertions(+), 44 deletions(-) diff --git a/lib/build/emailpassword-shared7.js b/lib/build/emailpassword-shared7.js index eb4e5bd38..b2430ed7c 100644 --- a/lib/build/emailpassword-shared7.js +++ b/lib/build/emailpassword-shared7.js @@ -556,6 +556,14 @@ var FormBase = function (props) { ); var onInputChange = React.useCallback( function (field) { + try { + if (typeof field.value !== "string") { + throw new Error("".concat(field.id, " value must be a string")); + } + } catch (error) { + console.error(error); + return props.onError("SOMETHING_WENT_WRONG_ERROR"); + } updateFieldState(field.id, function (os) { return genericComponentOverrideContext.__assign(genericComponentOverrideContext.__assign({}, os), { value: field.value, @@ -569,18 +577,7 @@ var FormBase = function (props) { var onFormSubmit = React.useCallback( function (e) { return genericComponentOverrideContext.__awaiter(void 0, void 0, void 0, function () { - var apiFields, - fieldsWithIncorrectValues, - errorFields, - fieldUpdates, - result, - generalError, - e_1, - _loop_1, - _i, - formFields_1, - field, - errorFields_1; + var apiFields, fieldUpdates, result, generalError, e_1, _loop_1, _i, formFields_1, field, errorFields_1; return genericComponentOverrideContext.__generator(this, function (_a) { switch (_a.label) { case 0: @@ -605,25 +602,6 @@ var FormBase = function (props) { value: fieldState === undefined ? "" : fieldState.value, }; }); - // field.value must be a string - try { - fieldsWithIncorrectValues = apiFields.filter(function (field) { - return typeof field.value !== "string"; - }); - if (fieldsWithIncorrectValues.length > 0) { - errorFields = fieldsWithIncorrectValues - .map(function (_a) { - var id = _a.id; - return id; - }) - .join(", "); - throw new Error("".concat(errorFields, " value must be a string")); - } - } catch (error) { - console.error(error); - setIsLoading(false); - return [2 /*return*/, props.onError("SOMETHING_WENT_WRONG_ERROR")]; - } fieldUpdates = []; _a.label = 1; case 1: diff --git a/lib/ts/recipe/emailpassword/components/library/formBase.tsx b/lib/ts/recipe/emailpassword/components/library/formBase.tsx index 5576f0483..14d540e11 100644 --- a/lib/ts/recipe/emailpassword/components/library/formBase.tsx +++ b/lib/ts/recipe/emailpassword/components/library/formBase.tsx @@ -120,6 +120,14 @@ export const FormBase: React.FC> = (props) => { const onInputChange = useCallback( (field: APIFormField) => { + try { + if (typeof field.value !== "string") { + throw new Error(`${field.id} value must be a string`); + } + } catch (error) { + console.error(error); + return props.onError("SOMETHING_WENT_WRONG_ERROR"); + } updateFieldState(field.id, (os) => ({ ...os, value: field.value, error: undefined })); props.clearError(); }, @@ -145,19 +153,6 @@ export const FormBase: React.FC> = (props) => { }; }); - // field.value must be a string - try { - const fieldsWithIncorrectValues = apiFields.filter((field) => typeof field.value !== "string"); - if (fieldsWithIncorrectValues.length > 0) { - const errorFields = fieldsWithIncorrectValues.map(({ id }) => id).join(", "); - throw new Error(`${errorFields} value must be a string`); - } - } catch (error) { - console.error(error); - setIsLoading(false); - return props.onError("SOMETHING_WENT_WRONG_ERROR"); - } - const fieldUpdates: FieldState[] = []; // Call API. try { From d574fc0e2e950dde527310ac764a7a65af637e31 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 26 Oct 2023 18:46:48 +0530 Subject: [PATCH 30/53] Add tests for default value --- examples/for-tests/src/App.js | 87 ++++++++++++++++++++++++++++++++-- test/end-to-end/signup.test.js | 62 ++++++++++++++++++++---- 2 files changed, 137 insertions(+), 12 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index f888f4bac..6fb78d984 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -165,15 +165,90 @@ const formFields = [ label: "Your Country", placeholder: "Where do you live?", optional: true, + getDefaultValue: "India", }, ]; -const customFields = [ +const formFieldsWithDefault = [ + { + id: "country", + label: "Your Country", + placeholder: "Where do you live?", + optional: true, + getDefaultValue: () => "India", + }, + { + id: "ratings", + label: "Ratings", + getDefaultValue: () => "best", + inputComponent: ({ value, name, onChange }) => ( + + ), + optional: true, + }, +]; + +const incorrectFormFields = [ + { + id: "country", + label: "Your Country", + placeholder: "Where do you live?", + optional: true, + getDefaultValue: () => 23, + }, { id: "ratings", label: "Ratings", + getDefaultValue: "best", + inputComponent: ({ value, name, onChange }) => ( + + ), + optional: true, + }, + { + id: "terms", + label: "", + optional: false, inputComponent: ({ name, onChange }) => ( - onChange(e.target.checked)}> + I agree to the terms and conditions +
+ ), + validate: async (value) => { + if (value === "true") { + return undefined; + } + return "Please check Terms and conditions"; + }, + }, +]; + +const customFields = [ + { + id: "ratings", + label: "Ratings", + inputComponent: ({ value, name, onChange }) => ( + { + if (inputProps.onChange) { + inputProps.onChange(e.target.checked.toString()); + } + }}> + I agree to the terms and conditions +
+ ), + validate: async (value) => { + if (value === "true") { + return undefined; + } + return "Please check Terms and conditions"; + }, + }, + { + id: "ratings", + label: "Ratings", + getDefaultValue: () => "best", + inputComponent: (inputProps) => ( + + ), + optional: true, + }, +]; + +const getFormFieldsForSignUp = () => { + return localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES" ? customFormFieldsWithDefault : formFieldsForSignUp; +}; + function getEmailPasswordConfigs() { return EmailPassword.init({ resetPasswordUsingTokenFeature: { @@ -344,36 +435,7 @@ function getEmailPasswordConfigs() { style: theme.style, privacyPolicyLink: "https://supertokens.io/legal/privacy-policy", termsOfServiceLink: "https://supertokens.io/legal/terms-and-conditions", - formFields: [ - { - id: "email", - label: "Your Email", - placeholder: "Your work email", - }, - { - id: "name", - label: "Full name", - placeholder: "First name and last name", - }, - { - id: "age", - label: "Your age", - placeholder: "How old are you?", - validate: async (value) => { - if (parseInt(value) > 18) { - return undefined; - } - - return "You must be over 18 to register"; - }, - }, - { - id: "country", - label: "Your Country", - placeholder: "Where do you live?", - optional: true, - }, - ], + formFields: getFormFieldsForSignUp(), }, }, From c305fffa77d4f35ca8e0a5c1b253dfb9398a1386 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Mon, 30 Oct 2023 13:24:02 +0530 Subject: [PATCH 39/53] Add tests for incorrect default props in formFields --- examples/for-tests/src/App.js | 5 ++--- test/end-to-end/signup.test.js | 40 ++++++++++++++++++++++++++++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 6fb78d984..379b46ff1 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -165,7 +165,6 @@ const formFields = [ label: "Your Country", placeholder: "Where do you live?", optional: true, - getDefaultValue: "India", }, ]; @@ -201,12 +200,12 @@ const incorrectFormFields = [ label: "Your Country", placeholder: "Where do you live?", optional: true, - getDefaultValue: () => 23, + getDefaultValue: () => 23, // return should be a string }, { id: "ratings", label: "Ratings", - getDefaultValue: "best", + getDefaultValue: "best", // should be function inputComponent: ({ value, name, onChange }) => ( onChange(e.target.value)} placeholder="Add Ratings"> + + + + + + )} +... +``` + ## [0.35.6] - 2023-10-16 ### Test changes From 284c6fa80f9a1c8aeef3772a64a7f7e1a83885aa Mon Sep 17 00:00:00 2001 From: amitbadala Date: Mon, 30 Oct 2023 17:30:53 +0530 Subject: [PATCH 42/53] Wrap ternary opeators into seperate func for better readibility --- examples/for-tests/src/App.js | 20 ++++++++++++-------- 1 file changed, 12 insertions(+), 8 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 379b46ff1..4496d10a2 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -666,6 +666,17 @@ function getEmailVerificationConfigs({ disableDefaultUI }) { }); } +function getFormFields() { + if (localStorage.getItem("SHOW_INCORRECT_FIELDS") === "YES") { + return incorrectFormFields; + } else if (localStorage.getItem("SHOW_DEFAULT_FIELDS") === "YES") { + return formFieldsWithDefault; + } else if (localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES") { + return customFields; + } + return formFields; +} + function getEmailPasswordConfigs({ disableDefaultUI }) { return EmailPassword.init({ style: ` @@ -751,14 +762,7 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { style: theme, privacyPolicyLink: "https://supertokens.com/legal/privacy-policy", termsOfServiceLink: "https://supertokens.com/legal/terms-and-conditions", - formFields: - localStorage.getItem("SHOW_INCORRECT_FIELDS") === "YES" - ? incorrectFormFields - : localStorage.getItem("SHOW_DEFAULT_FIELDS") === "YES" - ? formFieldsWithDefault - : localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES" - ? customFields - : formFields, + formFields: getFormFields(), }, }, }); From d4c53a51f0b82521c2edb9cdc808f75e3e086428 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Mon, 30 Oct 2023 19:01:25 +0530 Subject: [PATCH 43/53] Wrap inputComponent in a serperate component to avoid unecessary rerenders --- lib/build/emailpassword-shared7.js | 149 ++++++++++-------- lib/build/passwordless-shared3.js | 10 +- .../components/library/input.d.ts | 5 +- .../components/library/formBase.tsx | 148 ++++++++++------- .../components/library/input.tsx | 15 +- .../themes/signInUp/phoneNumberInput.tsx | 10 +- test/end-to-end/signup.test.js | 58 ------- 7 files changed, 186 insertions(+), 209 deletions(-) diff --git a/lib/build/emailpassword-shared7.js b/lib/build/emailpassword-shared7.js index a4f63ad4c..0abdcd070 100644 --- a/lib/build/emailpassword-shared7.js +++ b/lib/build/emailpassword-shared7.js @@ -320,18 +320,12 @@ var Input = function (_a) { */ function handleFocus() { if (onInputFocus !== undefined) { - onInputFocus({ - id: name, - value: value, - }); + onInputFocus(value); } } function handleBlur() { if (onInputBlur !== undefined) { - onInputBlur({ - id: name, - value: value, - }); + onInputBlur(value); } } function handleChange(event) { @@ -437,23 +431,78 @@ function Label(_a) { } var fetchDefaultValue = function (field) { - if (field && field.getDefaultValue) { - try { - if (typeof field.getDefaultValue !== "function") { - throw new Error("getDefaultValue for ".concat(field.id, " must be a function")); - } - var defaultValue = field.getDefaultValue(); - if (typeof defaultValue !== "string") { - throw new Error("getDefaultValue for ".concat(field.id, " must return a string")); - } else { - return defaultValue; - } - } catch (error) { - console.error(error); + if (field.getDefaultValue !== undefined) { + var defaultValue = field.getDefaultValue(); + if (typeof defaultValue !== "string") { + throw new Error("getDefaultValue for ".concat(field.id, " must return a string")); + } else { + return defaultValue; } } return ""; }; +function InputComponentWrapper(props) { + var field = props.field, + type = props.type, + fstate = props.fstate, + onInputFocus = props.onInputFocus, + onInputBlur = props.onInputBlur, + onInputChange = props.onInputChange; + var useCallbackOnInputFocus = React.useCallback( + function (value) { + onInputFocus({ + id: field.id, + value: value, + }); + }, + [onInputFocus, field] + ); + var useCallbackOnInputBlur = React.useCallback( + function (value) { + onInputBlur({ + id: field.id, + value: value, + }); + }, + [onInputBlur, field] + ); + var useCallbackOnInputChange = React.useCallback( + function (value) { + onInputChange({ + id: field.id, + value: value, + }); + }, + [onInputChange, field] + ); + return field.inputComponent !== undefined + ? jsxRuntime.jsx(field.inputComponent, { + type: type, + name: field.id, + validated: fstate.validated === true, + placeholder: field.placeholder, + value: fstate.value, + autoComplete: field.autoComplete, + autofocus: field.autofocus, + onInputFocus: useCallbackOnInputFocus, + onInputBlur: useCallbackOnInputBlur, + onChange: useCallbackOnInputChange, + hasError: fstate.error !== undefined, + }) + : jsxRuntime.jsx(Input, { + type: type, + name: field.id, + validated: fstate.validated === true, + placeholder: field.placeholder, + value: fstate.value, + autoComplete: field.autoComplete, + onInputFocus: useCallbackOnInputFocus, + onInputBlur: useCallbackOnInputBlur, + onChange: useCallbackOnInputChange, + autofocus: field.autofocus, + hasError: fstate.error !== undefined, + }); +} var FormBase = function (props) { var footer = props.footer, buttonLabel = props.buttonLabel, @@ -551,13 +600,8 @@ var FormBase = function (props) { ); var onInputChange = React.useCallback( function (field) { - try { - if (typeof field.value !== "string") { - throw new Error("".concat(field.id, " value must be a string")); - } - } catch (error) { - console.error(error); - return props.onError("SOMETHING_WENT_WRONG_ERROR"); + if (typeof field.value !== "string") { + throw new Error("".concat(field.id, " value must be a string")); } updateFieldState(field.id, function (os) { return genericComponentOverrideContext.__assign(genericComponentOverrideContext.__assign({}, os), { @@ -713,12 +757,10 @@ var FormBase = function (props) { } var fstate = fieldStates.find(function (s) { return s.id === field.id; - }) || { - id: field.id, - validated: false, - error: undefined, - value: "", - }; + }); + if (fstate === undefined) { + throw new Error("Should never come here"); + } return jsxRuntime.jsx( FormRow, genericComponentOverrideContext.__assign( @@ -733,37 +775,14 @@ var FormBase = function (props) { value: field.label, showIsRequired: field.showIsRequired, })), - field.inputComponent !== undefined - ? jsxRuntime.jsx(field.inputComponent, { - type: type, - name: field.id, - validated: fstate.validated === true, - placeholder: field.placeholder, - value: fstate.value, - autoComplete: field.autoComplete, - autofocus: field.autofocus, - onInputFocus: onInputFocus, - onInputBlur: onInputBlur, - onChange: function (value) { - return onInputChange({ id: field.id, value: value }); - }, - hasError: fstate.error !== undefined, - }) - : jsxRuntime.jsx(Input, { - type: type, - name: field.id, - validated: fstate.validated === true, - placeholder: field.placeholder, - value: fstate.value, - autoComplete: field.autoComplete, - onInputFocus: onInputFocus, - onInputBlur: onInputBlur, - onChange: function (value) { - return onInputChange({ id: field.id, value: value }); - }, - autofocus: field.autofocus, - hasError: fstate.error !== undefined, - }), + jsxRuntime.jsx(InputComponentWrapper, { + type: type, + field: field, + fstate: fstate, + onInputFocus: onInputFocus, + onInputBlur: onInputBlur, + onInputChange: onInputChange, + }), fstate.error && jsxRuntime.jsx(InputError, { error: fstate.error }), ], }), diff --git a/lib/build/passwordless-shared3.js b/lib/build/passwordless-shared3.js index 926983c06..73e421acf 100644 --- a/lib/build/passwordless-shared3.js +++ b/lib/build/passwordless-shared3.js @@ -2569,18 +2569,12 @@ function PhoneNumberInput(_a) { value = _a.value; function handleFocus() { if (onInputFocus !== undefined) { - onInputFocus({ - id: name, - value: value, - }); + onInputFocus(value); } } function handleBlur() { if (onInputBlur !== undefined) { - onInputBlur({ - id: name, - value: value, - }); + onInputBlur(value); } } var _b = React.useState(), diff --git a/lib/build/recipe/emailpassword/components/library/input.d.ts b/lib/build/recipe/emailpassword/components/library/input.d.ts index a9d8890e2..973042e7c 100644 --- a/lib/build/recipe/emailpassword/components/library/input.d.ts +++ b/lib/build/recipe/emailpassword/components/library/input.d.ts @@ -1,5 +1,4 @@ /// -import type { APIFormField } from "../../../../types"; export declare type InputProps = { type: string; name: string; @@ -9,8 +8,8 @@ export declare type InputProps = { hasError: boolean; placeholder: string; value: string; - onInputBlur?: (field: APIFormField) => void; - onInputFocus?: (field: APIFormField) => void; + onInputBlur?: (value: string) => void; + onInputFocus?: (value: string) => void; onChange?: (value: string) => void; }; declare const Input: React.FC; diff --git a/lib/ts/recipe/emailpassword/components/library/formBase.tsx b/lib/ts/recipe/emailpassword/components/library/formBase.tsx index 3418d68d5..f9d5e201b 100644 --- a/lib/ts/recipe/emailpassword/components/library/formBase.tsx +++ b/lib/ts/recipe/emailpassword/components/library/formBase.tsx @@ -38,24 +38,88 @@ type FieldState = { }; const fetchDefaultValue = (field: FormFieldThemeProps): string => { - if (field && field.getDefaultValue) { - try { - if (typeof field.getDefaultValue !== "function") { - throw new Error(`getDefaultValue for ${field.id} must be a function`); - } - const defaultValue = field.getDefaultValue(); - if (typeof defaultValue !== "string") { - throw new Error(`getDefaultValue for ${field.id} must return a string`); - } else { - return defaultValue; - } - } catch (error) { - console.error(error); + if (field.getDefaultValue !== undefined) { + const defaultValue = field.getDefaultValue(); + if (typeof defaultValue !== "string") { + throw new Error(`getDefaultValue for ${field.id} must return a string`); + } else { + return defaultValue; } } return ""; }; +function InputComponentWrapper(props: { + field: FormFieldThemeProps; + type: string; + fstate: FieldState; + onInputFocus: (field: APIFormField) => void; + onInputBlur: (field: APIFormField) => void; + onInputChange: (field: APIFormField) => void; +}) { + const { field, type, fstate, onInputFocus, onInputBlur, onInputChange } = props; + + const useCallbackOnInputFocus = useCallback<(value: string) => void>( + (value) => { + onInputFocus({ + id: field.id, + value, + }); + }, + [onInputFocus, field] + ); + + const useCallbackOnInputBlur = useCallback<(value: string) => void>( + (value) => { + onInputBlur({ + id: field.id, + value, + }); + }, + [onInputBlur, field] + ); + + const useCallbackOnInputChange = useCallback( + (value) => { + onInputChange({ + id: field.id, + value, + }); + }, + [onInputChange, field] + ); + + return field.inputComponent !== undefined ? ( + + ) : ( + + ); +} + export const FormBase: React.FC> = (props) => { const { footer, buttonLabel, showLabels, validateOnBlur, formFields } = props; @@ -114,13 +178,8 @@ export const FormBase: React.FC> = (props) => { const onInputChange = useCallback( (field: APIFormField) => { - try { - if (typeof field.value !== "string") { - throw new Error(`${field.id} value must be a string`); - } - } catch (error) { - console.error(error); - return props.onError("SOMETHING_WENT_WRONG_ERROR"); + if (typeof field.value !== "string") { + throw new Error(`${field.id} value must be a string`); } updateFieldState(field.id, (os) => ({ ...os, value: field.value, error: undefined })); props.clearError(); @@ -218,12 +277,11 @@ export const FormBase: React.FC> = (props) => { if (field.id === "confirm-password") { type = "password"; } - const fstate: FieldState = fieldStates.find((s) => s.id === field.id) || { - id: field.id, - validated: false, - error: undefined, - value: "", - }; + + const fstate: FieldState | undefined = fieldStates.find((s) => s.id === field.id); + if (fstate === undefined) { + throw new Error("Should never come here"); + } return ( @@ -235,36 +293,14 @@ export const FormBase: React.FC> = (props) => { diff --git a/lib/ts/recipe/emailpassword/components/library/input.tsx b/lib/ts/recipe/emailpassword/components/library/input.tsx index a72cea67d..0f1877e1b 100644 --- a/lib/ts/recipe/emailpassword/components/library/input.tsx +++ b/lib/ts/recipe/emailpassword/components/library/input.tsx @@ -20,7 +20,6 @@ import CheckedIcon from "../../../../components/assets/checkedIcon"; import ErrorIcon from "../../../../components/assets/errorIcon"; import ShowPasswordIcon from "../../../../components/assets/showPasswordIcon"; -import type { APIFormField } from "../../../../types"; import type { ChangeEvent } from "react"; export type InputProps = { @@ -32,8 +31,8 @@ export type InputProps = { hasError: boolean; placeholder: string; value: string; - onInputBlur?: (field: APIFormField) => void; - onInputFocus?: (field: APIFormField) => void; + onInputBlur?: (value: string) => void; + onInputFocus?: (value: string) => void; onChange?: (value: string) => void; }; @@ -59,19 +58,13 @@ const Input: React.FC = ({ function handleFocus() { if (onInputFocus !== undefined) { - onInputFocus({ - id: name, - value: value, - }); + onInputFocus(value); } } function handleBlur() { if (onInputBlur !== undefined) { - onInputBlur({ - id: name, - value, - }); + onInputBlur(value); } } diff --git a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx index 79f7eb314..2bff1c1d0 100644 --- a/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx +++ b/lib/ts/recipe/passwordless/components/themes/signInUp/phoneNumberInput.tsx @@ -45,19 +45,13 @@ function PhoneNumberInput({ }: InputProps & PhoneNumberInputProps): JSX.Element { function handleFocus() { if (onInputFocus !== undefined) { - onInputFocus({ - id: name, - value: value, - }); + onInputFocus(value); } } function handleBlur() { if (onInputBlur !== undefined) { - onInputBlur({ - id: name, - value: value, - }); + onInputBlur(value); } } diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 5f5709366..f835217ec 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -489,64 +489,6 @@ describe("SuperTokens SignUp", function () { assert.strictEqual(defaultRating, updatedFields["ratings"]); }); }); - - describe("Signup with Incorrect fields config", function () { - beforeEach(async function () { - // set cookie and reload which loads the form fields with incorrect field values - await page.evaluate(() => window.localStorage.setItem("SHOW_INCORRECT_FIELDS", "YES")); - - await page.reload({ - waitUntil: "domcontentloaded", - }); - await toggleSignInSignUp(page); - }); - - it("Check if throws an error in console log", async function () { - // based on incorrect fields config, should get following console.error - const expectedErrors = [ - "getDefaultValue for ratings must be a function", - "getDefaultValue for country must return a string", - ]; - let consoleErrorMessages = []; - - page.on("console", async (msg) => { - // serialize the args, returns error text - const args = await Promise.all( - msg.args().map((arg) => - arg.executionContext().evaluate((arg) => { - if (arg instanceof Error) { - return arg.message; - } - return null; - }, arg) - ) - ); - consoleErrorMessages.push(...args.filter(Boolean)); - }); - - await page.reload({ - waitUntil: "domcontentloaded", - }); - await toggleSignInSignUp(page); - - // Some delay so that console messages have been processed - await new Promise((resolve) => setTimeout(resolve, 1000)); - let isSubset = expectedErrors.every((err) => consoleErrorMessages.includes(err)); - assert(isSubset, "Throwing errors for incorrect field config - getDefaultValue prop"); - - // Supply NON-STRING value to onChange function should throw error - const expectedOnChangeError = ["terms value must be a string"]; - // check terms and condition checkbox - - let termsCheckbox = await waitForSTElement(page, '[name="terms"]'); - await page.evaluate((e) => e.click(), termsCheckbox); - isSubset = false; - - await new Promise((resolve) => setTimeout(resolve, 1000)); - isSubset = expectedOnChangeError.every((err) => consoleErrorMessages.includes(err)); - assert(isSubset, "Throwing errors for incorrect field config - onChange func."); - }); - }); }); describe("SuperTokens SignUp => Server Error", function () { From 259699a7cf04a42e8fc4bae783468bdf1a205a9a Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 31 Oct 2023 11:46:20 +0530 Subject: [PATCH 44/53] Add change log feedbacks --- CHANGELOG.md | 47 +++++++++++++++++++++++++++-------------------- 1 file changed, 27 insertions(+), 20 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc01ccd46..95328553a 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -5,36 +5,43 @@ All notable changes to this project will be documented in this file. The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/), and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html) -## [0.35.7] - 2023-10-30 +## [0.36.0] - 2023-10-30 ### Added -- Introduced the capability to utilize custom components in the Email-Password signup form fields by exposing inputComponent types. -- Implemented the functionality to assign default values to the form fields in the Email-Password Sign Up process. +- Introduced the capability to utilize custom components in the Email-Password based recipes' signup form fields by exposing inputComponent types. +- Implemented the functionality to assign default values to the form fields in the Email-Password based recipes. - Enhanced the onChange function to operate independently without requiring an id field. -- Accompanied the new features with comprehensive tests to ensure their proper functionality and reliability. Following is an example of how to use above features. ```tsx EmailPassword.init({ - signInAndUpFeature: { + signInAndUpFeature: { signUpForm: { - formFields: [ - { - id: "ratings", - label: "Ratings", - getDefaultValue: () => "best", - inputComponent: ({ value, name, onChange }) => ( - - )} + formFields: [ + { + id: "select-dropdown", + label: "Select Option", + getDefaultValue: () => "option 2", + inputComponent: ({ value, name, onChange }) => ( + + ) + } + ] + } + } +}); ... ``` From 492fee97fdef24403ef5403d3137c59c1a1427e5 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 31 Oct 2023 12:04:35 +0530 Subject: [PATCH 45/53] Better variable names, include formfields directly in typescript example --- examples/for-tests/src/App.js | 2 +- test/end-to-end/signup.test.js | 31 +++++- test/with-typescript/src/App.tsx | 185 ++++++++++++++++--------------- 3 files changed, 120 insertions(+), 98 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 4496d10a2..0b3c1db7e 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -669,7 +669,7 @@ function getEmailVerificationConfigs({ disableDefaultUI }) { function getFormFields() { if (localStorage.getItem("SHOW_INCORRECT_FIELDS") === "YES") { return incorrectFormFields; - } else if (localStorage.getItem("SHOW_DEFAULT_FIELDS") === "YES") { + } else if (localStorage.getItem("SHOW_CUSTOM_FIELDS_WITH_DEFAULT_VALUES") === "YES") { return formFieldsWithDefault; } else if (localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES") { return customFields; diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index f835217ec..bb2cc2d01 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -444,7 +444,7 @@ describe("SuperTokens SignUp", function () { describe("Signup default value for fields test", function () { beforeEach(async function () { // set cookie and reload which loads the form fields with default values - await page.evaluate(() => window.localStorage.setItem("SHOW_DEFAULT_FIELDS", "YES")); + await page.evaluate(() => window.localStorage.setItem("SHOW_CUSTOM_FIELDS_WITH_DEFAULT_VALUES", "YES")); await page.reload({ waitUntil: "domcontentloaded", @@ -480,14 +480,35 @@ describe("SuperTokens SignUp", function () { // input field default value const countryInput = await getInputField(page, "country"); - const defaultCountry = await countryInput.evaluate((f) => f.value); - assert.strictEqual(defaultCountry, updatedFields["country"]); + const updatedCountry = await countryInput.evaluate((f) => f.value); + assert.strictEqual(updatedCountry, updatedFields["country"]); // dropdown default value is also getting set correctly const ratingsDropdown = await waitForSTElement(page, "select"); - const defaultRating = await ratingsDropdown.evaluate((f) => f.value); - assert.strictEqual(defaultRating, updatedFields["ratings"]); + const updatedRating = await ratingsDropdown.evaluate((f) => f.value); + assert.strictEqual(updatedRating, updatedFields["ratings"]); }); + + // TODO + // it("Check if default values are getting sent in signup-payload", async function () { + // const updatedFields = { + // country: "USA", + // ratings: "good", + // }; + + // await setInputValues(page, [{ name: "country", value: updatedFields["country"] }]); + // await setSelectDropdownValue(page, 'select[name="ratings"]', updatedFields["ratings"]); + + // // input field default value + // const countryInput = await getInputField(page, "country"); + // const updatedCountry = await countryInput.evaluate((f) => f.value); + // assert.strictEqual(updatedCountry, updatedFields["country"]); + + // // dropdown default value is also getting set correctly + // const ratingsDropdown = await waitForSTElement(page, "select"); + // const updatedRating = await ratingsDropdown.evaluate((f) => f.value); + // assert.strictEqual(updatedRating, updatedFields["ratings"]); + // }); }); }); diff --git a/test/with-typescript/src/App.tsx b/test/with-typescript/src/App.tsx index a053d3303..fe7d38c44 100644 --- a/test/with-typescript/src/App.tsx +++ b/test/with-typescript/src/App.tsx @@ -326,97 +326,6 @@ function getRecipeList() { ]; } -const formFieldsForSignUp = [ - { - id: "email", - label: "Your Email", - placeholder: "Your work email", - }, - { - id: "name", - label: "Full name", - placeholder: "First name and last name", - }, - { - id: "age", - label: "Your age", - placeholder: "How old are you?", - validate: async (value) => { - if (parseInt(value) > 18) { - return undefined; - } - - return "You must be over 18 to register"; - }, - }, - { - id: "country", - label: "Your Country", - placeholder: "Where do you live?", - optional: true, - }, -]; - -const customFormFieldsWithDefault = [ - { - id: "terms", - label: "", - optional: false, - inputComponent: (inputProps) => ( -
- { - if (inputProps.onChange) { - inputProps.onChange(e.target.checked.toString()); - } - }}> - I agree to the terms and conditions -
- ), - validate: async (value) => { - if (value === "true") { - return undefined; - } - return "Please check Terms and conditions"; - }, - }, - { - id: "ratings", - label: "Ratings", - getDefaultValue: () => "best", - inputComponent: (inputProps) => ( - - ), - optional: true, - }, -]; - -const getFormFieldsForSignUp = () => { - return localStorage.getItem("SHOW_CUSTOM_FIELDS") === "YES" ? customFormFieldsWithDefault : formFieldsForSignUp; -}; - function getEmailPasswordConfigs() { return EmailPassword.init({ resetPasswordUsingTokenFeature: { @@ -435,7 +344,99 @@ function getEmailPasswordConfigs() { style: theme.style, privacyPolicyLink: "https://supertokens.io/legal/privacy-policy", termsOfServiceLink: "https://supertokens.io/legal/terms-and-conditions", - formFields: getFormFieldsForSignUp(), + formFields: [ + { + id: "email", + label: "Your Email", + placeholder: "Your work email", + }, + { + id: "name", + label: "Full name", + placeholder: "First name and last name", + }, + { + id: "age", + label: "Your age", + placeholder: "How old are you?", + validate: async (value) => { + if (parseInt(value) > 18) { + return undefined; + } + + return "You must be over 18 to register"; + }, + }, + { + id: "country", + label: "Your Country", + placeholder: "Where do you live?", + optional: true, + }, + { + id: "terms", + label: "", + optional: false, + inputComponent: (inputProps) => ( +
+ { + if (inputProps.onChange) { + inputProps.onChange(e.target.checked.toString()); + } + }}> + I agree to the terms and conditions +
+ ), + validate: async (value) => { + if (value === "true") { + return undefined; + } + return "Please check Terms and conditions"; + }, + }, + { + id: "select", + label: "Select", + getDefaultValue: () => "option 2", + inputComponent: (inputProps) => ( + + ), + optional: true, + }, + ], }, }, From 329aee4f90cb118717805fc18873aaf5c74de4bd Mon Sep 17 00:00:00 2001 From: amitbadala Date: Tue, 31 Oct 2023 17:49:53 +0530 Subject: [PATCH 46/53] Add more tests for default & onChange func, updated typescript file to show the latest changes --- examples/for-tests/src/App.js | 84 +++++++++++---- test/end-to-end/signup.test.js | 176 +++++++++++++++++++++++++++---- test/with-typescript/src/App.tsx | 6 +- 3 files changed, 225 insertions(+), 41 deletions(-) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 0b3c1db7e..2beab128d 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -177,21 +177,59 @@ const formFieldsWithDefault = [ getDefaultValue: () => "India", }, { - id: "ratings", - label: "Ratings", - getDefaultValue: () => "best", + id: "select-dropdown", + label: "Select Option", + getDefaultValue: () => "option 2", inputComponent: ({ value, name, onChange }) => ( - onChange(e.target.value)}> - - - + + + ), optional: true, }, + { + id: "terms", + label: "", + optional: false, + getDefaultValue: () => "true", + inputComponent: ({ name, onChange, value }) => ( +
+ onChange(e.target.checked.toString())}> + I agree to the terms and conditions +
+ ), + validate: async (value) => { + if (value === "true") { + return undefined; + } + return "Please check Terms and conditions"; + }, + }, + { + id: "email", + label: "Email", + getDefaultValue: () => "test@one.com", + }, + { + id: "password", + label: "Password", + getDefaultValue: () => "fakepassword123", + }, ]; const incorrectFormFields = [ @@ -203,22 +241,23 @@ const incorrectFormFields = [ getDefaultValue: () => 23, // return should be a string }, { - id: "ratings", - label: "Ratings", - getDefaultValue: "best", // should be function + id: "select-dropdown", + label: "Select Dropdown", + getDefaultValue: "option 2", // should be function inputComponent: ({ value, name, onChange }) => ( - onChange(e.target.value)}> - - - + + + ), optional: true, }, { + // onChange accepts only string value, here we pass boolean id: "terms", label: "", optional: false, @@ -244,16 +283,16 @@ const incorrectFormFields = [ const customFields = [ { - id: "ratings", - label: "Ratings", + id: "select-dropdown", + label: "Select Dropdown", inputComponent: ({ value, name, onChange }) => ( - onChange(e.target.value)}> - - - + + + ), optional: true, @@ -668,6 +707,11 @@ function getEmailVerificationConfigs({ disableDefaultUI }) { function getFormFields() { if (localStorage.getItem("SHOW_INCORRECT_FIELDS") === "YES") { + if (localStorage.getItem("INCORRECT_ONCHANGE") === "YES") { + // since page-error blocks all the other errors + // use this filter to test specific error + return incorrectFormFields.filter(({ id }) => id === "terms"); + } return incorrectFormFields; } else if (localStorage.getItem("SHOW_CUSTOM_FIELDS_WITH_DEFAULT_VALUES") === "YES") { return formFieldsWithDefault; diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index bb2cc2d01..f2d56edd7 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -403,20 +403,39 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(formFieldErrors, ["Please check Terms and conditions"]); }); - it("Check if custom values are part of the signup payload", async function () { + it.only("Check if custom values are part of the signup payload", async function () { const customFields = { - terms: true, // checked - ratings: "best", + terms: "true", + "select-dropdown": "option 3", }; - const requestHandler = (request) => { + let assertionError = null; + let interceptionPassed = false; + await page.setRequestInterception(true); + const requestHandler = async (request) => { + console.log("REQUEST", request.method()); if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { - Object.keys(customFields).every((key) => { - assert.strictEqual(data[key], customFields[key]); - }); + try { + console.log("TEdkjfhskjdfhsST", interceptionPassed); + const postData = JSON.parse(request.postData()); + Object.keys(customFields).forEach((key) => { + let findFormData = postData.formFields.find((inputData) => inputData.id === key); + if (findFormData) { + assert.strictEqual( + findFormData["value"], + customFields[key], + `Mismatch in payload for key: ${key}` + ); + } else { + throw new Error("Field not found in req.data"); + } + }); + interceptionPassed = true; + } catch (error) { + console.log("VALUER ADDED FOR ASSERTION"); + assertionError = error; // Store the error + } } - return request.continue(); }; - await page.setRequestInterception(true); page.on("request", requestHandler); // Fill and submit the form with custom fields @@ -426,17 +445,29 @@ describe("SuperTokens SignUp", function () { { name: "password", value: "Str0ngP@assw0rd" }, ]); - await setSelectDropdownValue(page, 'select[name="ratings"]', customFields["ratings"]); + await setSelectDropdownValue(page, "select", customFields["select-dropdown"]); // Check terms and condition checkbox let termsCheckbox = await waitForSTElement(page, '[name="terms"]'); await page.evaluate((e) => e.click(), termsCheckbox); + // Perform the button click and wait for all network activity to finish await submitForm(page); + await new Promise((r) => setTimeout(r, 5000)); + // await page.waitForNavigation({ waitUntil: "networkidle0" }); + // await page.waitForResponse(); } finally { page.off("request", requestHandler); await page.setRequestInterception(false); } + + if (assertionError) { + throw assertionError; + } + + if (!interceptionPassed) { + throw new Error("test failed"); + } }); }); @@ -455,7 +486,8 @@ describe("SuperTokens SignUp", function () { it("Check if default values are set already", async function () { const fieldsWithDefault = { country: "India", - ratings: "best", + "select-dropdown": "option 2", + terms: true, }; // regular input field default value @@ -464,19 +496,28 @@ describe("SuperTokens SignUp", function () { assert.strictEqual(defaultCountry, fieldsWithDefault["country"]); // custom dropdown default value is also getting set correctly - const ratingsDropdown = await waitForSTElement(page, "select"); - const defaultRating = await ratingsDropdown.evaluate((f) => f.value); - assert.strictEqual(defaultRating, fieldsWithDefault["ratings"]); + const selectDropdown = await waitForSTElement(page, "select"); + const defaultOption = await selectDropdown.evaluate((f) => f.value); + assert.strictEqual(defaultOption, fieldsWithDefault["select-dropdown"]); + + // custom dropdown default value is also getting set correctly + const termsCheckbox = await waitForSTElement(page, '[name="terms"]'); + // checkbox is checked + const defaultChecked = await termsCheckbox.evaluate((f) => f.checked); + assert.strictEqual(defaultChecked, fieldsWithDefault["terms"]); + // also the value = string + const defaultValue = await termsCheckbox.evaluate((f) => f.value); + assert.strictEqual(defaultValue, fieldsWithDefault["terms"].toString()); }); it("Check if changing the field value actually overwrites the default value", async function () { const updatedFields = { country: "USA", - ratings: "good", + "select-dropdown": "option 3", }; await setInputValues(page, [{ name: "country", value: updatedFields["country"] }]); - await setSelectDropdownValue(page, 'select[name="ratings"]', updatedFields["ratings"]); + await setSelectDropdownValue(page, 'select[name="select-dropdown"]', updatedFields["select-dropdown"]); // input field default value const countryInput = await getInputField(page, "country"); @@ -484,9 +525,106 @@ describe("SuperTokens SignUp", function () { assert.strictEqual(updatedCountry, updatedFields["country"]); // dropdown default value is also getting set correctly - const ratingsDropdown = await waitForSTElement(page, "select"); - const updatedRating = await ratingsDropdown.evaluate((f) => f.value); - assert.strictEqual(updatedRating, updatedFields["ratings"]); + const selectDropdown = await waitForSTElement(page, "select"); + const updatedOption = await selectDropdown.evaluate((f) => f.value); + assert.strictEqual(updatedOption, updatedFields["select-dropdown"]); + }); + + it("Check if default values are getting sent in signup-payload", async function () { + // directly submit the form and test the payload + const expectedDefautlValues = [ + { id: "email", value: "test@one.com" }, + { id: "password", value: "test@one.com" }, + { id: "terms", value: "true" }, + { id: "select-dropdown", value: "option 2" }, + { id: "country", value: "India" }, + ]; + + let assertionError = null; + const requestHandler = async (request) => { + if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { + try { + const postData = JSON.parse(request.postData()); + Object.keys(customFields).forEach((key) => { + let findFormData = postData.formFields.find((inputData) => inputData.id === key); + if (findFormData) { + assert.strictEqual( + findFormData["value"], + customFields[key], + `Mismatch in payload for key: ${key}` + ); + } else { + assert.fail("Field not found in req.data"); + } + }); + } catch (error) { + console.log("VALUER ADDED FOR ASSERTION"); + assertionError = error; // Store the error + } + } + return request.continue(); + }; + await page.setRequestInterception(true); + page.on("request", requestHandler); + + await submitForm(page); + await new Promise((resolve) => setTimeout(resolve, 3000)); + + if (assertionError) { + throw assertionError; + } + }); + }); + + describe("Incorrect field config test", function () { + beforeEach(async function () { + // set cookie and reload which loads the form fields with default values + await page.evaluate(() => window.localStorage.setItem("SHOW_INCORRECT_FIELDS", "YES")); + + await page.reload({ + waitUntil: "domcontentloaded", + }); + }); + + it("Check if incorrect getDefaultValue throws error", async function () { + let pageErrorMessage = ""; + page.on("pageerror", (err) => { + pageErrorMessage = err.message; + }); + + await page.reload({ + waitUntil: "domcontentloaded", + }); + await toggleSignInSignUp(page); + + const expectedErrorMessage = "getDefaultValue for country must return a string"; + assert( + pageErrorMessage.includes(expectedErrorMessage), + `Expected "${expectedErrorMessage}" to be included in page-error` + ); + }); + + it("Check if non-string params to onChange throws error", async function () { + await page.evaluate(() => window.localStorage.setItem("INCORRECT_ONCHANGE", "YES")); + await page.reload({ + waitUntil: "domcontentloaded", + }); + await toggleSignInSignUp(page); + + let pageErrorMessage = ""; + page.on("pageerror", (err) => { + pageErrorMessage = err.message; + }); + + // check terms and condition checkbox since it emits non-string value => boolean + let termsCheckbox = await waitForSTElement(page, '[name="terms"]'); + await page.evaluate((e) => e.click(), termsCheckbox); + + const expectedErrorMessage = "terms value must be a string"; + assert( + pageErrorMessage.includes(expectedErrorMessage), + `Expected "${expectedErrorMessage}" to be included in page-error` + ); }); // TODO diff --git a/test/with-typescript/src/App.tsx b/test/with-typescript/src/App.tsx index fe7d38c44..1f8a4b3fa 100644 --- a/test/with-typescript/src/App.tsx +++ b/test/with-typescript/src/App.tsx @@ -377,6 +377,7 @@ function getEmailPasswordConfigs() { id: "terms", label: "", optional: false, + getDefaultValue: () => "true", inputComponent: (inputProps) => (
{ if (inputProps.onChange) { inputProps.onChange(e.target.checked.toString()); @@ -424,8 +427,7 @@ function getEmailPasswordConfigs() { if (inputProps.onChange) { inputProps.onChange(e.target.value); } - }} - placeholder="Select option"> + }}> From 9aa6b2183387a64b39abaabae6b5c3b74ebe8492 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 14:30:09 +0530 Subject: [PATCH 47/53] Add more test, which intercepts request payload --- test/end-to-end/signup.test.js | 73 +++++++++++++++++++++++----------- test/helpers.js | 20 ++++++---- 2 files changed, 61 insertions(+), 32 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index f2d56edd7..515132134 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -403,19 +403,17 @@ describe("SuperTokens SignUp", function () { assert.deepStrictEqual(formFieldErrors, ["Please check Terms and conditions"]); }); - it.only("Check if custom values are part of the signup payload", async function () { + it("Check if custom values are part of the signup payload", async function () { const customFields = { terms: "true", "select-dropdown": "option 3", }; let assertionError = null; let interceptionPassed = false; - await page.setRequestInterception(true); + const requestHandler = async (request) => { - console.log("REQUEST", request.method()); if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { try { - console.log("TEdkjfhskjdfhsST", interceptionPassed); const postData = JSON.parse(request.postData()); Object.keys(customFields).forEach((key) => { let findFormData = postData.formFields.find((inputData) => inputData.id === key); @@ -430,16 +428,28 @@ describe("SuperTokens SignUp", function () { } }); interceptionPassed = true; + return request.respond({ + status: 200, + headers: { + "access-control-allow-origin": TEST_CLIENT_BASE_URL, + "access-control-allow-credentials": "true", + }, + body: JSON.stringify({ + status: "OK", + }), + }); } catch (error) { - console.log("VALUER ADDED FOR ASSERTION"); assertionError = error; // Store the error } } + return request.continue(); }; + + await page.setRequestInterception(true); page.on("request", requestHandler); - // Fill and submit the form with custom fields try { + // Fill and submit the form with custom fields await setInputValues(page, [ { name: "email", value: "john.doe@supertokens.io" }, { name: "password", value: "Str0ngP@assw0rd" }, @@ -452,10 +462,7 @@ describe("SuperTokens SignUp", function () { await page.evaluate((e) => e.click(), termsCheckbox); // Perform the button click and wait for all network activity to finish - await submitForm(page); - await new Promise((r) => setTimeout(r, 5000)); - // await page.waitForNavigation({ waitUntil: "networkidle0" }); - // await page.waitForResponse(); + await Promise.all([page.waitForNetworkIdle(), submitForm(page)]); } finally { page.off("request", requestHandler); await page.setRequestInterception(false); @@ -517,7 +524,7 @@ describe("SuperTokens SignUp", function () { }; await setInputValues(page, [{ name: "country", value: updatedFields["country"] }]); - await setSelectDropdownValue(page, 'select[name="select-dropdown"]', updatedFields["select-dropdown"]); + await setSelectDropdownValue(page, "select", updatedFields["select-dropdown"]); // input field default value const countryInput = await getInputField(page, "country"); @@ -534,45 +541,63 @@ describe("SuperTokens SignUp", function () { // directly submit the form and test the payload const expectedDefautlValues = [ { id: "email", value: "test@one.com" }, - { id: "password", value: "test@one.com" }, + { id: "password", value: "fakepassword123" }, { id: "terms", value: "true" }, { id: "select-dropdown", value: "option 2" }, { id: "country", value: "India" }, ]; let assertionError = null; + let interceptionPassed = false; + const requestHandler = async (request) => { if (request.url().includes(SIGN_UP_API) && request.method() === "POST") { try { const postData = JSON.parse(request.postData()); - Object.keys(customFields).forEach((key) => { - let findFormData = postData.formFields.find((inputData) => inputData.id === key); + expectedDefautlValues.forEach(({ id, value }) => { + let findFormData = postData.formFields.find((inputData) => inputData.id === id); if (findFormData) { - assert.strictEqual( - findFormData["value"], - customFields[key], - `Mismatch in payload for key: ${key}` - ); + assert.strictEqual(findFormData["value"], value, `Mismatch in payload for key: ${id}`); } else { - assert.fail("Field not found in req.data"); + throw new Error("Field not found in req.data"); } }); + interceptionPassed = true; + return request.respond({ + status: 200, + headers: { + "access-control-allow-origin": TEST_CLIENT_BASE_URL, + "access-control-allow-credentials": "true", + }, + body: JSON.stringify({ + status: "OK", + }), + }); } catch (error) { - console.log("VALUER ADDED FOR ASSERTION"); assertionError = error; // Store the error } } return request.continue(); }; + await page.setRequestInterception(true); page.on("request", requestHandler); - await submitForm(page); - await new Promise((resolve) => setTimeout(resolve, 3000)); + try { + // Perform the button click and wait for all network activity to finish + await Promise.all([page.waitForNetworkIdle(), submitForm(page)]); + } finally { + page.off("request", requestHandler); + await page.setRequestInterception(false); + } if (assertionError) { throw assertionError; } + + if (!interceptionPassed) { + throw new Error("test failed"); + } }); }); @@ -658,7 +683,7 @@ describe("SuperTokens SignUp => Server Error", function () { before(async function () { browser = await puppeteer.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: true, + headless: false, }); }); diff --git a/test/helpers.js b/test/helpers.js index 20ab9b6b4..ebfbbc067 100644 --- a/test/helpers.js +++ b/test/helpers.js @@ -418,17 +418,16 @@ export async function setInputValues(page, fields) { } export async function setSelectDropdownValue(page, selector, optionValue) { + const shadowRootHandle = await getShadowRootHandle(page); return await page.evaluate( - (selector, optionValue, ST_ROOT_SELECTOR) => { - const dropdownElement = document.querySelector(ST_ROOT_SELECTOR).shadowRoot.querySelector(selector); - if (dropdownElement) { - dropdownElement.value = optionValue; - dropdownElement.dispatchEvent(new Event("change", { bubbles: false })); - } + (root, selector, optionValue) => { + const select = root.querySelector(selector); + select.value = optionValue; + select.dispatchEvent(new Event("change", { bubbles: true })); }, + shadowRootHandle, selector, - optionValue, - ST_ROOT_SELECTOR + optionValue ); } @@ -1021,3 +1020,8 @@ export async function backendBeforeEach() { }).catch(console.error); } } + +export async function getShadowRootHandle(page) { + const hostElement = await page.$(ST_ROOT_SELECTOR); + return await page.evaluateHandle((el) => el.shadowRoot, hostElement); +} From e23f1d80804f2a9a59b76a711727aa8af75518ff Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 14:41:07 +0530 Subject: [PATCH 48/53] Cleanup comments --- test/end-to-end/signup.test.js | 21 --------------------- 1 file changed, 21 deletions(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 515132134..5fe0eb68f 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -651,27 +651,6 @@ describe("SuperTokens SignUp", function () { `Expected "${expectedErrorMessage}" to be included in page-error` ); }); - - // TODO - // it("Check if default values are getting sent in signup-payload", async function () { - // const updatedFields = { - // country: "USA", - // ratings: "good", - // }; - - // await setInputValues(page, [{ name: "country", value: updatedFields["country"] }]); - // await setSelectDropdownValue(page, 'select[name="ratings"]', updatedFields["ratings"]); - - // // input field default value - // const countryInput = await getInputField(page, "country"); - // const updatedCountry = await countryInput.evaluate((f) => f.value); - // assert.strictEqual(updatedCountry, updatedFields["country"]); - - // // dropdown default value is also getting set correctly - // const ratingsDropdown = await waitForSTElement(page, "select"); - // const updatedRating = await ratingsDropdown.evaluate((f) => f.value); - // assert.strictEqual(updatedRating, updatedFields["ratings"]); - // }); }); }); From a50d64b7cabd309057d26b6646efbce52b987ce3 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 14:46:14 +0530 Subject: [PATCH 49/53] Minor formatting --- CHANGELOG.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 95328553a..91ab49348 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,9 +31,9 @@ EmailPassword.init({ onChange={(e) => onChange(e.target.value)} placeholder="Select Option" > - - - + + + ) From 133964508a2bb40cd2c1112abf685f497794594c Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 16:13:05 +0530 Subject: [PATCH 50/53] Minor fix --- test/end-to-end/signup.test.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/end-to-end/signup.test.js b/test/end-to-end/signup.test.js index 5fe0eb68f..829ad2c4f 100644 --- a/test/end-to-end/signup.test.js +++ b/test/end-to-end/signup.test.js @@ -662,7 +662,7 @@ describe("SuperTokens SignUp => Server Error", function () { before(async function () { browser = await puppeteer.launch({ args: ["--no-sandbox", "--disable-setuid-sandbox"], - headless: false, + headless: true, }); }); From 0333dbf96f21f9da8d4701caf34e6879444ceed2 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 16:43:57 +0530 Subject: [PATCH 51/53] Add getDefaultValue to signin, hence shuffle the existing types to include that --- lib/build/recipe/emailpassword/types.d.ts | 3 --- lib/build/types.d.ts | 2 ++ lib/ts/recipe/emailpassword/types.ts | 7 ------- lib/ts/types.ts | 10 ++++++++++ 4 files changed, 12 insertions(+), 10 deletions(-) diff --git a/lib/build/recipe/emailpassword/types.d.ts b/lib/build/recipe/emailpassword/types.d.ts index 70c7ffcfd..d1cb6e9ea 100644 --- a/lib/build/recipe/emailpassword/types.d.ts +++ b/lib/build/recipe/emailpassword/types.d.ts @@ -79,7 +79,6 @@ export declare type NormalisedSignInAndUpFeatureConfig = { export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { formFields?: (FormField & { inputComponent?: (props: InputProps) => JSX.Element; - getDefaultValue?: () => string; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; @@ -87,7 +86,6 @@ export declare type SignUpFormFeatureUserInput = FeatureBaseConfig & { export declare type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { formFields: (NormalisedFormField & { inputComponent?: (props: InputProps) => JSX.Element; - getDefaultValue?: () => string; })[]; privacyPolicyLink?: string; termsOfServiceLink?: string; @@ -153,7 +151,6 @@ export declare type FormFieldThemeProps = NormalisedFormField & { showIsRequired?: boolean; clearOnSubmit?: boolean; inputComponent?: (props: InputProps) => JSX.Element; - getDefaultValue?: () => string; }; export declare type FormFieldError = { id: string; diff --git a/lib/build/types.d.ts b/lib/build/types.d.ts index 1aa7f2568..93605a26e 100644 --- a/lib/build/types.d.ts +++ b/lib/build/types.d.ts @@ -89,6 +89,7 @@ export declare type FormFieldBaseConfig = { id: string; label: string; placeholder?: string; + getDefaultValue?: () => string; }; export declare type FormField = FormFieldBaseConfig & { validate?: (value: any) => Promise; @@ -106,6 +107,7 @@ export declare type NormalisedFormField = { optional: boolean; autoComplete?: string; autofocus?: boolean; + getDefaultValue?: () => string; }; export declare type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); export declare type FeatureBaseConfig = { diff --git a/lib/ts/recipe/emailpassword/types.ts b/lib/ts/recipe/emailpassword/types.ts index 8618f6d1e..5ffe5b3d1 100644 --- a/lib/ts/recipe/emailpassword/types.ts +++ b/lib/ts/recipe/emailpassword/types.ts @@ -136,7 +136,6 @@ export type SignUpFormFeatureUserInput = FeatureBaseConfig & { */ formFields?: (FormField & { inputComponent?: (props: InputProps) => JSX.Element; - getDefaultValue?: () => string; })[]; /* @@ -156,7 +155,6 @@ export type NormalisedSignUpFormFeatureConfig = NormalisedBaseConfig & { */ formFields: (NormalisedFormField & { inputComponent?: (props: InputProps) => JSX.Element; - getDefaultValue?: () => string; })[]; /* @@ -289,11 +287,6 @@ export type FormFieldThemeProps = NormalisedFormField & { * Ability to add custom components */ inputComponent?: (props: InputProps) => JSX.Element; - - /* - * Ability to add custom components - */ - getDefaultValue?: () => string; }; export type FormFieldError = { diff --git a/lib/ts/types.ts b/lib/ts/types.ts index 8399585b5..16beba60b 100644 --- a/lib/ts/types.ts +++ b/lib/ts/types.ts @@ -222,6 +222,11 @@ export type FormFieldBaseConfig = { * placeholder of the input field. */ placeholder?: string; + + /* + *Ability to provide default value to input field. + */ + getDefaultValue?: () => string; }; export type FormField = FormFieldBaseConfig & { @@ -283,6 +288,11 @@ export type NormalisedFormField = { * Moves focus to the input element when it mounts */ autofocus?: boolean; + + /* + *Ability to provide default value to input field. + */ + getDefaultValue?: () => string; }; export type ReactComponentClass

= ComponentClass | ((props: P) => JSX.Element); From fa3b6f7a8488fa2c84c3a838f1bd0fa22cdbaba3 Mon Sep 17 00:00:00 2001 From: amitbadala Date: Wed, 1 Nov 2023 18:26:09 +0530 Subject: [PATCH 52/53] Add tests for default signin feature --- test/end-to-end/signin.test.js | 88 ++++++++++++++++++++++++++++++++++ 1 file changed, 88 insertions(+) diff --git a/test/end-to-end/signin.test.js b/test/end-to-end/signin.test.js index 86a204645..e9c4721f1 100644 --- a/test/end-to-end/signin.test.js +++ b/test/end-to-end/signin.test.js @@ -669,6 +669,94 @@ describe("SuperTokens SignIn", function () { }); }); }); + + describe("SignIn default field tests", function () { + it("Should contain email and password fields prefilled", async function () { + await page.evaluate(() => window.localStorage.setItem("SHOW_SIGNIN_DEFAULT_FIELDS", "YES")); + + await page.reload({ + waitUntil: "domcontentloaded", + }); + + const expectedDefaultValues = { + email: "abc@xyz.com", + password: "fakepassword123", + }; + + const emailInput = await getInputField(page, "email"); + const defaultEmail = await emailInput.evaluate((f) => f.value); + assert.strictEqual(defaultEmail, expectedDefaultValues["email"]); + + const passwordInput = await getInputField(page, "password"); + const defaultPassword = await passwordInput.evaluate((f) => f.value); + assert.strictEqual(defaultPassword, expectedDefaultValues["password"]); + }); + + it("Should have default values in the signin request payload", async function () { + await page.evaluate(() => window.localStorage.setItem("SHOW_SIGNIN_DEFAULT_FIELDS", "YES")); + + await page.reload({ + waitUntil: "domcontentloaded", + }); + + const expectedDefautlValues = [ + { id: "email", value: "abc@xyz.com" }, + { id: "password", value: "fakepassword123" }, + ]; + + let assertionError = null; + let interceptionPassed = false; + + const requestHandler = async (request) => { + if (request.url().includes(SIGN_IN_API) && request.method() === "POST") { + try { + const postData = JSON.parse(request.postData()); + expectedDefautlValues.forEach(({ id, value }) => { + let findFormData = postData.formFields.find((inputData) => inputData.id === id); + if (findFormData) { + assert.strictEqual(findFormData["value"], value, `Mismatch in payload for key: ${id}`); + } else { + throw new Error("Field not found in req.data"); + } + }); + interceptionPassed = true; + return request.respond({ + status: 200, + headers: { + "access-control-allow-origin": TEST_CLIENT_BASE_URL, + "access-control-allow-credentials": "true", + }, + body: JSON.stringify({ + status: "OK", + }), + }); + } catch (error) { + assertionError = error; // Store the error + } + } + return request.continue(); + }; + + await page.setRequestInterception(true); + page.on("request", requestHandler); + + try { + // Perform the button click and wait for all network activity to finish + await Promise.all([page.waitForNetworkIdle(), submitForm(page)]); + } finally { + page.off("request", requestHandler); + await page.setRequestInterception(false); + } + + if (assertionError) { + throw assertionError; + } + + if (!interceptionPassed) { + throw new Error("test failed"); + } + }); + }); }); describe("SuperTokens SignIn => Server Error", function () { From c5ee75c183f4b6f58259bbaf49763e4fb5190b1b Mon Sep 17 00:00:00 2001 From: amitbadala Date: Thu, 2 Nov 2023 10:20:03 +0530 Subject: [PATCH 53/53] Add signin form fields with default value for tests --- examples/for-tests/src/App.js | 20 ++++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/examples/for-tests/src/App.js b/examples/for-tests/src/App.js index 2beab128d..505beeb61 100644 --- a/examples/for-tests/src/App.js +++ b/examples/for-tests/src/App.js @@ -721,6 +721,25 @@ function getFormFields() { return formFields; } +function getSignInFormFields() { + let showDefaultFields = localStorage.getItem("SHOW_SIGNIN_DEFAULT_FIELDS"); + if (showDefaultFields === "YES") { + return { + formFields: [ + { + id: "email", + getDefaultValue: () => "abc@xyz.com", + }, + { + id: "password", + getDefaultValue: () => "fakepassword123", + }, + ], + }; + } + return {}; +} + function getEmailPasswordConfigs({ disableDefaultUI }) { return EmailPassword.init({ style: ` @@ -801,6 +820,7 @@ function getEmailPasswordConfigs({ disableDefaultUI }) { defaultToSignUp, signInForm: { style: theme, + ...getSignInFormFields(), }, signUpForm: { style: theme,