mirror of
https://github.com/elyby/accounts-frontend.git
synced 2024-12-23 13:39:54 +05:30
Some minor fixes and e2e tests for creating of website app
This commit is contained in:
parent
19453584d0
commit
0ad3499609
@ -4,19 +4,21 @@ export type Account = {
|
||||
username: string,
|
||||
email: string,
|
||||
token: string,
|
||||
refreshToken: ?string,
|
||||
refreshToken: ?string
|
||||
};
|
||||
|
||||
export type State = {
|
||||
active: ?number,
|
||||
available: Array<Account>,
|
||||
available: Array<Account>
|
||||
};
|
||||
|
||||
|
||||
export type AddAction = { type: 'accounts:add', payload: Account};
|
||||
export type RemoveAction = { type: 'accounts:remove', payload: Account};
|
||||
export type ActivateAction = { type: 'accounts:activate', payload: Account};
|
||||
export type UpdateTokenAction = { type: 'accounts:updateToken', payload: string };
|
||||
export type AddAction = { type: 'accounts:add', payload: Account };
|
||||
export type RemoveAction = { type: 'accounts:remove', payload: Account };
|
||||
export type ActivateAction = { type: 'accounts:activate', payload: Account };
|
||||
export type UpdateTokenAction = {
|
||||
type: 'accounts:updateToken',
|
||||
payload: string
|
||||
};
|
||||
export type ResetAction = { type: 'accounts:reset' };
|
||||
|
||||
type Action =
|
||||
@ -27,14 +29,14 @@ type Action =
|
||||
| ResetAction;
|
||||
|
||||
export function getActiveAccount(state: { accounts: State }): ?Account {
|
||||
const activeAccount = state.accounts.active;
|
||||
// TODO: remove activeAccount.id, when will be sure, that magor part of users have migrated to new state structure
|
||||
const accountId: number | void = typeof activeAccount === 'number' ? activeAccount : (activeAccount || {}).id;
|
||||
const accountId = state.accounts.active;
|
||||
|
||||
return state.accounts.available.find((account) => account.id === accountId);
|
||||
}
|
||||
|
||||
export function getAvailableAccounts(state: { accounts: State }): Array<Account> {
|
||||
export function getAvailableAccounts(state: {
|
||||
accounts: State
|
||||
}): Array<Account> {
|
||||
return state.accounts.available;
|
||||
}
|
||||
|
||||
@ -47,8 +49,14 @@ export default function accounts(
|
||||
): State {
|
||||
switch (action.type) {
|
||||
case 'accounts:add': {
|
||||
if (!action.payload || !action.payload.id || !action.payload.token) {
|
||||
throw new Error('Invalid or empty payload passed for accounts.add');
|
||||
if (
|
||||
!action.payload
|
||||
|| !action.payload.id
|
||||
|| !action.payload.token
|
||||
) {
|
||||
throw new Error(
|
||||
'Invalid or empty payload passed for accounts.add'
|
||||
);
|
||||
}
|
||||
const { payload } = action;
|
||||
|
||||
@ -68,8 +76,14 @@ export default function accounts(
|
||||
}
|
||||
|
||||
case 'accounts:activate': {
|
||||
if (!action.payload || !action.payload.id || !action.payload.token) {
|
||||
throw new Error('Invalid or empty payload passed for accounts.add');
|
||||
if (
|
||||
!action.payload
|
||||
|| !action.payload.id
|
||||
|| !action.payload.token
|
||||
) {
|
||||
throw new Error(
|
||||
'Invalid or empty payload passed for accounts.add'
|
||||
);
|
||||
}
|
||||
|
||||
const { payload } = action;
|
||||
@ -77,10 +91,10 @@ export default function accounts(
|
||||
return {
|
||||
available: state.available.map((account) => {
|
||||
if (account.id === payload.id) {
|
||||
return {...payload};
|
||||
return { ...payload };
|
||||
}
|
||||
|
||||
return {...account};
|
||||
return { ...account };
|
||||
}),
|
||||
active: payload.id
|
||||
};
|
||||
@ -94,14 +108,18 @@ export default function accounts(
|
||||
|
||||
case 'accounts:remove': {
|
||||
if (!action.payload || !action.payload.id) {
|
||||
throw new Error('Invalid or empty payload passed for accounts.remove');
|
||||
throw new Error(
|
||||
'Invalid or empty payload passed for accounts.remove'
|
||||
);
|
||||
}
|
||||
|
||||
const { payload } = action;
|
||||
|
||||
return {
|
||||
...state,
|
||||
available: state.available.filter((account) => account.id !== payload.id)
|
||||
available: state.available.filter(
|
||||
(account) => account.id !== payload.id
|
||||
)
|
||||
};
|
||||
}
|
||||
|
||||
@ -118,12 +136,12 @@ export default function accounts(
|
||||
if (account.id === state.active) {
|
||||
return {
|
||||
...account,
|
||||
token: payload,
|
||||
token: payload
|
||||
};
|
||||
}
|
||||
|
||||
return {...account};
|
||||
}),
|
||||
return { ...account };
|
||||
})
|
||||
};
|
||||
}
|
||||
|
||||
|
@ -48,7 +48,7 @@ export class ContactForm extends Component {
|
||||
const {onClose} = this.props;
|
||||
|
||||
return (
|
||||
<div className={isSuccessfullySent ? styles.successState : styles.contactForm}>
|
||||
<div data-e2e="feedbackPopup" className={isSuccessfullySent ? styles.successState : styles.contactForm}>
|
||||
<div className={popupStyles.popup}>
|
||||
<div className={popupStyles.header}>
|
||||
<h2 className={popupStyles.headerTitle}>
|
||||
|
@ -4,19 +4,30 @@ import { connect } from 'react-redux';
|
||||
import { create as createPopup } from 'components/ui/popup/actions';
|
||||
import ContactForm from './ContactForm';
|
||||
|
||||
function ContactLink({createContactPopup, ...props}: {
|
||||
function ContactLink({
|
||||
createContactPopup,
|
||||
...props
|
||||
}: {
|
||||
createContactPopup: () => void,
|
||||
props: Object
|
||||
}) {
|
||||
return (
|
||||
<a href="#" onClick={(event) => {
|
||||
event.preventDefault();
|
||||
<a
|
||||
href="#"
|
||||
data-e2e-button="feedbackPopup"
|
||||
onClick={(event) => {
|
||||
event.preventDefault();
|
||||
|
||||
createContactPopup();
|
||||
}} {...props} />
|
||||
createContactPopup();
|
||||
}}
|
||||
{...props}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
export default connect(null, {
|
||||
createContactPopup: () => createPopup(ContactForm),
|
||||
})(ContactLink);
|
||||
export default connect(
|
||||
null,
|
||||
{
|
||||
createContactPopup: () => createPopup(ContactForm)
|
||||
}
|
||||
)(ContactLink);
|
||||
|
@ -22,7 +22,7 @@ type Props = {
|
||||
applications: Array<OauthAppResponse>,
|
||||
isLoading: bool,
|
||||
deleteApp: string => Promise<any>,
|
||||
resetApp: (string, bool) => Promise<any>,
|
||||
resetApp: (string, bool) => Promise<any>
|
||||
};
|
||||
|
||||
export default class ApplicationsIndex extends Component<Props> {
|
||||
@ -139,7 +139,7 @@ function Guest() {
|
||||
|
||||
function NoApps() {
|
||||
return (
|
||||
<div className={styles.emptyState}>
|
||||
<div data-e2e="noApps" className={styles.emptyState}>
|
||||
<img src={cubeIcon} className={styles.emptyStateIcon} />
|
||||
<div className={styles.emptyStateText}>
|
||||
<div>
|
||||
@ -152,6 +152,7 @@ function NoApps() {
|
||||
|
||||
<LinkButton
|
||||
to="/dev/applications/new"
|
||||
data-e2e="newApp"
|
||||
label={messages.addNew}
|
||||
color={COLOR_GREEN}
|
||||
className={styles.emptyStateActionButton}
|
||||
|
@ -63,6 +63,8 @@ export default class ApplicationItem extends Component<
|
||||
className={classNames(styles.appItemContainer, {
|
||||
[styles.appExpanded]: expand
|
||||
})}
|
||||
data-e2e="appItem"
|
||||
data-e2e-app={app.clientId}
|
||||
>
|
||||
<div className={styles.appItemTile} onClick={this.onTileToggle}>
|
||||
<div className={styles.appTileTitle}>
|
||||
|
@ -49,6 +49,7 @@ export default class ApplicationsList extends React.Component<Props, State> {
|
||||
</div>
|
||||
<LinkButton
|
||||
to="/dev/applications/new"
|
||||
data-e2e="newApp"
|
||||
label={messages.addNew}
|
||||
color={COLOR_GREEN}
|
||||
className={styles.appsListAddNewAppBtn}
|
||||
|
@ -5,10 +5,10 @@ import { Link } from 'react-router-dom';
|
||||
|
||||
import Button from './Button';
|
||||
|
||||
export default function LinkButton(props: ElementProps<typeof Button> & ElementProps<typeof Link>) {
|
||||
const {to, ...restProps} = props;
|
||||
export default function LinkButton(
|
||||
props: ElementProps<typeof Button> & ElementProps<typeof Link>
|
||||
) {
|
||||
const { to, ...restProps } = props;
|
||||
|
||||
return (
|
||||
<Button component={Link} to={to} {...restProps} />
|
||||
);
|
||||
return <Button component={Link} to={to} {...restProps} />;
|
||||
}
|
||||
|
@ -7,7 +7,7 @@ import type { ComponentType } from 'react';
|
||||
import type { Account } from 'components/accounts';
|
||||
|
||||
const PrivateRoute = ({account, component: Component, ...rest}: {
|
||||
component: ComponentType<*>,
|
||||
component: ComponentType<any>,
|
||||
account: ?Account
|
||||
}) => (
|
||||
<Route {...rest} render={(props: {location: string}) => (
|
||||
|
@ -2,6 +2,7 @@
|
||||
import React from 'react';
|
||||
import { Redirect, Route, Switch } from 'react-router-dom';
|
||||
import { FooterMenu } from 'components/footerMenu';
|
||||
import PrivateRoute from 'containers/PrivateRoute';
|
||||
|
||||
import styles from './dev.scss';
|
||||
import ApplicationsListPage from './ApplicationsListPage';
|
||||
@ -11,12 +12,25 @@ import UpdateApplicationPage from './UpdateApplicationPage';
|
||||
export default function DevPage() {
|
||||
return (
|
||||
<div className={styles.container}>
|
||||
<Switch>
|
||||
<Route path="/dev/applications" exact component={ApplicationsListPage} />
|
||||
<Route path="/dev/applications/new" exact component={CreateNewApplicationPage} />
|
||||
<Route path="/dev/applications/:clientId" component={UpdateApplicationPage} />
|
||||
<Redirect to="/dev/applications" />
|
||||
</Switch>
|
||||
<div data-e2e-content>
|
||||
<Switch>
|
||||
<Route
|
||||
path="/dev/applications"
|
||||
exact
|
||||
component={ApplicationsListPage}
|
||||
/>
|
||||
<PrivateRoute
|
||||
path="/dev/applications/new"
|
||||
exact
|
||||
component={CreateNewApplicationPage}
|
||||
/>
|
||||
<PrivateRoute
|
||||
path="/dev/applications/:clientId"
|
||||
component={UpdateApplicationPage}
|
||||
/>
|
||||
<Redirect to="/dev/applications" />
|
||||
</Switch>
|
||||
</div>
|
||||
|
||||
<div className={styles.footer}>
|
||||
<FooterMenu />
|
||||
|
@ -49,7 +49,7 @@ type FormPayloads = {
|
||||
minecraftServerIp?: string,
|
||||
};
|
||||
|
||||
export default {
|
||||
const api = {
|
||||
validate(oauthData: OauthData) {
|
||||
return request.get(
|
||||
'/api/oauth2/v1/validate',
|
||||
@ -116,6 +116,13 @@ export default {
|
||||
return request.delete(`/api/v1/oauth2/${clientId}`);
|
||||
},
|
||||
};
|
||||
|
||||
if (window.Cypress) {
|
||||
window.oauthApi = api;
|
||||
}
|
||||
|
||||
export default api;
|
||||
|
||||
/**
|
||||
* @param {object} oauthData
|
||||
* @param {string} oauthData.clientId
|
||||
|
@ -1,11 +1,13 @@
|
||||
{
|
||||
"account1": {
|
||||
"id": "7",
|
||||
"username": "SleepWalker",
|
||||
"email": "danilenkos@auroraglobal.com",
|
||||
"login": "SleepWalker",
|
||||
"password": "qwer1234"
|
||||
},
|
||||
"account2": {
|
||||
"id": 102,
|
||||
"username": "test",
|
||||
"email": "admin@udf.su",
|
||||
"login": "test",
|
||||
|
28
tests-e2e/cypress/integration/dev/guest.test.js
Normal file
28
tests-e2e/cypress/integration/dev/guest.test.js
Normal file
@ -0,0 +1,28 @@
|
||||
describe('/dev/applications - guest', () => {
|
||||
it('should render login button', () => {
|
||||
cy.visit('/dev/applications');
|
||||
|
||||
cy.get('[data-e2e-content] [href="/login"]').click();
|
||||
|
||||
cy.url().should('include', '/login');
|
||||
});
|
||||
|
||||
it('should not allow create new app', () => {
|
||||
cy.visit('/dev/applications/new');
|
||||
|
||||
cy.url().should('include', '/login');
|
||||
});
|
||||
|
||||
it('should not allow edit app', () => {
|
||||
cy.visit('/dev/applications/foo-bar');
|
||||
|
||||
cy.url().should('include', '/login');
|
||||
});
|
||||
|
||||
it('should have feedback popup link', () => {
|
||||
cy.visit('/dev/applications');
|
||||
|
||||
cy.get('[data-e2e-content] [data-e2e-button="feedbackPopup"]').click();
|
||||
cy.get('[data-e2e="feedbackPopup"]').should('be.visible');
|
||||
});
|
||||
});
|
42
tests-e2e/cypress/integration/dev/user.test.js
Normal file
42
tests-e2e/cypress/integration/dev/user.test.js
Normal file
@ -0,0 +1,42 @@
|
||||
describe('/dev/applications - user', () => {
|
||||
before(() => {
|
||||
cy.login({ account: 'default' }).then(({ user }) => {
|
||||
cy.visit('/dev/applications');
|
||||
|
||||
// remove all previousely added apps
|
||||
cy.window().then(async ({ oauthApi }) => {
|
||||
const apps = await oauthApi.getAppsByUser(user.id);
|
||||
|
||||
await Promise.all(
|
||||
apps.map((app) => oauthApi.delete(app.clientId))
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
// TODO: test the first screen is without any list rendered
|
||||
// TODO: test validation
|
||||
|
||||
it('should add website app', () => {
|
||||
cy.visit('/dev/applications');
|
||||
|
||||
cy.get('[data-e2e="noApps"]').should('exist');
|
||||
|
||||
cy.get('[data-e2e="newApp"]').click();
|
||||
|
||||
cy.url().should('include', '/dev/applications/new');
|
||||
|
||||
cy.get('[value="application"]').check({ force: true });
|
||||
|
||||
cy.get('[name="name"]').type('The Foo');
|
||||
cy.get('[name="description"]').type('The Foo Description');
|
||||
cy.get('[name="websiteUrl"]').type('https://ely.by');
|
||||
cy.get('[name="redirectUri"]').type('https://ely.by/the/redirect/uri');
|
||||
|
||||
cy.get('[type="submit"]').click();
|
||||
|
||||
cy.url().should('include', '/dev/applications#the-foo');
|
||||
|
||||
cy.get('[data-e2e-app="the-foo"]').should('exist');
|
||||
});
|
||||
});
|
@ -1,3 +1,5 @@
|
||||
import { account1, account2 } from '../fixtures/accounts.json';
|
||||
|
||||
// ***********************************************
|
||||
// This example commands.js shows you how to
|
||||
// create various custom commands and overwrite
|
||||
@ -23,3 +25,69 @@
|
||||
//
|
||||
// -- This is will overwrite an existing command --
|
||||
// Cypress.Commands.overwrite("visit", (originalFn, url, options) => { ... })
|
||||
|
||||
const accountsMap = {
|
||||
// default: account1,
|
||||
default: account2
|
||||
};
|
||||
|
||||
Cypress.Commands.add('login', async ({ login, password, account }) => {
|
||||
let credentials;
|
||||
|
||||
if (account) {
|
||||
credentials = accountsMap[account];
|
||||
|
||||
if (!credentials) {
|
||||
throw new Error(`Unknown account name: ${account}`);
|
||||
}
|
||||
}
|
||||
|
||||
const resp = await fetch('/api/authentication/login', {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/x-www-form-urlencoded; charset=UTF-8'
|
||||
},
|
||||
body: `login=${credentials.login}&password=${credentials.password}&rememberMe=1`
|
||||
}).then((resp) => resp.json());
|
||||
|
||||
const state = createState([
|
||||
{
|
||||
id: credentials.id,
|
||||
username: credentials.username,
|
||||
email: credentials.email,
|
||||
token: resp.access_token,
|
||||
refreshToken: resp.refresh_token
|
||||
}
|
||||
]);
|
||||
|
||||
localStorage.setItem('redux-storage', JSON.stringify(state));
|
||||
|
||||
return state;
|
||||
});
|
||||
|
||||
function createState(accounts) {
|
||||
return {
|
||||
accounts: {
|
||||
available: accounts,
|
||||
active: accounts[0].id
|
||||
},
|
||||
user: {
|
||||
id: 102,
|
||||
uuid: 'e49cafdc-6e0c-442d-b608-dacdb864ee34',
|
||||
username: 'test',
|
||||
token: '',
|
||||
email: 'admin@udf.su',
|
||||
maskedEmail: '',
|
||||
avatar: '',
|
||||
lang: 'en',
|
||||
isActive: true,
|
||||
isOtpEnabled: true,
|
||||
shouldAcceptRules: false,
|
||||
passwordChangedAt: 1478961317,
|
||||
hasMojangUsernameCollision: true,
|
||||
isGuest: false,
|
||||
registeredAt: 1478961317,
|
||||
elyProfileLink: 'http://ely.by/u102'
|
||||
}
|
||||
};
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user