Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[CFDS]/hasan/CFDS-4263/Refactor JS components to TS #16507

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
2e165e9
feat: refactor success dialog
hasan-deriv Aug 15, 2024
949f5e8
fix: fixed typescript error in test cases
hasan-deriv Aug 15, 2024
14c4d8f
feat: refactor error component
hasan-deriv Aug 15, 2024
51d367f
feat: refactor error component
hasan-deriv Aug 15, 2024
7301ff9
feat: refactor inverstor password manager component
hasan-deriv Aug 15, 2024
fdcb476
feat: remove unused loading component
hasan-deriv Aug 15, 2024
35bb34b
fix: fixed error component typescript issue
hasan-deriv Aug 15, 2024
59b923f
fix: removed link component and it's helpers function
hasan-deriv Aug 5, 2024
ae61d8a
fix: converted routes and binary routes to ts
hasan-deriv Aug 15, 2024
ee33013
fix: removed unnecessary props
hasan-deriv Aug 15, 2024
f99532c
feat: convert 404 page int TS
hasan-deriv Aug 15, 2024
b201200
feat: added types to work on route config
hasan-deriv Aug 15, 2024
a926a66
feat: convert routes-config to TS
hasan-deriv Aug 15, 2024
86f9388
feat: convert routes component to TS
hasan-deriv Aug 15, 2024
72a1c5e
fix: fixed Error Component according to Routes component
hasan-deriv Aug 15, 2024
9e425cf
fix: missing imports
hasan-deriv Aug 15, 2024
913cc1b
feat: convert route-with-sub-routes to TS
hasan-deriv Aug 15, 2024
4673599
fix: fixed build errors
hasan-deriv Aug 15, 2024
092a6aa
feat: removed prop types package
hasan-deriv Aug 15, 2024
1610638
fix: fixed failed import in cfd top up demo modal
hasan-deriv Aug 15, 2024
abccfcb
feat: added test to binary routes
hasan-deriv Aug 15, 2024
40f0799
feat: added test to route with subroute component
hasan-deriv Aug 15, 2024
498893b
fix: route-with-sub-route test cases
hasan-deriv Aug 19, 2024
19b6fd1
feat: added routes test cases
hasan-deriv Aug 19, 2024
c300423
feat: added cfd-password-success-message test cases
hasan-deriv Aug 19, 2024
b2c446b
fix: added passthrough to routes
hasan-deriv Aug 19, 2024
19245d4
Merge branch 'binary-com:master' into hasan/CFDS-4263/refactor-js-com…
shontzu-deriv Sep 10, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion packages/cfd/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -101,7 +101,6 @@
"formik": "^2.1.4",
"mobx": "^6.6.1",
"null-loader": "^4.0.1",
"prop-types": "^15.7.2",
"react": "^17.0.2",
"react-dom": "^17.0.2",
"react-qrcode": "^0.3.5",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,133 @@
import React from 'react';
import { MemoryRouter } from 'react-router-dom';
import { cleanup, fireEvent, render, screen } from '@testing-library/react';
import { routes } from '@deriv/shared';
import { mockStore, StoreProvider } from '@deriv/stores';
import ErrorComponent from '../error-component';

const mockErrorData = {
header: 'Error Occurred',
message: 'An unexpected error has occurred.',
redirect_label: 'Go to Home',
should_show_refresh: true,
redirectOnClick: jest.fn(),
};

const wrapper = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mockStore({})}>{children}</StoreProvider>
);

describe('ErrorComponent', () => {
let modal_root_el: HTMLElement;
beforeAll(() => {
modal_root_el = document.createElement('div');
modal_root_el.setAttribute('id', 'modal_root');
document.body.appendChild(modal_root_el);
});

afterAll(() => {
document.body.removeChild(modal_root_el);
});
beforeEach(() => {
jest.spyOn(global, 'location', 'get').mockReturnValue({
...window.location,
reload: jest.fn(),
});
});
afterEach(() => {
cleanup();
jest.clearAllMocks();
});

it('should render as a dialog when `is_dialog` is true', () => {
render(<ErrorComponent is_dialog={true} />);
expect(screen.getByRole('dialog')).toBeInTheDocument();
});

it('should not render as a dialog when `is_dialog` is false', () => {
render(<ErrorComponent is_dialog={false} />);
expect(screen.queryByRole('dialog')).not.toBeInTheDocument();
});

it('should render correctly with provided mock data', () => {
render(
<MemoryRouter>
<ErrorComponent {...mockErrorData} />
</MemoryRouter>
);
expect(
screen.getByRole('heading', {
level: 3,
name: mockErrorData.header,
})
).toBeInTheDocument();
expect(screen.getByText(mockErrorData.message)).toBeInTheDocument();
expect(screen.getByText(/Please refresh this page to continue./i)).toBeInTheDocument();
const linkEl = screen.getByRole('link', { name: mockErrorData.redirect_label });
expect(linkEl).toBeInTheDocument();
expect(linkEl).toHaveAttribute('href', routes.trade);
fireEvent.click(linkEl);
expect(mockErrorData.redirectOnClick).toHaveBeenCalledTimes(1);
});

it('should render correctly with default data when only minimum props are provided', () => {
const mockMinimumErrorData = {
header: 'Error Occurred',
message: 'An unexpected error has occurred.',
should_show_refresh: false,
};
render(
<MemoryRouter>
<ErrorComponent {...mockMinimumErrorData} />
</MemoryRouter>
);
expect(screen.queryByText(/Please refresh this page to continue./i)).not.toBeInTheDocument();
const linkEl = screen.getByRole('link', { name: 'Refresh' });
fireEvent.click(linkEl);
expect(global.location.reload).toHaveBeenCalledTimes(1);
});

it('should render ErrorModal with only `message` prop', () => {
render(
<MemoryRouter>
<ErrorComponent message='An unexpected error has occurred.' />
</MemoryRouter>,
{ wrapper }
);
expect(screen.getByText(/An unexpected error has occurred./i)).toBeInTheDocument();
});

it('should render as a dialog with provided data', () => {
render(<ErrorComponent is_dialog={true} {...mockErrorData} />);
expect(
screen.getByRole('heading', {
level: 1,
name: mockErrorData.header,
})
).toBeInTheDocument();
expect(screen.getByText(mockErrorData.message)).toBeInTheDocument();
const buttonEl = screen.getByRole('button', {
name: mockErrorData.redirect_label,
});
expect(buttonEl).toBeInTheDocument();
fireEvent.click(buttonEl);
expect(mockErrorData.redirectOnClick).toHaveBeenCalledTimes(1);
});

it('should render as a dialog with default data when only is_dialog is set to true', () => {
render(<ErrorComponent is_dialog={true} />);
expect(
screen.getByRole('heading', {
level: 1,
name: /there was an error/i,
})
).toBeInTheDocument();
expect(screen.getByText(/Sorry, an error occured while processing your request./i)).toBeInTheDocument();
const buttonEl = screen.getByRole('button', {
name: /ok/i,
});
expect(buttonEl).toBeInTheDocument();
fireEvent.click(buttonEl);
expect(global.location.reload).toHaveBeenCalledTimes(1);
});
});
Original file line number Diff line number Diff line change
@@ -1,17 +1,25 @@
import PropTypes from 'prop-types';
import React from 'react';
import { Dialog, PageErrorContainer } from '@deriv/components';
import { routes } from '@deriv/shared';
import { localize } from '@deriv/translations';

type TErrorComponentProps = {
header?: JSX.Element | string;
is_dialog?: boolean;
message?: JSX.Element | string;
redirect_label?: string;
redirectOnClick?: (() => void) | null;
should_show_refresh?: boolean;
};

const ErrorComponent = ({
header,
message,
is_dialog,
redirect_label,
redirectOnClick,
should_show_refresh = true,
}) => {
}: TErrorComponentProps) => {
const refresh_message = should_show_refresh ? localize('Please refresh this page to continue.') : '';

if (is_dialog) {
Expand All @@ -37,14 +45,4 @@ const ErrorComponent = ({
);
};

ErrorComponent.propTypes = {
header: PropTypes.string,
is_dialog: PropTypes.bool,
message: PropTypes.oneOfType([PropTypes.node, PropTypes.string, PropTypes.object]),
redirect_label: PropTypes.string,
redirectOnClick: PropTypes.func,
should_show_refresh: PropTypes.bool,
type: PropTypes.string,
};

export default ErrorComponent;
3 changes: 0 additions & 3 deletions packages/cfd/src/Components/Errors/index.js

This file was deleted.

3 changes: 3 additions & 0 deletions packages/cfd/src/Components/Errors/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
import ErrorComponent from './error-component';

export default ErrorComponent;
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import React from 'react';
import { Router } from 'react-router-dom';
import { createBrowserHistory } from 'history';

import { mockStore, StoreProvider } from '@deriv/stores';
import { render, screen } from '@testing-library/react';

import BinaryRoutes from '../binary-routes';

jest.mock('../route-with-sub-routes', () => jest.fn(() => <div>RouteWithSubRoutes</div>));

jest.mock('Constants/routes-config', () => () => [{}]);

describe('<BinaryRoutes />', () => {
const history = createBrowserHistory();

it('should render BinaryRoutes with mocked route component', () => {
const mock = mockStore({
modules: {
common: {
current_language: 'EN',
},
},
});
const wrapper = ({ children }: { children: JSX.Element }) => (
<StoreProvider store={mock}>{children}</StoreProvider>
);
render(
<Router history={history}>
<BinaryRoutes is_logged_in is_logging_in={false} />
</Router>,
{ wrapper }
);

expect(screen.getByText('RouteWithSubRoutes')).toBeInTheDocument();
});
});
Original file line number Diff line number Diff line change
@@ -1,24 +1,45 @@
import React from 'react';
import { Redirect } from 'react-router-dom';
import { render, screen } from '@testing-library/react';
import { redirectToLogin, isEmptyObject, removeBranchName, default_title } from '@deriv/shared';
import RouteWithSubRoutes from '../route-with-sub-routes';

type TRenderProps = {
location: {
pathname: string;
};
};
type TMockFunction = {
path: string;
exact?: boolean;
render?: (props: TRenderProps) => React.ReactNode;
};

jest.mock('react-router-dom', () => ({
...jest.requireActual('react-router-dom'),
Route: jest.fn(({ path, exact }: TMockFunction) => (
Route: jest.fn(({ path, exact, render }: TMockFunction) => (
<div>
<span>{`path param: ${path}`}</span>
<span>{`exact param: ${exact}`}</span>
{render?.({ location: { pathname: path } })}
</div>
)),
Redirect: jest.fn(({ to }) => <div>{`Redirect to: ${to}`}</div>),
}));

jest.mock('@deriv/shared', () => ({
redirectToLogin: jest.fn(),
isEmptyObject: (obj: Record<string, any>) => Object.keys(obj).length === 0,
routes: { index: '/index', is_logged_in: '/login' },
removeBranchName: jest.fn(pathname => pathname.replace('/index', '')),
default_title: 'Default Title',
}));

jest.mock('@deriv/translations', () => ({
getLanguage: jest.fn().mockReturnValue('EN'),
}));

afterEach(() => jest.clearAllMocks());
beforeEach(() => jest.clearAllMocks());

const route = {
getTitle: jest.fn(),
Expand All @@ -45,4 +66,87 @@ describe('RouteWithSubRoutes component', () => {
expect(path_param).toBeInTheDocument();
expect(exact_param).toBeInTheDocument();
});
it('should redirect to the specified path', () => {
render(<MockRouteWithSubRoutes />);
const redirect_element = screen.getByText(/Redirect to:/i);
expect(redirect_element).toBeInTheDocument();
});
it('should call redirectToLogin when not logged in and not logging in', () => {
const new_route = {
...route,
component: () => <div>Redirect to login</div>,
is_logging_in: false,
is_logged_in: false,
is_authenticated: true,
};
render(<RouteWithSubRoutes {...new_route} />);
expect(redirectToLogin).toHaveBeenCalled();
});
it('should removes /index from the URL in the localhost environment', () => {
const mockLocation = {
pathname: '/somepath/index',
};

const routeProps = {
...route,
path: '/index',
routes: [],
to: '/index',
location: mockLocation,
};

render(<RouteWithSubRoutes {...routeProps} />);
const expectedPath = '/somepath';
expect(mockLocation.pathname.toLowerCase().replace(routeProps.path, '')).toBe(expectedPath);
});
it('should set document title to default title when getTitle is not defined', () => {
render(<MockRouteWithSubRoutes />);
expect(document.title).toBe('| Default Title');
});
it('should set document title based on route.getTitle', () => {
const title = 'Test Title';
route.getTitle.mockReturnValue(title);
render(<MockRouteWithSubRoutes />);
expect(document.title).toBe(`Test Title | Default Title`);
});
it('should redirect to default subroute if pathname matches route path', () => {
const default_subroute = {
path: '/default-subroute',
default: true,
component: jest.fn(),
getTitle: jest.fn(),
};
const new_route = {
...route,
routes: [
{
subroutes: [default_subroute],
},
],
is_logging_in: false,
is_logged_in: true,
component: () => <div>Component</div>,
};

Object.defineProperty(window, 'location', {
writable: true,
value: { pathname: '/test-path' },
});

render(<RouteWithSubRoutes {...new_route} />);
const redirect_element = screen.getByText(/Redirect to: \/default-subroute/i);
expect(redirect_element).toBeInTheDocument();
});
it('should render the route component if not redirecting or logging in', () => {
const TestComponent = () => <div>Test Component</div>;
const new_route = {
...route,
is_logging_in: false,
is_logged_in: true,
component: TestComponent,
};
render(<RouteWithSubRoutes {...new_route} />);
const component_element = screen.getByText(/Test Component/i);
expect(component_element).toBeInTheDocument();
});
});
30 changes: 0 additions & 30 deletions packages/cfd/src/Components/Routes/binary-link.jsx

This file was deleted.

Loading
Loading