Skip to content

Commit

Permalink
style: convert login page to carbon (#4724)
Browse files Browse the repository at this point in the history
* chore: copy old login component to Carbon folder

* style: carbonize login page

* style: disable login button when any field is empty
  • Loading branch information
huygur committed Jun 26, 2023
1 parent bc8b83a commit 9d1739c
Show file tree
Hide file tree
Showing 9 changed files with 610 additions and 1 deletion.
80 changes: 80 additions & 0 deletions client/src/App/Carbon/Login/Disclaimer/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

import {render, screen} from 'modules/testing-library';
import {ThemeProvider} from 'modules/theme/ThemeProvider';
import {Disclaimer} from './index';

const DISCLAIMER_TEXT =
'Non-Production License. If you would like information on production usage, please refer to our terms & conditions page or contact sales.';

describe('<Disclaimer />', () => {
afterEach(() => {
window.clientConfig = undefined;
});

it('should show the disclaimer', () => {
const {rerender} = render(<Disclaimer />, {wrapper: ThemeProvider});

// we need this custom selector because the text contains a link
expect(
screen.getByText((content, element) => {
return content !== '' && element?.textContent === DISCLAIMER_TEXT;
})
).toBeInTheDocument();
expect(
screen.getByRole('link', {name: 'terms & conditions page'})
).toHaveAttribute(
'href',
'https://camunda.com/legal/terms/camunda-platform/camunda-platform-8-self-managed/'
);
expect(screen.getByRole('link', {name: 'contact sales'})).toHaveAttribute(
'href',
'https://camunda.com/contact/'
);

window.clientConfig = {
isEnterprise: false,
};
rerender(<Disclaimer />);

expect(
screen.getByText((content, element) => {
return content !== '' && element?.textContent === DISCLAIMER_TEXT;
})
).toBeInTheDocument();
expect(
screen.getByRole('link', {name: 'terms & conditions page'})
).toHaveAttribute(
'href',
'https://camunda.com/legal/terms/camunda-platform/camunda-platform-8-self-managed/'
);
expect(screen.getByRole('link', {name: 'contact sales'})).toHaveAttribute(
'href',
'https://camunda.com/contact/'
);
});

it('should not render the disclaimer', () => {
window.clientConfig = {
isEnterprise: true,
};
render(<Disclaimer />);

expect(
screen.queryByText((content, element) => {
return content !== '' && element?.textContent === DISCLAIMER_TEXT;
})
).not.toBeInTheDocument();
expect(
screen.queryByRole('link', {name: 'terms & conditions page'})
).not.toBeInTheDocument();
expect(
screen.queryByRole('link', {name: 'contact sales'})
).not.toBeInTheDocument();
});
});
32 changes: 32 additions & 0 deletions client/src/App/Carbon/Login/Disclaimer/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

import {Link} from '@carbon/react';
import {Container} from './styled';

const Disclaimer: React.FC = () => {
return window.clientConfig?.isEnterprise ? null : (
<Container>
Non-Production License. If you would like information on production usage,
please refer to our{' '}
<Link
href="https://camunda.com/legal/terms/camunda-platform/camunda-platform-8-self-managed/"
target="_blank"
inline
>
terms & conditions page
</Link>{' '}
or{' '}
<Link href="https://camunda.com/contact/" target="_blank" inline>
contact sales
</Link>
.
</Container>
);
};

export {Disclaimer};
22 changes: 22 additions & 0 deletions client/src/App/Carbon/Login/Disclaimer/styled.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

import styled from 'styled-components';
import {styles} from '@carbon/elements';

const Container = styled.span`
color: var(--cds-text-secondary);
text-align: center;
padding-top: var(--cds-spacing-05);
&,
& a {
${styles.legal01};
}
`;

export {Container};
14 changes: 14 additions & 0 deletions client/src/App/Carbon/Login/LoadingSpinner.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

import {Loading} from '@carbon/react';

const LoadingSpinner: typeof Loading = (props) => {
return <Loading data-testid="spinner" withOverlay={false} {...props} />;
};

export {LoadingSpinner};
11 changes: 11 additions & 0 deletions client/src/App/Carbon/Login/constants.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

const LOGIN_ERROR = 'Username and Password do not match';
const GENERIC_ERROR = 'Credentials could not be verified';

export {LOGIN_ERROR, GENERIC_ERROR};
173 changes: 173 additions & 0 deletions client/src/App/Carbon/Login/index.test.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
/*
* Copyright Camunda Services GmbH and/or licensed to Camunda Services GmbH
* under one or more contributor license agreements. Licensed under a proprietary license.
* See the License.txt file for more information. You may not use this file
* except in compliance with the proprietary license.
*/

import {
render,
screen,
waitForElementToBeRemoved,
waitFor,
} from 'modules/testing-library';
import {Link, MemoryRouter, To} from 'react-router-dom';
import {ThemeProvider} from 'modules/theme/ThemeProvider';
import {Login} from './index';
import {LOGIN_ERROR, GENERIC_ERROR} from './constants';
import {LocationLog} from 'modules/utils/LocationLog';
import {authenticationStore} from 'modules/stores/authentication';
import {mockLogin} from 'modules/mocks/api/login';

function createWrapper(
initialPath: string = '/carbon/login',
referrer: To = {pathname: '/carbon/processes'}
) {
const Wrapper: React.FC<{children?: React.ReactNode}> = ({children}) => {
return (
<ThemeProvider>
<MemoryRouter initialEntries={[initialPath]}>
{children}
<Link
to="/carbon/login"
state={{
referrer,
}}
>
emulate auth check
</Link>
<LocationLog />
</MemoryRouter>
</ThemeProvider>
);
};

return Wrapper;
}

describe('<Login />', () => {
afterEach(() => {
authenticationStore.reset();
});

it('should login', async () => {
mockLogin().withSuccess(null);

const {user} = render(<Login />, {
wrapper: createWrapper('/carbon/login'),
});

await user.type(screen.getByLabelText(/username/i), 'demo');
await user.type(screen.getByLabelText(/password/i), 'demo');
await user.click(screen.getByRole('button', {name: 'Login'}));

await waitFor(() =>
expect(screen.getByTestId('pathname')).toHaveTextContent(/^\/carbon$/)
);
});

it('should show a loading spinner', async () => {
mockLogin().withServerError();

const {user} = render(<Login />, {
wrapper: createWrapper(),
});

await user.type(screen.getByLabelText(/username/i), 'demo');
await user.type(screen.getByLabelText(/password/i), 'demo');
await user.click(screen.getByRole('button', {name: 'Login'}));

expect(screen.getByTestId('spinner')).toBeInTheDocument();
await waitForElementToBeRemoved(screen.getByTestId('spinner'));

mockLogin().withSuccess(null);

await user.click(screen.getByRole('button', {name: 'Login'}));

expect(screen.getByTestId('spinner')).toBeInTheDocument();
await waitForElementToBeRemoved(screen.getByTestId('spinner'));
});

it('should redirect to the previous page', async () => {
mockLogin().withSuccess(null);

const {user} = render(<Login />, {
wrapper: createWrapper('/carbon/login'),
});

await user.click(screen.getByText(/emulate auth check/i));

await user.type(screen.getByLabelText(/username/i), 'demo');
await user.type(screen.getByLabelText(/password/i), 'demo');
await user.click(screen.getByRole('button', {name: 'Login'}));

await waitFor(() =>
expect(screen.getByTestId('pathname')).toHaveTextContent(
/^\/carbon\/processes$/
)
);
});

it('should disable the login button when any field is empty', async () => {
const {user} = render(<Login />, {
wrapper: createWrapper(),
});

expect(screen.getByRole('button', {name: 'Login'})).toBeDisabled();

await user.type(screen.getByLabelText(/username/i), 'demo');

expect(screen.getByRole('button', {name: 'Login'})).toBeDisabled();

await user.type(screen.getByLabelText(/password/i), 'demo');

expect(screen.getByRole('button', {name: 'Login'})).toBeEnabled();

await user.clear(screen.getByLabelText(/password/i));
await user.clear(screen.getByLabelText(/username/i));

expect(screen.getByRole('button', {name: 'Login'})).toBeDisabled();
});

it('should handle wrong credentials', async () => {
mockLogin().withServerError(401);

const {user} = render(<Login />, {
wrapper: createWrapper(),
});

await user.type(screen.getByLabelText(/username/i), 'wrong');
await user.type(screen.getByLabelText(/password/i), 'credentials');
await user.click(screen.getByRole('button', {name: 'Login'}));

expect(await screen.findByText(LOGIN_ERROR)).toBeInTheDocument();
});

it('should handle generic errors', async () => {
mockLogin().withServerError();

const {user} = render(<Login />, {
wrapper: createWrapper(),
});

await user.type(screen.getByLabelText(/username/i), 'demo');
await user.type(screen.getByLabelText(/password/i), 'demo');
await user.click(screen.getByRole('button', {name: 'Login'}));

expect(await screen.findByText(GENERIC_ERROR)).toBeInTheDocument();
});

it('should handle request failures', async () => {
mockLogin().withNetworkError();

const {user} = render(<Login />, {
wrapper: createWrapper(),
});

await user.type(screen.getByLabelText(/username/i), 'demo');
await user.type(screen.getByLabelText(/password/i), 'demo');
await user.click(screen.getByRole('button', {name: 'Login'}));

expect(await screen.findByText(GENERIC_ERROR)).toBeInTheDocument();
});
});
Loading

0 comments on commit 9d1739c

Please sign in to comment.