diff --git a/src/core/client/auth/components/SignIn.tsx b/src/core/client/auth/components/SignIn.tsx index b1bbe514a..fb973eba3 100644 --- a/src/core/client/auth/components/SignIn.tsx +++ b/src/core/client/auth/components/SignIn.tsx @@ -124,6 +124,7 @@ const SignIn: StatelessComponent = props => { + + + +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+ + + +`; + +exports[`accepts valid email 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`checks for invalid email 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+ + warning + + + Please enter a valid email address. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`renders sign in form 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+
+ + +
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`shows error when submitting empty form 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`shows server error 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+
+ + +
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`shows server error 2`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ Server Error +
+
+ + +
+
+ + +
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`submits form successfully 1`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ Server Error +
+
+ + +
+
+ + +
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; + +exports[`submits form successfully 2`] = ` +
+
+
+

+ Sign in to join the conversation +

+
+ + +
+
+ + +
+ +
+
+ +
+
+ + Don't have an account? <button>Sign Up</button> + +
+
+
+
+
+`; diff --git a/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap new file mode 100644 index 000000000..0d13ca1bc --- /dev/null +++ b/src/core/client/auth/test/__snapshots__/signUp.spec.tsx.snap @@ -0,0 +1,2355 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`accepts correct password 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`accepts correct password confirmation 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`accepts valid email 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + This field is required. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`accepts valid username 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for invalid characters in username 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + Invalid characters. Try again. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for invalid email 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+ + warning + + + Please enter a valid email address. + +
+
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + This field is required. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for too long username 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + Usernames cannot be longer than {$maxLength} characters. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for too short password 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + Password must contain at least {$minLength} characters. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for too short username 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + Usernames must contain at least {$minLength} characters. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`checks for wrong password confirmation 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ + warning + + + Passwords do not match. Try again. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`renders sign up form 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`shows error when submitting empty form 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+ + warning + + + This field is required. + +
+
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+ + warning + + + This field is required. + +
+
+
+ +

+ Must be at least {$minLength} characters +

+ +
+ + warning + + + This field is required. + +
+
+
+ + +
+ + warning + + + This field is required. + +
+
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`shows server error 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`shows server error 2`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ Server Error +
+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`submits form successfully 1`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ Server Error +
+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; + +exports[`submits form successfully 2`] = ` +
+
+
+

+ Sign up to join the conversation +

+
+ + +
+
+ +

+ A unique identifier displayed on your comments. You may use “_” and “.” +

+ +
+
+ +

+ Must be at least {$minLength} characters +

+ +
+
+ + +
+ +
+
+ + Already have an account? <button>Sign In </button> + +
+
+
+
+
+`; diff --git a/src/core/client/auth/test/createEnvironment.ts b/src/core/client/auth/test/createEnvironment.ts new file mode 100644 index 000000000..700b354c9 --- /dev/null +++ b/src/core/client/auth/test/createEnvironment.ts @@ -0,0 +1,28 @@ +import { IResolvers } from "graphql-tools"; +import { Environment, RecordProxy, RecordSourceProxy } from "relay-runtime"; +import { createRelayEnvironment } from "talk-framework/testHelpers"; + +interface CreateEnvironmentParams { + logNetwork?: boolean; + resolvers?: IResolvers; + initLocalState?: ( + local: RecordProxy, + source: RecordSourceProxy, + environment: Environment + ) => void; +} + +export default function createEnvironment(params: CreateEnvironmentParams) { + return createRelayEnvironment({ + network: { + logNetwork: params.logNetwork, + resolvers: params.resolvers || {}, + projectName: "tenant", + }, + initLocalState: (localRecord, source, environment) => { + if (params.initLocalState) { + params.initLocalState(localRecord, source, environment); + } + }, + }); +} diff --git a/src/core/client/auth/test/navigation.disabledspec.tsx b/src/core/client/auth/test/navigation.disabledspec.tsx new file mode 100644 index 000000000..0abf24548 --- /dev/null +++ b/src/core/client/auth/test/navigation.disabledspec.tsx @@ -0,0 +1,59 @@ +// Enable after this is solved: https://github.com/projectfluent/fluent.js/issues/280 + +import React from "react"; +import TestRenderer from "react-test-renderer"; +import { RecordProxy } from "relay-runtime"; + +import AppContainer from "talk-auth/containers/AppContainer"; +import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; +import { PostMessageService } from "talk-framework/lib/postMessage"; +import { RestClient } from "talk-framework/lib/rest"; +import { createInMemoryStorage } from "talk-framework/lib/storage"; + +import createEnvironment from "./createEnvironment"; + +const environment = createEnvironment({ + initLocalState: (localRecord: RecordProxy) => { + localRecord.setValue("SIGN_IN", "view"); + }, +}); + +const context: TalkContext = { + relayEnvironment: environment, + localeBundles: [], + localStorage: createInMemoryStorage(), + sessionStorage: createInMemoryStorage(), + rest: new RestClient("http://localhost/api"), + postMessage: new PostMessageService(), +}; + +const testRenderer = TestRenderer.create( + + + +); + +it("renders sign in form", async () => { + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("navigates to sign up form", async () => { + testRenderer.root + .findByProps({ id: "signIn-gotoSignUpButton" }) + .props.onClick(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("navigates to sign in form", async () => { + testRenderer.root + .findByProps({ id: "signUp-gotoSignInButton" }) + .props.onClick(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("navigates to forgot password form", async () => { + testRenderer.root + .findByProps({ id: "signIn-gotoForgotPasswordButton" }) + .props.onClick(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); diff --git a/src/core/client/auth/test/signIn.spec.tsx b/src/core/client/auth/test/signIn.spec.tsx new file mode 100644 index 000000000..90447438a --- /dev/null +++ b/src/core/client/auth/test/signIn.spec.tsx @@ -0,0 +1,140 @@ +import React from "react"; +import TestRenderer, { ReactTestInstance } from "react-test-renderer"; +import { RecordProxy } from "relay-runtime"; +import sinon from "sinon"; + +import AppContainer from "talk-auth/containers/AppContainer"; +import { animationFrame, timeout } from "talk-common/utils"; +import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; +import { PostMessageService } from "talk-framework/lib/postMessage"; +import { RestClient } from "talk-framework/lib/rest"; +import { createInMemoryStorage } from "talk-framework/lib/storage"; + +import createEnvironment from "./createEnvironment"; + +const environment = createEnvironment({ + initLocalState: (localRecord: RecordProxy) => { + localRecord.setValue("SIGN_IN", "view"); + }, +}); + +const context: TalkContext = { + relayEnvironment: environment, + localeBundles: [], + localStorage: createInMemoryStorage(), + sessionStorage: createInMemoryStorage(), + rest: new RestClient("http://localhost/api"), + postMessage: new PostMessageService(), +}; + +const testRenderer = TestRenderer.create( + + + +); + +const inputPredicate = (name: string) => (n: ReactTestInstance) => { + return n.props.name === name && n.props.onChange; +}; + +const form = testRenderer.root.findByType("form"); + +it("renders sign in form", async () => { + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("shows error when submitting empty form", async () => { + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for invalid email", async () => { + form + .find(inputPredicate("email")) + .props.onChange({ target: { value: "invalid-email" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts valid email", async () => { + form + .find(inputPredicate("email")) + .props.onChange({ target: { value: "hans@test.com" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts correct password", async () => { + form + .find(inputPredicate("password")) + .props.onChange({ target: { value: "testtest" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("shows server error", async () => { + const windowMock = sinon.mock(window); + windowMock.expects("resizeTo"); + + const error = new Error("Server Error"); + const restMock = sinon.mock(context.rest); + restMock + .expects("fetch") + .withArgs("/tenant/auth/local", { + method: "POST", + body: { + email: "hans@test.com", + password: "testtest", + }, + }) + .once() + .throws(error); + + const postMessageMock = sinon.mock(context.postMessage); + postMessageMock + .expects("send") + .withArgs("authError", error.toString(), window.opener) + .once(); + + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + // popup resize will be triggered if we wait for the animation frame first. + await animationFrame(); + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + restMock.verify(); + postMessageMock.verify(); + windowMock.verify(); +}); + +it("submits form successfully", async () => { + const windowMock = sinon.mock(window); + windowMock.expects("close").once(); + windowMock.expects("resizeTo"); + + const restMock = sinon.mock(context.rest); + restMock + .expects("fetch") + .withArgs("/tenant/auth/local", { + method: "POST", + body: { + email: "hans@test.com", + password: "testtest", + }, + }) + .once() + .returns({ token: "auth-token" }); + + const postMessageMock = sinon.mock(context.postMessage); + postMessageMock + .expects("send") + .withArgs("setAuthToken", "auth-token", window.opener) + .once(); + + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + // popup resize will be triggered if we wait for the animation frame first. + await animationFrame(); + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + restMock.verify(); + postMessageMock.verify(); + windowMock.verify(); +}); diff --git a/src/core/client/auth/test/signUp.spec.tsx b/src/core/client/auth/test/signUp.spec.tsx new file mode 100644 index 000000000..307f2c4b2 --- /dev/null +++ b/src/core/client/auth/test/signUp.spec.tsx @@ -0,0 +1,191 @@ +import React from "react"; +import TestRenderer, { ReactTestInstance } from "react-test-renderer"; +import { RecordProxy } from "relay-runtime"; +import sinon from "sinon"; + +import AppContainer from "talk-auth/containers/AppContainer"; +import { animationFrame, timeout } from "talk-common/utils"; +import { TalkContext, TalkContextProvider } from "talk-framework/lib/bootstrap"; +import { PostMessageService } from "talk-framework/lib/postMessage"; +import { RestClient } from "talk-framework/lib/rest"; +import { createInMemoryStorage } from "talk-framework/lib/storage"; + +import createEnvironment from "./createEnvironment"; + +const environment = createEnvironment({ + initLocalState: (localRecord: RecordProxy) => { + localRecord.setValue("SIGN_UP", "view"); + }, +}); + +const context: TalkContext = { + relayEnvironment: environment, + localeBundles: [], + localStorage: createInMemoryStorage(), + sessionStorage: createInMemoryStorage(), + rest: new RestClient("http://localhost/api"), + postMessage: new PostMessageService(), +}; + +const testRenderer = TestRenderer.create( + + + +); + +const inputPredicate = (name: string) => (n: ReactTestInstance) => { + return n.props.name === name && n.props.onChange; +}; + +const form = testRenderer.root.findByType("form"); + +it("renders sign up form", async () => { + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("shows error when submitting empty form", async () => { + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for invalid email", async () => { + form + .find(inputPredicate("email")) + .props.onChange({ target: { value: "invalid-email" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts valid email", async () => { + form + .find(inputPredicate("email")) + .props.onChange({ target: { value: "hans@test.com" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for too short username", async () => { + form + .find(inputPredicate("username")) + .props.onChange({ target: { value: "u" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for too long username", async () => { + form + .find(inputPredicate("username")) + .props.onChange({ target: { value: "a".repeat(100) } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for invalid characters in username", async () => { + form + .find(inputPredicate("username")) + .props.onChange({ target: { value: "$%$§$%$§%" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts valid username", async () => { + form + .find(inputPredicate("username")) + .props.onChange({ target: { value: "hans" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for too short password", async () => { + form + .find(inputPredicate("password")) + .props.onChange({ target: { value: "pass" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts correct password", async () => { + form + .find(inputPredicate("password")) + .props.onChange({ target: { value: "testtest" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("checks for wrong password confirmation", async () => { + form + .find(inputPredicate("confirmPassword")) + .props.onChange({ target: { value: "not-matching" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("accepts correct password confirmation", async () => { + form + .find(inputPredicate("confirmPassword")) + .props.onChange({ target: { value: "testtest" } }); + expect(testRenderer.toJSON()).toMatchSnapshot(); +}); + +it("shows server error", async () => { + const windowMock = sinon.mock(window); + windowMock.expects("resizeTo"); + + const error = new Error("Server Error"); + const restMock = sinon.mock(context.rest); + restMock + .expects("fetch") + .withArgs("/tenant/auth/local/signup", { + method: "POST", + body: { + username: "hans", + email: "hans@test.com", + password: "testtest", + }, + }) + .once() + .throws(error); + + const postMessageMock = sinon.mock(context.postMessage); + postMessageMock + .expects("send") + .withArgs("authError", error.toString(), window.opener) + .once(); + + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + // popup resize will be triggered if we wait for the animation frame first. + await animationFrame(); + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + restMock.verify(); + postMessageMock.verify(); + windowMock.verify(); +}); + +it("submits form successfully", async () => { + const windowMock = sinon.mock(window); + windowMock.expects("close").once(); + windowMock.expects("resizeTo"); + + const restMock = sinon.mock(context.rest); + restMock + .expects("fetch") + .withArgs("/tenant/auth/local/signup", { + method: "POST", + body: { + username: "hans", + email: "hans@test.com", + password: "testtest", + }, + }) + .once() + .returns({ token: "auth-token" }); + + const postMessageMock = sinon.mock(context.postMessage); + postMessageMock + .expects("send") + .withArgs("setAuthToken", "auth-token", window.opener) + .once(); + + form.props.onSubmit(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + // popup resize will be triggered if we wait for the animation frame first. + await animationFrame(); + await timeout(); + expect(testRenderer.toJSON()).toMatchSnapshot(); + restMock.verify(); + postMessageMock.verify(); + windowMock.verify(); +}); diff --git a/src/core/client/ui/components/TextField/TextField.tsx b/src/core/client/ui/components/TextField/TextField.tsx index 9da45ef58..2e4fe448b 100644 --- a/src/core/client/ui/components/TextField/TextField.tsx +++ b/src/core/client/ui/components/TextField/TextField.tsx @@ -5,6 +5,7 @@ import { withStyles } from "talk-ui/hocs"; import * as styles from "./TextField.css"; export interface TextFieldProps { + id?: string; /** * The content value of the component. */ diff --git a/src/core/common/utils/animationFrame.ts b/src/core/common/utils/animationFrame.ts new file mode 100644 index 000000000..8ef6f152c --- /dev/null +++ b/src/core/common/utils/animationFrame.ts @@ -0,0 +1,4 @@ +/** A promisified animation frame. */ +export default function animationFrame() { + return new Promise(resolve => requestAnimationFrame(resolve)); +} diff --git a/src/core/common/utils/index.ts b/src/core/common/utils/index.ts index b5b0a0be6..28435047e 100644 --- a/src/core/common/utils/index.ts +++ b/src/core/common/utils/index.ts @@ -1,3 +1,4 @@ export { default as timeout } from "./timeout"; +export { default as animationFrame } from "./animationFrame"; export { default as pascalCase } from "./pascalCase"; export { default as oncePerFrame } from "./oncePerFrame"; diff --git a/src/locales/en-US/auth.ftl b/src/locales/en-US/auth.ftl index 236530dd6..e860b4beb 100644 --- a/src/locales/en-US/auth.ftl +++ b/src/locales/en-US/auth.ftl @@ -16,7 +16,6 @@ signIn-passwordTextField = signIn-forgotYourPassword = Forgot your password? -signIn-noAccountSignUp = Don't have an account? ## Sign Up diff --git a/src/locales/en-US/framework.ftl b/src/locales/en-US/framework.ftl index 42e6d2751..f3be4f2f4 100644 --- a/src/locales/en-US/framework.ftl +++ b/src/locales/en-US/framework.ftl @@ -6,7 +6,6 @@ framework-validation-required = This field is required. framework-validation-tooShort = The field must contain at least {$minLength} characters. -framework-validation-passwordTooShort = Password must contain at least {$minLength} characters. framework-validation-usernameTooShort = Username must contain at least {$minLength} characters. framework-validation-usernameTooLong = Usernames cannot be longer than {$maxLength} characters. framework-validation-invalidCharacters = Invalid characters. Try again.