diff --git a/packages/link/index.d.ts b/packages/link/index.d.ts
index 936f277f14..f2dfc731e4 100644
--- a/packages/link/index.d.ts
+++ b/packages/link/index.d.ts
@@ -1 +1,2 @@
-export { default, getUrl, getTarget, AvLinkProps } from './types/Link';
+export { default, AvLinkProps } from './types/Link';
+export { getLocation, getTarget, getLocation } from './types/util';
diff --git a/packages/link/index.js b/packages/link/index.js
index 602deea77f..753e6a0165 100644
--- a/packages/link/index.js
+++ b/packages/link/index.js
@@ -1 +1,2 @@
-export { default, getTarget, getUrl } from './src/Link';
+export { default } from './src/Link';
+export { getLocation, getTarget, getUrl } from './src/util';
diff --git a/packages/link/src/Link.js b/packages/link/src/Link.js
index 2d2d192eb8..7a50072b2f 100644
--- a/packages/link/src/Link.js
+++ b/packages/link/src/Link.js
@@ -3,94 +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, absolute) => {
- if (absolute || !loadApp) 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 (
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}
);
};
-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: . */
- 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: . */
+ 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,
};
diff --git a/packages/link/src/util.js b/packages/link/src/util.js
new file mode 100644
index 0000000000..9cd93f8fa9
--- /dev/null
+++ b/packages/link/src/util.js
@@ -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;
+};
diff --git a/packages/link/tests/Link.test.js b/packages/link/tests/Link.test.js
index cee295ed5c..938b1b9dfe 100644
--- a/packages/link/tests/Link.test.js
+++ b/packages/link/tests/Link.test.js
@@ -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', () => {
diff --git a/packages/link/tests/util.test.js b/packages/link/tests/util.test.js
new file mode 100644
index 0000000000..461280d678
--- /dev/null
+++ b/packages/link/tests/util.test.js
@@ -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();
+ });
+ });
+});
diff --git a/packages/link/types/Link.d.ts b/packages/link/types/Link.d.ts
index fc746e8d40..0c5b22178f 100644
--- a/packages/link/types/Link.d.ts
+++ b/packages/link/types/Link.d.ts
@@ -1,18 +1,12 @@
export interface AvLinkProps extends React.AnchorHTMLAttributes {
- target?: string;
- tag?: React.ReactType | string;
- onClick?: (event: React.SyntheticEvent, url: string) => void;
href: string;
loadApp?: boolean;
+ onClick?: (event: React.SyntheticEvent, url: string) => void;
rel?: string;
+ tag?: React.ReactType | string;
+ target?: string;
}
declare const AvLink: React.FC;
-declare function getUrl(url: string, loadApp: boolean, absolute: boolean): string;
-
-declare function getTarget(target: string): string;
-
-export { getUrl, getTarget };
-
export default AvLink;
diff --git a/packages/link/types/util.d.ts b/packages/link/types/util.d.ts
new file mode 100644
index 0000000000..d09a59d885
--- /dev/null
+++ b/packages/link/types/util.d.ts
@@ -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 };