Browse Source

SONAR-11955 Allow more levels in documentation navigation

tags/7.8
Wouter Admiraal 5 years ago
parent
commit
15b6c5780c

+ 12
- 0
server/sonar-docs/config/jest/SetupJest.ts View File

@@ -0,0 +1,12 @@
/*
* Copyright (C) 2017-2019 SonarSource SA
* All rights reserved
* mailto:info AT sonarsource DOT com
*/
import { GlobalWithFetchMock } from 'jest-fetch-mock';

const customGlobal: GlobalWithFetchMock = global as GlobalWithFetchMock;

customGlobal.fetch = require('jest-fetch-mock');

customGlobal.fetchMock = customGlobal.fetch;

+ 3
- 1
server/sonar-docs/package.json View File

@@ -47,6 +47,7 @@
"glob-promise": "3.4.0",
"graphql-code-generator": "0.5.2",
"jest": "24.5.0",
"jest-fetch-mock": "2.1.2",
"prettier": "1.16.4",
"react-test-renderer": "16.8.5",
"remark": "10.0.1",
@@ -92,7 +93,8 @@
"^.+\\.css$": "<rootDir>/config/jest/CSSStub.js"
},
"setupFiles": [
"<rootDir>/config/jest/SetupEnzyme.js"
"<rootDir>/config/jest/SetupEnzyme.js",
"<rootDir>/config/jest/SetupJest.ts"
],
"snapshotSerializers": [
"enzyme-to-json/serializer"

+ 1
- 1
server/sonar-docs/src/@types/types.d.ts View File

@@ -27,7 +27,7 @@ export type DocNavigationItem = string | DocsNavigationBlock | DocsNavigationExt

export interface DocsNavigationBlock {
title: string;
children: string[];
children: (DocNavigationItem | string)[];
}

export interface DocsNavigationExternalLink {

+ 4
- 9
server/sonar-docs/src/__tests__/BrokenLinkSafetyNet.test.js View File

@@ -53,23 +53,18 @@ it('should have valid links in trees files', () => {
let hasErrors = false;
trees.forEach(file => {
const tree = JSON.parse(fs.readFileSync(path.join(rootPath, '..', 'static', file), 'utf8'));
tree.forEach(leaf => {
const walk = leaf => {
if (typeof leaf === 'object') {
if (leaf.children) {
leaf.children.forEach(child => {
// Check children markdown file path validity
if (!urlExists(parsedFiles, child)) {
console.log(`[${child}] is not a valid link, in ${file}`);
hasErrors = true;
}
});
leaf.children.forEach(walk);
}
} else if (!urlExists(parsedFiles, leaf)) {
// Check markdown file path validity
console.log(`[${leaf}] is not a valid link, in ${file}`);
hasErrors = true;
}
});
};
tree.forEach(walk);
});
expect(hasErrors).toBeFalsy();
});

+ 44
- 9
server/sonar-docs/src/components/CategoryBlockLink.tsx View File

@@ -25,22 +25,46 @@ import ChevronUpIcon from './icons/ChevronUpIcon';
import { MarkdownRemark } from '../@types/graphql-types';

interface Props {
children: MarkdownRemark[];
children: (MarkdownRemark | JSX.Element)[];
location: Location;
onToggle: (title: string) => void;
open: boolean;
openByDefault: boolean;
title: string;
}

export default class CategoryLink extends React.PureComponent<Props> {
interface State {
open: boolean;
}

export default class CategoryLink extends React.PureComponent<Props, State> {
state: State;

static defaultProps = {
openByDefault: false
};

constructor(props: Props) {
super(props);

this.state = {
open: props.openByDefault
};
}

handleToggle = (event: React.MouseEvent<HTMLAnchorElement>) => {
event.preventDefault();
event.stopPropagation();
this.props.onToggle(this.props.title);
this.setState(prevState => ({
open: !prevState.open
}));
};

isMarkdownRemark = (child: any): child is MarkdownRemark => {
return child.id !== undefined;
};

render() {
const { children, location, title, open } = this.props;
const { children, location, title } = this.props;
const { open } = this.state;
return (
<div>
<a
@@ -52,9 +76,20 @@ export default class CategoryLink extends React.PureComponent<Props> {
</a>
{children && open && (
<div className="sub-menu">
{children.map(page => (
<PageLink className="sub-menu-link" key={page.id} location={location} node={page} />
))}
{children.map((child, i) => {
if (this.isMarkdownRemark(child)) {
return (
<PageLink
className="sub-menu-link"
key={child.id}
location={location}
node={child}
/>
);
} else {
return <React.Fragment key={`child-${i}`}>{child}</React.Fragment>;
}
})}
</div>
)}
</div>

+ 60
- 53
server/sonar-docs/src/components/Sidebar.tsx View File

@@ -26,7 +26,13 @@ import Search from './Search';
import SearchEntryResult from './SearchEntryResult';
import VersionSelect from './VersionSelect';
import DownloadIcon from './icons/DownloadIcon';
import { getNavTree, isDocsNavigationBlock, isDocsNavigationExternalLink } from './navTreeUtils';
import {
getNavTree,
isDocsNavigationBlock,
isDocsNavigationExternalLink,
getOpenChainFromPath,
testPathAgainstUrl
} from './navTreeUtils';
import { MarkdownRemark } from '../@types/graphql-types';
import { SearchResult, DocVersion, DocNavigationItem } from '../@types/types';

@@ -39,7 +45,7 @@ interface Props {
interface State {
loaded: boolean;
navTree: DocNavigationItem[];
openBlockTitle: string;
openChain?: DocNavigationItem[];
query: string;
results: SearchResult[];
versions: DocVersion[];
@@ -52,7 +58,7 @@ export default class Sidebar extends React.PureComponent<Props, State> {
this.state = {
loaded: false,
navTree,
openBlockTitle: this.getOpenBlockFromLocation(this.props.location, navTree),
openChain: getOpenChainFromPath(this.props.location.pathname, navTree),
query: '',
results: [],
versions: []
@@ -66,20 +72,11 @@ export default class Sidebar extends React.PureComponent<Props, State> {
componentDidUpdate(prevProps: Props) {
if (this.props.location.pathname !== prevProps.location.pathname) {
this.setState(({ navTree }) => ({
openBlockTitle: this.getOpenBlockFromLocation(this.props.location, navTree)
openChain: getOpenChainFromPath(this.props.location.pathname, navTree)
}));
}
}

// A block is opened if the current page is set to one of his children
getOpenBlockFromLocation({ pathname }: Location, navTree: DocNavigationItem[]) {
const element = navTree.find(
leave =>
isDocsNavigationBlock(leave) && leave.children.some(child => pathname.endsWith(child))
);
return isDocsNavigationBlock(element) ? element.title : '';
}

loadVersions() {
fetch('/DocsVersions.json')
.then(response => response.json())
@@ -90,59 +87,69 @@ export default class Sidebar extends React.PureComponent<Props, State> {
}

getNodeFromUrl = (url: string) => {
return this.props.pages.find(p =>
Boolean(
(p.fields && p.fields.slug === url + '/') || (p.frontmatter && p.frontmatter.url === url)
)
);
};
return this.props.pages.find(p => {
if (p.fields && p.fields.slug) {
if (testPathAgainstUrl(p.fields.slug, url)) {
return true;
}
}

if (p.frontmatter && p.frontmatter.url) {
if (testPathAgainstUrl(p.frontmatter.url, url)) {
return true;
}
}

handleToggle = (title: string) => {
this.setState(state => ({ openBlockTitle: state.openBlockTitle === title ? '' : title }));
return false;
});
};

handleSearch = (results: SearchResult[], query: string) => {
this.setState({ results, query });
};

renderCategories = () => {
return (
<nav>
{this.state.navTree.map(leave => {
if (isDocsNavigationBlock(leave)) {
return (
<CategoryBlockLink
key={leave.title}
location={this.props.location}
onToggle={this.handleToggle}
open={leave.title === this.state.openBlockTitle}
title={leave.title}>
{
leave.children
.map(child => this.getNodeFromUrl(child))
.filter(Boolean) as MarkdownRemark[]
}
</CategoryBlockLink>
);
renderCategory = (leaf: DocNavigationItem) => {
if (isDocsNavigationBlock(leaf)) {
let children: (MarkdownRemark | JSX.Element)[] = [];
leaf.children.forEach(child => {
if (typeof child === 'string') {
const node = this.getNodeFromUrl(child);
if (node) {
children.push(node);
}
} else {
children = children.concat(this.renderCategory(child));
}
});
return (
<CategoryBlockLink
key={leaf.title}
location={this.props.location}
openByDefault={this.state.openChain && this.state.openChain.includes(leaf)}
title={leaf.title}>
{children}
</CategoryBlockLink>
);
}

if (isDocsNavigationExternalLink(leave)) {
return <ExternalLink external={leave.url} key={leave.title} title={leave.title} />;
}
if (isDocsNavigationExternalLink(leaf)) {
return <ExternalLink external={leaf.url} key={leaf.title} title={leaf.title} />;
}

return (
<PageLink
className="page-indexes-link"
key={leave}
location={this.props.location}
node={this.getNodeFromUrl(leave)}
/>
);
})}
</nav>
return (
<PageLink
className="page-indexes-link"
key={leaf}
location={this.props.location}
node={this.getNodeFromUrl(leaf)}
/>
);
};

renderCategories = () => {
return <nav>{this.state.navTree.map(this.renderCategory)}</nav>;
};

renderResults = () => {
return (
<div>

+ 2
- 3
server/sonar-docs/src/components/__tests__/CategoryBlockLink-test.tsx View File

@@ -27,15 +27,14 @@ it('should render correctly', () => {
});

it('should render correctly when closed', () => {
expect(shallowRender({ open: false })).toMatchSnapshot();
expect(shallowRender({ openByDefault: false })).toMatchSnapshot();
});

function shallowRender(props: Partial<CategoryBlockLink['props']> = {}) {
return shallow(
<CategoryBlockLink
location={{} as Location}
onToggle={jest.fn()}
open={true}
openByDefault={true}
title="My category"
{...props}>
{[{ id: '1' }, { id: '2' }] as MarkdownRemark[]}

+ 106
- 0
server/sonar-docs/src/components/__tests__/Sidebar-test.tsx View File

@@ -0,0 +1,106 @@
/*
* SonarQube
* Copyright (C) 2009-2019 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import * as React from 'react';
import { FetchMock } from 'jest-fetch-mock';
import { shallow } from 'enzyme';
import Sidebar from '../Sidebar';
import { MarkdownRemark } from '../../@types/graphql-types';

jest.mock('../navTreeUtils', () => {
return {
...require.requireActual('../navTreeUtils'),
getNavTree: jest.fn().mockReturnValue([
'/foo/',
{
title: 'Foo subs',
children: [
'/foo/bar/',
'/foo/baz/',
{
title: 'Foo Baz subs',
children: [
'/foo/baz/bar/',
'/foo/baz/foo/',
{
title: 'Foo Baz Foo subs',
children: ['/foo/baz/foo/bar/', '/foo/baz/foo/baz']
}
]
}
]
},
'/bar/',
{
title: 'Bar subs',
children: [{ title: 'External link 1', url: 'http://example.com/1' }, '/bar/foo/']
},
{ title: 'External link 2', url: 'http://example.com/2' }
])
};
});

beforeEach(() => {
(fetch as FetchMock).resetMocks();
(fetch as FetchMock).mockResponse(`[
{ "value": "2.0", "current": true },
{ "value": "1.0", "current": false }
]`);
});

it('should render correctly', () => {
const wrapper = shallowRender();
expect(wrapper).toMatchSnapshot();
});

function shallowRender(props: Partial<Sidebar['props']> = {}) {
return shallow(
<Sidebar
location={{ pathname: '/foo/baz/foo/bar' } as Location}
pages={[
{
fields: {
slug: '/foo/'
},
frontmatter: {
title: 'Foo'
}
} as MarkdownRemark,
{
fields: {
slug: '/foo/baz/bar'
},
frontmatter: {
title: 'Foo Baz Bar'
}
} as MarkdownRemark,
{
fields: {
slug: '/bar/'
},
frontmatter: {
title: 'Bar'
}
} as MarkdownRemark
]}
version="2.0"
{...props}
/>
);
}

+ 248
- 0
server/sonar-docs/src/components/__tests__/__snapshots__/Sidebar-test.tsx.snap View File

@@ -0,0 +1,248 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP

exports[`should render correctly 1`] = `
<div
className="page-sidebar"
>
<div
className="sidebar-header"
>
<ForwardRef
to="/"
>
<img
alt="Continuous Code Quality"
className="sidebar-logo"
src="/2.0/images/SonarQubeIcon.svg"
title="Continuous Code Quality"
width="160"
/>
</ForwardRef>
<VersionSelect
isOnCurrentVersion={true}
selectedVersionValue="2.0"
versions={Array []}
/>
</div>
<div
className="page-indexes"
>
<Search
navigation={
Array [
"/foo/",
Object {
"children": Array [
"/foo/bar/",
"/foo/baz/",
Object {
"children": Array [
"/foo/baz/bar/",
"/foo/baz/foo/",
Object {
"children": Array [
"/foo/baz/foo/bar/",
"/foo/baz/foo/baz",
],
"title": "Foo Baz Foo subs",
},
],
"title": "Foo Baz subs",
},
],
"title": "Foo subs",
},
"/bar/",
Object {
"children": Array [
Object {
"title": "External link 1",
"url": "http://example.com/1",
},
"/bar/foo/",
],
"title": "Bar subs",
},
Object {
"title": "External link 2",
"url": "http://example.com/2",
},
]
}
onResultsChange={[Function]}
pages={
Array [
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
},
Object {
"fields": Object {
"slug": "/foo/baz/bar",
},
"frontmatter": Object {
"title": "Foo Baz Bar",
},
},
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
},
]
}
/>
<nav>
<PageLink
className="page-indexes-link"
key="/foo/"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/foo/",
},
"frontmatter": Object {
"title": "Foo",
},
}
}
/>
<CategoryLink
key="Foo subs"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo subs"
>
<CategoryLink
key="Foo Baz subs"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz subs"
>
<Component />
<CategoryLink
key="Foo Baz Foo subs"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
openByDefault={true}
title="Foo Baz Foo subs"
/>
</CategoryLink>
</CategoryLink>
<PageLink
className="page-indexes-link"
key="/bar/"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
node={
Object {
"fields": Object {
"slug": "/bar/",
},
"frontmatter": Object {
"title": "Bar",
},
}
}
/>
<CategoryLink
key="Bar subs"
location={
Object {
"pathname": "/foo/baz/foo/bar",
}
}
openByDefault={false}
title="Bar subs"
>
<ExternalLink
external="http://example.com/1"
key="External link 1"
title="External link 1"
/>
</CategoryLink>
<ExternalLink
external="http://example.com/2"
key="External link 2"
title="External link 2"
/>
</nav>
</div>
<div
className="sidebar-footer"
>
<a
href="https://www.sonarqube.org/"
rel="noopener noreferrer"
target="_blank"
>
<DownloadIcon />
SonarQube
</a>
<a
href="https://community.sonarsource.com/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Community"
src="/2.0/images/community.svg"
/>
Community
</a>
<a
className="icon-only"
href="https://twitter.com/SonarQube"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Twitter"
src="/2.0/images/twitter.svg"
/>
</a>
<a
className="icon-only"
href="https://www.sonarsource.com/resources/product-news/"
rel="noopener noreferrer"
target="_blank"
>
<img
alt="Product News"
src="/2.0/images/newspaper.svg"
/>
<span
className="tooltip"
>
Product News
</span>
</a>
</div>
</div>
`;

+ 87
- 0
server/sonar-docs/src/components/__tests__/navTreeUtils-test.ts View File

@@ -0,0 +1,87 @@
/*
* SonarQube
* Copyright (C) 2009-2019 SonarSource SA
* mailto:info AT sonarsource DOT com
*
* This program is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 3 of the License, or (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Lesser General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public License
* along with this program; if not, write to the Free Software Foundation,
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
*/
import { getUrlsList, getOpenChainFromPath, testPathAgainstUrl } from '../navTreeUtils';

const navTree = [
'path/value',
{
title: 'My paths',
children: [
'child/path/1',
{
title: 'Child paths',
children: [
'sub/child/path/1',
{
title: 'External link 2',
url: 'http://example.com/2'
},
{
title: 'Last ones, promised',
children: ['sub/sub/child/path/1']
},
'sub/child/path/3'
]
},
'child/path/2'
]
},
{
title: 'External link',
url: 'http://example.com'
}
];

describe('getUrlsList', () => {
it('should return the correct values for a list of paths', () => {
expect(getUrlsList(navTree)).toEqual([
'path/value',
'child/path/1',
'sub/child/path/1',
'http://example.com/2',
'sub/sub/child/path/1',
'sub/child/path/3',
'child/path/2',
'http://example.com'
]);
});
});

describe('getOpenChainFromPath', () => {
it('should correctly fetch the chain of open elements for a given path', () => {
expect(getOpenChainFromPath('path/value/', navTree)).toEqual([navTree[0]]);
expect(getOpenChainFromPath('sub/child/path/3', navTree)).toEqual([
navTree[1],
(navTree as any)[1].children[1],
(navTree as any)[1].children[1].children[3]
]);
});
});

describe('testPathAgainstUrl', () => {
it('should handle paths with trailing and/or leading slashes', () => {
expect(testPathAgainstUrl('path/foo/', 'path/bar')).toBe(false);
expect(testPathAgainstUrl('/path/foo/', '/path/bar/')).toBe(false);
expect(testPathAgainstUrl('path/foo/', 'path/foo')).toBe(true);
expect(testPathAgainstUrl('path/foo', 'path/foo/')).toBe(true);
expect(testPathAgainstUrl('/path/foo/', 'path/foo')).toBe(true);
expect(testPathAgainstUrl('path/foo', '/path/foo/')).toBe(true);
});
});

+ 52
- 12
server/sonar-docs/src/components/navTreeUtils.ts View File

@@ -29,26 +29,66 @@ export function getNavTree() {
return NavigationTree as DocNavigationItem[];
}

export function getUrlsList(navTree: DocNavigationItem[]) {
export function getUrlsList(navTree: DocNavigationItem[]): string[] {
return flatten(
navTree.map(leave => {
if (isDocsNavigationBlock(leave)) {
return leave.children;
navTree.map(leaf => {
if (isDocsNavigationBlock(leaf)) {
return getUrlsList(leaf.children);
}
if (isDocsNavigationExternalLink(leave)) {
return leave.url;
if (isDocsNavigationExternalLink(leaf)) {
return [leaf.url];
}
return [leave];
return [leaf];
})
);
}

export function isDocsNavigationBlock(leave?: DocNavigationItem): leave is DocsNavigationBlock {
return typeof leave === 'object' && (leave as DocsNavigationBlock).children !== undefined;
export function getOpenChainFromPath(pathname: string, navTree: DocNavigationItem[]) {
let chain: DocNavigationItem[] = [];

let found = false;
const walk = (leaf: DocNavigationItem, parents: DocNavigationItem[] = []) => {
if (found) {
return;
}

parents = parents.concat(leaf);

if (isDocsNavigationBlock(leaf)) {
leaf.children.forEach(child => {
if (typeof child === 'string' && testPathAgainstUrl(child, pathname)) {
chain = parents.concat(child);
found = true;
} else {
walk(child, parents);
}
});
} else if (typeof leaf === 'string' && testPathAgainstUrl(leaf, pathname)) {
chain = parents;
found = true;
}
};

navTree.forEach(leaf => walk(leaf));

return chain;
}

export function isDocsNavigationBlock(leaf?: DocNavigationItem): leaf is DocsNavigationBlock {
return typeof leaf === 'object' && (leaf as DocsNavigationBlock).children !== undefined;
}

export function isDocsNavigationExternalLink(
leave?: DocNavigationItem
): leave is DocsNavigationExternalLink {
return typeof leave === 'object' && (leave as DocsNavigationExternalLink).url !== undefined;
leaf?: DocNavigationItem
): leaf is DocsNavigationExternalLink {
return typeof leaf === 'object' && (leaf as DocsNavigationExternalLink).url !== undefined;
}

export function testPathAgainstUrl(path: string, url: string) {
const leadingRegEx = /^\//;
const trailingRegEx = /\/$/;
return (
path.replace(leadingRegEx, '').replace(trailingRegEx, '') ===
url.replace(leadingRegEx, '').replace(trailingRegEx, '')
);
}

+ 5
- 0
server/sonar-docs/src/layouts/layout.css View File

@@ -283,6 +283,11 @@ a.search-result .note {
display: block;
}

.sub-menu .page-indexes-link,
.sub-menu .sub-menu {
margin-left: -10px;
}

.page-indexes-link svg {
float: right;
transform: translateY(9px);

+ 1
- 2
server/sonar-docs/static/SonarQubeNavigationTree.json View File

@@ -40,7 +40,7 @@
"/user-guide/security-hotspots/",
"/user-guide/rules/",
"/user-guide/security-rules/",
"/user-guide/built-in-rule-tags/",
"/user-guide/built-in-rule-tags/",
"/user-guide/quality-gates/",
"/user-guide/metric-definitions/",
"/user-guide/concepts/",
@@ -100,5 +100,4 @@
]
},
"/faq/"

]

+ 21
- 0
server/sonar-docs/yarn.lock View File

@@ -3007,6 +3007,14 @@ cross-fetch@2.2.2:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"

cross-fetch@^2.2.2:
version "2.2.3"
resolved "https://registry.yarnpkg.com/cross-fetch/-/cross-fetch-2.2.3.tgz#e8a0b3c54598136e037f8650f8e823ccdfac198e"
integrity sha512-PrWWNH3yL2NYIb/7WF/5vFG3DCQiXDOVf8k3ijatbrtnwNuhMWLC7YF7uqf53tbTFDzHIUD8oITw4Bxt8ST3Nw==
dependencies:
node-fetch "2.1.2"
whatwg-fetch "2.0.4"

cross-spawn@5.1.0, cross-spawn@^5.0.1:
version "5.1.0"
resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-5.1.0.tgz#e8bd0efee58fcff6f8f94510a0a554bbfa235449"
@@ -6584,6 +6592,14 @@ jest-environment-node@^24.5.0:
jest-mock "^24.5.0"
jest-util "^24.5.0"

jest-fetch-mock@2.1.2:
version "2.1.2"
resolved "https://registry.yarnpkg.com/jest-fetch-mock/-/jest-fetch-mock-2.1.2.tgz#1260b347918e3931c4ec743ceaf60433da661bd0"
integrity sha512-tcSR4Lh2bWLe1+0w/IwvNxeDocMI/6yIA2bijZ0fyWxC4kQ18lckQ1n7Yd40NKuisGmcGBRFPandRXrW/ti/Bw==
dependencies:
cross-fetch "^2.2.2"
promise-polyfill "^7.1.1"

jest-get-type@^24.3.0:
version "24.3.0"
resolved "https://registry.yarnpkg.com/jest-get-type/-/jest-get-type-24.3.0.tgz#582cfd1a4f91b5cdad1d43d2932f816d543c65da"
@@ -9118,6 +9134,11 @@ promise-inflight@^1.0.1:
resolved "https://registry.yarnpkg.com/promise-inflight/-/promise-inflight-1.0.1.tgz#98472870bf228132fcbdd868129bad12c3c029e3"
integrity sha1-mEcocL8igTL8vdhoEputEsPAKeM=

promise-polyfill@^7.1.1:
version "7.1.2"
resolved "https://registry.yarnpkg.com/promise-polyfill/-/promise-polyfill-7.1.2.tgz#ab05301d8c28536301622d69227632269a70ca3b"
integrity sha512-FuEc12/eKqqoRYIGBrUptCBRhobL19PS2U31vMNTfyck1FxPyMfgsXyW4Mav85y/ZN1hop3hOwRlUDok23oYfQ==

promise@^7.1.1:
version "7.3.1"
resolved "https://registry.yarnpkg.com/promise/-/promise-7.3.1.tgz#064b72602b18f90f29192b8b1bc418ffd1ebd3bf"

Loading…
Cancel
Save