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

Feature/update av link for essentials #1487

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
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
3 changes: 2 additions & 1 deletion packages/link/index.d.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default, getUrl, getTarget, AvLinkProps } from './types/Link';
export { default, AvLinkProps } from './types/Link';
export { getLocation, getTarget, getLocation } from './types/util';
3 changes: 2 additions & 1 deletion packages/link/index.js
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export { default, getTarget, getUrl } from './src/Link';
export { default } from './src/Link';
export { getLocation, getTarget, getUrl } from './src/util';
88 changes: 23 additions & 65 deletions packages/link/src/Link.js
Original file line number Diff line number Diff line change
Expand Up @@ -3,96 +3,54 @@ import PropTypes from 'prop-types';
import classNames from 'classnames';
import { isAbsoluteUrl } from '@availity/resolve-url';

// if absolute or loadApp is disabled, return url. otherwise loadappify the url
export const getUrl = (url = '', loadApp = false, absolute = false) => {
const isEssentials = /(test|qa(p?)-)?essentials\.availity\.com/.test(url);

if ((absolute || !loadApp) && !isEssentials) return url;

return `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(url)}`;
};

export const getTarget = (target) => {
// should start with _, otherwise it is specifying a specific frame name
// _blank = new tab/window, _self = same frame, _parent = parent frame (use for home page from modals), _top = document body, framename = specific frame
if (target && !target.startsWith('_')) {
// Thanos uses BODY
// 'newBody' hard-coded in spaces -> should we keep this logic?
if (target === 'BODY' || target === 'newBody') {
return '_self';
}
if (target === 'TAB') {
return '_blank';
}
}

return target || '_self';
};

// takes href and transforms it so that we can compare hostnames and other properties
const getLocation = (href) => {
const location = document.createElement('a');
location.href = href;
return location;
};

const setRel = (url, target, absolute) => {
if (target === '_blank' && absolute) {
const dest = getLocation(url);
if (dest.hostname !== window.location.hostname) {
// default rel when linking to external destinations for performance and security
return 'noopener noreferrer';
}
}
// eslint-disable-next-line unicorn/no-useless-undefined
return undefined;
};
import { getRel, getTarget, getUrl } from './util';

const linkStyles = { fontWeight: 'bold' };

const AvLink = ({ tag: Tag, href, target, children, onClick, loadApp, className, ...props }) => {
const AvLink = ({ href, children, className, loadApp = true, onClick, tag: Tag = 'a', target, ...rest }) => {
const absolute = isAbsoluteUrl(href);
const url = getUrl(href, loadApp, absolute);
const classnames = classNames('link', className);
target = getTarget(target);
const rel = getRel(url, target, absolute);

const handleOnClick = (event) => {
if (onClick) onClick(event, url);
};

return (
<Tag
href={url}
target={target}
style={linkStyles}
className={classnames}
onClick={(event) => onClick && onClick(event, url)}
data-testid="av-link-tag"
rel={setRel(url, target, absolute)}
{...props}
onClick={handleOnClick}
rel={rel}
style={linkStyles}
target={target}
{...rest}
>
{children}
</Tag>
);
};

AvLink.defaultProps = {
tag: 'a',
loadApp: true,
};

AvLink.propTypes = {
/** Where to open the linked document. */
target: PropTypes.string,
/** The tag to use in the link that gets rendered. Default: <a>. */
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/** Children can be a react child or render pop. */
children: PropTypes.node,
/** The url of the page the link goes to. */
href: PropTypes.string.isRequired,
/** Function to run onClick of the link. The first argument passed to onClick is the event. The second argument is the processed url. */
onClick: PropTypes.func,
/** When false, the url prop to AvLink is not formatted to leverage loadApp. */
loadApp: PropTypes.bool,
/** Children can be a react child or render pop. */
children: PropTypes.node,
/** Additional classes that should be applied to Link.
or Pass a string containing the class names as a prop. */
className: PropTypes.string,
/** When false, the url prop to AvLink is not formatted to leverage loadApp. */
loadApp: PropTypes.bool,
/** The tag to use in the link that gets rendered. Default: <a>. */
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
/** Where to open the linked document. */
target: PropTypes.string,
/** Function that is called when the element is clicked. The first argument passed to onClick is the event. The second argument is the processed url. */
onClick: PropTypes.func,
/** The relationship of the linked URL as space-separated link types. */
rel: PropTypes.string,
};

Expand Down
46 changes: 46 additions & 0 deletions packages/link/src/util.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
export const isEssentialsUrl = (url) => /(test|qa(p?)-)?essentials\.availity\.com/.test(url);

/** If absolute or loadApp is disabled, return url. Otherwise loadappify the url */
export const getUrl = (url = '', loadApp = false, absolute = false) => {
// if ((absolute || !loadApp) && !isEssentialsUrl(url)) return url;
if (absolute || !loadApp) return url;

return `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(url)}`;
};

/** Return a valid target based on what is passed in */
export const getTarget = (target) => {
// should start with _, otherwise it is specifying a specific frame name
// _blank = new tab/window, _self = same frame, _parent = parent frame (use for home page from modals), _top = document body, framename = specific frame
if (target && !target.startsWith('_')) {
// Thanos uses BODY
// 'newBody' hard-coded in spaces -> should we keep this logic?
if (target === 'BODY' || target === 'newBody') {
return '_self';
}
if (target === 'TAB') {
return '_blank';
}
}

return target || '_self';
};

/** Takes href and transforms it so that we can compare hostnames and other properties */
export const getLocation = (href) => {
const location = document.createElement('a');
location.href = href;
return location;
};

export const getRel = (url, target, absolute) => {
if (target === '_blank' && absolute) {
const dest = getLocation(url);
if (dest.hostname !== window.location.hostname) {
// default rel when linking to external destinations for performance and security
return 'noopener noreferrer';
}
}
// eslint-disable-next-line unicorn/no-useless-undefined
return undefined;
};
26 changes: 2 additions & 24 deletions packages/link/tests/Link.test.js
Original file line number Diff line number Diff line change
@@ -1,8 +1,7 @@
import React from 'react';
import { render, cleanup, fireEvent } from '@testing-library/react';
import AvLink from '..';
import { render, fireEvent } from '@testing-library/react';

afterEach(cleanup);
import AvLink from '..';

describe('AvLink', () => {
test('should render absolute url', () => {
Expand Down Expand Up @@ -114,25 +113,4 @@ describe('AvLink', () => {
expect(link).toHaveAttribute('class', 'link card-link');
expect(link).toHaveAttribute('style', 'font-weight: bold;');
});

test('should render urls from the test-essentials.availity.com domain correctly', () => {
const exampleHref = 'https://test-essentials.availity.com/static/avonly/post/cs/enhanced-claim-status-ui/';

const { getByTestId } = render(<AvLink href={exampleHref}>My App</AvLink>);

const tag = getByTestId('av-link-tag');
const expected = `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(exampleHref)}`;

expect(tag.getAttribute('href')).toBe(expected);
});
test('should render urls from the qa-essentials.availity.com domain correctly', () => {
const exampleHref = 'https://qa-essentials.availity.com/static/avonly/post/cs/enhanced-claim-status-ui/';

const { getByTestId } = render(<AvLink href={exampleHref}>My App</AvLink>);

const tag = getByTestId('av-link-tag');
const expected = `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(exampleHref)}`;

expect(tag.getAttribute('href')).toBe(expected);
});
});
105 changes: 105 additions & 0 deletions packages/link/tests/util.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { getLocation, getRel, getTarget, getUrl, isEssentialsUrl } from '../src/util';

describe('AvLink utils', () => {
const APPS = 'https://apps.availity.com';
const ESSENTIALS = 'https://essentials.availity.com';

beforeEach(() => {
global.jsdom.reconfigure({
url: 'https://apps.availity.com/public/apps/home/#!/',
});
});

describe('getLocation', () => {
test('should return current href in apps', () => {
expect(getLocation(APPS).href).toBe(`${APPS}/`);
});
test('should return current href in essentials', () => {
expect(getLocation(ESSENTIALS).href).toBe(`${ESSENTIALS}/`);
});
});

describe('getTarget', () => {
const SELF = '_self';
test('handles newBody', () => {
expect(getTarget('newBody')).toBe(SELF);
});
test('handles BODY', () => {
expect(getTarget('BODY')).toBe(SELF);
});
test('handles TAB', () => {
expect(getTarget('TAB')).toBe('_blank');
});
test('handles underline prefix', () => {
expect(getTarget('_test')).toBe('_test');
});
test('handles other', () => {
expect(getTarget('foobar')).toBe('foobar');
});
test('handles no target', () => {
expect(getTarget()).toBe(SELF);
});
});

describe('getUrl', () => {
test('apps domain loadApp false and absolute false', () => {
expect(getUrl(APPS, false, false)).toBe(APPS);
});
test('apps domain loadApp false and absolute true', () => {
expect(getUrl(APPS, false, true)).toBe(APPS);
});
test('apps domain loadApp true and absolute false', () => {
expect(getUrl(APPS, true, false)).toBe('/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fapps.availity.com');
});
test('apps domain loadApp true and absolute true', () => {
expect(getUrl(APPS, true, true)).toBe(APPS);
});
// test('essentials domain loadApp false and absolute false', () => {
// expect(getUrl(ESSENTIALS, false, false)).toBe(
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
// );
// });
// test('essentials domain loadApp false and absolute true', () => {
// expect(getUrl(ESSENTIALS, false, true)).toBe(
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
// );
// });
// test('essentials domain loadApp true and absolute false', () => {
// expect(getUrl(ESSENTIALS, true, false)).toBe(
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
// );
// });
// test('essentials domain loadApp true and absolute true', () => {
// expect(getUrl(ESSENTIALS, true, true)).toBe(
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
// );
// });
});

describe('getRel', () => {
test('handles _blank target and relative url', () => {
expect(getRel(APPS, '_blank', false)).toBeUndefined();
});
test('handles _blank target and absolute url', () => {
expect(getRel(APPS, '_blank', true)).toBeUndefined();
});
test('handles non _blank target and relative url', () => {
expect(getRel(APPS, '_blank', false)).toBeUndefined();
});
test('handles non _blank target and absolute url', () => {
expect(getRel(APPS, '_blank', true)).toBeUndefined();
});
});

describe('isEssentialsUrl', () => {
test('handles apps domain', () => {
expect(isEssentialsUrl(APPS)).toBeFalsy();
});

test('handles essentials domain', () => {
expect(isEssentialsUrl(ESSENTIALS)).toBeTruthy();
expect(isEssentialsUrl(ESSENTIALS.replace('essentials', 'test-essentials'))).toBeTruthy();
expect(isEssentialsUrl(ESSENTIALS.replace('essentials', 'qa-essentials'))).toBeTruthy();
});
});
});
12 changes: 3 additions & 9 deletions packages/link/types/Link.d.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,12 @@
export interface AvLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
target?: string;
tag?: React.ReactType | string;
onClick?: (event: React.SyntheticEvent<HTMLAnchorElement>, url: string) => void;
href: string;
loadApp?: boolean;
onClick?: (event: React.SyntheticEvent<HTMLAnchorElement>, url: string) => void;
rel?: string;
tag?: React.ReactType | string;
target?: string;
}

declare const AvLink: React.FC<AvLinkProps>;

declare function getUrl(url: string, loadApp: boolean, absolute: boolean): string;

declare function getTarget(target: string): string;

export { getUrl, getTarget };

export default AvLink;
11 changes: 11 additions & 0 deletions packages/link/types/util.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
declare function getLocation(href: string): HTMLAnchorElement;

declare function getTarget(target?: string): string;

declare function getUrl(url?: string, loadApp?: boolean, absolute?: boolean): string;

declare function getRel(): string | undefined;

declare function isEssentialsUrl(url: string): boolean;

export { getLocation, getTarget, getUrl, getRel };
Loading