Skip to content

Commit 3528b98

Browse files
chore(link): refactor and add unit tests
1 parent b00b020 commit 3528b98

File tree

8 files changed

+194
-77
lines changed

8 files changed

+194
-77
lines changed

packages/link/index.d.ts

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { default, getUrl, getTarget, AvLinkProps } from './types/Link';
1+
export { default, AvLinkProps } from './types/Link';
2+
export { getLocation, getTarget, getLocation } from './types/util';

packages/link/index.js

+2-1
Original file line numberDiff line numberDiff line change
@@ -1 +1,2 @@
1-
export { default, getTarget, getUrl } from './src/Link';
1+
export { default } from './src/Link';
2+
export { getLocation, getTarget, getUrl } from './src/util';

packages/link/src/Link.js

+23-63
Original file line numberDiff line numberDiff line change
@@ -3,94 +3,54 @@ import PropTypes from 'prop-types';
33
import classNames from 'classnames';
44
import { isAbsoluteUrl } from '@availity/resolve-url';
55

6-
// if absolute or loadApp is disabled, return url. otherwise loadappify the url
7-
export const getUrl = (url = '', loadApp, absolute) => {
8-
if (absolute || !loadApp) return url;
9-
10-
return `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(url)}`;
11-
};
12-
13-
export const getTarget = (target) => {
14-
// should start with _, otherwise it is specifying a specific frame name
15-
// _blank = new tab/window, _self = same frame, _parent = parent frame (use for home page from modals), _top = document body, framename = specific frame
16-
if (target && !target.startsWith('_')) {
17-
// Thanos uses BODY
18-
// 'newBody' hard-coded in spaces -> should we keep this logic?
19-
if (target === 'BODY' || target === 'newBody') {
20-
return '_self';
21-
}
22-
if (target === 'TAB') {
23-
return '_blank';
24-
}
25-
}
26-
27-
return target || '_self';
28-
};
29-
30-
// takes href and transforms it so that we can compare hostnames and other properties
31-
const getLocation = (href) => {
32-
const location = document.createElement('a');
33-
location.href = href;
34-
return location;
35-
};
36-
37-
const setRel = (url, target, absolute) => {
38-
if (target === '_blank' && absolute) {
39-
const dest = getLocation(url);
40-
if (dest.hostname !== window.location.hostname) {
41-
// default rel when linking to external destinations for performance and security
42-
return 'noopener noreferrer';
43-
}
44-
}
45-
// eslint-disable-next-line unicorn/no-useless-undefined
46-
return undefined;
47-
};
6+
import { getRel, getTarget, getUrl } from './util';
487

498
const linkStyles = { fontWeight: 'bold' };
509

51-
const AvLink = ({ tag: Tag, href, target, children, onClick, loadApp, className, ...props }) => {
10+
const AvLink = ({ href, children, className, loadApp = true, onClick, tag: Tag = 'a', target, ...rest }) => {
5211
const absolute = isAbsoluteUrl(href);
5312
const url = getUrl(href, loadApp, absolute);
5413
const classnames = classNames('link', className);
5514
target = getTarget(target);
15+
const rel = getRel(url, target, absolute);
16+
17+
const handleOnClick = (event) => {
18+
if (onClick) onClick(event, url);
19+
};
5620

5721
return (
5822
<Tag
5923
href={url}
60-
target={target}
61-
style={linkStyles}
6224
className={classnames}
63-
onClick={(event) => onClick && onClick(event, url)}
6425
data-testid="av-link-tag"
65-
rel={setRel(url, target, absolute)}
66-
{...props}
26+
onClick={handleOnClick}
27+
rel={rel}
28+
style={linkStyles}
29+
target={target}
30+
{...rest}
6731
>
6832
{children}
6933
</Tag>
7034
);
7135
};
7236

73-
AvLink.defaultProps = {
74-
tag: 'a',
75-
loadApp: true,
76-
};
77-
7837
AvLink.propTypes = {
79-
/** Where to open the linked document. */
80-
target: PropTypes.string,
81-
/** The tag to use in the link that gets rendered. Default: <a>. */
82-
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
83-
/** Children can be a react child or render pop. */
84-
children: PropTypes.node,
8538
/** The url of the page the link goes to. */
8639
href: PropTypes.string.isRequired,
87-
/** Function to run onClick of the link. The first argument passed to onClick is the event. The second argument is the processed url. */
88-
onClick: PropTypes.func,
89-
/** When false, the url prop to AvLink is not formatted to leverage loadApp. */
90-
loadApp: PropTypes.bool,
40+
/** Children can be a react child or render pop. */
41+
children: PropTypes.node,
9142
/** Additional classes that should be applied to Link.
9243
or Pass a string containing the class names as a prop. */
9344
className: PropTypes.string,
45+
/** When false, the url prop to AvLink is not formatted to leverage loadApp. */
46+
loadApp: PropTypes.bool,
47+
/** The tag to use in the link that gets rendered. Default: <a>. */
48+
tag: PropTypes.oneOfType([PropTypes.func, PropTypes.string]),
49+
/** Where to open the linked document. */
50+
target: PropTypes.string,
51+
/** 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. */
52+
onClick: PropTypes.func,
53+
/** The relationship of the linked URL as space-separated link types. */
9454
rel: PropTypes.string,
9555
};
9656

packages/link/src/util.js

+46
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
export const isEssentialsUrl = (url) => /(test|qa(p?)-)?essentials\.availity\.com/.test(url);
2+
3+
/** If absolute or loadApp is disabled, return url. Otherwise loadappify the url */
4+
export const getUrl = (url = '', loadApp = false, absolute = false) => {
5+
// if ((absolute || !loadApp) && !isEssentialsUrl(url)) return url;
6+
if (absolute || !loadApp) return url;
7+
8+
return `/public/apps/home/#!/loadApp?appUrl=${encodeURIComponent(url)}`;
9+
};
10+
11+
/** Return a valid target based on what is passed in */
12+
export const getTarget = (target) => {
13+
// should start with _, otherwise it is specifying a specific frame name
14+
// _blank = new tab/window, _self = same frame, _parent = parent frame (use for home page from modals), _top = document body, framename = specific frame
15+
if (target && !target.startsWith('_')) {
16+
// Thanos uses BODY
17+
// 'newBody' hard-coded in spaces -> should we keep this logic?
18+
if (target === 'BODY' || target === 'newBody') {
19+
return '_self';
20+
}
21+
if (target === 'TAB') {
22+
return '_blank';
23+
}
24+
}
25+
26+
return target || '_self';
27+
};
28+
29+
/** Takes href and transforms it so that we can compare hostnames and other properties */
30+
export const getLocation = (href) => {
31+
const location = document.createElement('a');
32+
location.href = href;
33+
return location;
34+
};
35+
36+
export const getRel = (url, target, absolute) => {
37+
if (target === '_blank' && absolute) {
38+
const dest = getLocation(url);
39+
if (dest.hostname !== window.location.hostname) {
40+
// default rel when linking to external destinations for performance and security
41+
return 'noopener noreferrer';
42+
}
43+
}
44+
// eslint-disable-next-line unicorn/no-useless-undefined
45+
return undefined;
46+
};

packages/link/tests/Link.test.js

+2-3
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import React from 'react';
2-
import { render, cleanup, fireEvent } from '@testing-library/react';
3-
import AvLink from '..';
2+
import { render, fireEvent } from '@testing-library/react';
43

5-
afterEach(cleanup);
4+
import AvLink from '..';
65

76
describe('AvLink', () => {
87
test('should render absolute url', () => {

packages/link/tests/util.test.js

+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
import { getLocation, getRel, getTarget, getUrl, isEssentialsUrl } from '../src/util';
2+
3+
describe('AvLink utils', () => {
4+
const APPS = 'https://apps.availity.com';
5+
const ESSENTIALS = 'https://essentials.availity.com';
6+
7+
beforeEach(() => {
8+
global.jsdom.reconfigure({
9+
url: 'https://apps.availity.com/public/apps/home/#!/',
10+
});
11+
});
12+
13+
describe('getLocation', () => {
14+
test('should return current href in apps', () => {
15+
expect(getLocation(APPS).href).toBe(`${APPS}/`);
16+
});
17+
test('should return current href in essentials', () => {
18+
expect(getLocation(ESSENTIALS).href).toBe(`${ESSENTIALS}/`);
19+
});
20+
});
21+
22+
describe('getTarget', () => {
23+
const SELF = '_self';
24+
test('handles newBody', () => {
25+
expect(getTarget('newBody')).toBe(SELF);
26+
});
27+
test('handles BODY', () => {
28+
expect(getTarget('BODY')).toBe(SELF);
29+
});
30+
test('handles TAB', () => {
31+
expect(getTarget('TAB')).toBe('_blank');
32+
});
33+
test('handles underline prefix', () => {
34+
expect(getTarget('_test')).toBe('_test');
35+
});
36+
test('handles other', () => {
37+
expect(getTarget('foobar')).toBe('foobar');
38+
});
39+
test('handles no target', () => {
40+
expect(getTarget()).toBe(SELF);
41+
});
42+
});
43+
44+
describe('getUrl', () => {
45+
test('apps domain loadApp false and absolute false', () => {
46+
expect(getUrl(APPS, false, false)).toBe(APPS);
47+
});
48+
test('apps domain loadApp false and absolute true', () => {
49+
expect(getUrl(APPS, false, true)).toBe(APPS);
50+
});
51+
test('apps domain loadApp true and absolute false', () => {
52+
expect(getUrl(APPS, true, false)).toBe('/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fapps.availity.com');
53+
});
54+
test('apps domain loadApp true and absolute true', () => {
55+
expect(getUrl(APPS, true, true)).toBe(APPS);
56+
});
57+
// test('essentials domain loadApp false and absolute false', () => {
58+
// expect(getUrl(ESSENTIALS, false, false)).toBe(
59+
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
60+
// );
61+
// });
62+
// test('essentials domain loadApp false and absolute true', () => {
63+
// expect(getUrl(ESSENTIALS, false, true)).toBe(
64+
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
65+
// );
66+
// });
67+
// test('essentials domain loadApp true and absolute false', () => {
68+
// expect(getUrl(ESSENTIALS, true, false)).toBe(
69+
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
70+
// );
71+
// });
72+
// test('essentials domain loadApp true and absolute true', () => {
73+
// expect(getUrl(ESSENTIALS, true, true)).toBe(
74+
// '/public/apps/home/#!/loadApp?appUrl=https%3A%2F%2Fessentials.availity.com'
75+
// );
76+
// });
77+
});
78+
79+
describe('getRel', () => {
80+
test('handles _blank target and relative url', () => {
81+
expect(getRel(APPS, '_blank', false)).toBeUndefined();
82+
});
83+
test('handles _blank target and absolute url', () => {
84+
expect(getRel(APPS, '_blank', true)).toBeUndefined();
85+
});
86+
test('handles non _blank target and relative url', () => {
87+
expect(getRel(APPS, '_blank', false)).toBeUndefined();
88+
});
89+
test('handles non _blank target and absolute url', () => {
90+
expect(getRel(APPS, '_blank', true)).toBeUndefined();
91+
});
92+
});
93+
94+
describe('isEssentialsUrl', () => {
95+
test('handles apps domain', () => {
96+
expect(isEssentialsUrl(APPS)).toBeFalsy();
97+
});
98+
99+
test('handles essentials domain', () => {
100+
expect(isEssentialsUrl(ESSENTIALS)).toBeTruthy();
101+
expect(isEssentialsUrl(ESSENTIALS.replace('essentials', 'test-essentials'))).toBeTruthy();
102+
expect(isEssentialsUrl(ESSENTIALS.replace('essentials', 'qa-essentials'))).toBeTruthy();
103+
});
104+
});
105+
});

packages/link/types/Link.d.ts

+3-9
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,12 @@
11
export interface AvLinkProps extends React.AnchorHTMLAttributes<HTMLAnchorElement> {
2-
target?: string;
3-
tag?: React.ReactType | string;
4-
onClick?: (event: React.SyntheticEvent<HTMLAnchorElement>, url: string) => void;
52
href: string;
63
loadApp?: boolean;
4+
onClick?: (event: React.SyntheticEvent<HTMLAnchorElement>, url: string) => void;
75
rel?: string;
6+
tag?: React.ReactType | string;
7+
target?: string;
88
}
99

1010
declare const AvLink: React.FC<AvLinkProps>;
1111

12-
declare function getUrl(url: string, loadApp: boolean, absolute: boolean): string;
13-
14-
declare function getTarget(target: string): string;
15-
16-
export { getUrl, getTarget };
17-
1812
export default AvLink;

packages/link/types/util.d.ts

+11
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
declare function getLocation(href: string): HTMLAnchorElement;
2+
3+
declare function getTarget(target?: string): string;
4+
5+
declare function getUrl(url?: string, loadApp?: boolean, absolute?: boolean): string;
6+
7+
declare function getRel(): string | undefined;
8+
9+
declare function isEssentialsUrl(url: string): boolean;
10+
11+
export { getLocation, getTarget, getUrl, getRel };

0 commit comments

Comments
 (0)