Large diffs are not rendered by default.

Large diffs are not rendered by default.

@@ -1,10 +1,18 @@
{
"name": "auth0-lock",
"version": "10.23.1",
"version": "11.21.0",
"description": "Auth0 Lock",
"author": "Auth0 <support@auth0.com> (http://auth0.com)",
"license": "MIT",
"keywords": ["auth0", "auth", "openid", "authentication", "passwordless", "browser", "jwt"],
"keywords": [
"auth0",
"auth",
"openid",
"authentication",
"passwordless",
"browser",
"jwt"
],
"repository": {
"type": "git",
"url": "git://github.com/auth0/lock"
@@ -20,19 +28,18 @@
"precommit": "lint-staged",
"lint": "eslint --ext .jsx,.js src/",
"test": "cross-env BABEL_ENV=test zuul -- test/**/*.test.js",
"test:browser":
"cross-env BABEL_ENV=test zuul --local 8080 --disable-tunnel -- test/**/*.test.js",
"test:cli":
"cross-env BABEL_ENV=test mochify --extension=.jsx --transform=babelify ./test/setup.js test/**/*.test.js",
"test:watch":
"cross-env BABEL_ENV=test mochify --watch --extension=.jsx --transform=babelify ./test/setup.js test/**/*.test.js",
"test:browser": "cross-env BABEL_ENV=test zuul --local 8080 --disable-tunnel -- test/**/*.test.js",
"test:cli": "cross-env BABEL_ENV=test mochify --extension=.jsx --transform=babelify ./test/setup.js test/**/*.test.js",
"test:watch": "cross-env BABEL_ENV=test mochify --watch --extension=.jsx --transform=babelify ./test/setup.js test/**/*.test.js",
"test:jest": "jest --coverage --runInBand",
"test:jest:watch": "jest --watch --coverage",
"publish:cdn": "ccu",
"test:es-check": "es-check es5 'build/*.js'",
"publish:cdn": "ccu --trace",
"release": "scripts/release.sh",
"i18n:translate": "grunt dist && node scripts/complete-translations.js"
},
"devDependencies": {
"@auth0/component-cdn-uploader": "^2.2.2",
"babel-core": "^6.17.0",
"babel-eslint": "^7.2.2",
"babel-loader": "^6.2.5",
@@ -44,12 +51,12 @@
"babel-preset-stage-0": "^6.3.13",
"babelify": "^7.2.0",
"bump-version": "^0.5.0",
"component-cdn-uploader": "auth0/component-cdn-uploader#v1.3.0",
"cross-env": "^3.1.4",
"css-loader": "^0.26.1",
"dotenv": "^4.0.0",
"dotenv": "^8.0.0",
"enzyme": "^3.1.0",
"enzyme-adapter-react-15": "^1.0.1",
"es-check": "^4.0.0",
"eslint": "^4.8.0",
"eslint-config-prettier": "^2.6.0",
"eslint-plugin-react": "^7.4.0",
@@ -68,13 +75,14 @@
"json-beautify": "^1.0.1",
"jsonwebtoken": "^7.3.0",
"lint-staged": "^4.2.3",
"mochify": "^3.3.0",
"mochify": "^6.3.0",
"prettier": "^1.7.4",
"react-test-renderer": "^15.6.2",
"semver": "^5.3.0",
"semver": "^6.2.0",
"sinon": "^1.15.4",
"stylus": "^0.54.5",
"stylus-loader": "^2.3.1",
"superagent": "^3.8.2",
"uglify-js": "^2.7.4",
"unminified-webpack-plugin": "^1.1.1",
"unreleased": "^0.1.0",
@@ -86,31 +94,43 @@
"zuul-ngrok": "4.0.0"
},
"dependencies": {
"auth0-js": "~8.10.1",
"auth0-js": "^9.12.2",
"auth0-password-policies": "^1.0.2",
"blueimp-md5": "2.3.1",
"fbjs": "^0.3.1",
"idtoken-verifier": "^1.0.1",
"immutable": "^3.7.3",
"jsonp": "^0.2.0",
"jsonp": "^0.2.1",
"password-sheriff": "^1.1.0",
"prop-types": "^15.6.0",
"qs": "^6.7.0",
"react": "^15.6.2",
"react-dom": "^15.6.2",
"react-transition-group": "^2.2.1",
"superagent": "^3.3.1",
"trim": "0.0.1",
"url-join": "^1.1.0"
},
"cdn-component": {
"ccu": {
"name": "lock",
"cdn": "https://cdn.auth0.com",
"mainBundleFile": "lock.min.js",
"bucket": "assets.us.auth0.com",
"localPath": "build"
"localPath": "build",
"digest": {
"hashes": [
"sha384"
],
"extensions": [
".js"
]
}
},
"jest": {
"modulePaths": ["<rootDir>/src/", "<rootDir>/src/__tests__"],
"setupFiles": ["<rootDir>/src/__tests__/setup-tests.js"],
"modulePaths": [
"<rootDir>/src/",
"<rootDir>/src/__tests__"
],
"setupFiles": [
"<rootDir>/src/__tests__/setup-tests.js"
],
"coveragePathIgnorePatterns": [
"/node_modules/",
"<rootDir>/test/",
@@ -125,10 +145,18 @@
"<rootDir>/src/__tests__/testUtils.js",
"<rootDir>/src/__tests__/setup-tests.js"
],
"coverageReporters": ["lcov", "text-summary"]
"coverageReporters": [
"lcov",
"text-summary"
]
},
"lint-staged": {
"*.{js,jsx}": ["npm run lint"],
"*.{js,jsx,json,html}": ["prettier --write --print-width 100 --single-quote", "git add"]
"*.{js,jsx}": [
"npm run lint"
],
"*.{js,jsx,json}": [
"prettier --write --print-width 100 --single-quote",
"git add"
]
}
}
@@ -11,18 +11,23 @@ const restoreWildCards = str =>
str.replace(/__( d|d |d)__/gi, '%d').replace(/__( s|s |s)__/gi, '%s');

const processLanguage = async lang => {
console.log(`translating: ${lang}`);
const langDictionary = require('../lib/i18n/' + lang).default;
await processNode(enDictionary, langDictionary, lang);
const communityAlert = `
try {
console.log(`translating: ${lang}`);
const langDictionary = require('../lib/i18n/' + lang).default;
await processNode(enDictionary, langDictionary, lang);
const communityAlert = `
// This file was automatically translated.
// Feel free to submit a PR if you find a more accurate translation.
`;
const jsContent = `
const jsContent = `
${isSupportedByAuth0(lang) ? '' : communityAlert}
export default ${JSON.stringify(langDictionary, null, 2)};
`;
await writeFileAsync(`src/i18n/${lang}.js`, jsContent);
await writeFileAsync(`src/i18n/${lang}.js`, jsContent);
} catch (error) {
console.log(`Error translating ${lang}.`);
console.log(error.message);
}
};

const processNode = async (enNode, langNode, lang) => {
@@ -31,6 +36,7 @@ const processNode = async (enNode, langNode, lang) => {
await processNode(enNode[enKey], langNode[enKey], lang);
} else {
if (!langNode[enKey]) {
console.log('translating ', enKey);
const translation = await translateKey(enNode[enKey], lang);
langNode[enKey] = translation;
}
@@ -44,7 +50,9 @@ const translateKey = async (toTranslate, lang) => {
.set('Content-Type', 'application/json')
.query({ tl: lang })
.query({ q: escapeWildCards(toTranslate) });
return restoreWildCards(result.body[0][0][0]);
const phrases = result.body[0].map(p => p[0]);
const singlePhrase = phrases.join('');
return restoreWildCards(singlePhrase);
};

const run = async () => {
@@ -78,6 +78,10 @@ case "$choice" in
* ) exit 0;;
esac

git checkout master
git pull
git checkout -b prepare-$NEW_V_VERSION

echo "Updating package.json"
jq ".version=$QUOTED_NEW_VERSION" package.json > package.json.new
jq ".version=$QUOTED_NEW_VERSION" bower.json > bower.json.new
@@ -0,0 +1,75 @@
'use strict';

/**
* Copyright (c) 2013-present, Facebook, Inc.
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*
* @typechecks
*/

/**
* The CSSCore module specifies the API (and implements most of the methods)
* that should be used when dealing with the display of elements (via their
* CSS classes and visibility on screen. It is an API focused on mutating the
* display and not reading it as no logical state should be encoded in the
* display of elements.
*/

var CSSCore = {
/**
* Adds the class passed in to the element if it doesn't already have it.
*
* @param {DOMElement} element the element to set the class on
* @param {string} className the CSS className
* @return {DOMElement} the element passed in
*/
addClass: function addClass(element, className) {
if (className) {
if (element.classList) {
element.classList.add(className);
} else if (!CSSCore.hasClass(element, className)) {
element.className = element.className + ' ' + className;
}
}
return element;
},

/**
* Removes the class passed in from the element
*
* @param {DOMElement} element the element to set the class on
* @param {string} className the CSS className
* @return {DOMElement} the element passed in
*/
removeClass: function removeClass(element, className) {
if (className) {
if (element.classList) {
element.classList.remove(className);
} else if (CSSCore.hasClass(element, className)) {
element.className = element.className
.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)', 'g'), '$1')
.replace(/\s+/g, ' ') // multiple spaces to one
.replace(/^\s*|\s*$/g, ''); // trim the ends
}
}
return element;
},

/**
* Tests whether the element has the class specified.
*
* @param {DOMNode|DOMWindow} element the element to check the class on
* @param {string} className the CSS className
* @return {boolean} true if the element has the class, false if not
*/
hasClass: function hasClass(element, className) {
if (element.classList) {
return !!className && element.classList.contains(className);
}
return (' ' + element.className + ' ').indexOf(' ' + className + ' ') > -1;
}
};

module.exports = CSSCore;
@@ -1,10 +1,9 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`AuthButton renders correctly 1`] = `
<button
<a
className="auth0-lock-social-button auth0-lock-social-big-button"
data-provider="strategy"
disabled={false}
onClick={[Function]}
style={Object {}}
type="button"
@@ -19,36 +18,13 @@ exports[`AuthButton renders correctly 1`] = `
>
label
</div>
</button>
`;

exports[`AuthButton renders when \`big\` is false 1`] = `
<button
className="auth0-lock-social-button"
data-provider="strategy"
disabled={false}
onClick={[Function]}
style={Object {}}
type="button"
>
<div
className="auth0-lock-social-button-icon"
style={Object {}}
/>
<div
className="auth0-lock-social-button-text"
style={Object {}}
>
label
</div>
</button>
</a>
`;

exports[`AuthButton renders with style customizations 1`] = `
<button
<a
className="auth0-lock-social-button auth0-lock-social-big-button"
data-provider="strategy"
disabled={false}
onClick={[Function]}
style={
Object {
@@ -75,5 +51,5 @@ exports[`AuthButton renders with style customizations 1`] = `
>
label
</div>
</button>
</a>
`;
@@ -24,12 +24,9 @@ describe('AuthButton', () => {
/>
).toMatchSnapshot();
});
it('renders when `big` is false', () => {
expectComponent(<AuthButton {...defaultProps} isBig={false} />).toMatchSnapshot();
});
it('should trigger onClick when clicked', () => {
const wrapper = mount(<AuthButton {...defaultProps} />);
wrapper.find('button').simulate('click');
wrapper.find('a').simulate('click');
expect(defaultProps.onClick.mock.calls.length).toBe(1);
});
});
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`PasswordResetConfirmation renders correctly 1`] = `
<div
className="auth0-lock-confirmation"
>
<div
className="auth0-lock-confirmation-content"
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "<svg focusable=\\"false\\" width=\\"56px\\" height=\\"56px\\" viewBox=\\"0 0 52 52\\" version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\" class=\\"checkmark\\"> <circle cx=\\"26\\" cy=\\"26\\" r=\\"25\\" fill=\\"none\\" class=\\"checkmark__circle\\"></circle> <path fill=\\"none\\" d=\\"M14.1 27.2l7.1 7.2 16.7-16.8\\" class=\\"checkmark__check\\"></path> </svg>",
}
}
/>
<p />
</div>
</div>
`;
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`ResetPasswordScreen isSubmitDisabled returns false when \`isEnterpriseDomain\` is false 1`] = `
Array [
"updateEntity",
"lock",
"id",
"clearGlobalError",
]
`;

exports[`ResetPasswordScreen isSubmitDisabled returns true when \`isEnterpriseDomain\` is true 1`] = `
Array [
"updateEntity",
"lock",
"id",
"setGlobalError",
"error,forgotPassword,enterprise_email",
]
`;
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SignedUpConfirmation renders correctly 1`] = `
<div
className="auth0-lock-confirmation"
>
<div
className="auth0-lock-confirmation-content"
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "<svg focusable=\\"false\\" width=\\"56px\\" height=\\"56px\\" viewBox=\\"0 0 52 52\\" version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\" class=\\"checkmark\\"> <circle cx=\\"26\\" cy=\\"26\\" r=\\"25\\" fill=\\"none\\" class=\\"checkmark__circle\\"></circle> <path fill=\\"none\\" d=\\"M14.1 27.2l7.1 7.2 16.7-16.8\\" class=\\"checkmark__check\\"></path> </svg>",
}
}
/>
<p />
</div>
</div>
`;
@@ -0,0 +1,84 @@
import Immutable, { List, Map } from 'immutable';
import { signUp } from '../../../connection/database/actions';
import { swap, setEntity } from '../../../store';

const webApiMock = () => require('core/web_api');
const coreActionsMock = () => require('core/actions');
jest.mock('core/actions', () => ({
validateAndSubmit: jest.fn()
}));

jest.mock('core/web_api', () => ({
signUp: jest.fn()
}));

describe('database/actions.js', () => {
it('signUp splits root attributes correctly', () => {
const id = 1;
require('connection/database/index').databaseConnectionName = () => 'test-connection';
require('connection/database/index').shouldAutoLogin = () => true;
const m = Immutable.fromJS({
field: {
email: {
value: 'test@email.com'
},
password: {
value: 'testpass'
},
family_name: {
value: 'test-family-name'
},
given_name: {
value: 'test-given-name'
},
name: {
value: 'test-name'
},
nickname: {
value: 'test-nickname'
},
picture: {
value: 'test-pic'
},
other_prop: {
value: 'test-other'
}
},
database: {
additionalSignUpFields: [
{ name: 'family_name', storage: 'root' },
{ name: 'given_name', storage: 'root' },
{ name: 'name', storage: 'root' },
{ name: 'nickname', storage: 'root' },
{ name: 'picture', storage: 'root' },
{ name: 'other_prop' }
]
}
});
swap(setEntity, 'lock', id, m);
signUp(id);
const { validateAndSubmit: { mock: validateAndSubmitMock } } = coreActionsMock();
expect(validateAndSubmitMock.calls.length).toBe(1);
expect(validateAndSubmitMock.calls[0][0]).toBe(id);
expect(validateAndSubmitMock.calls[0][1]).toContain('email');
expect(validateAndSubmitMock.calls[0][1]).toContain('password');
validateAndSubmitMock.calls[0][2](m);
const { signUp: { mock: signUpMock } } = webApiMock();
expect(signUpMock.calls.length).toBe(1);
expect(signUpMock.calls[0][0]).toBe(id);
expect(signUpMock.calls[0][1]).toMatchObject({
connection: 'test-connection',
email: 'test@email.com',
password: 'testpass',
autoLogin: true,
family_name: 'test-family-name',
given_name: 'test-given-name',
name: 'test-name',
nickname: 'test-nickname',
picture: 'test-pic',
user_metadata: {
other_prop: 'test-other'
}
});
});
});
@@ -0,0 +1,124 @@
import Immutable, { List, Map } from 'immutable';
import { databaseUsernameValue, initDatabase } from '../../../connection/database';

describe('database/index.js', () => {
describe('databaseUsernameValue', () => {
const getModel = (email, username, usernameRequired) =>
Immutable.fromJS({
field: {
email: {
value: email
},
username: {
value: username
}
},
core: {
transient: {
connections: {
database: [
{
requireUsername: usernameRequired
}
]
}
}
}
});

beforeEach(() => {
jest.resetAllMocks();
});

describe('for database connection without username required', () => {
const model = getModel('user@auth0.com', null, false);

it('should get the email', () => {
expect(databaseUsernameValue(model)).toEqual('user@auth0.com');
});
});

describe('for database connection with username required', () => {
const model = getModel('user@auth0.com', 'user', true);

it('should get the username when `emailFirst` is not set', () => {
expect(databaseUsernameValue(model)).toEqual('user');
});
it('should get the username when `emailFirst` is false', () => {
expect(databaseUsernameValue(model, { emailFirst: false })).toEqual('user');
});
it('should get the email when `emailFirst` is true', () => {
expect(databaseUsernameValue(model, { emailFirst: true })).toEqual('user@auth0.com');
});

describe('and only email address is filled in', () => {
const model = getModel('user@auth0.com', null, true);

it('should get the email address', () => {
expect(databaseUsernameValue(model)).toEqual('user@auth0.com');
});
});
});
});
describe('initDatabase', () => {
describe('calls initNS with the correct additionalSignUpFields', () => {
describe('uses the `storage` attribute', () => {
const model = Immutable.fromJS({});
const modelOut = initDatabase(model, {
additionalSignUpFields: [
{
type: 'hidden',
name: 'hidden_field',
value: 'hidden_value',
storage: 'root'
}
]
});
const modelOutJS = modelOut.toJS();
expect(modelOutJS.database.additionalSignUpFields).toEqual([
{
type: 'hidden',
name: 'hidden_field',
value: 'hidden_value',
storage: 'root'
}
]);
});
describe('with a valid hidden field', () => {
const model = Immutable.fromJS({});
const modelOut = initDatabase(model, {
additionalSignUpFields: [
{
type: 'hidden',
name: 'hidden_field',
value: 'hidden_value'
}
]
});
const modelOutJS = modelOut.toJS();
expect(modelOutJS.field).toEqual({
hidden_field: { showInvalid: false, valid: true, value: 'hidden_value' }
});
expect(modelOutJS.database.additionalSignUpFields).toEqual([
{
type: 'hidden',
name: 'hidden_field',
value: 'hidden_value'
}
]);
});
describe('with a hidden field without a value', () => {
const model = Immutable.fromJS({});
const modelOut = initDatabase(model, {
additionalSignUpFields: [
{
type: 'hidden',
name: 'hidden_field'
}
]
});
expect(modelOut.toJS().database.additionalSignUpFields.length).toBe(0);
});
});
});
});
@@ -0,0 +1,14 @@
import React from 'react';
import I from 'immutable';

import { expectComponent } from 'testUtils';

import PasswordResetConfirmation from '../../../connection/database/password_reset_confirmation';

const lock = I.fromJS({ id: '__lock-id__' });

describe('PasswordResetConfirmation', () => {
it('renders correctly', async () => {
expectComponent(<PasswordResetConfirmation lock={lock} />).toMatchSnapshot();
});
});
@@ -0,0 +1,51 @@
import React from 'react';
import { mount } from 'enzyme';

const getScreen = () => {
const ResetPasswordScreen = require('connection/database/reset_password').default;
return new ResetPasswordScreen();
};

describe('ResetPasswordScreen', () => {
beforeEach(() => {
jest.resetModules();

jest.mock('connection/database/index', () => ({
databaseUsernameValue: (model, options) => {
expect(options.emailFirst).toBe(true);
return 'foo@test.com';
}
}));

jest.mock('connection/enterprise', () => ({
isEnterpriseDomain: () => true
}));

jest.mock('i18n', () => ({ str: (_, keys) => keys.join(',') }));

jest.mock('core/index', () => ({
id: () => 'id',
setGlobalError: 'setGlobalError',
clearGlobalError: 'clearGlobalError'
}));

jest.mock('store/index', () => ({
swap: jest.fn(),
updateEntity: 'updateEntity'
}));
});
it('isSubmitDisabled returns true when `isEnterpriseDomain` is true', () => {
jest.useFakeTimers();
require('connection/enterprise').isEnterpriseDomain = () => true;
const screen = getScreen();
expect(screen.isSubmitDisabled()).toBe(true);
jest.runTimersToTime(50);
expect(require('store/index').swap.mock.calls[0]).toMatchSnapshot();
});
it('isSubmitDisabled returns false when `isEnterpriseDomain` is false', () => {
require('connection/enterprise').isEnterpriseDomain = () => false;
const screen = getScreen();
expect(screen.isSubmitDisabled()).toBe(false);
expect(require('store/index').swap.mock.calls[0]).toMatchSnapshot();
});
});
@@ -0,0 +1,14 @@
import React from 'react';
import I from 'immutable';

import { expectComponent } from 'testUtils';

import SignedUpConfirmation from '../../../connection/database/signed_up_confirmation';

const lock = I.fromJS({ id: '__lock-id__' });

describe('SignedUpConfirmation', () => {
it('renders correctly', async () => {
expectComponent(<SignedUpConfirmation lock={lock} />).toMatchSnapshot();
});
});
@@ -0,0 +1,59 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`EmailSentConfirmation renders correctly 1`] = `
<div
className="auth0-lock-confirmation"
>
<span
aria-label="back"
className="auth0-lock-back-button"
dangerouslySetInnerHTML={
Object {
"__html": "<svg aria-hidden=\\"true\\" focusable=\\"false\\" enable-background=\\"new 0 0 24 24\\" version=\\"1.0\\" viewBox=\\"0 0 24 24\\" xml:space=\\"preserve\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\"> <polyline fill=\\"none\\" points=\\"12.5,21 3.5,12 12.5,3 \\" stroke=\\"#000000\\" stroke-miterlimit=\\"10\\" stroke-width=\\"2\\"></polyline> <line fill=\\"none\\" stroke=\\"#000000\\" stroke-miterlimit=\\"10\\" stroke-width=\\"2\\" x1=\\"22\\" x2=\\"3.5\\" y1=\\"12\\" y2=\\"12\\"></line> </svg>",
}
}
id="__lock-id__-back-button"
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
/>
<div
className="auth0-lock-confirmation-content"
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "<svg focusable=\\"false\\" width=\\"56px\\" height=\\"56px\\" viewBox=\\"0 0 52 52\\" version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\" class=\\"checkmark\\"> <circle cx=\\"26\\" cy=\\"26\\" r=\\"25\\" fill=\\"none\\" class=\\"checkmark__circle\\"></circle> <path fill=\\"none\\" d=\\"M14.1 27.2l7.1 7.2 16.7-16.8\\" class=\\"checkmark__check\\"></path> </svg>",
}
}
/>
<p>
<span
dangerouslySetInnerHTML={
Object {
"__html": " ",
}
}
/>
</p>
<span>
<a
className="auth0-lock-resend-link"
href="javascript:void(0)"
onClick={[Function]}
>


<span
dangerouslySetInnerHTML={
Object {
"__html": "<svg focusable=\\"false\\" height=\\"32px\\" style=\\"enable-background:new 0 0 32 32;\\" version=\\"1.1\\" viewBox=\\"0 0 32 32\\" width=\\"32px\\" xml:space=\\"preserve\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\"> <path d=\\"M27.877,19.662c0.385-1.23,0.607-2.531,0.607-3.884c0-7.222-5.83-13.101-13.029-13.194v4.238 c4.863,0.093,8.793,4.071,8.793,8.956c0,0.678-0.088,1.332-0.232,1.966l-3.963-1.966l2.76,8.199l8.197-2.762L27.877,19.662z\\"></path> <path d=\\"M7.752,16.222c0-0.678,0.088-1.332,0.232-1.967l3.963,1.967l-2.76-8.199L0.99,10.785l3.133,1.553 c-0.384,1.23-0.607,2.531-0.607,3.885c0,7.223,5.83,13.1,13.03,13.194v-4.238C11.682,25.086,7.752,21.107,7.752,16.222z\\"></path> </svg>",
}
}
/>
</a>
</span>
</div>
</div>
`;
@@ -0,0 +1,302 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`passwordless actions login() calls webApi.passwordlessVerify() with email options 1`] = `
Array [
"id",
Object {
"auth": "params",
"connection": "email",
"email": "email",
"verificationCode": "vcode",
},
[Function],
]
`;

exports[`passwordless actions login() calls webApi.passwordlessVerify() with sms options 1`] = `
Array [
"id",
Object {
"auth": "params",
"connection": "sms",
"phoneNumber": "phoneNumberWithDiallingCode",
"verificationCode": "vcode",
},
[Function],
]
`;

exports[`passwordless actions login() on webApi.passwordlessVerify() callback calls logInSuccess on success 1`] = `
Array [
"id",
Object {
"result": true,
},
]
`;

exports[`passwordless actions login() on webApi.passwordlessVerify() callback when there is an error emits the "authorization_error" event 1`] = `
Array [
"model",
[Error: foobar],
]
`;

exports[`passwordless actions login() on webApi.passwordlessVerify() callback when there is an error formats the error 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
true,
]
`;

exports[`passwordless actions login() on webApi.passwordlessVerify() callback when there is an error formats the error 2`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
false,
"error,passwordless,some_error_code",
]
`;

exports[`passwordless actions login() sets setSubmitting to true 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`passwordless actions login() sets setSubmitting to true 2`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
true,
]
`;

exports[`passwordless actions requestPasswordlessEmail() calls setPasswordlessStarted() on success 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
]
`;

exports[`passwordless actions requestPasswordlessEmail() calls setPasswordlessStarted() on success 2`] = `
Array [
"model",
false,
]
`;

exports[`passwordless actions requestPasswordlessEmail() calls setPasswordlessStarted() on success 3`] = `
Array [
"model",
true,
]
`;

exports[`passwordless actions requestPasswordlessEmail() calls startPasswordless 1`] = `
Array [
"id",
Object {
"connection": "email",
"email": "email",
"send": "send",
},
[Function],
]
`;

exports[`passwordless actions requestPasswordlessEmail() calls validateAndSubmit() 1`] = `
Array [
"id",
Array [
"email",
],
[Function],
]
`;

exports[`passwordless actions requestPasswordlessEmail() normalizes the error message with a generic error 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`passwordless actions requestPasswordlessEmail() normalizes the error message with a generic error 2`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
false,
"error,passwordless,some_error_code",
]
`;

exports[`passwordless actions requestPasswordlessEmail() normalizes the error message with a sms_provider_error error and description includes (Code: 21211) 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
false,
"error,passwordless,bad.phone_number",
]
`;

exports[`passwordless actions resendEmail() calls setResendFailed on error 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`passwordless actions resendEmail() calls setResendFailed on error 2`] = `
Array [
"updateEntity",
"lock",
"id",
"resend",
]
`;

exports[`passwordless actions resendEmail() calls setResendFailed on error 3`] = `
Array [
"model",
]
`;

exports[`passwordless actions resendEmail() calls setResendSuccess() on success 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`passwordless actions resendEmail() calls setResendSuccess() on success 2`] = `
Array [
"updateEntity",
"lock",
"id",
"resend",
]
`;

exports[`passwordless actions resendEmail() calls setResendSuccess() on success 3`] = `
Array [
"model",
]
`;

exports[`passwordless actions restart calls restartPasswordless 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
]
`;

exports[`passwordless actions sendSMS() calls setPasswordlessStarted() on success 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
]
`;

exports[`passwordless actions sendSMS() calls setPasswordlessStarted() on success 2`] = `
Array [
"model",
false,
]
`;

exports[`passwordless actions sendSMS() calls setPasswordlessStarted() on success 3`] = `
Array [
"model",
true,
]
`;

exports[`passwordless actions sendSMS() calls startPasswordless 1`] = `
Array [
"id",
Object {
"connection": "sms",
"phoneNumber": "phoneNumberWithDiallingCode",
"send": "send",
},
[Function],
]
`;

exports[`passwordless actions sendSMS() calls validateAndSubmit() 1`] = `
Array [
"id",
Array [
"phoneNumber",
],
[Function],
]
`;

exports[`passwordless actions sendSMS() normalizes the error message with a generic error 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`passwordless actions sendSMS() normalizes the error message with a generic error 2`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
false,
"error,passwordless,some_error_code",
]
`;

exports[`passwordless actions sendSMS() normalizes the error message with a sms_provider_error error and description includes (Code: 21211) 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
false,
"error,passwordless,bad.phone_number",
]
`;

exports[`passwordless actions toggleTermsAcceptance() calls internalToggleTermsAcceptance() 1`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
]
`;

exports[`passwordless actions toggleTermsAcceptance() calls internalToggleTermsAcceptance() 2`] = `
Array [
"model",
]
`;
@@ -0,0 +1,108 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`passwordless connection initPasswordless() calls initNS with mustAcceptTerms:false when opts.mustAcceptTerms is false 1`] = `
Array [
null,
Immutable.Map {
"send": "code",
"mustAcceptTerms": false,
},
]
`;

exports[`passwordless connection initPasswordless() calls initNS with mustAcceptTerms:true when opts.mustAcceptTerms is true 1`] = `
Array [
null,
Immutable.Map {
"send": "code",
"mustAcceptTerms": true,
},
]
`;

exports[`passwordless connection initPasswordless() calls initNS with send:code when opts.passwordlessMethod is code 1`] = `
Array [
null,
Immutable.Map {
"send": "code",
"mustAcceptTerms": false,
},
]
`;

exports[`passwordless connection initPasswordless() calls initNS with send:code when opts.passwordlessMethod is undefined 1`] = `
Array [
null,
Immutable.Map {
"send": "code",
"mustAcceptTerms": false,
},
]
`;

exports[`passwordless connection initPasswordless() calls initNS with send:link when opts.passwordlessMethod is link 1`] = `
Array [
null,
Immutable.Map {
"send": "link",
"mustAcceptTerms": false,
},
]
`;

exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 1`] = `
Array [
undefined,
"location",
Object {
"recoverResult": "US",
"successFn": [Function],
"syncFn": [Function],
},
]
`;

exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 2`] = `
Array [
"id",
"cb",
]
`;

exports[`passwordless connection initPasswordless() should call webAPI.getUserCountry when there is no default location 3`] = `
Array [
"model",
"en",
]
`;

exports[`passwordless connection initPasswordless() should load default location via options.defaultLocation 1`] = `
Array [
undefined,
"EN",
]
`;

exports[`passwordless connection mustAcceptTerms() should return \`mustAcceptTerms\` 1`] = `
Array [
"model",
"mustAcceptTerms",
false,
]
`;

exports[`passwordless connection toggleTermsAcceptance() should tset \`termsAccepted\` to false when \`termsAccepted\` is true 1`] = `
Array [
"model",
"termsAccepted",
true,
]
`;

exports[`passwordless connection toggleTermsAcceptance() should tset \`termsAccepted\` to true when \`termsAccepted\` is false 1`] = `
Array [
"model",
"termsAccepted",
false,
]
`;
@@ -0,0 +1,14 @@
import React from 'react';
import I from 'immutable';

import { expectComponent } from 'testUtils';

import EmailSentConfirmation from '../../../connection/passwordless/email_sent_confirmation';

const lock = I.fromJS({ id: '__lock-id__' });

describe('EmailSentConfirmation', () => {
it('renders correctly', async () => {
expectComponent(<EmailSentConfirmation lock={lock} />).toMatchSnapshot();
});
});
@@ -0,0 +1,261 @@
import passwordless from 'connection/passwordless/actions';
import { expectMockToMatch } from 'testUtils';

jest.useFakeTimers();

describe('passwordless actions', () => {
let mockFns;
let actions;
beforeEach(() => {
jest.resetModules();

jest.mock('connection/passwordless/index', () => ({
isEmail: jest.fn(),
isSendLink: jest.fn(),
resend: 'resend',
restartPasswordless: jest.fn(),
send: () => 'send',
setPasswordlessStarted: jest.fn(),
setResendFailed: jest.fn(),
setResendSuccess: jest.fn(),
toggleTermsAcceptance: jest.fn()
}));
jest.mock('field/phone_number', () => ({
phoneNumberWithDiallingCode: () => 'phoneNumberWithDiallingCode'
}));
jest.mock('field/index', () => ({
getFieldValue: (m, field) => field
}));
jest.mock('core/web_api', () => ({
startPasswordless: jest.fn(),
passwordlessVerify: jest.fn()
}));
jest.mock('core/actions', () => ({
closeLock: jest.fn(),
logIn: jest.fn(),
validateAndSubmit: jest.fn(),
logInSuccess: jest.fn()
}));
jest.mock('i18n', () => ({ html: (_, keys) => keys.join(',') }));
jest.mock('core/index', () => ({
id: () => 'id',
setSubmitting: jest.fn(m => m),
auth: {
params: () => ({
toJS: () => ({
auth: 'params'
})
})
},
emitAuthorizationErrorEvent: jest.fn()
}));
jest.mock('store/index', () => ({
read: jest.fn(() => 'model'),
getEntity: 'getEntity',
swap: jest.fn(),
updateEntity: 'updateEntity'
}));

actions = require('connection/passwordless/actions');
});
describe('requestPasswordlessEmail()', () => {
it('calls validateAndSubmit()', () => {
actions.requestPasswordlessEmail('id');
expectMockToMatch(require('core/actions').validateAndSubmit, 1);
});
it('calls startPasswordless', () => {
actions.requestPasswordlessEmail('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
expectMockToMatch(require('core/web_api').startPasswordless, 1);
});
it('calls setPasswordlessStarted() on success', () => {
actions.requestPasswordlessEmail('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');

require('core/web_api').startPasswordless.mock.calls[0][2](null);

const { swap } = require('store/index');
expectMockToMatch(swap, 1);

swap.mock.calls[0][3]('model');
expectMockToMatch(require('core/index').setSubmitting, 1);
expectMockToMatch(require('connection/passwordless/index').setPasswordlessStarted, 1);
});
describe('normalizes the error message', () => {
it('with a generic error', () => {
actions.requestPasswordlessEmail('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
const error = new Error('foobar');
error.error = 'some_error_code';
require('core/web_api').startPasswordless.mock.calls[0][2](error);

jest.runAllTimers();

const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);
});
it('with a sms_provider_error error and description includes (Code: 21211)', () => {
actions.requestPasswordlessEmail('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
const error = new Error('foobar');
error.error = 'sms_provider_error';
error.description = 'something (Code: 21211)';
require('core/web_api').startPasswordless.mock.calls[0][2](error);

jest.runAllTimers();

const { read, swap } = require('store/index');
expectMockToMatch(swap, 1);
});
});
});
describe('resendEmail()', () => {
it('calls setResendSuccess() on success', () => {
actions.resendEmail('id');

const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);

require('core/web_api').startPasswordless.mock.calls[0][2](null);

swap.mock.calls[1][3]('model');
expectMockToMatch(require('connection/passwordless/index').setResendSuccess, 1);
});
it('calls setResendFailed on error', () => {
actions.resendEmail('id');

const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);

require('core/web_api').startPasswordless.mock.calls[0][2](new Error('foobar'));
jest.runAllTimers();
swap.mock.calls[1][3]('model');
expectMockToMatch(require('connection/passwordless/index').setResendFailed, 1);
});
});
describe('sendSMS()', () => {
it('calls validateAndSubmit()', () => {
actions.sendSMS('id');
expectMockToMatch(require('core/actions').validateAndSubmit, 1);
});
it('calls startPasswordless', () => {
actions.sendSMS('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
expectMockToMatch(require('core/web_api').startPasswordless, 1);
});
it('calls setPasswordlessStarted() on success', () => {
actions.sendSMS('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');

require('core/web_api').startPasswordless.mock.calls[0][2](null);

const { swap } = require('store/index');
expectMockToMatch(swap, 1);

swap.mock.calls[0][3]('model');
expectMockToMatch(require('core/index').setSubmitting, 1);
expectMockToMatch(require('connection/passwordless/index').setPasswordlessStarted, 1);
});
describe('normalizes the error message', () => {
it('with a generic error', () => {
actions.sendSMS('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
const error = new Error('foobar');
error.error = 'some_error_code';
require('core/web_api').startPasswordless.mock.calls[0][2](error);

jest.runAllTimers();

const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);
});
it('with a sms_provider_error error and description includes (Code: 21211)', () => {
actions.sendSMS('id');
require('core/actions').validateAndSubmit.mock.calls[0][2]('model');
const error = new Error('foobar');
error.error = 'sms_provider_error';
error.description = 'something (Code: 21211)';
require('core/web_api').startPasswordless.mock.calls[0][2](error);

jest.runAllTimers();

const { read, swap } = require('store/index');
expectMockToMatch(swap, 1);
});
});
});
describe('login()', () => {
it('sets setSubmitting to true', () => {
actions.logIn('id');

const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);
});

it('calls webApi.passwordlessVerify() with sms options', () => {
actions.logIn('id');
expectMockToMatch(require('core/web_api').passwordlessVerify, 1);
});

it('calls webApi.passwordlessVerify() with email options', () => {
require('connection/passwordless/index').isEmail = () => true;
actions.logIn('id');
expectMockToMatch(require('core/web_api').passwordlessVerify, 1);
});

describe('on webApi.passwordlessVerify() callback', () => {
describe('when there is an error', () => {
it('formats the error', () => {
actions.logIn('id');

const error = new Error('foobar');
error.error = 'some_error_code';
require('core/web_api').passwordlessVerify.mock.calls[0][2](error);

const { swap } = require('store/index');
expectMockToMatch(swap, 2);
});

it('emits the "authorization_error" event', () => {
actions.logIn('id');

const error = new Error('foobar');
error.error = 'some_error_code';
require('core/web_api').passwordlessVerify.mock.calls[0][2](error);

expectMockToMatch(require('core/index').emitAuthorizationErrorEvent, 1);
});
});

it('calls logInSuccess on success', () => {
actions.logIn('id');
require('core/web_api').passwordlessVerify.mock.calls[0][2](null, { result: true });

expectMockToMatch(require('core/actions').logInSuccess, 1);
});
});
});
describe('toggleTermsAcceptance()', () => {
it('calls internalToggleTermsAcceptance()', () => {
actions.toggleTermsAcceptance('id');

const { swap } = require('store/index');
expectMockToMatch(swap, 1);

swap.mock.calls[0][3]('model');

expectMockToMatch(require('connection/passwordless/index').toggleTermsAcceptance, 1);
});
});
it('restart calls restartPasswordless', () => {
actions.restart('id');

const { swap } = require('store/index');
expectMockToMatch(swap, 1);
});
});
@@ -0,0 +1,105 @@
import { expectMockToMatch } from 'testUtils';

describe('passwordless connection', () => {
let mockFns;
beforeEach(() => {
jest.resetModules();

mockFns = {
get: jest.fn(),
initNS: jest.fn(),
tget: jest.fn(),
tremove: jest.fn(),
tset: jest.fn()
};

jest.mock('utils/data_utils', () => ({
dataFns: arr => {
return mockFns;
}
}));
jest.mock('field/phone_number', () => ({
initLocation: jest.fn()
}));
jest.mock('core/web_api', () => ({
getUserCountry: jest.fn()
}));
jest.mock('sync', () => jest.fn());
jest.mock('core/index', () => ({
id: () => 'id'
}));
});
describe('initPasswordless()', () => {
let initPasswordless;
beforeEach(() => {
initPasswordless = require('connection/passwordless/index').initPasswordless;
});
describe('calls initNS ', () => {
it('with send:code when opts.passwordlessMethod is undefined', () => {
initPasswordless(null, {});
expectMockToMatch(mockFns.initNS, 1);
});
it('with send:code when opts.passwordlessMethod is code', () => {
initPasswordless(null, {
passwordlessMethod: 'code'
});
expectMockToMatch(mockFns.initNS, 1);
});
it('with send:link when opts.passwordlessMethod is link', () => {
initPasswordless(null, {
passwordlessMethod: 'link'
});
expectMockToMatch(mockFns.initNS, 1);
});
it('with mustAcceptTerms:true when opts.mustAcceptTerms is true', () => {
initPasswordless(null, {
mustAcceptTerms: true
});
expectMockToMatch(mockFns.initNS, 1);
});
it('with mustAcceptTerms:false when opts.mustAcceptTerms is false', () => {
initPasswordless(null, {
mustAcceptTerms: false
});
expectMockToMatch(mockFns.initNS, 1);
});
});
it('should load default location via options.defaultLocation', () => {
initPasswordless(null, {
defaultLocation: 'en'
});
expectMockToMatch(require('field/phone_number').initLocation, 1);
});
it('should call webAPI.getUserCountry when there is no default location', () => {
initPasswordless(null, {});
const sync = require('sync');
expectMockToMatch(sync, 1);

const { syncFn, successFn } = sync.mock.calls[0][2];
syncFn(null, 'cb');
expectMockToMatch(require('core/web_api').getUserCountry, 1);

successFn('model', 'en');
expectMockToMatch(require('field/phone_number').initLocation, 1);
});
});
describe('mustAcceptTerms()', () => {
it('should return `mustAcceptTerms`', () => {
require('connection/passwordless/index').mustAcceptTerms('model');
expectMockToMatch(mockFns.get, 1);
});
});

describe('toggleTermsAcceptance()', () => {
it('should tset `termsAccepted` to false when `termsAccepted` is true', () => {
mockFns.get.mockReturnValue(true);
require('connection/passwordless/index').toggleTermsAcceptance('model');
expectMockToMatch(mockFns.tset, 1);
});
it('should tset `termsAccepted` to true when `termsAccepted` is false', () => {
mockFns.get.mockReturnValue(false);
require('connection/passwordless/index').toggleTermsAcceptance('model');
expectMockToMatch(mockFns.tset, 1);
});
});
});
@@ -0,0 +1,25 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`core.actions checkSession should set submitting on start 1`] = `
Array [
"getEntity",
"lock",
"id",
]
`;

exports[`core.actions checkSession should set submitting on start 2`] = `
Array [
"updateEntity",
"lock",
"id",
[Function],
]
`;

exports[`core.actions checkSession should set submitting on start 3`] = `
Array [
"model",
true,
]
`;
@@ -26,3 +26,63 @@ exports[`setResolvedConnection validates format 1`] = `"Invalid connection objec
exports[`setResolvedConnection validates format 2`] = `"Invalid connection object. The resolved connection must look like: \`{ type: \\"database\\", name: \\"connection name\\" }\`."`;

exports[`setResolvedConnection validates format 3`] = `"Invalid connection object. The resolved connection must look like: \`{ type: \\"database\\", name: \\"connection name\\" }\`."`;

exports[`setup should work with redirect:false and responseType:id_token 1`] = `
Object {
"allowedConnections": Array [],
"auth": Object {
"audience": undefined,
"autoParseHash": true,
"connectionScopes": Object {},
"nonce": undefined,
"params": Object {
"scope": "openid profile email",
},
"redirect": false,
"redirectUrl": "https://test.com/path/",
"responseMode": undefined,
"responseType": "id_token",
"sso": true,
"state": undefined,
},
"clientBaseUrl": "https://domain",
"clientID": "clientID",
"connectionResolver": undefined,
"defaultADUsernameFromEmailPrefix": true,
"domain": "domain",
"emitEventFn": "emitEventFn",
"hashCleanup": true,
"hookRunner": "hookRunner",
"languageBaseUrl": "https://cdn.auth0.com",
"prefill": Object {},
"tenantBaseUrl": "https://domain/info-v1.js",
"ui": Object {
"allowAutocomplete": false,
"allowPasswordAutocomplete": false,
"allowShowPassword": false,
"appendContainer": true,
"authButtonsTheme": Object {},
"autoclose": false,
"autofocus": true,
"avatar": true,
"avatarProvider": Object {
"displayName": [Function],
"url": [Function],
},
"closable": true,
"containerID": "auth0-lock-container-id",
"dict": Object {},
"disableWarnings": false,
"hideMainScreenTitle": false,
"labeledSubmitButton": true,
"language": "en",
"logo": undefined,
"mobile": false,
"popupOptions": Object {},
"primaryColor": undefined,
"rememberLastLogin": true,
"scrollGlobalMessagesIntoView": true,
},
"useTenantInfo": false,
}
`;
@@ -0,0 +1,20 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`SignedInConfirmation renders correctly 1`] = `
<div
className="auth0-lock-confirmation"
>
<div
className="auth0-lock-confirmation-content"
>
<span
dangerouslySetInnerHTML={
Object {
"__html": "<svg focusable=\\"false\\" width=\\"56px\\" height=\\"56px\\" viewBox=\\"0 0 52 52\\" version=\\"1.1\\" xmlns=\\"http://www.w3.org/2000/svg\\" xmlns:xlink=\\"http://www.w3.org/1999/xlink\\" class=\\"checkmark\\"> <circle cx=\\"26\\" cy=\\"26\\" r=\\"25\\" fill=\\"none\\" class=\\"checkmark__circle\\"></circle> <path fill=\\"none\\" d=\\"M14.1 27.2l7.1 7.2 16.7-16.8\\" class=\\"checkmark__check\\"></path> </svg>",
}
}
/>
<p />
</div>
</div>
`;
@@ -8,7 +8,11 @@ Object {
"allowForgot": true,
"allowSignup": true,
"name": "test-connection-database",
"passwordPolicy": "none",
"passwordPolicy": Object {
"length": Object {
"minLength": 1,
},
},
"requireUsername": false,
"strategy": "auth0",
"type": "database",
@@ -32,12 +36,15 @@ Object {
"allowForgot": false,
"allowSignup": false,
"name": "test-connection-database",
"passwordPolicy": "test-passwordPolicy",
"passwordPolicy": Object {
"length": Object {
"minLength": 1,
},
},
"requireUsername": true,
"strategy": "auth0",
"type": "database",
"validation": Object {
"passwordPolicy": "test-passwordPolicy",
"username": Object {
"max": 15,
"min": 1,
@@ -62,12 +69,15 @@ Object {
"allowForgot": false,
"allowSignup": false,
"name": "test-connection-database",
"passwordPolicy": "test-passwordPolicy",
"passwordPolicy": Object {
"length": Object {
"minLength": 1,
},
},
"requireUsername": true,
"strategy": "auth0",
"type": "database",
"validation": Object {
"passwordPolicy": "test-passwordPolicy",
"username": Object {
"max": 15,
"min": 1,
@@ -92,12 +102,15 @@ Object {
"allowForgot": false,
"allowSignup": false,
"name": "test-connection-database",
"passwordPolicy": "test-passwordPolicy",
"passwordPolicy": Object {
"length": Object {
"minLength": 1,
},
},
"requireUsername": true,
"strategy": "auth0",
"type": "database",
"validation": Object {
"passwordPolicy": "test-passwordPolicy",
"username": Object {
"max": 5,
"min": 4,
@@ -122,7 +135,11 @@ Object {
"allowForgot": true,
"allowSignup": true,
"name": "test-connection-database",
"passwordPolicy": "none",
"passwordPolicy": Object {
"length": Object {
"minLength": 1,
},
},
"requireUsername": false,
"strategy": "auth0",
"type": "database",
@@ -138,6 +155,40 @@ Object {
}
`;

exports[`initTenant() with database connection maps password policy correctly 1`] = `
Object {
"connections": Object {
"database": Array [
Object {
"allowForgot": false,
"allowSignup": false,
"name": "test-connection-database",
"passwordPolicy": Object {
"length": Object {
"minLength": 6,
},
},
"requireUsername": true,
"strategy": "auth0",
"type": "database",
"validation": Object {
"passwordPolicy": "low",
"username": Object {
"max": 5,
"min": 4,
},
},
},
],
"enterprise": Array [],
"passwordless": Array [],
"social": Array [],
"unknown": Array [],
},
"defaultDirectory": null,
}
`;

exports[`initTenant() with enterprise connection maps connection correctly 1`] = `
Object {
"connections": Object {
@@ -0,0 +1,34 @@
import { checkSession } from '../../core/actions';
import { expectMockToMatch } from 'testUtils';

jest.mock('../../core/web_api', () => ({
checkSession: jest.fn()
}));

jest.mock('store/index', () => ({
read: jest.fn(() => 'model'),
getEntity: 'getEntity',
swap: jest.fn(),
updateEntity: 'updateEntity'
}));

jest.mock('core/index', () => ({
id: () => 'id',
setSubmitting: jest.fn()
}));

describe('core.actions', () => {
beforeEach(() => {
jest.resetAllMocks();
});
describe('checkSession', () => {
it('should set submitting on start', () => {
checkSession('id', 'params', 'cb');
const { read, swap } = require('store/index');
expectMockToMatch(read, 1);
expectMockToMatch(swap, 1);
swap.mock.calls[0][3]('model');
expectMockToMatch(require('core/index').setSubmitting, 1);
});
});
});
@@ -0,0 +1,61 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`core/client/index initClient loads password policy 'excellent' correctly with a password_complexity_options option 1`] = `
Object {
"minLength": 4,
}
`;

exports[`core/client/index initClient loads password policy 'excellent' correctly without a password_complexity_options option 1`] = `
Object {
"minLength": 10,
}
`;

exports[`core/client/index initClient loads password policy 'fair' correctly with a password_complexity_options option 1`] = `
Object {
"minLength": 4,
}
`;

exports[`core/client/index initClient loads password policy 'fair' correctly without a password_complexity_options option 1`] = `
Object {
"minLength": 8,
}
`;

exports[`core/client/index initClient loads password policy 'good' correctly with a password_complexity_options option 1`] = `
Object {
"minLength": 4,
}
`;

exports[`core/client/index initClient loads password policy 'good' correctly without a password_complexity_options option 1`] = `
Object {
"minLength": 8,
}
`;

exports[`core/client/index initClient loads password policy 'low' correctly with a password_complexity_options option 1`] = `
Object {
"minLength": 4,
}
`;

exports[`core/client/index initClient loads password policy 'low' correctly without a password_complexity_options option 1`] = `
Object {
"minLength": 6,
}
`;

exports[`core/client/index initClient loads password policy 'none' correctly with a password_complexity_options option 1`] = `
Object {
"minLength": 4,
}
`;

exports[`core/client/index initClient loads password policy 'none' correctly without a password_complexity_options option 1`] = `
Object {
"minLength": 1,
}
`;
@@ -0,0 +1,44 @@
import Immutable from 'immutable';
import { initClient } from '../../../core/client';

describe('core/client/index', () => {
describe('initClient', () => {
['none', 'low', 'fair', 'good', 'excellent'].forEach(policy => {
it(`loads password policy '${policy}' correctly without a password_complexity_options option`, () => {
const client = {
strategies: [
{
name: 'auth0',
connections: [
{
name: 'Username-Password-Authentication',
passwordPolicy: policy
}
]
}
]
};
const result = initClient(Immutable.fromJS({}), client).toJS();
expect(result.client.connections.database[0].passwordPolicy.length).toMatchSnapshot();
});
it(`loads password policy '${policy}' correctly with a password_complexity_options option`, () => {
const client = {
strategies: [
{
name: 'auth0',
connections: [
{
name: 'Username-Password-Authentication',
passwordPolicy: policy,
password_complexity_options: { min_length: 4 }
}
]
}
]
};
const result = initClient(Immutable.fromJS({}), client).toJS();
expect(result.client.connections.database[0].passwordPolicy.length).toMatchSnapshot();
});
});
});
});
@@ -1,6 +1,6 @@
import Immutable from 'immutable';
import { dataFns } from '../../utils/data_utils';
import { clientID, domain } from '../../core/index';
import { clientID, domain, loginErrorMessage } from '../../core/index';
import { initI18n } from '../../i18n';
import { setURL } from '../testUtils';

@@ -12,7 +12,8 @@ let mockSet;
let mockInit;

jest.mock('i18n', () => ({
initI18n: jest.fn()
initI18n: jest.fn(),
html: (...keys) => keys.join()
}));

jest.mock('utils/data_utils', () => ({
@@ -37,6 +38,28 @@ describe('setup', () => {
const model = mock.calls[0][1].toJS();
expect(model.auth.redirectUrl).toBe('https://test.com/path/');
});
it('default redirectUrl should work when `window.location.origin` is not available', () => {
setURL('https://test.com/path/#not-this-part', { noOrigin: true });
const options = {};
setup('id', 'clientID', 'domain', options, 'hookRunner', 'emitEventFn');
const { mock } = mockInit;
expect(mock.calls.length).toBe(1);
const model = mock.calls[0][1].toJS();
expect(model.auth.redirectUrl).toBe('https://test.com/path/');
});
it('should work with redirect:false and responseType:id_token', () => {
const options = {
auth: {
redirect: false,
responseType: 'id_token'
}
};
setup('id', 'clientID', 'domain', options, 'hookRunner', 'emitEventFn');
const { mock } = mockInit;
expect(mock.calls.length).toBe(1);
const model = mock.calls[0][1].toJS();
expect(model).toMatchSnapshot();
});
});

describe('setResolvedConnection', () => {
@@ -70,3 +93,11 @@ describe('setResolvedConnection', () => {
expect(Immutable.Map.isMap(mockSet.mock.calls[0][2])).toBe(true);
});
});

describe('loginErrorMessage', () => {
it('maps `password_expired` to `password_change_required`', () => {
const result = loginErrorMessage(mockLock, { code: 'password_expired' }, 'type');

expect(result).toBe([mockLock, 'error', 'login', 'password_change_required'].join());
});
});
@@ -0,0 +1,39 @@
const getSyncRemoteData = () => require('core/remote_data').syncRemoteData;

describe('remote_data.syncRemoteData()', () => {
beforeEach(() => {
jest.resetModules();

jest.mock('sync', () => jest.fn());

jest.mock('connection/enterprise', () => ({
isADEnabled: () => true
}));

jest.mock('core/index', () => ({
useTenantInfo: () => true,
id: () => 'id'
}));

jest.mock('core/sso/data', () => ({
fetchSSOData: jest.fn()
}));
});
describe('calls getSSOData with AD information', () => {
[true, false].forEach(isAdEnabled => {
it(`when isADEnabled is ${isAdEnabled}`, () => {
require('connection/enterprise').isADEnabled = () => isAdEnabled;
const syncRemoteData = getSyncRemoteData();
syncRemoteData();
const ssoCall = require('sync').mock.calls.find(c => c[1] === 'sso');
ssoCall[2].syncFn('model', 'callback');
const [
id,
sendADInformation,
callback
] = require('core/sso/data').fetchSSOData.mock.calls[0];
expect(sendADInformation).toBe(isAdEnabled);
});
});
});
});
@@ -0,0 +1,14 @@
import React from 'react';
import I from 'immutable';

import { expectComponent } from 'testUtils';

import SignedInConfirmation from '../../core/signed_in_confirmation';

const lock = I.fromJS({ id: '__lock-id__' });

describe('SignedInConfirmation', () => {
it('renders correctly', async () => {
expectComponent(<SignedInConfirmation lock={lock} />).toMatchSnapshot();
});
});
@@ -1,5 +1,43 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`LastLoginScreen renders correct buttonLabel uses SOCIAL_STRATEGY mapping when there is not a lastUsedUsername 1`] = `
<div
data-__type="quick_auth_pane"
data-alternativeClickHandler={[Function]}
data-alternativeLabel="notYourAccountAction"
data-buttonClickHandler={[Function]}
data-buttonIcon={undefined}
data-buttonLabel="Twitter"
data-foregroundColor={undefined}
data-header={
<p>
lastLoginInstructions
</p>
}
data-primaryColor={undefined}
data-strategy="twitter"
/>
`;

exports[`LastLoginScreen renders correct buttonLabel uses lastUsedConnectionName when there is not a lastUsedUsername and no SOCIAL_STRATEGY mapping 1`] = `
<div
data-__type="quick_auth_pane"
data-alternativeClickHandler={[Function]}
data-alternativeLabel="notYourAccountAction"
data-buttonClickHandler={[Function]}
data-buttonIcon={undefined}
data-buttonLabel="lastUsedConnection"
data-foregroundColor={undefined}
data-header={
<p>
lastLoginInstructions
</p>
}
data-primaryColor={undefined}
data-strategy="auth0"
/>
`;

exports[`LastLoginScreen renders correct icon when strategy is adfs 1`] = `
<div
data-__type="quick_auth_pane"
@@ -19,6 +57,25 @@ exports[`LastLoginScreen renders correct icon when strategy is adfs 1`] = `
/>
`;

exports[`LastLoginScreen renders correct icon when strategy is empty, use name instead 1`] = `
<div
data-__type="quick_auth_pane"
data-alternativeClickHandler={[Function]}
data-alternativeLabel="notYourAccountAction"
data-buttonClickHandler={[Function]}
data-buttonIcon={undefined}
data-buttonLabel="lastUsedUsername"
data-foregroundColor={undefined}
data-header={
<p>
lastLoginInstructions
</p>
}
data-primaryColor={undefined}
data-strategy="auth0"
/>
`;

exports[`LastLoginScreen renders correct icon when strategy is google-apps 1`] = `
<div
data-__type="quick_auth_pane"
@@ -2,7 +2,7 @@ import React from 'react';
import { mount } from 'enzyme';
import Immutable from 'immutable';

import { expectComponent, extractPropsFromWrapper, mockComponent } from 'testUtils';
import { expectComponent, extractPropsFromWrapper, mockComponent, setURL } from 'testUtils';

jest.mock('ui/pane/quick_auth_pane', () => mockComponent('quick_auth_pane'));

@@ -21,11 +21,13 @@ describe('LastLoginScreen', () => {

jest.mock('quick-auth/actions', () => ({
logIn: jest.fn(),
checkSession: jest.fn(),
skipQuickAuth: jest.fn()
}));

jest.mock('core/index', () => ({
id: () => 'id'
id: () => 'id',
domain: () => 'me.auth0.com'
}));

jest.mock('core/sso/index', () => ({
@@ -36,7 +38,9 @@ describe('LastLoginScreen', () => {
}));

jest.mock('connection/social/index', () => ({
STRATEGIES: {},
STRATEGIES: {
twitter: 'Twitter'
},
authButtonsTheme: () => ({
get: () => undefined
})
@@ -89,8 +93,45 @@ describe('LastLoginScreen', () => {
'waad',
'some-other-strategy'
].forEach(testStrategy);

it(`when strategy is empty, use name instead`, () => {
require('core/sso/index').lastUsedConnection = () =>
Immutable.fromJS({
name: testStrategyName
});
const Component = getComponent();
expectComponent(<Component {...defaultProps} />).toMatchSnapshot();
});
});
describe('renders correct buttonLabel', () => {
it('uses SOCIAL_STRATEGY mapping when there is not a lastUsedUsername', () => {
require('core/sso/index').lastUsedConnection = () => ({
get: () => 'twitter'
});
require('core/sso/index').lastUsedUsername = () => undefined;
const Component = getComponent();
expectComponent(<Component {...defaultProps} />).toMatchSnapshot();
});
it('uses lastUsedConnectionName when there is not a lastUsedUsername and no SOCIAL_STRATEGY mapping', () => {
require('core/sso/index').lastUsedUsername = () => undefined;
const Component = getComponent();
expectComponent(<Component {...defaultProps} />).toMatchSnapshot();
});
});
it('calls checkSession in the buttonClickHandler when outside of the universal login page', () => {
setURL('https://other-url.auth0.com');
const Component = getComponent();
const wrapper = mount(<Component {...defaultProps} />);
const props = extractPropsFromWrapper(wrapper);
props.buttonClickHandler();
const { mock } = require('quick-auth/actions').checkSession;
expect(mock.calls.length).toBe(1);
expect(mock.calls[0][0]).toBe('id');
expect(mock.calls[0][1].get()).toBe('lastUsedConnection');
expect(mock.calls[0][2]).toBe('lastUsedUsername');
});
it('calls logIn in the buttonClickHandler', () => {
it('calls logIn in the buttonClickHandler when inside of the universal login page', () => {
setURL('https://me.auth0.com');
const Component = getComponent();
const wrapper = mount(<Component {...defaultProps} />);
const props = extractPropsFromWrapper(wrapper);
@@ -53,7 +53,29 @@ describe('initTenant()', () => {
requiresUsername: true,
strategy: 'auth0',
validation: {
passwordPolicy: 'test-passwordPolicy',
username: {
min: 4,
max: 5
}
}
}
]
}
};
runTest(initTenant, mockDataFns, client);
});
it('maps password policy correctly', () => {
const client = {
connections: {
database: [
{
allowForgot: false,
allowSignup: false,
name: 'test-connection-database',
requiresUsername: true,
strategy: 'auth0',
validation: {
passwordPolicy: 'low', //minLength: 6
username: {
min: 4,
max: 5
@@ -76,7 +98,6 @@ describe('initTenant()', () => {
requiresUsername: true,
strategy: 'auth0',
validation: {
passwordPolicy: 'test-passwordPolicy',
username: {
min: 'foo',
max: 'bar'
@@ -99,7 +120,6 @@ describe('initTenant()', () => {
requiresUsername: true,
strategy: 'auth0',
validation: {
passwordPolicy: 'test-passwordPolicy',
username: {
min: 5,
max: 4
@@ -0,0 +1,65 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`normalizeError access_denied to invalid_user_password mapping domain doesn't match current host should map access_denied error to invalid_user_password when error.code === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`normalizeError access_denied to invalid_user_password mapping domain doesn't match current host should map access_denied error to invalid_user_password when error.error === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`normalizeError access_denied to invalid_user_password mapping domain is undefined should map access_denied error to invalid_user_password when error.code === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`normalizeError access_denied to invalid_user_password mapping domain is undefined should map access_denied error to invalid_user_password when error.error === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`normalizeError access_denied to invalid_user_password mapping domain match current host should not map access_denied error to invalid_user_password when error.code === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`normalizeError access_denied to invalid_user_password mapping domain match current host should not map access_denied error to invalid_user_password when error.error === access_denied 1`] = `
Object {
"code": "invalid_user_password",
"description": "foobar",
"error": "invalid_user_password",
}
`;

exports[`webAuthOverrides should omit overrides that are not compatible with WebAuth 1`] = `
Object {
"__jwks_uri": "https://jwks.com",
"__tenant": "tenant1",
"__token_issuer": "issuer1",
}
`;

exports[`webAuthOverrides should return overrides if any field is compatible with WebAuth 1`] = `
Object {
"__jwks_uri": "https://jwks.com",
"__tenant": "tenant1",
"__token_issuer": "issuer1",
}
`;

This file was deleted.

@@ -1,29 +1,316 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`Auth0APIClient getSSOData should call client.client.getSSOData 1`] = `
Array [
true,
[Function],
]
`;

exports[`Auth0APIClient getUserCountry should call getUserCountry 1`] = `
Array [
"cb",
]
`;

exports[`Auth0APIClient init with overrides always uses telemetry set in the \`auth0Client\` query param and inside the ULP 1`] = `
Object {
"env": Object {
"auth0.js-ulp": "a0js.version",
"envOverride": true,
"lock.js-ulp": "lock.version",
},
"name": "test-sdk",
"version": "1.0.0",
}
`;

exports[`Auth0APIClient init with overrides forwards options to WebAuth 1`] = `
Object {
"_sendTelemetry": true,
"_telemetryInfo": Object {
"env": Object {
"auth0.js": "a0js.version",
},
"name": "lock.js",
"version": "lock.version",
},
"audience": "foo",
"clientID": "cid",
"domain": "me.auth0.com",
"leeway": 30,
"nonce": "nonce",
"overrides": Object {
"__jwks_uri": "https://jwks.com",
"__tenant": "tenant1",
"__token_issuer": "issuer1",
},
"plugins": Array [
Object {
"name": "ExamplePlugin",
},
],
"redirectUri": "//localhost:8080/login/callback",
"responseMode": "query",
"responseType": "code",
"scope": "custom_scope",
"state": "state",
}
`;

exports[`Auth0APIClient init with overrides forwards options to WebAuth with a default leeway 1`] = `
Object {
"_sendTelemetry": true,
"_telemetryInfo": Object {
"env": Object {
"auth0.js": "a0js.version",
},
"name": "lock.js",
"version": "lock.version",
},
"audience": "foo",
"clientID": "cid",
"domain": "me.auth0.com",
"leeway": 60,
"nonce": "nonce",
"overrides": Object {
"__jwks_uri": "https://jwks.com",
"__tenant": "tenant1",
"__token_issuer": "issuer1",
},
"plugins": Array [
Object {
"name": "ExamplePlugin",
},
],
"redirectUri": "//localhost:8080/login/callback",
"responseMode": "query",
"responseType": "code",
"scope": "custom_scope",
"state": "state",
}
`;

exports[`Auth0APIClient init with overrides overrides telemetry when outside the ULP 1`] = `
Object {
"env": Object {
"auth0.js": "a0js.version",
"envOverride": true,
"lock.js": "lock.version",
},
"name": "test-sdk",
"version": "1.0.0",
}
`;

exports[`Auth0APIClient logIn should trim spaces in the email 1`] = `
Object {
"email": "foo@example.com",
"nonce": undefined,
"popupOptions": undefined,
"realm": undefined,
"scope": undefined,
"state": undefined,
}
`;

exports[`Auth0APIClient logIn should trim spaces in the mfa_code 1`] = `
Object {
"mfa_code": "123456",
"nonce": undefined,
"popupOptions": undefined,
"realm": undefined,
"scope": undefined,
"state": undefined,
"username": "foo",
}
`;

exports[`Auth0APIClient logIn should trim spaces in the username 1`] = `
Object {
"nonce": undefined,
"popupOptions": undefined,
"realm": undefined,
"scope": undefined,
"state": undefined,
"username": "foo",
}
`;

exports[`Auth0APIClient logIn should trim spaces in the username with a space 1`] = `
Object {
"nonce": undefined,
"popupOptions": undefined,
"realm": undefined,
"scope": undefined,
"state": undefined,
"username": "foo bar",
}
`;

exports[`Auth0APIClient logIn with credentials should call client.login 1`] = `
Object {
"nonce": undefined,
"popupOptions": undefined,
"realm": undefined,
"sso": undefined,
"scope": undefined,
"state": undefined,
"username": "foo",
}
`;

exports[`Auth0APIClient logIn with credentials should fail when in popup mode 1`] = `"Cross origin login is not supported in popup mode"`;
exports[`Auth0APIClient logIn with credentials should call popup.loginWithCredentials when redirect is false and sso is false 1`] = `
Object {
"nonce": undefined,
"popupOptions": undefined,
"scope": undefined,
"state": undefined,
"username": "foo",
}
`;

exports[`Auth0APIClient logIn with credentials should call popup.loginWithCredentials when redirect is false and sso is true 1`] = `
Object {
"nonce": undefined,
"popupOptions": undefined,
"scope": undefined,
"state": undefined,
"username": "foo",
}
`;

exports[`Auth0APIClient logIn with social/enterprise (without username and email) should call authorize when redirect===true 1`] = `
Object {
"nonce": undefined,
"sso": undefined,
"popupOptions": undefined,
"scope": undefined,
"state": undefined,
}
`;

exports[`Auth0APIClient logIn with social/enterprise (without username and email) should call popup.authorize when redirect===false 1`] = `
Object {
"nonce": undefined,
"sso": undefined,
"owp": true,
"popupOptions": undefined,
"scope": undefined,
"state": undefined,
}
`;

exports[`Auth0APIClient parseHash should pass __enableIdPInitiatedLogin when options._enableIdPInitiatedLogin===true 1`] = `
Array [
Object {
"__enableIdPInitiatedLogin": true,
"hash": "hash",
"nonce": undefined,
"state": undefined,
},
"cb",
]
`;

exports[`Auth0APIClient parseHash should pass __enableIdPInitiatedLogin when options._enableImpersonation===true 1`] = `
Array [
Object {
"__enableIdPInitiatedLogin": true,
"hash": "hash",
"nonce": undefined,
"state": undefined,
},
"cb",
]
`;

exports[`Auth0APIClient parseHash should pass __enableIdPInitiatedLogin:false when options._enableImpersonation and options._enableIdPInitiatedLogin are not present 1`] = `
Array [
Object {
"__enableIdPInitiatedLogin": false,
"hash": "hash",
"nonce": undefined,
"state": undefined,
},
"cb",
]
`;

exports[`Auth0APIClient passwordlessStart should call client.passwordlessStart 1`] = `
Array [
Object {
"foo": "bar",
},
[Function],
]
`;

exports[`Auth0APIClient passwordlessStart should trim spaces in the email 1`] = `
Object {
"email": "foo@example.com",
}
`;

exports[`Auth0APIClient passwordlessStart should trim spaces in the phoneNumber 1`] = `
Object {
"phoneNumber": "+554899999",
}
`;

exports[`Auth0APIClient passwordlessVerify should call client.passwordlessLogin 1`] = `
Array [
Object {
"foo": "bar",
"popup": true,
},
[Function],
]
`;

exports[`Auth0APIClient passwordlessVerify should trim spaces in the email 1`] = `
Object {
"email": " foo@example.com ",
"popup": false,
}
`;

exports[`Auth0APIClient passwordlessVerify should trim spaces in the phoneNumber 1`] = `
Object {
"phoneNumber": " +554899999 ",
"popup": false,
}
`;

exports[`Auth0APIClient resetPassword should trim spaces in the email 1`] = `
Object {
"email": "foo@example.com",
}
`;

exports[`Auth0APIClient resetPassword should trim spaces in the username 1`] = `
Object {
"username": "foo",
}
`;

exports[`Auth0APIClient resetPassword should trim spaces in the username with a space 1`] = `
Object {
"username": "foo bar",
}
`;

exports[`Auth0APIClient signUp should trim spaces in the email 1`] = `
Object {
"email": "foo@example.com",
}
`;

exports[`Auth0APIClient signUp should trim spaces in the username 1`] = `
Object {
"username": "foo",
}
`;

exports[`Auth0APIClient signUp should trim spaces in the username with a space 1`] = `
Object {
"username": "foo bar",
}
`;
@@ -1,21 +1,94 @@
import expect from 'expect.js';
import { webAuthOverrides } from 'core/web_api/helper';
import { webAuthOverrides, normalizeError } from 'core/web_api/helper';

describe('webAuthOverrides', () => {
it('should return overrides if any field is compatible with WebAuth', function() {
expect(webAuthOverrides({ __tenant: 'tenant1', __token_issuer: 'issuer1' })).to.eql({
__tenant: 'tenant1',
__token_issuer: 'issuer1'
});
expect(
webAuthOverrides({
__tenant: 'tenant1',
__token_issuer: 'issuer1',
__jwks_uri: 'https://jwks.com'
})
).toMatchSnapshot();
});

it('should omit overrides that are not compatible with WebAuth', function() {
expect(
webAuthOverrides({ __tenant: 'tenant1', __token_issuer: 'issuer1', backgroundColor: 'blue' })
).to.eql({ __tenant: 'tenant1', __token_issuer: 'issuer1' });
webAuthOverrides({
__tenant: 'tenant1',
__token_issuer: 'issuer1',
__jwks_uri: 'https://jwks.com',
backgroundColor: 'blue'
})
).toMatchSnapshot();
});

it('should return null if no fields are compatible with WebAuth', function() {
expect(webAuthOverrides({ backgroundColor: 'blue' })).to.not.be.ok();
expect(webAuthOverrides({ backgroundColor: 'blue' })).toBe(null);
});
});

describe('normalizeError', () => {
it('does nothing when there is no error', () => {
const normalized = normalizeError(undefined);
expect(normalized).toBe(undefined);
});

describe('access_denied to invalid_user_password mapping', function() {
const domainMock = 'domainMock';
const errorObjWithError = {
error: 'access_denied',
description: 'foobar'
};
const errorObjWithCode = {
code: 'access_denied',
description: 'foobar'
};
let currentWindowObj;

beforeAll(function() {
currentWindowObj = global.window;
global.window = {
locaction: {
host: domainMock
}
};
});

afterAll(function() {
global.window = currentWindowObj;
});

describe('domain is undefined', function() {
it('should map access_denied error to invalid_user_password when error.error === access_denied', () => {
const actualError = normalizeError(errorObjWithError);
expect(actualError).toMatchSnapshot();
});
it('should map access_denied error to invalid_user_password when error.code === access_denied', () => {
const actualError = normalizeError(errorObjWithCode);
expect(actualError).toMatchSnapshot();
});
});

describe("domain doesn't match current host", function() {
it('should map access_denied error to invalid_user_password when error.error === access_denied', () => {
const actualError = normalizeError(errorObjWithError, 'loremIpsum');
expect(actualError).toMatchSnapshot();
});
it('should map access_denied error to invalid_user_password when error.code === access_denied', () => {
const actualError = normalizeError(errorObjWithCode, 'loremIpsum');
expect(actualError).toMatchSnapshot();
});
});

describe('domain match current host', function() {
it('should not map access_denied error to invalid_user_password when error.error === access_denied', () => {
const actualError = normalizeError(errorObjWithError, domainMock);
expect(actualError).toMatchSnapshot();
});
it('should not map access_denied error to invalid_user_password when error.code === access_denied', () => {
const actualError = normalizeError(errorObjWithCode, domainMock);
expect(actualError).toMatchSnapshot();
});
});
});
});