Migrate remaining react-test-renderer tests to @testing-library/react (#28141)

Co-authored-by: gitstart-sourcegraph <gitstart@users.noreply.github.com>
This commit is contained in:
GitStart-SourceGraph 2021-11-29 15:13:16 +01:00 committed by GitHub
parent f5cd52bc90
commit 546c53a4c2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
17 changed files with 1380 additions and 1286 deletions

View File

@ -1,9 +1,9 @@
import { nextTick } from 'process'
import { promisify } from 'util'
import { RenderResult } from '@testing-library/react'
import { Remote } from 'comlink'
import { uniqueId, noop, isEmpty, pick } from 'lodash'
import renderer from 'react-test-renderer'
import { BehaviorSubject, NEVER, of, Subject, Subscription } from 'rxjs'
import { filter, take, first } from 'rxjs/operators'
import { TestScheduler } from 'rxjs/testing'
@ -52,7 +52,7 @@ const notificationClassNames = {
[NotificationType.Error]: 'error',
}
const elementRenderedAtMount = (mount: Element): renderer.ReactTestRendererJSON | undefined => {
const elementRenderedAtMount = (mount: Element): RenderResult | undefined => {
const call = RENDER.args.find(call => call[1] === mount)
return call?.[0]
}

View File

@ -1,6 +1,7 @@
import { render, screen, waitFor } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import * as H from 'history'
import React from 'react'
import renderer from 'react-test-renderer'
import { NEVER } from 'rxjs'
import { createBarrier } from '../api/integration-test/testHelpers'
@ -16,7 +17,7 @@ describe('ActionItem', () => {
const history = H.createMemoryHistory()
test('non-actionItem variant', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -26,11 +27,11 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('actionItem variant', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -41,11 +42,11 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('noop command', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -55,11 +56,11 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('pressed toggle actionItem', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'a', command: 'c', actionItem: { pressed: true, label: 'b' } }}
@ -70,11 +71,11 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('non-pressed actionItem', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'a', command: 'c', actionItem: { pressed: false, label: 'b' } }}
@ -85,11 +86,11 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('title element', () => {
const component = renderer.create(
const component = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -101,13 +102,13 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(component.asFragment()).toMatchSnapshot()
})
test('run command', async () => {
const { wait, done } = createBarrier()
const component = renderer.create(
const { container, asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -121,23 +122,20 @@ describe('ActionItem', () => {
)
// Run command and wait for execution to finish.
let tree = component.toJSON()
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
tree = component.toJSON()
expect(tree).toMatchSnapshot()
userEvent.click(container)
expect(asFragment()).toMatchSnapshot()
// Finish execution. (Use setTimeout to wait for the executeCommand resolution to result in the setState
// call.)
done()
await new Promise<void>(resolve => setTimeout(resolve))
tree = component.toJSON()
expect(tree).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
test('run command with showLoadingSpinnerDuringExecution', async () => {
const { wait, done } = createBarrier()
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -151,21 +149,23 @@ describe('ActionItem', () => {
)
// Run command and wait for execution to finish.
let tree = component.toJSON()
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
tree = component.toJSON()
expect(tree).toMatchSnapshot()
userEvent.click(screen.getByRole('button'))
await waitFor(() => {
expect(screen.getByTestId('action-item-spinner')).toBeInTheDocument()
})
expect(asFragment()).toMatchSnapshot()
// Finish execution. (Use setTimeout to wait for the executeCommand resolution to result in the setState
// call.)
done()
await new Promise<void>(resolve => setTimeout(resolve))
tree = component.toJSON()
expect(tree).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
test('run command with error', async () => {
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -183,15 +183,15 @@ describe('ActionItem', () => {
// Run command (which will reject with an error). (Use setTimeout to wait for the executeCommand resolution
// to result in the setState call.)
let tree = component.toJSON()
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
userEvent.click(screen.getByRole('button'))
await new Promise<void>(resolve => setTimeout(resolve))
tree = component.toJSON()
expect(tree).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
test('run command with error with showInlineError', async () => {
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'c', title: 't', description: 'd', iconURL: 'u', category: 'g' }}
@ -209,18 +209,18 @@ describe('ActionItem', () => {
// Run command (which will reject with an error). (Use setTimeout to wait for the executeCommand resolution
// to result in the setState call.)
let tree = component.toJSON()
tree!.props.onClick({ preventDefault: () => undefined, currentTarget: { blur: () => undefined } })
userEvent.click(screen.getByRole('button'))
await new Promise<void>(resolve => setTimeout(resolve))
tree = component.toJSON()
expect(tree).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
describe('"open" command', () => {
it('renders as link', () => {
jsdom.reconfigure({ url: 'https://example.com/foo' })
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'open', commandArguments: ['https://example.com/bar'], title: 't' }}
@ -230,13 +230,13 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
it('renders as link with icon and opens a new tab for a different origin', () => {
jsdom.reconfigure({ url: 'https://example.com/foo' })
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c', command: 'open', commandArguments: ['https://other.com/foo'], title: 't' }}
@ -246,13 +246,13 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
it('renders as link that opens in a new tab, but without icon for a different origin as the alt action and a primary action defined', () => {
jsdom.reconfigure({ url: 'https://example.com/foo' })
const component = renderer.create(
const { asFragment } = render(
<ActionItem
active={true}
action={{ id: 'c1', command: 'whatever', title: 'primary' }}
@ -263,7 +263,7 @@ describe('ActionItem', () => {
platformContext={NOOP_PLATFORM_CONTEXT}
/>
)
expect(component.toJSON()).toMatchSnapshot()
expect(asFragment()).toMatchSnapshot()
})
})
})

View File

@ -277,7 +277,7 @@ export class ActionItem extends React.PureComponent<ActionItemProps, State> {
<OpenInNewIcon className={this.props.iconClassName} />
)}
{showLoadingSpinner && (
<div className={styles.loader}>
<div className={styles.loader} data-testid="action-item-spinner">
<LoadingSpinner className={this.props.iconClassName} />
</div>
)}

View File

@ -1,307 +1,262 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`ActionItem "open" command renders as link 1`] = `
<a
className="action-item test-action-item"
href="https://example.com/bar"
onClick={[Function]}
onKeyPress={[Function]}
tabIndex={0}
>
t
</a>
<DocumentFragment>
<a
class="action-item test-action-item"
href="https://example.com/bar"
tabindex="0"
>
t
</a>
</DocumentFragment>
`;
exports[`ActionItem "open" command renders as link that opens in a new tab, but without icon for a different origin as the alt action and a primary action defined 1`] = `
<a
className="action-item test-action-item"
href="https://other.com/foo"
onClick={[Function]}
onKeyPress={[Function]}
rel="noopener noreferrer"
tabIndex={0}
target="_blank"
>
primary
</a>
<DocumentFragment>
<a
class="action-item test-action-item"
href="https://other.com/foo"
rel="noopener noreferrer"
tabindex="0"
target="_blank"
>
primary
</a>
</DocumentFragment>
`;
exports[`ActionItem "open" command renders as link with icon and opens a new tab for a different origin 1`] = `
<a
className="action-item test-action-item"
href="https://other.com/foo"
onClick={[Function]}
onKeyPress={[Function]}
rel="noopener noreferrer"
tabIndex={0}
target="_blank"
>
t
<OpenInNewIcon />
</a>
<DocumentFragment>
<a
class="action-item test-action-item"
href="https://other.com/foo"
rel="noopener noreferrer"
tabindex="0"
target="_blank"
>
t
<openinnewicon />
</a>
</DocumentFragment>
`;
exports[`ActionItem actionItem variant 1`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem non-actionItem variant 1`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem non-pressed actionItem 1`] = `
<a
aria-pressed={false}
className="action-item test-action-item"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
b
</a>
<DocumentFragment>
<a
aria-pressed="false"
class="action-item test-action-item"
href=""
role="button"
tabindex="0"
>
b
</a>
</DocumentFragment>
`;
exports[`ActionItem noop command 1`] = `
<span
data-tooltip="d"
>
<img
alt="d"
src="u"
/>
g:
t
</span>
<DocumentFragment>
<span
data-tooltip="d"
>
<img
alt="d"
src="u"
/>
g: t
</span>
</DocumentFragment>
`;
exports[`ActionItem pressed toggle actionItem 1`] = `
<a
aria-pressed={true}
className="action-item test-action-item"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
b
</a>
<DocumentFragment>
<a
aria-pressed="true"
class="action-item test-action-item"
href=""
role="button"
tabindex="0"
>
b
</a>
</DocumentFragment>
`;
exports[`ActionItem run command 1`] = `
<a
aria-label="d"
className="action-item test-action-item disabled"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={-1}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem run command 2`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem run command with error 1`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem run command with error with showInlineError 1`] = `
<a
aria-label="Error: x"
className="action-item test-action-item"
data-tooltip="Error: x"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="Error: x"
class="action-item test-action-item"
data-tooltip="Error: x"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem run command with showLoadingSpinnerDuringExecution 1`] = `
<a
aria-label="d"
className="action-item test-action-item actionItemLoading disabled"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={-1}
>
<img
alt="d"
src="u"
/>
g:
t
<div
className="loader"
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item actionItemLoading disabled"
data-tooltip="d"
href=""
role="button"
tabindex="-1"
>
<div
className="loading-spinner "
<img
alt="d"
src="u"
/>
</div>
</a>
g: t
<div
class="loader"
data-testid="action-item-spinner"
>
<div
class="loading-spinner "
/>
</div>
</a>
</DocumentFragment>
`;
exports[`ActionItem run command with showLoadingSpinnerDuringExecution 2`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<img
alt="d"
src="u"
/>
g:
t
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<img
alt="d"
src="u"
/>
g: t
</a>
</DocumentFragment>
`;
exports[`ActionItem title element 1`] = `
<a
aria-label="d"
className="action-item test-action-item"
data-tooltip="d"
href=""
onAuxClick={[Function]}
onClick={[Function]}
onKeyPress={[Function]}
role="button"
tabIndex={0}
>
<span>
t2
</span>
</a>
<DocumentFragment>
<a
aria-label="d"
class="action-item test-action-item"
data-tooltip="d"
href=""
role="button"
tabindex="0"
>
<span>
t2
</span>
</a>
</DocumentFragment>
`;

View File

@ -1,5 +1,6 @@
import { render, screen } from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import React from 'react'
import renderer, { act } from 'react-test-renderer'
import { UserProductSubscriptionStatus } from './UserProductSubscriptionStatus'
@ -9,7 +10,7 @@ jest.mock('../../../components/CopyableText', () => ({ CopyableText: 'CopyableTe
describe('UserProductSubscriptionStatus', () => {
test('toggle', () => {
const component = renderer.create(
const { asFragment } = render(
<UserProductSubscriptionStatus
subscriptionName="L-123"
productNameWithBrand="P"
@ -18,10 +19,11 @@ describe('UserProductSubscriptionStatus', () => {
licenseKey="lk"
/>
)
expect(component.toJSON()).toMatchSnapshot('license key hidden')
expect(asFragment()).toMatchSnapshot('license key hidden')
// Click "Reveal license key" button.
act(() => component.root.findByType('button').props.onClick())
expect(component.toJSON()).toMatchSnapshot('license key revealed')
const button = screen.getByRole('button')
userEvent.click(button)
expect(asFragment()).toMatchSnapshot('license key revealed')
})
})

View File

@ -1,167 +1,159 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`UserProductSubscriptionStatus toggle: license key hidden 1`] = `
<div
className="card test-product-certificate"
>
<DocumentFragment>
<div
className="card-body d-flex align-items-center"
>
<img
alt="Sourcegraph logo"
className="logo mr-1 p-2"
src="/.assets/img/sourcegraph-mark.svg?v2"
/>
<div>
<h2
className="font-weight-normal mb-1"
>
P
</h2>
<h3
className="text-muted font-weight-normal"
>
123-user
license,
<span>
expired on
<span
title="Jan 1, 1970, 12:00:23 AM"
>
1970-01-01
</span>
(36 years ago)
</span>
</h3>
</div>
</div>
<div
className="bg"
/>
<div
className="footer"
class="card test-product-certificate"
>
<div
className="card-footer d-flex align-items-center justify-content-between flex-wrap"
class="card-body d-flex align-items-center"
>
<button
className="btn btn-primary mr-4 my-1"
onClick={[Function]}
type="button"
<img
alt="Sourcegraph logo"
class="logo mr-1 p-2"
src="/.assets/img/sourcegraph-mark.svg?v2"
/>
<div>
<h2
class="font-weight-normal mb-1"
>
P
</h2>
<h3
class="text-muted font-weight-normal"
>
123-user license,
<span>
expired on
<span
title="Jan 1, 1970, 12:00:23 AM"
>
1970-01-01
</span>
(36 years ago)
</span>
</h3>
</div>
</div>
<div
class="bg"
/>
<div
class="footer"
>
<div
class="card-footer d-flex align-items-center justify-content-between flex-wrap"
>
<KeyIcon
className="icon-inline"
<button
class="btn btn-primary mr-4 my-1"
type="button"
>
<keyicon
class="icon-inline"
/>
Reveal license key
</button>
<div
class="flex-fill"
/>
Reveal
license key
</button>
<div
className="flex-fill"
/>
<div
className="my-1"
/>
<div
class="my-1"
/>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`UserProductSubscriptionStatus toggle: license key revealed 1`] = `
<div
className="card test-product-certificate"
>
<DocumentFragment>
<div
className="card-body d-flex align-items-center"
class="card test-product-certificate"
>
<img
alt="Sourcegraph logo"
className="logo mr-1 p-2"
src="/.assets/img/sourcegraph-mark.svg?v2"
/>
<div>
<h2
className="font-weight-normal mb-1"
>
P
</h2>
<h3
className="text-muted font-weight-normal"
>
123-user
license,
<span>
expired on
<span
title="Jan 1, 1970, 12:00:23 AM"
>
1970-01-01
<div
class="card-body d-flex align-items-center"
>
<img
alt="Sourcegraph logo"
class="logo mr-1 p-2"
src="/.assets/img/sourcegraph-mark.svg?v2"
/>
<div>
<h2
class="font-weight-normal mb-1"
>
P
</h2>
<h3
class="text-muted font-weight-normal"
>
123-user license,
<span>
expired on
<span
title="Jan 1, 1970, 12:00:23 AM"
>
1970-01-01
</span>
(36 years ago)
</span>
(36 years ago)
</span>
</h3>
</div>
</div>
<div
className="bg"
/>
<div
className="footer"
>
<div
className="card-footer d-flex align-items-center justify-content-between flex-wrap"
>
<button
className="btn btn-primary mr-4 my-1"
onClick={[Function]}
type="button"
>
<KeyIcon
className="icon-inline"
/>
Hide
license key
</button>
<div
className="flex-fill"
/>
<div
className="my-1"
/>
</h3>
</div>
</div>
<div
className="card-footer"
class="bg"
/>
<div
class="footer"
>
<h3>
License key
</h3>
<CopyableText
className="d-block"
text="lk"
/>
<small
className="mt-2 d-flex align-items-center"
<div
class="card-footer d-flex align-items-center justify-content-between flex-wrap"
>
<InformationIcon
className="icon-inline mr-1"
<button
class="btn btn-primary mr-4 my-1"
type="button"
>
<keyicon
class="icon-inline"
/>
Hide license key
</button>
<div
class="flex-fill"
/>
<span>
Use this license key as the
<div
class="my-1"
/>
</div>
<div
class="card-footer"
>
<h3>
License key
</h3>
<copyabletext
class="d-block"
text="lk"
/>
<small
class="mt-2 d-flex align-items-center"
>
<informationicon
class="icon-inline mr-1"
/>
<code>
<strong>
licenseKey
</strong>
</code>
property value in Sourcegraph site configuration.
</span>
</small>
<span>
Use this license key as the
<code>
<strong>
licenseKey
</strong>
</code>
property value in Sourcegraph site configuration.
</span>
</small>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@ -1,7 +1,7 @@
import { render } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import React from 'react'
import { Router } from 'react-router'
import renderer from 'react-test-renderer'
import { NOOP_TELEMETRY_SERVICE } from '@sourcegraph/shared/src/telemetry/telemetryService'
@ -18,31 +18,29 @@ describe('RegistryExtensionOverviewPage', () => {
test('renders', () => {
const history = createMemoryHistory()
expect(
renderer
.create(
<Router history={history}>
<RegistryExtensionOverviewPage
telemetryService={NOOP_TELEMETRY_SERVICE}
extension={{
id: 'x',
rawManifest: '{}',
manifest: {
url: 'https://example.com',
activationEvents: ['*'],
categories: ['Programming languages', 'Other'],
tags: ['T1', 'T2'],
readme: '**A**',
repository: {
url: 'https://github.com/foo/bar',
type: 'git',
},
render(
<Router history={history}>
<RegistryExtensionOverviewPage
telemetryService={NOOP_TELEMETRY_SERVICE}
extension={{
id: 'x',
rawManifest: '{}',
manifest: {
url: 'https://example.com',
activationEvents: ['*'],
categories: ['Programming languages', 'Other'],
tags: ['T1', 'T2'],
readme: '**A**',
repository: {
url: 'https://github.com/foo/bar',
type: 'git',
},
}}
isLightTheme={true}
/>
</Router>
)
.toJSON()
},
}}
isLightTheme={true}
/>
</Router>
).asFragment()
).toMatchSnapshot()
})
@ -50,38 +48,36 @@ describe('RegistryExtensionOverviewPage', () => {
test('renders with no tags', () => {
const history = createMemoryHistory()
expect(
renderer
.create(
<Router history={history}>
<RegistryExtensionOverviewPage
telemetryService={NOOP_TELEMETRY_SERVICE}
extension={{
id: 'x',
rawManifest: '{}',
manifest: {
url: 'https://example.com',
activationEvents: ['*'],
categories: ['Programming languages', 'Other'],
tags: [],
readme: '**A**',
repository: {
url: 'https://github.com/foo/bar',
type: 'git',
},
render(
<Router history={history}>
<RegistryExtensionOverviewPage
telemetryService={NOOP_TELEMETRY_SERVICE}
extension={{
id: 'x',
rawManifest: '{}',
manifest: {
url: 'https://example.com',
activationEvents: ['*'],
categories: ['Programming languages', 'Other'],
tags: [],
readme: '**A**',
repository: {
url: 'https://github.com/foo/bar',
type: 'git',
},
}}
isLightTheme={true}
/>
</Router>
)
.toJSON()
},
}}
isLightTheme={true}
/>
</Router>
).asFragment()
).toMatchSnapshot()
})
describe('categories', () => {
test('filters out unrecognized categories', () => {
const history = createMemoryHistory()
const output = renderer.create(
const output = render(
<Router history={history}>
<RegistryExtensionOverviewPage
telemetryService={NOOP_TELEMETRY_SERVICE}
@ -97,25 +93,24 @@ describe('RegistryExtensionOverviewPage', () => {
isLightTheme={true}
/>
</Router>
).root
expect(
toText(
output.findAll(({ props: { className } }) =>
className?.includes('test-registry-extension-categories')
)
)
).toEqual(['Programming languages', 'Other' /* no 'invalid' */])
)
expect(toText(output.getAllByTestId('test-registry-extension-categories'))).toEqual([
'Programming languages',
'Other' /* no 'invalid' */,
])
})
})
})
function toText(values: (string | renderer.ReactTestInstance)[]): string[] {
function toText(values: string[] | HTMLElement[] | NodeListOf<ChildNode>): string[] {
const textNodes: string[] = []
for (const value of values) {
if (typeof value === 'string') {
textNodes.push(value)
} else {
textNodes.push(...toText(value.children))
} else if (value.hasChildNodes()) {
textNodes.push(...toText(value.childNodes))
} else if (value.textContent !== null) {
textNodes.push(value.textContent)
}
}
return textNodes

View File

@ -179,7 +179,7 @@ export const RegistryExtensionOverviewPage: React.FunctionComponent<Props> = ({
{categories && (
<div className={classNames('pb-0', styles.sidebarSection)}>
<h3>Categories</h3>
<ul className="list-inline test-registry-extension-categories">
<ul className="list-inline" data-testid="test-registry-extension-categories">
{categories.map(category => (
<li key={category} className="list-inline-item mb-2">
<Link

View File

@ -1,284 +1,286 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`RegistryExtensionOverviewPage renders 1`] = `
<div
className="d-flex flex-wrap"
>
<DocumentFragment>
<div
className="mr-3 readme"
class="d-flex flex-wrap"
>
<div
className="markdown"
dangerouslySetInnerHTML={
Object {
"__html": "<p><strong>A</strong></p>
",
}
}
onClick={[Function]}
/>
</div>
<aside
className="sidebar"
>
<svg
className="mb-3 icon"
fill="none"
height="48"
width="48"
xmlns="http://www.w3.org/2000/svg"
class="mr-3 readme"
>
<path
d="M41.407 23.21c-1.64.468-3.681 3.22-3.757 2.091-.033-.56-.175-1.857-.184-3.18-.025-3.773.193-9.429.193-9.429 0-.192-.017-.376-.059-.552a2.512 2.512 0 00-.72-1.355c-.025-.025-.05-.042-.075-.067a4.077 4.077 0 00-.292-.243c-.11-.075-.235-.142-.352-.209-.05-.025-.092-.05-.142-.075-.025-.009-.05-.017-.067-.025a4.16 4.16 0 00-1.623-.343l-1.548.033s-1.966.067-4.225.117H22.89c-.468-.025-.778-.058-.895-.117-1.255-.586 1.481-2.644 1.891-3.832C24.958 2.894 22.147 0 18.834 0c-3.322 0-6.133 2.895-5.046 6.04.419 1.189 3.288 3.28 1.891 3.833-.184.075-1.23.109-2.593.109h-1.289c-3.104-.009-6.92-.1-6.92-.1L3.33 9.847c-1.389 0-3.004.652-3.246 2.217-.05.209-.084.636-.084.636l.025 11.646c.017.193.034.335.059.385.552 1.397 3.597-2.25 4.785-2.669 3.138-1.096 5.037 3.623 5.037 6.953 0 3.338-2.886 6.15-6.024 5.062-1.188-.419-3.238-3.155-3.823-1.9a.773.773 0 00-.059.243v12.91A2.703 2.703 0 002.694 48h12.5c.167-.017.293-.033.343-.05 1.397-.56-.519-1.866-.895-3.196-.912-3.205.862-3.765 4.192-3.765s4.902.836 4.902 3.12c0 1.172-3.145 3.247-1.89 3.832.041.017.117.034.217.05H34.94a2.703 2.703 0 002.702-2.71s-.268-12.057-.05-13.571c.159-1.096 2.702 1.18 3.815 1.648 4.142 1.724 6.074-1.732 6.074-5.087 0-3.355-2.853-5.982-6.074-5.062z"
fill="#95A5C6"
fillOpacity=".32"
/>
</svg>
<div
className="pt-2 pb-3"
>
<h3>
Publisher
</h3>
<small>
x
</small>
<div
class="markdown"
>
<p>
<strong>
A
</strong>
</p>
</div>
</div>
<div
className="sidebarSection"
<aside
class="sidebar"
>
<h3>
Resources
</h3>
<small>
<a
className="d-block mb-1"
href="https://example.com"
rel="nofollow noopener noreferrer"
target="_blank"
>
Source code (JavaScript)
</a>
<div
className="d-flex"
>
<GithubIcon
className="icon-inline mr-1"
/>
<svg
class="mb-3 icon"
fill="none"
height="48"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M41.407 23.21c-1.64.468-3.681 3.22-3.757 2.091-.033-.56-.175-1.857-.184-3.18-.025-3.773.193-9.429.193-9.429 0-.192-.017-.376-.059-.552a2.512 2.512 0 00-.72-1.355c-.025-.025-.05-.042-.075-.067a4.077 4.077 0 00-.292-.243c-.11-.075-.235-.142-.352-.209-.05-.025-.092-.05-.142-.075-.025-.009-.05-.017-.067-.025a4.16 4.16 0 00-1.623-.343l-1.548.033s-1.966.067-4.225.117H22.89c-.468-.025-.778-.058-.895-.117-1.255-.586 1.481-2.644 1.891-3.832C24.958 2.894 22.147 0 18.834 0c-3.322 0-6.133 2.895-5.046 6.04.419 1.189 3.288 3.28 1.891 3.833-.184.075-1.23.109-2.593.109h-1.289c-3.104-.009-6.92-.1-6.92-.1L3.33 9.847c-1.389 0-3.004.652-3.246 2.217-.05.209-.084.636-.084.636l.025 11.646c.017.193.034.335.059.385.552 1.397 3.597-2.25 4.785-2.669 3.138-1.096 5.037 3.623 5.037 6.953 0 3.338-2.886 6.15-6.024 5.062-1.188-.419-3.238-3.155-3.823-1.9a.773.773 0 00-.059.243v12.91A2.703 2.703 0 002.694 48h12.5c.167-.017.293-.033.343-.05 1.397-.56-.519-1.866-.895-3.196-.912-3.205.862-3.765 4.192-3.765s4.902.836 4.902 3.12c0 1.172-3.145 3.247-1.89 3.832.041.017.117.034.217.05H34.94a2.703 2.703 0 002.702-2.71s-.268-12.057-.05-13.571c.159-1.096 2.702 1.18 3.815 1.648 4.142 1.724 6.074-1.732 6.074-5.087 0-3.355-2.853-5.982-6.074-5.062z"
fill="#95A5C6"
fill-opacity=".32"
/>
</svg>
<div
class="pt-2 pb-3"
>
<h3>
Publisher
</h3>
<small>
x
</small>
</div>
<div
class="sidebarSection"
>
<h3>
Resources
</h3>
<small>
<a
className="d-block"
href="https://github.com/foo/bar"
rel="nofollow noreferrer noopener"
class="d-block mb-1"
href="https://example.com"
rel="nofollow noopener noreferrer"
target="_blank"
>
Repository
Source code (JavaScript)
</a>
</div>
</small>
</div>
<div
className="sidebarSection"
>
<h3>
Extension ID
</h3>
<small
className="text-muted"
<div
class="d-flex"
>
<githubicon
class="icon-inline mr-1"
/>
<a
class="d-block"
href="https://github.com/foo/bar"
rel="nofollow noreferrer noopener"
target="_blank"
>
Repository
</a>
</div>
</small>
</div>
<div
class="sidebarSection"
>
x
</small>
</div>
<div
className="pb-0 sidebarSection"
>
<h3>
Categories
</h3>
<ul
className="list-inline test-registry-extension-categories"
<h3>
Extension ID
</h3>
<small
class="text-muted"
>
x
</small>
</div>
<div
class="pb-0 sidebarSection"
>
<li
className="list-inline-item mb-2"
<h3>
Categories
</h3>
<ul
class="list-inline"
data-testid="test-registry-extension-categories"
>
<a
className="btn btn-outline-secondary btn-sm"
href="/extensions?category=Programming+languages"
onClick={[Function]}
<li
class="list-inline-item mb-2"
>
Programming languages
</a>
</li>
<li
className="list-inline-item mb-2"
>
<a
className="btn btn-outline-secondary btn-sm"
href="/extensions?category=Other"
onClick={[Function]}
<a
class="btn btn-outline-secondary btn-sm"
href="/extensions?category=Programming+languages"
>
Programming languages
</a>
</li>
<li
class="list-inline-item mb-2"
>
Other
</a>
</li>
</ul>
</div>
<div
className="pb-0 sidebarSection"
>
<h3>
Tags
</h3>
<ul
className="list-inline"
<a
class="btn btn-outline-secondary btn-sm"
href="/extensions?category=Other"
>
Other
</a>
</li>
</ul>
</div>
<div
class="pb-0 sidebarSection"
>
<li
className="list-inline-item mb-2"
<h3>
Tags
</h3>
<ul
class="list-inline"
>
<a
className="btn btn-outline-secondary btn-sm tag"
href="/extensions?query=tag%3AT1"
onClick={[Function]}
<li
class="list-inline-item mb-2"
>
T1
</a>
</li>
<li
className="list-inline-item mb-2"
>
<a
className="btn btn-outline-secondary btn-sm tag"
href="/extensions?query=tag%3AT2"
onClick={[Function]}
<a
class="btn btn-outline-secondary btn-sm tag"
href="/extensions?query=tag%3AT1"
>
T1
</a>
</li>
<li
class="list-inline-item mb-2"
>
T2
</a>
</li>
</ul>
</div>
</aside>
</div>
<a
class="btn btn-outline-secondary btn-sm tag"
href="/extensions?query=tag%3AT2"
>
T2
</a>
</li>
</ul>
</div>
</aside>
</div>
</DocumentFragment>
`;
exports[`RegistryExtensionOverviewPage renders with no tags 1`] = `
<div
className="d-flex flex-wrap"
>
<DocumentFragment>
<div
className="mr-3 readme"
class="d-flex flex-wrap"
>
<div
className="markdown"
dangerouslySetInnerHTML={
Object {
"__html": "<p><strong>A</strong></p>
",
}
}
onClick={[Function]}
/>
</div>
<aside
className="sidebar"
>
<svg
className="mb-3 icon"
fill="none"
height="48"
width="48"
xmlns="http://www.w3.org/2000/svg"
class="mr-3 readme"
>
<path
d="M41.407 23.21c-1.64.468-3.681 3.22-3.757 2.091-.033-.56-.175-1.857-.184-3.18-.025-3.773.193-9.429.193-9.429 0-.192-.017-.376-.059-.552a2.512 2.512 0 00-.72-1.355c-.025-.025-.05-.042-.075-.067a4.077 4.077 0 00-.292-.243c-.11-.075-.235-.142-.352-.209-.05-.025-.092-.05-.142-.075-.025-.009-.05-.017-.067-.025a4.16 4.16 0 00-1.623-.343l-1.548.033s-1.966.067-4.225.117H22.89c-.468-.025-.778-.058-.895-.117-1.255-.586 1.481-2.644 1.891-3.832C24.958 2.894 22.147 0 18.834 0c-3.322 0-6.133 2.895-5.046 6.04.419 1.189 3.288 3.28 1.891 3.833-.184.075-1.23.109-2.593.109h-1.289c-3.104-.009-6.92-.1-6.92-.1L3.33 9.847c-1.389 0-3.004.652-3.246 2.217-.05.209-.084.636-.084.636l.025 11.646c.017.193.034.335.059.385.552 1.397 3.597-2.25 4.785-2.669 3.138-1.096 5.037 3.623 5.037 6.953 0 3.338-2.886 6.15-6.024 5.062-1.188-.419-3.238-3.155-3.823-1.9a.773.773 0 00-.059.243v12.91A2.703 2.703 0 002.694 48h12.5c.167-.017.293-.033.343-.05 1.397-.56-.519-1.866-.895-3.196-.912-3.205.862-3.765 4.192-3.765s4.902.836 4.902 3.12c0 1.172-3.145 3.247-1.89 3.832.041.017.117.034.217.05H34.94a2.703 2.703 0 002.702-2.71s-.268-12.057-.05-13.571c.159-1.096 2.702 1.18 3.815 1.648 4.142 1.724 6.074-1.732 6.074-5.087 0-3.355-2.853-5.982-6.074-5.062z"
fill="#95A5C6"
fillOpacity=".32"
/>
</svg>
<div
className="pt-2 pb-3"
>
<h3>
Publisher
</h3>
<small>
x
</small>
<div
class="markdown"
>
<p>
<strong>
A
</strong>
</p>
</div>
</div>
<div
className="sidebarSection"
<aside
class="sidebar"
>
<h3>
Resources
</h3>
<small>
<a
className="d-block mb-1"
href="https://example.com"
rel="nofollow noopener noreferrer"
target="_blank"
>
Source code (JavaScript)
</a>
<div
className="d-flex"
>
<GithubIcon
className="icon-inline mr-1"
/>
<svg
class="mb-3 icon"
fill="none"
height="48"
width="48"
xmlns="http://www.w3.org/2000/svg"
>
<path
d="M41.407 23.21c-1.64.468-3.681 3.22-3.757 2.091-.033-.56-.175-1.857-.184-3.18-.025-3.773.193-9.429.193-9.429 0-.192-.017-.376-.059-.552a2.512 2.512 0 00-.72-1.355c-.025-.025-.05-.042-.075-.067a4.077 4.077 0 00-.292-.243c-.11-.075-.235-.142-.352-.209-.05-.025-.092-.05-.142-.075-.025-.009-.05-.017-.067-.025a4.16 4.16 0 00-1.623-.343l-1.548.033s-1.966.067-4.225.117H22.89c-.468-.025-.778-.058-.895-.117-1.255-.586 1.481-2.644 1.891-3.832C24.958 2.894 22.147 0 18.834 0c-3.322 0-6.133 2.895-5.046 6.04.419 1.189 3.288 3.28 1.891 3.833-.184.075-1.23.109-2.593.109h-1.289c-3.104-.009-6.92-.1-6.92-.1L3.33 9.847c-1.389 0-3.004.652-3.246 2.217-.05.209-.084.636-.084.636l.025 11.646c.017.193.034.335.059.385.552 1.397 3.597-2.25 4.785-2.669 3.138-1.096 5.037 3.623 5.037 6.953 0 3.338-2.886 6.15-6.024 5.062-1.188-.419-3.238-3.155-3.823-1.9a.773.773 0 00-.059.243v12.91A2.703 2.703 0 002.694 48h12.5c.167-.017.293-.033.343-.05 1.397-.56-.519-1.866-.895-3.196-.912-3.205.862-3.765 4.192-3.765s4.902.836 4.902 3.12c0 1.172-3.145 3.247-1.89 3.832.041.017.117.034.217.05H34.94a2.703 2.703 0 002.702-2.71s-.268-12.057-.05-13.571c.159-1.096 2.702 1.18 3.815 1.648 4.142 1.724 6.074-1.732 6.074-5.087 0-3.355-2.853-5.982-6.074-5.062z"
fill="#95A5C6"
fill-opacity=".32"
/>
</svg>
<div
class="pt-2 pb-3"
>
<h3>
Publisher
</h3>
<small>
x
</small>
</div>
<div
class="sidebarSection"
>
<h3>
Resources
</h3>
<small>
<a
className="d-block"
href="https://github.com/foo/bar"
rel="nofollow noreferrer noopener"
class="d-block mb-1"
href="https://example.com"
rel="nofollow noopener noreferrer"
target="_blank"
>
Repository
Source code (JavaScript)
</a>
</div>
</small>
</div>
<div
className="sidebarSection"
>
<h3>
Extension ID
</h3>
<small
className="text-muted"
>
x
</small>
</div>
<div
className="pb-0 sidebarSection"
>
<h3>
Categories
</h3>
<ul
className="list-inline test-registry-extension-categories"
>
<li
className="list-inline-item mb-2"
>
<a
className="btn btn-outline-secondary btn-sm"
href="/extensions?category=Programming+languages"
onClick={[Function]}
<div
class="d-flex"
>
Programming languages
</a>
</li>
<li
className="list-inline-item mb-2"
<githubicon
class="icon-inline mr-1"
/>
<a
class="d-block"
href="https://github.com/foo/bar"
rel="nofollow noreferrer noopener"
target="_blank"
>
Repository
</a>
</div>
</small>
</div>
<div
class="sidebarSection"
>
<h3>
Extension ID
</h3>
<small
class="text-muted"
>
<a
className="btn btn-outline-secondary btn-sm"
href="/extensions?category=Other"
onClick={[Function]}
x
</small>
</div>
<div
class="pb-0 sidebarSection"
>
<h3>
Categories
</h3>
<ul
class="list-inline"
data-testid="test-registry-extension-categories"
>
<li
class="list-inline-item mb-2"
>
Other
</a>
</li>
</ul>
</div>
</aside>
</div>
<a
class="btn btn-outline-secondary btn-sm"
href="/extensions?category=Programming+languages"
>
Programming languages
</a>
</li>
<li
class="list-inline-item mb-2"
>
<a
class="btn btn-outline-secondary btn-sm"
href="/extensions?category=Other"
>
Other
</a>
</li>
</ul>
</div>
</aside>
</div>
</DocumentFragment>
`;

View File

@ -1,29 +1,38 @@
import { ShortcutProvider } from '@slimsag/react-shortcuts'
import { render, fireEvent, waitFor, screen } from '@testing-library/react'
import React from 'react'
import renderer from 'react-test-renderer'
import { Modal } from 'reactstrap'
import { KeyboardShortcutsHelp } from './KeyboardShortcutsHelp'
describe('KeyboardShortcutsHelp', () => {
test('', () => {
const output = renderer.create(
<KeyboardShortcutsHelp
keyboardShortcuts={[
{
test('is triggered correctly', async () => {
render(
<ShortcutProvider>
<KeyboardShortcutsHelp
keyboardShortcuts={[
{
id: 'x',
title: 't',
keybindings: [{ ordered: ['x'] }],
},
]}
keyboardShortcutForShow={{
id: 'x',
title: 't',
keybindings: [{ held: ['Alt'], ordered: ['x'] }],
},
]}
keyboardShortcutForShow={{
id: 'x',
title: 't',
keybindings: [{ held: ['Alt'], ordered: ['x'] }],
}}
/>
keybindings: [{ ordered: ['x'] }],
}}
/>
</ShortcutProvider>
)
// Modal is hidden by default and uses portal, so we can't easily test its contents. Grab
// its inner .modal-body and snapshot that instead.
expect(renderer.create(output.root.findByType(Modal).props.children[1]).toJSON()).toMatchSnapshot()
// couldn't trigger event with ctrl/alt/shift key so use shortcut without held keys
fireEvent.keyDown(document, { key: 'x', keyCode: 88 })
await waitFor(() => {
expect(screen.getByText(/keyboard shortcuts/i)).toBeInTheDocument()
expect(screen.getByRole('dialog')).toHaveClass('show')
})
expect(document.body).toMatchSnapshot()
})
})

View File

@ -1,39 +1,90 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`KeyboardShortcutsHelp 1`] = `
<div
className="modal-body modal-body--full"
exports[`KeyboardShortcutsHelp is triggered correctly 1`] = `
<body
class="modal-open"
>
<ul
className="list-group list-group-flush"
<div />
<div
style="position: relative; z-index: 1050; display: block;"
tabindex="-1"
>
<li
className="list-group-item d-flex align-items-center justify-content-between"
<div
class=""
>
t
<span>
<span>
<kbd>
Alt
</kbd>
<kbd>
x
</kbd>
</span>
</span>
</li>
<li
className="list-group-item d-flex align-items-center justify-content-between"
>
Expand URL to its canonical form (on file or tree page)
<span>
<span>
<kbd>
y
</kbd>
</span>
</span>
</li>
</ul>
</div>
<div
class="modal show"
role="dialog"
style="display: block;"
tabindex="-1"
>
<div
class="modal-dialog modal-dialog-centered"
role="document"
>
<div
class="modal-content"
>
<div
class="modal-header"
>
<h4
class="modal-title"
>
Keyboard shortcuts
</h4>
<button
aria-label="Close"
class="btn btn-icon"
data-dismiss="modal"
type="button"
>
<span
aria-hidden="true"
>
×
</span>
</button>
</div>
<div
class="modal-body modal-body--full"
>
<ul
class="list-group list-group-flush"
>
<li
class="list-group-item d-flex align-items-center justify-content-between"
>
t
<span>
<span>
<kbd>
x
</kbd>
</span>
</span>
</li>
<li
class="list-group-item d-flex align-items-center justify-content-between"
>
Expand URL to its canonical form (on file or tree page)
<span>
<span>
<kbd>
y
</kbd>
</span>
</span>
</li>
</ul>
</div>
</div>
</div>
</div>
<div
class="modal-backdrop show"
/>
</div>
</div>
</body>
`;

View File

@ -1,6 +1,7 @@
import { render } from '@testing-library/react'
import { createMemoryHistory } from 'history'
import React from 'react'
import { MemoryRouter, Redirect } from 'react-router'
import renderer from 'react-test-renderer'
import { Router } from 'react-router'
import { EMPTY_FEATURE_FLAGS } from '../../featureFlags/featureFlags'
@ -18,8 +19,9 @@ describe('SiteInitPage', () => {
})
test('site already initialized', () => {
const component = renderer.create(
<MemoryRouter>
const history = createMemoryHistory({ initialEntries: ['/'] })
render(
<Router history={history}>
<SiteInitPage
isLightTheme={true}
needsSiteInit={false}
@ -27,40 +29,34 @@ describe('SiteInitPage', () => {
context={{ authProviders: [], sourcegraphDotComMode: false }}
featureFlags={EMPTY_FEATURE_FLAGS}
/>
</MemoryRouter>
</Router>
)
const redirect = component.root.findByType(Redirect)
expect(redirect).toBeDefined()
expect(redirect.props.to).toEqual('/search')
expect(history.location.pathname).toEqual('/search')
})
test('unexpected authed user', () =>
expect(
renderer
.create(
<SiteInitPage
isLightTheme={true}
needsSiteInit={true}
authenticatedUser={{ username: 'alice' }}
context={{ authProviders: [], sourcegraphDotComMode: false }}
featureFlags={EMPTY_FEATURE_FLAGS}
/>
)
.toJSON()
render(
<SiteInitPage
isLightTheme={true}
needsSiteInit={true}
authenticatedUser={{ username: 'alice' }}
context={{ authProviders: [], sourcegraphDotComMode: false }}
featureFlags={EMPTY_FEATURE_FLAGS}
/>
).asFragment()
).toMatchSnapshot())
test('normal', () =>
expect(
renderer
.create(
<SiteInitPage
isLightTheme={true}
needsSiteInit={true}
authenticatedUser={null}
context={{ authProviders: [], sourcegraphDotComMode: false }}
featureFlags={EMPTY_FEATURE_FLAGS}
/>
)
.toJSON()
render(
<SiteInitPage
isLightTheme={true}
needsSiteInit={true}
authenticatedUser={null}
context={{ authProviders: [], sourcegraphDotComMode: false }}
featureFlags={EMPTY_FEATURE_FLAGS}
/>
).asFragment()
).toMatchSnapshot())
})

View File

@ -1,165 +1,160 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SiteInitPage normal 1`] = `
<div
className="siteInitPage"
>
<DocumentFragment>
<div
className="card content"
class="siteInitPage"
>
<div
className="card-body p-4"
class="card content"
>
<img
alt="Sourcegraph logo"
className="w-100 mb-3"
src="/img/sourcegraph-logo-light.svg"
/>
<h2
className="site-init-page__header"
<div
class="card-body p-4"
>
Welcome
</h2>
<p>
Create an admin account to start using Sourcegraph.
</p>
<form
className="signinSignupForm test-signup-form rounded p-4 text-left mt-4 w-100"
noValidate={true}
onSubmit={[Function]}
>
<div
className="form-group d-flex flex-column align-content-start"
<img
alt="Sourcegraph logo"
class="w-100 mb-3"
src="/img/sourcegraph-logo-light.svg"
/>
<h2
class="site-init-page__header"
>
Welcome
</h2>
<p>
Create an admin account to start using Sourcegraph.
</p>
<form
class="signinSignupForm test-signup-form rounded p-4 text-left mt-4 w-100"
novalidate=""
>
<label
className="align-self-start"
htmlFor="email"
>
Email
</label>
<div
className="container"
class="form-group d-flex flex-column align-content-start"
>
<input
autoComplete="email"
autoFocus={true}
className="form-control"
disabled={false}
id="email"
name="email"
onChange={[Function]}
placeholder=" "
required={true}
spellCheck={false}
type="email"
value=""
/>
<label
class="align-self-start"
for="email"
>
Email
</label>
<div
class="container"
>
<input
autocomplete="email"
class="form-control"
id="email"
name="email"
placeholder=" "
required=""
spellcheck="false"
type="email"
value=""
/>
</div>
</div>
</div>
<div
className="form-group d-flex flex-column align-content-start"
>
<label
className="align-self-start"
htmlFor="username"
>
Username
</label>
<div
className="container"
class="form-group d-flex flex-column align-content-start"
>
<input
autoCapitalize="off"
autoComplete="username"
className="form-control"
disabled={false}
id="username"
maxLength={255}
name="username"
onChange={[Function]}
pattern="^[\\\\dA-Za-z](?:[\\\\dA-Za-z]|[.-](?=[\\\\dA-Za-z]))*-?$"
placeholder=" "
required={true}
spellCheck={false}
type="text"
value=""
/>
<label
class="align-self-start"
for="username"
>
Username
</label>
<div
class="container"
>
<input
autocapitalize="off"
autocomplete="username"
class="form-control"
id="username"
maxlength="255"
name="username"
pattern="^[\\\\dA-Za-z](?:[\\\\dA-Za-z]|[.-](?=[\\\\dA-Za-z]))*-?$"
placeholder=" "
required=""
spellcheck="false"
type="text"
value=""
/>
</div>
</div>
</div>
<div
className="form-group d-flex flex-column align-content-start"
>
<label
className="align-self-start"
htmlFor="password"
>
Password
</label>
<div
className="container"
class="form-group d-flex flex-column align-content-start"
>
<input
autoComplete="new-password"
className="form-control"
disabled={false}
formNoValidate={true}
id="password"
minLength={12}
name="password"
onChange={[Function]}
onInvalid={[Function]}
placeholder=" "
required={true}
type="password"
value=""
/>
<label
class="align-self-start"
for="password"
>
Password
</label>
<div
class="container"
>
<input
autocomplete="new-password"
class="form-control"
formnovalidate=""
id="password"
minlength="12"
name="password"
placeholder=" "
required=""
type="password"
value=""
/>
</div>
<small
class="form-text text-muted"
>
At least 12 characters
</small>
</div>
<small
className="form-text text-muted"
<div
class="form-group mb-0"
>
At least 12 characters
</small>
</div>
<div
className="form-group mb-0"
>
<button
className="btn btn-primary btn-block d-flex justify-content-center align-items-center"
disabled={true}
type="submit"
>
Create admin account & continue
</button>
</div>
</form>
<button
class="btn btn-primary btn-block d-flex justify-content-center align-items-center"
disabled=""
type="submit"
>
Create admin account & continue
</button>
</div>
</form>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`SiteInitPage unexpected authed user 1`] = `
<div
className="siteInitPage"
>
<DocumentFragment>
<div
className="card content"
class="siteInitPage"
>
<div
className="card-body p-4"
class="card content"
>
<img
alt="Sourcegraph logo"
className="w-100 mb-3"
src="/img/sourcegraph-logo-light.svg"
/>
<p>
You're signed in as
<strong>
alice
</strong>
. A site admin must initialize Sourcegraph before you can continue.
</p>
<div
class="card-body p-4"
>
<img
alt="Sourcegraph logo"
class="w-100 mb-3"
src="/img/sourcegraph-logo-light.svg"
/>
<p>
You're signed in as
<strong>
alice
</strong>
. A site admin must initialize Sourcegraph before you can continue.
</p>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;

View File

@ -1,6 +1,6 @@
import { render, waitFor } from '@testing-library/react'
import * as H from 'history'
import React from 'react'
import renderer from 'react-test-renderer'
import { of } from 'rxjs'
import sinon from 'sinon'
@ -21,8 +21,8 @@ describe('SiteAdminOverviewPage', () => {
overviewComponents: [],
}
test('activation in progress', done => {
const component = renderer.create(
test('activation in progress', async () => {
const component = render(
<SiteAdminOverviewPage
{...baseProps}
activation={{
@ -65,14 +65,11 @@ describe('SiteAdminOverviewPage', () => {
/>
)
// ensure the hooks ran and the "API response" has been received
setTimeout(() => {
expect(component.toJSON()).toMatchSnapshot()
done()
})
await waitFor(() => expect(component.asFragment()).toMatchSnapshot())
})
test('< 2 users', done => {
const component = renderer.create(
test('< 2 users', async () => {
const component = render(
<SiteAdminOverviewPage
{...baseProps}
_fetchOverview={() =>
@ -101,12 +98,10 @@ describe('SiteAdminOverviewPage', () => {
/>
)
// ensure the hooks ran and the "API response" has been received
setTimeout(() => {
expect(component.toJSON()).toMatchSnapshot()
done()
})
await waitFor(() => expect(component.asFragment()).toMatchSnapshot())
})
test('>= 2 users', done => {
test('>= 2 users', async () => {
const usageStat: ISiteUsagePeriod = {
__typename: 'SiteUsagePeriod',
userCount: 10,
@ -115,7 +110,14 @@ describe('SiteAdminOverviewPage', () => {
integrationUserCount: 0,
startTime: new Date().toISOString(),
}
const component = renderer.create(
// Do this mock for resolving issue
// `Error: Uncaught [TypeError: tspan.node(...).getComputedTextLength is not a function`
// from `client/web/src/components/d3/BarChart.tsx`
// eslint-disable-next-line @typescript-eslint/no-explicit-any, @typescript-eslint/no-unsafe-member-access
;(window.SVGElement as any).prototype.getComputedTextLength = () => 500
const component = render(
<SiteAdminOverviewPage
{...baseProps}
_fetchOverview={() =>
@ -144,9 +146,6 @@ describe('SiteAdminOverviewPage', () => {
/>
)
// ensure the hooks ran and the "API response" has been received
setTimeout(() => {
expect(component.toJSON()).toMatchSnapshot()
done()
})
await waitFor(() => expect(component.asFragment()).toMatchSnapshot())
})
})

View File

@ -1,353 +1,452 @@
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`SiteAdminOverviewPage < 2 users 1`] = `
<div
className="site-admin-overview-page"
>
<DocumentFragment>
<div
className="pt-3 mb-4"
/>
<div
className="list-group"
class="site-admin-overview-page"
>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
<div
class="pt-3 mb-4"
/>
<div
class="list-group"
>
100
repositories
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556
bytes stored
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264
lines of code indexed
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
100 repositories
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556 bytes stored
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264 lines of code indexed
</a>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`SiteAdminOverviewPage >= 2 users 1`] = `
<div
className="site-admin-overview-page"
>
<DocumentFragment>
<div
className="pt-3 mb-4"
/>
<div
className="list-group"
class="site-admin-overview-page"
>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
100
repositories
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556
bytes stored
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264
lines of code indexed
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/users"
>
10
users
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/organizations"
>
5
organizations
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/surveys"
>
100
user survey responses
</a>
<div
className="list-group-item"
class="pt-3 mb-4"
/>
<div
class="list-group"
>
<div
className="d-flex justify-content-between align-items-center position-relative"
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
100 repositories
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556 bytes stored
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264 lines of code indexed
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/users"
>
10 users
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/organizations"
>
5 organizations
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/surveys"
>
100 user survey responses
</a>
<div
class="list-group-item"
>
<span
className="h5 mb-0 font-weight-normal p-2"
>
10
active users
last week
</span>
<button
aria-label="Collapse section"
className="d-flex btn btn-icon expandBtn stretched-link"
onClick={[Function]}
type="button"
>
<svg
aria-label="Close section"
className="mdi-icon icon-inline"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
>
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
/>
</svg>
</button>
</div>
<div>
<div
className="site-admin-overview-page__detail-header"
class="d-flex justify-content-between align-items-center position-relative"
>
<h2>
Weekly unique users
</h2>
<h3>
<a
className="btn btn-secondary"
href="/site-admin/usage-statistics"
<span
class="h5 mb-0 font-weight-normal p-2"
>
10 active users last week
</span>
<button
aria-label="Collapse section"
class="d-flex btn btn-icon expandBtn stretched-link"
type="button"
>
<svg
aria-label="Close section"
class="mdi-icon icon-inline"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
>
View all usage statistics
<svg
className="mdi-icon icon-inline"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
/>
</svg>
</button>
</div>
<div>
<div
class="site-admin-overview-page__detail-header"
>
<h2>
Weekly unique users
</h2>
<h3>
<a
class="btn btn-secondary"
href="/site-admin/usage-statistics"
>
View all usage statistics
<svg
class="mdi-icon icon-inline"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path
d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"
/>
</svg>
</a>
</h3>
</div>
<svg
class="d3BarChart"
preserveAspectRatio="xMinYMin"
viewBox="0 0 500 200"
>
<g
class="bar-holder"
>
<g>
<g
fill="#a2b0cd"
>
<rect
class="bar"
data-tooltip="8 users"
height="160"
width="248"
x="1"
y="39.99999999999999"
/>
<rect
class="bar"
data-tooltip="8 users"
height="160"
width="248"
x="1"
y="39.99999999999999"
/>
</g>
<g
fill="#cad2e2"
>
<rect
class="bar"
data-tooltip="2 users"
height="39.99999999999999"
width="248"
x="1"
y="0"
/>
<rect
class="bar"
data-tooltip="2 users"
height="39.99999999999999"
width="248"
x="1"
y="0"
/>
</g>
</g>
<g>
<text
dx="124"
dy="-0.5em"
text-anchor="middle"
x="0"
y="0"
>
10
</text>
<text
dx="124"
dy="-0.5em"
text-anchor="middle"
x="0"
y="0"
>
10
</text>
</g>
<g
class="axis"
fill="none"
font-family="sans-serif"
font-size="10"
text-anchor="middle"
transform="translate(0, 200)"
>
<path
d="M14,3V5H17.59L7.76,14.83L9.17,16.24L19,6.41V10H21V3M19,19H5V5H12V3H5C3.89,3 3,3.9 3,5V19A2,2 0 0,0 5,21H19A2,2 0 0,0 21,19V12H19V19Z"
class="domain"
d="M0.5,6V0.5H500.5V6"
stroke="currentColor"
/>
</svg>
</a>
</h3>
<g
class="tick"
opacity="1"
transform="translate(250.5,0)"
>
<line
stroke="currentColor"
y2="6"
/>
<text
dy="0.71em"
fill="currentColor"
y="9"
>
<tspan
dy="0.71em"
x="0"
y="9"
/>
<tspan
dy="1.81em"
x="0"
y="9"
>
Tue,
</tspan>
<tspan
dy="2.91em"
x="0"
y="9"
>
Jan
</tspan>
<tspan
dy="4.01em"
x="0"
y="9"
>
3
</tspan>
</text>
</g>
</g>
</g>
</svg>
<small
class="tzNote"
>
<i>
GMT/UTC time
</i>
</small>
</div>
<svg
viewBox="0 0 500 200"
/>
<small
className="tzNote"
>
<i>
GMT/UTC time
</i>
</small>
</div>
</div>
</div>
</div>
</DocumentFragment>
`;
exports[`SiteAdminOverviewPage activation in progress 1`] = `
<div
className="site-admin-overview-page"
>
<DocumentFragment>
<div
className="pt-3 mb-4"
class="site-admin-overview-page"
>
<div
className="p-0 list-group-item font-weight-normal test-site-admin-overview-menu"
class="pt-3 mb-4"
>
<div
className="d-flex justify-content-between align-items-center position-relative mb-0 py-3 px-3"
class="p-0 list-group-item font-weight-normal test-site-admin-overview-menu"
>
<div
className="d-flex flex-column"
>
<span
className="h5 mb-0 font-weight-bold"
>
<div
className="p-2"
>
Welcome to Sourcegraph
</div>
</span>
<div
className="h5 mb-0 font-weight-normal"
>
Complete the steps below to finish onboarding to Sourcegraph
</div>
</div>
<button
aria-label="Collapse section"
className="d-flex btn btn-icon expandBtn stretched-link"
onClick={[Function]}
type="button"
>
<svg
aria-label="Close section"
className="mdi-icon icon-inline"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
>
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
/>
</svg>
</button>
</div>
<div
className="list-group list-group-flush activationChecklist"
>
<div
data-reach-accordion=""
class="d-flex justify-content-between align-items-center position-relative mb-0 py-3 px-3"
>
<div
className="list-group-item container"
data-reach-accordion-item=""
data-state="open"
class="d-flex flex-column"
>
<button
aria-controls="panel--1---1"
aria-expanded={true}
className="list-group-item list-group-item-action btn-link button"
data-reach-accordion-button=""
id="button--1---1"
onClick={[Function]}
onKeyDown={[Function]}
<span
class="h5 mb-0 font-weight-bold"
>
<div
className="d-flex justify-content-between activationChecklistItem h5 mb-0 font-weight-normal"
class="p-2"
>
Welcome to Sourcegraph
</div>
</span>
<div
class="h5 mb-0 font-weight-normal"
>
Complete the steps below to finish onboarding to Sourcegraph
</div>
</div>
<button
aria-label="Collapse section"
class="d-flex btn btn-icon expandBtn stretched-link"
type="button"
>
<svg
aria-label="Close section"
class="mdi-icon icon-inline"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
/>
</svg>
</button>
</div>
<div
class="list-group list-group-flush activationChecklist"
>
<div
data-reach-accordion=""
>
<div
class="list-group-item container"
data-reach-accordion-item=""
data-state="collapsed"
>
<button
aria-controls="panel--1--0"
aria-expanded="false"
class="list-group-item list-group-item-action btn-link button"
data-reach-accordion-button=""
id="button--1--0"
>
<div
className="d-flex align-items-center"
class="d-flex justify-content-between activationChecklistItem h5 mb-0 font-weight-normal"
>
<span
className="icon-inline iconContainer iconDown"
<div
class="d-flex align-items-center"
>
<span
class="icon-inline iconContainer iconDown"
>
<svg
class="mdi-icon icon"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
/>
</svg>
</span>
<span
class="icon-inline iconContainer iconRight"
>
<svg
class="mdi-icon icon"
fill="currentColor"
height="24"
viewBox="0 0 24 24"
width="24"
>
<path
d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"
/>
</svg>
</span>
<span>
Add repositories
</span>
</div>
<div>
<svg
className="mdi-icon icon"
class="mdi-icon icon-inline text-muted"
fill="currentColor"
height={24}
height="24"
viewBox="0 0 24 24"
width={24}
width="24"
>
<path
d="M7.41,8.58L12,13.17L16.59,8.58L18,10L12,16L6,10L7.41,8.58Z"
d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"
/>
</svg>
</span>
<span
className="icon-inline iconContainer iconRight"
>
<svg
className="mdi-icon icon"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
>
<path
d="M8.59,16.58L13.17,12L8.59,7.41L10,6L16,12L10,18L8.59,16.58Z"
/>
</svg>
</span>
<span>
Add repositories
</span>
</div>
</div>
<div>
<svg
className="mdi-icon icon-inline text-muted"
fill="currentColor"
height={24}
viewBox="0 0 24 24"
width={24}
>
<path
d="M12,20A8,8 0 0,1 4,12A8,8 0 0,1 12,4A8,8 0 0,1 20,12A8,8 0 0,1 12,20M12,2A10,10 0 0,0 2,12A10,10 0 0,0 12,22A10,10 0 0,0 22,12A10,10 0 0,0 12,2Z"
/>
</svg>
</div>
</div>
</button>
<div
aria-labelledby="button--1---1"
className="px-2"
data-reach-accordion-panel=""
data-state="open"
hidden={false}
id="panel--1---1"
role="region"
tabIndex={-1}
>
</button>
<div
className="pb-1 detail"
aria-labelledby="button--1--0"
class="px-2"
data-reach-accordion-panel=""
data-state="collapsed"
hidden=""
id="panel--1--0"
role="region"
tabindex="-1"
>
Configure Sourcegraph to talk to your code host
<div
class="pb-1 detail"
>
Configure Sourcegraph to talk to your code host
</div>
</div>
</div>
</div>
</div>
</div>
</div>
<div
class="list-group"
>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
0 repositories
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556 bytes stored
</a>
<a
class="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264 lines of code indexed
</a>
</div>
</div>
<div
className="list-group"
>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
0
repositories
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
1,825,299,556
bytes stored
</a>
<a
className="list-group-item list-group-item-action h5 mb-0 font-weight-normal py-2 px-3"
href="/site-admin/repositories"
>
2,616,264
lines of code indexed
</a>
</div>
</div>
</DocumentFragment>
`;

View File

@ -296,7 +296,6 @@
"puppeteer-firefox": "^0.5.1",
"react-refresh": "^0.10.0",
"react-spring": "^9.0.0",
"react-test-renderer": "^16.14.0",
"sass": "^1.32.4",
"sass-loader": "^12.1.0",
"shelljs": "^0.8.4",

View File

@ -19681,7 +19681,7 @@ react-syntax-highlighter@^13.5.3:
prismjs "^1.21.0"
refractor "^3.1.0"
react-test-renderer@^16.0.0-0, react-test-renderer@^16.14.0, "react-test-renderer@^16.8.0 || ^17.0.0":
react-test-renderer@^16.0.0-0, "react-test-renderer@^16.8.0 || ^17.0.0":
version "16.14.0"
resolved "https://registry.npmjs.org/react-test-renderer/-/react-test-renderer-16.14.0.tgz#e98360087348e260c56d4fe2315e970480c228ae"
integrity sha512-L8yPjqPE5CZO6rKsKXRO/rVPiaCOy0tQQJbC+UjPNlobl5mad59lvPjwFsQHTvL03caVDIVr9x9/OSgDe6I5Eg==