mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 15:51:43 +00:00
[SG-33302] Upgrade to the latest react version and migrate to the new root API (#36045)
* feat: react v18 and new root api with some errors fixes * web: repo page integration test flake fix (#37592) * web: fix `blog-viewer` integration test flake (#38069) * fix: build-ts * fix: new failing unit tests * fix: unit test * web: fix search integration test * feat: applied request changes * fix: new failing test because of global mockReactVisibilitySensor * fix: unexpected onLayoutChange call on SmartInsightsViewGrid * fix: make ForwardReferenceComponent support custom children * fix: more new ts lint issue * fix: remaining issue BatchSpec * fix: SmartInsightsViewGrid issue with useLayoutEffect Co-authored-by: gitstart-sourcegraph <gitstart@users.noreply.github.com> Co-authored-by: Valery Bugakov <skymk1@gmail.com>
This commit is contained in:
parent
e599e7b792
commit
da154b5a93
@ -11,7 +11,9 @@ import { DeprecatedTooltip, WildcardThemeContext } from '@sourcegraph/wildcard'
|
||||
|
||||
import brandedStyles from '../global-styles/index.scss'
|
||||
|
||||
export interface BrandedProps extends MemoryRouterProps, Pick<MockedStoryProviderProps, 'mocks' | 'useStrictMocking'> {
|
||||
export interface BrandedProps
|
||||
extends Omit<MemoryRouterProps, 'children'>,
|
||||
Pick<MockedStoryProviderProps, 'mocks' | 'useStrictMocking'> {
|
||||
children: React.FunctionComponent<React.PropsWithChildren<ThemeProps>>
|
||||
styles?: string
|
||||
}
|
||||
@ -20,7 +22,7 @@ export interface BrandedProps extends MemoryRouterProps, Pick<MockedStoryProvide
|
||||
* Wrapper component for branded Storybook stories that provides light theme and react-router props.
|
||||
* Takes a render function as children that gets called with the props.
|
||||
*/
|
||||
export const BrandedStory: React.FunctionComponent<React.PropsWithChildren<BrandedProps>> = ({
|
||||
export const BrandedStory: React.FunctionComponent<BrandedProps> = ({
|
||||
children: Children,
|
||||
styles = brandedStyles,
|
||||
mocks,
|
||||
|
||||
@ -60,7 +60,7 @@ export interface PanelViewWithComponent extends PanelViewData {
|
||||
/**
|
||||
* The React element to render in the panel view.
|
||||
*/
|
||||
reactElement?: React.ReactFragment
|
||||
reactElement?: React.ReactNode
|
||||
|
||||
// Should the content of the panel be put inside a wrapper container with padding or not.
|
||||
noWrapper?: boolean
|
||||
@ -75,7 +75,7 @@ export interface PanelViewWithComponent extends PanelViewData {
|
||||
interface TabbedPanelItem {
|
||||
id: string
|
||||
|
||||
label: React.ReactFragment
|
||||
label: React.ReactNode
|
||||
/**
|
||||
* Controls the relative order of panel items. The items are laid out from highest priority (at the beginning)
|
||||
* to lowest priority (at the end). The default is 0.
|
||||
|
||||
@ -3,7 +3,7 @@ import '../../shared/polyfills'
|
||||
|
||||
import React from 'react'
|
||||
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { AnchorLink, setLinkComponent } from '@sourcegraph/wildcard'
|
||||
|
||||
@ -23,4 +23,6 @@ const AfterInstallPage: React.FunctionComponent<React.PropsWithChildren<unknown>
|
||||
</ThemeWrapper>
|
||||
)
|
||||
|
||||
render(<AfterInstallPage />, document.querySelector('#root'))
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(<AfterInstallPage />)
|
||||
|
||||
@ -4,7 +4,7 @@ import '../../shared/polyfills'
|
||||
import React, { useCallback, useEffect, useMemo, useState } from 'react'
|
||||
|
||||
import { trimEnd, uniq } from 'lodash'
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { from, noop, Observable, of } from 'rxjs'
|
||||
import { catchError, distinctUntilChanged, filter, map, mapTo } from 'rxjs/operators'
|
||||
import { Optional } from 'utility-types'
|
||||
@ -275,7 +275,9 @@ const Options: React.FunctionComponent<React.PropsWithChildren<unknown>> = () =>
|
||||
}
|
||||
|
||||
const inject = (): void => {
|
||||
render(<Options />, document.body)
|
||||
const root = createRoot(document.body)
|
||||
|
||||
root.render(<Options />)
|
||||
}
|
||||
|
||||
document.addEventListener('DOMContentLoaded', inject)
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import classNames from 'classnames'
|
||||
import { trimStart } from 'lodash'
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { defer, fromEvent, of } from 'rxjs'
|
||||
import { distinctUntilChanged, filter, map, startWith } from 'rxjs/operators'
|
||||
import { Omit } from 'utility-types'
|
||||
@ -552,8 +552,9 @@ function enhanceSearchPage(sourcegraphURL: string): void {
|
||||
utm_source: getPlatformName(),
|
||||
utm_campaign: utmCampaign,
|
||||
})
|
||||
const root = createRoot(container)
|
||||
|
||||
render(
|
||||
root.render(
|
||||
<SourcegraphIconButton
|
||||
label="Search on Sourcegraph"
|
||||
title="Search on Sourcegraph to get hover tooltips, go to definition and more"
|
||||
@ -570,8 +571,7 @@ function enhanceSearchPage(sourcegraphURL: string): void {
|
||||
searchQuery ? `&q=${searchQuery}` : ''
|
||||
}`
|
||||
}}
|
||||
/>,
|
||||
container
|
||||
/>
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -3,7 +3,8 @@ import * as React from 'react'
|
||||
import classNames from 'classnames'
|
||||
import * as H from 'history'
|
||||
import { isEqual } from 'lodash'
|
||||
import { render as reactDOMRender, Renderer } from 'react-dom'
|
||||
import { Renderer } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import {
|
||||
asyncScheduler,
|
||||
combineLatest,
|
||||
@ -1604,8 +1605,14 @@ export function injectCodeIntelligenceToCodeHost(
|
||||
// Flag to hide the actions in the code view toolbar (hide ActionNavItems) leaving only the "Open on Sourcegraph" button in the toolbar.
|
||||
const hideActions = codeHost.type === 'gerrit'
|
||||
|
||||
const renderWithThemeProvider = (element: React.ReactNode, container: Element | null): void =>
|
||||
reactDOMRender(<WildcardThemeProvider isBranded={false}>{element}</WildcardThemeProvider>, container)
|
||||
const renderWithThemeProvider = (element: React.ReactNode, container: Element | null): void => {
|
||||
if (!container) {
|
||||
return
|
||||
}
|
||||
|
||||
const root = createRoot(container)
|
||||
root.render(<WildcardThemeProvider isBranded={false}>{element}</WildcardThemeProvider>)
|
||||
}
|
||||
|
||||
subscriptions.add(
|
||||
// eslint-disable-next-line rxjs/no-async-subscribe, @typescript-eslint/no-misused-promises
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import classNames from 'classnames'
|
||||
import * as H from 'history'
|
||||
import { isEqual } from 'lodash'
|
||||
import { render } from 'react-dom'
|
||||
import { Renderer } from 'react-dom'
|
||||
|
||||
import { ContributableMenu } from '@sourcegraph/client-api'
|
||||
import { DiffPart } from '@sourcegraph/codeintellify'
|
||||
@ -51,7 +51,7 @@ interface InjectProps
|
||||
extends PlatformContextProps<'forceUpdateTooltip' | 'settings' | 'sideloadedExtensionURL' | 'sourcegraphURL'>,
|
||||
ExtensionsControllerProps {
|
||||
history: H.History
|
||||
render: typeof render
|
||||
render: Renderer
|
||||
}
|
||||
|
||||
interface RenderCommandPaletteProps
|
||||
|
||||
@ -1,7 +1,4 @@
|
||||
import * as React from 'react'
|
||||
|
||||
import { cleanup, getByText, render } from '@testing-library/react'
|
||||
import _VisibilitySensor from 'react-visibility-sensor'
|
||||
import { of } from 'rxjs'
|
||||
import { map } from 'rxjs/operators'
|
||||
|
||||
@ -10,28 +7,10 @@ import {
|
||||
HIGHLIGHTED_FILE_LINES_LONG,
|
||||
HIGHLIGHTED_FILE_LINES_SIMPLE,
|
||||
} from '@sourcegraph/shared/src/testing/searchTestHelpers'
|
||||
import '@sourcegraph/shared/dev/mockReactVisibilitySensor'
|
||||
|
||||
import { CodeExcerpt } from './CodeExcerpt'
|
||||
|
||||
export class MockVisibilitySensor extends React.Component<{ onChange?: (isVisible: boolean) => void }> {
|
||||
constructor(props: { onChange?: (isVisible: boolean) => void }) {
|
||||
super(props)
|
||||
if (props.onChange) {
|
||||
props.onChange(true)
|
||||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return <>{this.props.children}</>
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock('react-visibility-sensor', (): typeof _VisibilitySensor => ({ children, onChange }) => (
|
||||
<>
|
||||
<MockVisibilitySensor onChange={onChange}>{children}</MockVisibilitySensor>
|
||||
</>
|
||||
))
|
||||
|
||||
describe('CodeExcerpt', () => {
|
||||
afterAll(cleanup)
|
||||
|
||||
@ -66,7 +45,7 @@ describe('CodeExcerpt', () => {
|
||||
// at least exist.
|
||||
const { container } = render(<CodeExcerpt {...defaultProps} />)
|
||||
const dataLines = container.querySelectorAll('[data-line]')
|
||||
expect(dataLines.length).toMatchInlineSnapshot('3')
|
||||
expect(dataLines).toHaveLength(3)
|
||||
})
|
||||
|
||||
it('renders the code portion of each row', () => {
|
||||
|
||||
@ -1,6 +1,5 @@
|
||||
import { cleanup } from '@testing-library/react'
|
||||
import * as H from 'history'
|
||||
import _VisibilitySensor from 'react-visibility-sensor'
|
||||
import { of } from 'rxjs'
|
||||
import sinon from 'sinon'
|
||||
|
||||
@ -12,16 +11,10 @@ import {
|
||||
NOOP_SETTINGS_CASCADE,
|
||||
HIGHLIGHTED_FILE_LINES,
|
||||
} from '@sourcegraph/shared/src/testing/searchTestHelpers'
|
||||
import '@sourcegraph/shared/dev/mockReactVisibilitySensor'
|
||||
|
||||
import { MockVisibilitySensor } from './CodeExcerpt.test'
|
||||
import { FileMatchChildren } from './FileMatchChildren'
|
||||
|
||||
jest.mock('react-visibility-sensor', (): typeof _VisibilitySensor => ({ children, onChange }) => (
|
||||
<>
|
||||
<MockVisibilitySensor onChange={onChange}>{children}</MockVisibilitySensor>
|
||||
</>
|
||||
))
|
||||
|
||||
const history = H.createBrowserHistory()
|
||||
history.replace({ pathname: '/search' })
|
||||
|
||||
|
||||
@ -166,7 +166,7 @@ export const FileMatchChildren: React.FunctionComponent<React.PropsWithChildren<
|
||||
} = props
|
||||
|
||||
const fetchHighlightedFileRangeLines = React.useCallback(
|
||||
(isFirst, startLine, endLine) => {
|
||||
(isFirst: boolean, startLine: number, endLine: number) => {
|
||||
const startTime = Date.now()
|
||||
return fetchHighlightedFileLineRanges(
|
||||
{
|
||||
|
||||
@ -1,7 +1,6 @@
|
||||
import { cleanup, getAllByTestId, getByTestId } from '@testing-library/react'
|
||||
import { createBrowserHistory } from 'history'
|
||||
import FileIcon from 'mdi-react/FileIcon'
|
||||
import _VisibilitySensor from 'react-visibility-sensor'
|
||||
import sinon from 'sinon'
|
||||
|
||||
import { MatchGroup } from '@sourcegraph/shared/src/components/ranking/PerFileResultRanking'
|
||||
@ -13,16 +12,10 @@ import {
|
||||
NOOP_SETTINGS_CASCADE,
|
||||
RESULT,
|
||||
} from '@sourcegraph/shared/src/testing/searchTestHelpers'
|
||||
import '@sourcegraph/shared/dev/mockReactVisibilitySensor'
|
||||
|
||||
import { MockVisibilitySensor } from './CodeExcerpt.test'
|
||||
import { FileSearchResult, limitGroup } from './FileSearchResult'
|
||||
|
||||
jest.mock('react-visibility-sensor', (): typeof _VisibilitySensor => ({ children, onChange }) => (
|
||||
<>
|
||||
<MockVisibilitySensor onChange={onChange}>{children}</MockVisibilitySensor>
|
||||
</>
|
||||
))
|
||||
|
||||
describe('FileSearchResult', () => {
|
||||
afterAll(cleanup)
|
||||
const history = createBrowserHistory()
|
||||
|
||||
@ -37,7 +37,7 @@ export interface ResultContainerProps {
|
||||
/**
|
||||
* The title component.
|
||||
*/
|
||||
title: React.ReactFragment
|
||||
title: React.ReactNode
|
||||
|
||||
/**
|
||||
* CSS class name to apply to the title element.
|
||||
@ -45,20 +45,18 @@ export interface ResultContainerProps {
|
||||
titleClassName?: string
|
||||
|
||||
/** The content to display next to the title. */
|
||||
description?: React.ReactFragment
|
||||
description?: React.ReactNode
|
||||
|
||||
/**
|
||||
* The content of the result displayed underneath the result container's
|
||||
* header when collapsed.
|
||||
*/
|
||||
collapsedChildren?: React.ReactFragment
|
||||
|
||||
collapsedChildren?: React.ReactNode
|
||||
/**
|
||||
* The content of the result displayed underneath the result container's
|
||||
* header when expanded.
|
||||
*/
|
||||
expandedChildren?: React.ReactFragment
|
||||
|
||||
expandedChildren?: React.ReactNode
|
||||
/**
|
||||
* The label to display next to the collapse button
|
||||
*/
|
||||
|
||||
@ -32,7 +32,7 @@ export const ModalVideo: React.FunctionComponent<React.PropsWithChildren<ModalVi
|
||||
}) => {
|
||||
const [isOpen, setIsOpen] = useState(false)
|
||||
const toggleDialog = useCallback(
|
||||
isOpen => {
|
||||
(isOpen: boolean) => {
|
||||
setIsOpen(isOpen)
|
||||
if (onToggle) {
|
||||
onToggle(isOpen)
|
||||
@ -88,7 +88,7 @@ export const ModalVideo: React.FunctionComponent<React.PropsWithChildren<ModalVi
|
||||
<Button
|
||||
variant="icon"
|
||||
className="p-1"
|
||||
data-testId="modal-video-close"
|
||||
data-testid="modal-video-close"
|
||||
onClick={() => toggleDialog(false)}
|
||||
aria-label="Close"
|
||||
>
|
||||
|
||||
@ -189,7 +189,7 @@ export const NoResultsPage: React.FunctionComponent<React.PropsWithChildren<NoRe
|
||||
const [hiddenSectionIDs, setHiddenSectionIds] = useTemporarySetting('search.hiddenNoResultsSections')
|
||||
|
||||
const onClose = useCallback(
|
||||
sectionID => {
|
||||
(sectionID: SectionID) => {
|
||||
telemetryService.log('NoResultsPanel', { panelID: sectionID, action: 'closed' })
|
||||
setHiddenSectionIds((hiddenSectionIDs = []) =>
|
||||
!hiddenSectionIDs.includes(sectionID) ? [...hiddenSectionIDs, sectionID] : hiddenSectionIDs
|
||||
|
||||
@ -404,7 +404,7 @@ const SearchReferenceEntry = <T extends SearchReferenceInfo>({
|
||||
const [collapsed, setCollapsed] = useState(true)
|
||||
const collapseIcon = collapsed ? mdiChevronLeft : mdiChevronDown
|
||||
|
||||
const handleOpenChange = useCallback(collapsed => setCollapsed(!collapsed), [])
|
||||
const handleOpenChange = useCallback((collapsed: boolean) => setCollapsed(!collapsed), [])
|
||||
|
||||
let buttonTextPrefix: ReactElement | null = null
|
||||
if (isFilterInfo(searchReference)) {
|
||||
@ -620,6 +620,6 @@ const SearchReference = React.memo(
|
||||
|
||||
export function getSearchReferenceFactory(
|
||||
props: Omit<SearchReferenceProps, 'filter'>
|
||||
): (filter: string) => ReactElement {
|
||||
): (filter: string) => React.ReactNode {
|
||||
return (filter: string) => <SearchReference {...props} filter={filter} />
|
||||
}
|
||||
|
||||
@ -41,12 +41,12 @@ export interface SearchSidebarProps
|
||||
/**
|
||||
* Not yet implemented in the VS Code extension (blocked on Apollo Client integration).
|
||||
*/
|
||||
getRevisions?: (revisionsProps: Omit<RevisionsProps, 'query'>) => (query: string) => JSX.Element
|
||||
getRevisions?: (revisionsProps: Omit<RevisionsProps, 'query'>) => (query: string) => React.ReactNode
|
||||
|
||||
/**
|
||||
* Content to render inside sidebar, but before other sections.
|
||||
*/
|
||||
prefixContent?: JSX.Element
|
||||
prefixContent?: React.ReactNode
|
||||
|
||||
buildSearchURLQueryFromQueryState: (queryParameters: BuildSearchQueryURLParameters) => string
|
||||
|
||||
|
||||
@ -10,28 +10,26 @@ import { FilterLink, FilterLinkProps } from './FilterLink'
|
||||
|
||||
import styles from './SearchSidebarSection.module.scss'
|
||||
|
||||
export const SearchSidebarSection: React.FunctionComponent<
|
||||
React.PropsWithChildren<{
|
||||
sectionId: string
|
||||
header: string
|
||||
children?: React.ReactElement | React.ReactElement[] | ((filter: string) => React.ReactElement)
|
||||
className?: string
|
||||
showSearch?: boolean // Search only works if children are FilterLink
|
||||
onToggle?: (id: string, open: boolean) => void
|
||||
startCollapsed?: boolean
|
||||
/**
|
||||
* Shown when the built-in search doesn't find any results.
|
||||
*/
|
||||
noResultText?: React.ReactElement | string
|
||||
/**
|
||||
* Clear the search input whenever this value changes. This is supposed to
|
||||
* be used together with function children, which use the search input but
|
||||
* handle search on their own.
|
||||
* Defaults to the component's children.
|
||||
*/
|
||||
clearSearchOnChange?: {}
|
||||
}>
|
||||
> = React.memo(
|
||||
export const SearchSidebarSection: React.FunctionComponent<{
|
||||
sectionId: string
|
||||
header: string
|
||||
children?: React.ReactNode | React.ReactNode[] | ((filter: string) => React.ReactNode)
|
||||
className?: string
|
||||
showSearch?: boolean // Search only works if children are FilterLink
|
||||
onToggle?: (id: string, open: boolean) => void
|
||||
startCollapsed?: boolean
|
||||
/**
|
||||
* Shown when the built-in search doesn't find any results.
|
||||
*/
|
||||
noResultText?: React.ReactElement | string
|
||||
/**
|
||||
* Clear the search input whenever this value changes. This is supposed to
|
||||
* be used together with function children, which use the search input but
|
||||
* handle search on their own.
|
||||
* Defaults to the component's children.
|
||||
*/
|
||||
clearSearchOnChange?: {}
|
||||
}> = React.memo(
|
||||
({
|
||||
sectionId,
|
||||
header,
|
||||
@ -93,7 +91,7 @@ export const SearchSidebarSection: React.FunctionComponent<
|
||||
|
||||
const [isOpened, setOpened] = useState(!startCollapsed)
|
||||
const handleOpenChange = useCallback(
|
||||
isOpen => {
|
||||
(isOpen: boolean) => {
|
||||
if (onToggle) {
|
||||
onToggle(sectionId, isOpen)
|
||||
}
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
|
||||
import { FilterType } from '@sourcegraph/shared/src/search/query/filters'
|
||||
import { Filter } from '@sourcegraph/shared/src/search/stream'
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { act, renderHook } from '@testing-library/react-hooks'
|
||||
import { act, renderHook } from '@testing-library/react'
|
||||
import { times } from 'lodash'
|
||||
|
||||
import { INCREMENTAL_ITEMS_TO_SHOW, DEFAULT_INITIAL_ITEMS_TO_SHOW, useItemsToShow } from './use-items-to-show'
|
||||
|
||||
27
client/shared/dev/mockReactVisibilitySensor.tsx
Normal file
27
client/shared/dev/mockReactVisibilitySensor.tsx
Normal file
@ -0,0 +1,27 @@
|
||||
import React from 'react'
|
||||
|
||||
import _VisibilitySensor from 'react-visibility-sensor'
|
||||
|
||||
type VisibilitySensorPropsType = React.ComponentProps<typeof _VisibilitySensor>
|
||||
|
||||
export class MockVisibilitySensor extends React.Component<VisibilitySensorPropsType> {
|
||||
constructor(props: { onChange?: (isVisible: boolean) => void }) {
|
||||
super(props)
|
||||
if (props.onChange) {
|
||||
props.onChange(true)
|
||||
}
|
||||
}
|
||||
|
||||
public render(): JSX.Element {
|
||||
return <>{this.props.children}</>
|
||||
}
|
||||
}
|
||||
|
||||
jest.mock(
|
||||
'react-visibility-sensor',
|
||||
(): typeof _VisibilitySensor => ({ children, onChange }: VisibilitySensorPropsType) => (
|
||||
<>
|
||||
<MockVisibilitySensor onChange={onChange}>{children}</MockVisibilitySensor>
|
||||
</>
|
||||
)
|
||||
)
|
||||
@ -185,7 +185,8 @@ describe('ActionItem', () => {
|
||||
// to result in the setState call.)
|
||||
userEvent.click(screen.getByRole('button'))
|
||||
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
// we should wait for the button to be enabled again after got errors. Otherwise it will be flaky
|
||||
await waitFor(() => expect(screen.getByLabelText('d')).toBeEnabled())
|
||||
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
@ -211,7 +212,7 @@ describe('ActionItem', () => {
|
||||
// to result in the setState call.)
|
||||
userEvent.click(screen.getByRole('button'))
|
||||
|
||||
await new Promise<void>(resolve => setTimeout(resolve))
|
||||
await waitFor(() => expect(screen.getByLabelText('Error: x')).toBeInTheDocument())
|
||||
|
||||
expect(asFragment()).toMatchSnapshot()
|
||||
})
|
||||
|
||||
@ -39,7 +39,7 @@ interface Props extends ActionsProps, TelemetryProps {
|
||||
}
|
||||
|
||||
/** Displays the actions in a container, with a wrapper and/or empty element. */
|
||||
export const ActionsContainer: React.FunctionComponent<React.PropsWithChildren<Props>> = props => {
|
||||
export const ActionsContainer: React.FunctionComponent<Props> = props => {
|
||||
const { scope, extraContext, returnInactiveMenuItems, extensionsController, menu, empty } = props
|
||||
|
||||
const contributions = useObservable(
|
||||
|
||||
@ -228,8 +228,9 @@ exports[`ActionItem run command with showLoadingSpinnerDuringExecution 2`] = `
|
||||
<DocumentFragment>
|
||||
<button
|
||||
aria-label="d"
|
||||
class="test-action-item"
|
||||
class="test-action-item actionItemLoading"
|
||||
data-tooltip="d"
|
||||
disabled=""
|
||||
type="button"
|
||||
>
|
||||
<img
|
||||
@ -237,6 +238,17 @@ exports[`ActionItem run command with showLoadingSpinnerDuringExecution 2`] = `
|
||||
src="u"
|
||||
/>
|
||||
g: t
|
||||
<div
|
||||
class="loader"
|
||||
data-testid="action-item-spinner"
|
||||
>
|
||||
<div
|
||||
aria-label="Loading"
|
||||
aria-live="polite"
|
||||
class="mdi-icon loadingSpinner"
|
||||
role="img"
|
||||
/>
|
||||
</div>
|
||||
</button>
|
||||
</DocumentFragment>
|
||||
`;
|
||||
|
||||
@ -60,7 +60,7 @@ export class VirtualList<TItem, TExtraItemProps = undefined> extends React.PureC
|
||||
<Element className={this.props.className} ref={this.props.onRef} aria-label={this.props['aria-label']}>
|
||||
{this.props.items.slice(0, this.props.itemsToShow).map((item, index) => (
|
||||
<VisibilitySensor
|
||||
onChange={isVisible => this.onChangeVisibility(isVisible, index)}
|
||||
onChange={(isVisible: boolean) => this.onChangeVisibility(isVisible, index)}
|
||||
key={this.props.itemKey(item)}
|
||||
containment={this.props.containment}
|
||||
partialVisibility={true}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
import assert from 'assert'
|
||||
|
||||
import { render } from '@testing-library/react'
|
||||
import { createMemoryHistory } from 'history'
|
||||
import ReactDOM from 'react-dom'
|
||||
import * as sinon from 'sinon'
|
||||
|
||||
import { Link } from '@sourcegraph/wildcard'
|
||||
@ -15,17 +15,14 @@ describe('createLinkClickHandler', () => {
|
||||
const history = createMemoryHistory({ initialEntries: [] })
|
||||
expect(history).toHaveLength(0)
|
||||
|
||||
const root = document.createElement('div')
|
||||
document.body.append(root)
|
||||
ReactDOM.render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div onClick={createLinkClickHandler(history)}>
|
||||
<Link to="https://sourcegraph.test/else/where">Test</Link>
|
||||
</div>,
|
||||
root
|
||||
</div>
|
||||
)
|
||||
|
||||
const anchor = root.querySelector('a')
|
||||
const anchor = container.querySelector('a')
|
||||
assert(anchor)
|
||||
|
||||
const spy = sinon.spy((_event: MouseEvent) => undefined)
|
||||
@ -45,17 +42,14 @@ describe('createLinkClickHandler', () => {
|
||||
const history = createMemoryHistory({ initialEntries: [] })
|
||||
expect(history).toHaveLength(0)
|
||||
|
||||
const root = document.createElement('div')
|
||||
document.body.append(root)
|
||||
ReactDOM.render(
|
||||
const { container } = render(
|
||||
// eslint-disable-next-line jsx-a11y/click-events-have-key-events, jsx-a11y/no-static-element-interactions
|
||||
<div onClick={createLinkClickHandler(history)}>
|
||||
<Link to="https://github.com/some/where">Test</Link>
|
||||
</div>,
|
||||
root
|
||||
</div>
|
||||
)
|
||||
|
||||
const anchor = root.querySelector('a')
|
||||
const anchor = container.querySelector('a')
|
||||
assert(anchor)
|
||||
|
||||
const spy = sinon.spy((_event: MouseEvent) => undefined)
|
||||
|
||||
@ -2,8 +2,7 @@ import { useEffect } from 'react'
|
||||
|
||||
import { gql } from '@apollo/client'
|
||||
import { createMockClient } from '@apollo/client/testing'
|
||||
import { render } from '@testing-library/react'
|
||||
import { renderHook, act as actHook } from '@testing-library/react-hooks'
|
||||
import { render, renderHook, act as actHook } from '@testing-library/react'
|
||||
|
||||
import { TemporarySettingsContext } from './TemporarySettingsProvider'
|
||||
import { InMemoryMockSettingsBackend, TemporarySettingsStorage } from './TemporarySettingsStorage'
|
||||
|
||||
@ -11,13 +11,18 @@ export interface RenderWithBrandedContextResult extends RenderResult {
|
||||
history: MemoryHistory
|
||||
}
|
||||
|
||||
interface RenderWithBrandedContextOptions {
|
||||
route?: string
|
||||
history?: MemoryHistory<unknown>
|
||||
}
|
||||
|
||||
const wildcardTheme: WildcardTheme = {
|
||||
isBranded: true,
|
||||
}
|
||||
|
||||
export function renderWithBrandedContext(
|
||||
children: ReactNode,
|
||||
{ route = '/', history = createMemoryHistory({ initialEntries: [route] }) } = {}
|
||||
{ route = '/', history = createMemoryHistory({ initialEntries: [route] }) }: RenderWithBrandedContextOptions = {}
|
||||
): RenderWithBrandedContextResult {
|
||||
return {
|
||||
...render(
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { last, min, noop } from 'lodash'
|
||||
import { BehaviorSubject, Observable, of, Subject, Subscription } from 'rxjs'
|
||||
import { delay } from 'rxjs/operators'
|
||||
|
||||
@ -145,7 +145,7 @@ export const SearchHomeView: React.FunctionComponent<React.PropsWithChildren<Sea
|
||||
)
|
||||
|
||||
const fetchStreamSuggestions = useCallback(
|
||||
(query): Observable<SearchMatch[]> =>
|
||||
(query: string): Observable<SearchMatch[]> =>
|
||||
wrapRemoteObservable(extensionCoreAPI.fetchStreamSuggestions(query, instanceURL)),
|
||||
[extensionCoreAPI, instanceURL]
|
||||
)
|
||||
|
||||
@ -244,7 +244,7 @@ export const SearchResultsView: React.FunctionComponent<React.PropsWithChildren<
|
||||
)
|
||||
|
||||
const fetchStreamSuggestions = useCallback(
|
||||
(query): Observable<SearchMatch[]> =>
|
||||
(query: string): Observable<SearchMatch[]> =>
|
||||
wrapRemoteObservable(extensionCoreAPI.fetchStreamSuggestions(query, instanceURL)),
|
||||
[extensionCoreAPI, instanceURL]
|
||||
)
|
||||
|
||||
@ -169,7 +169,7 @@ export const FileMatchChildren: React.FunctionComponent<React.PropsWithChildren<
|
||||
const { openFile, openSymbol } = useOpenSearchResultsContext()
|
||||
|
||||
const fetchHighlightedFileRangeLines = React.useCallback(
|
||||
(isFirst, startLine, endLine) => {
|
||||
(isFirst: boolean, startLine: number, endLine: number) => {
|
||||
const startTime = Date.now()
|
||||
return fetchHighlightedFileLineRanges(
|
||||
{
|
||||
|
||||
@ -5,7 +5,7 @@ import React, { useMemo } from 'react'
|
||||
import { ShortcutProvider } from '@slimsag/react-shortcuts'
|
||||
import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'
|
||||
import * as Comlink from 'comlink'
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { MemoryRouter } from 'react-router'
|
||||
import { CompatRouter } from 'react-router-dom-v5-compat'
|
||||
|
||||
@ -117,7 +117,9 @@ const Main: React.FC<React.PropsWithChildren<unknown>> = () => {
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(
|
||||
<ShortcutProvider>
|
||||
<WildcardThemeContext.Provider value={{ isBranded: true }}>
|
||||
{/* Required for shared components that depend on `location`. */}
|
||||
@ -128,6 +130,5 @@ render(
|
||||
</MemoryRouter>
|
||||
<DeprecatedTooltip key={1} className="sourcegraph-tooltip" />
|
||||
</WildcardThemeContext.Provider>
|
||||
</ShortcutProvider>,
|
||||
document.querySelector('#root')
|
||||
</ShortcutProvider>
|
||||
)
|
||||
|
||||
@ -4,7 +4,7 @@ import React, { useMemo } from 'react'
|
||||
|
||||
import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'
|
||||
import * as Comlink from 'comlink'
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common'
|
||||
import { AnchorLink, setLinkComponent, useObservable } from '@sourcegraph/wildcard'
|
||||
@ -51,4 +51,6 @@ const Main: React.FC<React.PropsWithChildren<unknown>> = () => {
|
||||
)
|
||||
}
|
||||
|
||||
render(<Main />, document.querySelector('#root'))
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(<Main />)
|
||||
|
||||
@ -5,7 +5,7 @@ import React, { useMemo, useState } from 'react'
|
||||
import { ShortcutProvider } from '@slimsag/react-shortcuts'
|
||||
import { VSCodeProgressRing } from '@vscode/webview-ui-toolkit/react'
|
||||
import * as Comlink from 'comlink'
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
import { useDeepCompareEffectNoCheck } from 'use-deep-compare-effect'
|
||||
|
||||
import { wrapRemoteObservable } from '@sourcegraph/shared/src/api/client/api/common'
|
||||
@ -125,12 +125,13 @@ const Main: React.FC<React.PropsWithChildren<unknown>> = () => {
|
||||
)
|
||||
}
|
||||
|
||||
render(
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(
|
||||
<ShortcutProvider>
|
||||
<WildcardThemeContext.Provider value={{ isBranded: true }}>
|
||||
<Main />
|
||||
<DeprecatedTooltip key={1} className="sourcegraph-tooltip" />
|
||||
</WildcardThemeContext.Provider>
|
||||
</ShortcutProvider>,
|
||||
document.querySelector('#root')
|
||||
</ShortcutProvider>
|
||||
)
|
||||
|
||||
@ -190,7 +190,10 @@ const history = createBrowserHistory()
|
||||
/**
|
||||
* The root component.
|
||||
*/
|
||||
export class SourcegraphWebApp extends React.Component<SourcegraphWebAppProps, SourcegraphWebAppState> {
|
||||
export class SourcegraphWebApp extends React.Component<
|
||||
React.PropsWithChildren<SourcegraphWebAppProps>,
|
||||
SourcegraphWebAppState
|
||||
> {
|
||||
private readonly subscriptions = new Subscription()
|
||||
private readonly userRepositoriesUpdates = new Subject<void>()
|
||||
private readonly platformContext: PlatformContext = createPlatformContext()
|
||||
@ -311,7 +314,7 @@ export class SourcegraphWebApp extends React.Component<SourcegraphWebAppProps, S
|
||||
this.subscriptions.unsubscribe()
|
||||
}
|
||||
|
||||
public render(): React.ReactFragment | null {
|
||||
public render(): React.ReactNode {
|
||||
if (window.pageError && window.pageError.statusCode !== 404) {
|
||||
const statusCode = window.pageError.statusCode
|
||||
const statusText = window.pageError.statusText
|
||||
|
||||
@ -8,7 +8,7 @@ import styles from './Tooltip.module.scss'
|
||||
|
||||
const TOOLTIP_PADDING = createRectangle(0, 0, 10, 10)
|
||||
|
||||
export const Tooltip: React.FunctionComponent = props => {
|
||||
export const Tooltip: React.FunctionComponent<React.PropsWithChildren<{}>> = props => {
|
||||
const [virtualElement, setVirtualElement] = useState<PopoverPoint | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -22,7 +22,7 @@ interface Props {
|
||||
* Optional children that appear below the title bar that can be expanded/collapsed. If present,
|
||||
* a button that expands or collapses the children will be shown.
|
||||
*/
|
||||
children?: React.ReactFragment
|
||||
children?: React.ReactNode
|
||||
|
||||
/**
|
||||
* Whether the children are expanded and visible by default.
|
||||
@ -50,7 +50,7 @@ interface Props {
|
||||
* Collapsible is an element with a title that is always displayed and children that are displayed
|
||||
* only when expanded.
|
||||
*/
|
||||
export const Collapsible: React.FunctionComponent<React.PropsWithChildren<Props>> = ({
|
||||
export const Collapsible: React.FunctionComponent<Props> = ({
|
||||
title,
|
||||
detail,
|
||||
children,
|
||||
|
||||
@ -45,7 +45,7 @@ interface State {
|
||||
* Components should handle their own errors (and must not rely on this error boundary). This error
|
||||
* boundary is a last resort in case of an unexpected error.
|
||||
*/
|
||||
export class ErrorBoundary extends React.PureComponent<Props, State> {
|
||||
export class ErrorBoundary extends React.PureComponent<React.PropsWithChildren<Props>, State> {
|
||||
public state: State = {}
|
||||
|
||||
public static getDerivedStateFromError(error: any): Pick<State, 'error'> {
|
||||
|
||||
@ -16,7 +16,7 @@ const getBrandName = (): string => {
|
||||
|
||||
let titleSet = false
|
||||
|
||||
export const PageTitle: React.FunctionComponent<PageTitleProps> = ({ title }) => {
|
||||
export const PageTitle: React.FunctionComponent<React.PropsWithChildren<PageTitleProps>> = ({ title }) => {
|
||||
useEffect(() => {
|
||||
if (titleSet) {
|
||||
console.error('more than one PageTitle used at the same time')
|
||||
|
||||
@ -22,11 +22,11 @@ if (!window.context) {
|
||||
window.context = {} as SourcegraphContext & Mocha.SuiteFunction
|
||||
}
|
||||
|
||||
export interface WebStoryProps extends MemoryRouterProps, Pick<MockedStoryProviderProps, 'mocks' | 'useStrictMocking'> {
|
||||
export interface WebStoryProps
|
||||
extends Omit<MemoryRouterProps, 'children'>,
|
||||
Pick<MockedStoryProviderProps, 'mocks' | 'useStrictMocking'> {
|
||||
children: React.FunctionComponent<
|
||||
React.PropsWithChildren<
|
||||
ThemeProps & BreadcrumbSetters & BreadcrumbsProps & TelemetryProps & RouteComponentProps<any>
|
||||
>
|
||||
ThemeProps & BreadcrumbSetters & BreadcrumbsProps & TelemetryProps & RouteComponentProps<any>
|
||||
>
|
||||
}
|
||||
|
||||
@ -34,7 +34,7 @@ export interface WebStoryProps extends MemoryRouterProps, Pick<MockedStoryProvid
|
||||
* Wrapper component for webapp Storybook stories that provides light theme and react-router props.
|
||||
* Takes a render function as children that gets called with the props.
|
||||
*/
|
||||
export const WebStory: React.FunctionComponent<React.PropsWithChildren<WebStoryProps>> = ({
|
||||
export const WebStory: React.FunctionComponent<WebStoryProps> = ({
|
||||
children,
|
||||
mocks,
|
||||
useStrictMocking,
|
||||
|
||||
@ -111,7 +111,7 @@ export const FileDiffConnection: React.FunctionComponent<React.PropsWithChildren
|
||||
|
||||
const diffsUpdates = useMemo(() => new ReplaySubject<Connection<FileDiffFields> | ErrorLike | undefined>(1), [])
|
||||
const nextDiffsUpdate: FileDiffConnectionProps['onUpdate'] = useCallback(
|
||||
fileDiffsOrError => diffsUpdates.next(fileDiffsOrError),
|
||||
(fileDiffsOrError: Connection<FileDiffFields> | ErrorLike | undefined) => diffsUpdates.next(fileDiffsOrError),
|
||||
[diffsUpdates]
|
||||
)
|
||||
|
||||
|
||||
@ -59,7 +59,7 @@ export const FileDiffNode: React.FunctionComponent<React.PropsWithChildren<FileD
|
||||
setRenderDeleted(true)
|
||||
}, [])
|
||||
|
||||
let path: React.ReactFragment
|
||||
let path: React.ReactNode
|
||||
if (node.newPath && (node.newPath === node.oldPath || !node.oldPath)) {
|
||||
path = <span title={node.newPath}>{node.newPath}</span>
|
||||
} else if (node.newPath && node.oldPath && node.newPath !== node.oldPath) {
|
||||
@ -76,7 +76,7 @@ export const FileDiffNode: React.FunctionComponent<React.PropsWithChildren<FileD
|
||||
path = <span title={node.oldPath!}>{node.oldPath}</span>
|
||||
}
|
||||
|
||||
let stat: React.ReactFragment
|
||||
let stat: React.ReactNode
|
||||
// If one of the files was binary, display file size change instead of DiffStat.
|
||||
if (node.oldFile?.binary || node.newFile?.binary) {
|
||||
const sizeChange = (node.newFile?.byteSize ?? 0) - (node.oldFile?.byteSize ?? 0)
|
||||
|
||||
@ -59,7 +59,7 @@ export interface AddExternalServiceOptions {
|
||||
/**
|
||||
* Instructions that will appear on the add / edit page
|
||||
*/
|
||||
instructions?: JSX.Element | string
|
||||
instructions?: React.ReactNode | string
|
||||
|
||||
/**
|
||||
* The JSON schema of the external service configuration
|
||||
@ -119,15 +119,15 @@ const editorActionComments = {
|
||||
// (https://docs.sourcegraph.com/admin/repo/permissions#sudo-access-token).`,
|
||||
}
|
||||
|
||||
const Field = (props: { children: React.ReactChildren | string | string[] }): JSX.Element => (
|
||||
const Field: React.FunctionComponent<{ children: React.ReactNode | string | string[] }> = props => (
|
||||
<Code className="hljs-type">{props.children}</Code>
|
||||
)
|
||||
|
||||
const Value = (props: { children: React.ReactChildren | string | string[] }): JSX.Element => (
|
||||
const Value: React.FunctionComponent<{ children: React.ReactNode | string | string[] }> = props => (
|
||||
<Code className="hljs-attr">{props.children}</Code>
|
||||
)
|
||||
|
||||
const githubInstructions = (isEnterprise: boolean): JSX.Element => (
|
||||
const githubInstructions = (isEnterprise: boolean): React.ReactNode => (
|
||||
<div>
|
||||
<ol>
|
||||
{isEnterprise && (
|
||||
|
||||
@ -29,7 +29,7 @@ export interface BatchSpecProps extends ThemeProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
export const BatchSpec: React.FunctionComponent<React.PropsWithChildren<BatchSpecProps>> = ({
|
||||
export const BatchSpec: React.FunctionComponent<BatchSpecProps> = ({
|
||||
originalInput,
|
||||
isLightTheme,
|
||||
className,
|
||||
@ -90,19 +90,12 @@ export const BatchSpecDownloadLink: React.FunctionComponent<
|
||||
|
||||
// TODO: Consider merging this component with BatchSpecDownloadLink
|
||||
export const BatchSpecDownloadButton: React.FunctionComponent<
|
||||
React.PropsWithChildren<BatchSpecProps & Pick<BatchChangeFields, 'name'>>
|
||||
BatchSpecProps & Pick<BatchChangeFields, 'name'>
|
||||
> = React.memo(function BatchSpecDownloadButton(props) {
|
||||
return (
|
||||
<Button
|
||||
className="text-right text-nowrap"
|
||||
{...props}
|
||||
variant="secondary"
|
||||
outline={true}
|
||||
as={BatchSpecDownloadLink}
|
||||
asButton={false}
|
||||
>
|
||||
<BatchSpecDownloadLink className="text-right text-nowrap" {...props} asButton={false}>
|
||||
<Icon aria-hidden={true} svgPath={mdiFileDownload} /> Download YAML
|
||||
</Button>
|
||||
</BatchSpecDownloadLink>
|
||||
)
|
||||
})
|
||||
|
||||
|
||||
@ -35,7 +35,7 @@ export default config
|
||||
|
||||
export const Unstarted: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={new WildcardMockLink(UNSTARTED_CONNECTION_MOCKS)}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
@ -46,7 +46,7 @@ export const Unstarted: Story = () => (
|
||||
}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -55,7 +55,7 @@ export const Unstarted: Story = () => (
|
||||
|
||||
export const UnstartedWithCachedConnectionResult: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={new WildcardMockLink(UNSTARTED_WITH_CACHE_CONNECTION_MOCKS)}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
@ -66,7 +66,7 @@ export const UnstartedWithCachedConnectionResult: Story = () => (
|
||||
}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -113,7 +113,7 @@ export const QueuedInProgress: Story = () => {
|
||||
|
||||
return (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={inProgressConnectionMocks}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
@ -124,7 +124,7 @@ export const QueuedInProgress: Story = () => {
|
||||
}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -172,14 +172,14 @@ export const QueuedInProgressWithCachedConnectionResult: Story = () => {
|
||||
|
||||
return (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={inProgressConnectionMocks}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
batchSpec={mockBatchSpec()}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -228,14 +228,14 @@ export const FailedErrored: Story = () => {
|
||||
|
||||
return (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={failedConnectionMocks}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
batchSpec={mockBatchSpec()}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -284,14 +284,14 @@ export const FailedErroredWithCachedConnectionResult: Story = () => {
|
||||
|
||||
return (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={failedConnectionMocks}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
batchSpec={mockBatchSpec()}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -303,7 +303,7 @@ FailedErroredWithCachedConnectionResult.storyName = 'failed/errored, with cached
|
||||
|
||||
export const Succeeded: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={new WildcardMockLink(UNSTARTED_WITH_CACHE_CONNECTION_MOCKS)}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
@ -322,7 +322,7 @@ export const Succeeded: Story = () => (
|
||||
},
|
||||
}}
|
||||
>
|
||||
<WorkspacesPreview {...props} />
|
||||
<WorkspacesPreview />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
@ -331,14 +331,14 @@ export const Succeeded: Story = () => (
|
||||
|
||||
export const ReadOnly: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<MockedTestProvider link={new WildcardMockLink(UNSTARTED_WITH_CACHE_CONNECTION_MOCKS)}>
|
||||
<BatchSpecContextProvider
|
||||
batchChange={mockBatchChange()}
|
||||
batchSpec={mockBatchSpec()}
|
||||
refetchBatchChange={() => Promise.resolve()}
|
||||
>
|
||||
<WorkspacesPreview {...props} isReadOnly={true} />
|
||||
<WorkspacesPreview isReadOnly={true} />
|
||||
</BatchSpecContextProvider>
|
||||
</MockedTestProvider>
|
||||
)}
|
||||
|
||||
@ -22,9 +22,9 @@ export default config
|
||||
|
||||
export const Executing: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<BatchSpecContextProvider batchChange={mockBatchChange()} batchSpec={EXECUTING_BATCH_SPEC}>
|
||||
<ActionsMenu {...props} />
|
||||
<ActionsMenu />
|
||||
</BatchSpecContextProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -32,9 +32,9 @@ export const Executing: Story = () => (
|
||||
|
||||
export const Failed: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<BatchSpecContextProvider batchChange={mockBatchChange()} batchSpec={COMPLETED_WITH_ERRORS_BATCH_SPEC}>
|
||||
<ActionsMenu {...props} />
|
||||
<ActionsMenu />
|
||||
</BatchSpecContextProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -42,9 +42,9 @@ export const Failed: Story = () => (
|
||||
|
||||
export const Completed: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<BatchSpecContextProvider batchChange={mockBatchChange()} batchSpec={COMPLETED_BATCH_SPEC}>
|
||||
<ActionsMenu {...props} />
|
||||
<ActionsMenu />
|
||||
</BatchSpecContextProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
@ -52,9 +52,9 @@ export const Completed: Story = () => (
|
||||
|
||||
export const CompletedWithErrors: Story = () => (
|
||||
<WebStory>
|
||||
{props => (
|
||||
{() => (
|
||||
<BatchSpecContextProvider batchChange={mockBatchChange()} batchSpec={COMPLETED_WITH_ERRORS_BATCH_SPEC}>
|
||||
<ActionsMenu {...props} />
|
||||
<ActionsMenu />
|
||||
</BatchSpecContextProvider>
|
||||
)}
|
||||
</WebStory>
|
||||
|
||||
@ -33,7 +33,7 @@ import { CancelExecutionModal } from './CancelExecutionModal'
|
||||
|
||||
import styles from './ActionsMenu.module.scss'
|
||||
|
||||
export const ActionsMenu: React.FunctionComponent<React.PropsWithChildren<{}>> = () => {
|
||||
export const ActionsMenu: React.FunctionComponent = () => {
|
||||
const { batchChange, batchSpec, setActionsError } = useBatchSpecContext<BatchSpecExecutionFields>()
|
||||
|
||||
return <MemoizedActionsMenu batchChange={batchChange} batchSpec={batchSpec} setActionsError={setActionsError} />
|
||||
|
||||
@ -58,7 +58,7 @@ export const OldBatchChangePageContent: React.FunctionComponent<React.PropsWithC
|
||||
|
||||
return (
|
||||
<>
|
||||
<H2>1. Write a batch spec YAML file</H2>
|
||||
<H2 data-testid="batch-spec-yaml-file">1. Write a batch spec YAML file</H2>
|
||||
<Container className="mb-3">
|
||||
<Text className="mb-0">
|
||||
The batch spec (
|
||||
|
||||
@ -13,8 +13,6 @@ const config: Meta = {
|
||||
|
||||
export default config
|
||||
|
||||
export const CreatingNewBatchChangeFromSearch: Story = () => (
|
||||
<WebStory>{props => <SearchTemplatesBanner {...props} />}</WebStory>
|
||||
)
|
||||
export const CreatingNewBatchChangeFromSearch: Story = () => <WebStory>{() => <SearchTemplatesBanner />}</WebStory>
|
||||
|
||||
CreatingNewBatchChangeFromSearch.storyName = 'Creating new batch change from search'
|
||||
|
||||
@ -76,7 +76,11 @@ export const DeleteMonitorModal: React.FunctionComponent<React.PropsWithChildren
|
||||
)}
|
||||
</div>
|
||||
)}
|
||||
{deleteCompletedOrError && <div>{deleteCompletedOrError === 'loading' && <LoadingSpinner />}</div>}
|
||||
{/*
|
||||
* Issue: This JSX tag's 'children' prop expects a single child of type 'ReactNode', but multiple children were provided
|
||||
* It seems that v18 requires explicit boolean value
|
||||
*/}
|
||||
{!!deleteCompletedOrError && <div>{deleteCompletedOrError === 'loading' && <LoadingSpinner />}</div>}
|
||||
</Modal>
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,7 +3,7 @@ import React, { FunctionComponent, useCallback, useEffect, useMemo } from 'react
|
||||
import { useApolloClient } from '@apollo/client'
|
||||
import { mdiChevronRight } from '@mdi/js'
|
||||
import classNames from 'classnames'
|
||||
import { RouteComponentProps, useHistory } from 'react-router'
|
||||
import { RouteComponentProps, useHistory, useLocation } from 'react-router'
|
||||
import { Subject } from 'rxjs'
|
||||
|
||||
import { GitObjectType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
@ -81,6 +81,7 @@ export const CodeIntelConfigurationPage: FunctionComponent<
|
||||
useEffect(() => telemetryService.logViewEvent('CodeIntelConfiguration'), [telemetryService])
|
||||
|
||||
const history = useHistory()
|
||||
const location = useLocation<{ message: string; modal: string }>()
|
||||
|
||||
const apolloClient = useApolloClient()
|
||||
const queryPoliciesCallback = useCallback(
|
||||
@ -109,9 +110,7 @@ export const CodeIntelConfigurationPage: FunctionComponent<
|
||||
{authenticatedUser?.siteAdmin && <PolicyListActions history={history} />}
|
||||
</CodeIntelConfigurationPageHeader>
|
||||
|
||||
{history.location.state && (
|
||||
<FlashMessage state={history.location.state.modal} message={history.location.state.message} />
|
||||
)}
|
||||
{location.state && <FlashMessage state={location.state.modal} message={location.state.message} />}
|
||||
<Container>
|
||||
<FilteredConnection<CodeIntelligenceConfigurationPolicyFields, {}>
|
||||
listComponent="div"
|
||||
|
||||
@ -3,7 +3,7 @@ import { FunctionComponent, useCallback, useEffect, useState } from 'react'
|
||||
import { ApolloError } from '@apollo/client'
|
||||
import { mdiDelete } from '@mdi/js'
|
||||
import * as H from 'history'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
import { RouteComponentProps, useLocation } from 'react-router'
|
||||
|
||||
import { ErrorAlert } from '@sourcegraph/branded/src/components/alerts'
|
||||
import { GitObjectType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
@ -45,6 +45,7 @@ export const CodeIntelConfigurationPolicyPage: FunctionComponent<
|
||||
lockfileIndexingEnabled = window.context?.codeIntelLockfileIndexingEnabled,
|
||||
}) => {
|
||||
useEffect(() => telemetryService.logViewEvent('CodeIntelConfigurationPolicy'), [telemetryService])
|
||||
const location = useLocation<{ message: string; modal: string }>()
|
||||
|
||||
const { policyConfig, loadingPolicyConfig, policyConfigError } = usePolicyConfigurationByID(id)
|
||||
const [saved, setSaved] = useState<CodeIntelligenceConfigurationPolicyFields>()
|
||||
@ -129,9 +130,7 @@ export const CodeIntelConfigurationPolicyPage: FunctionComponent<
|
||||
{savingError && <ErrorAlert prefix="Error saving configuration policy" error={savingError} />}
|
||||
{deleteError && <ErrorAlert prefix="Error deleting configuration policy" error={deleteError} />}
|
||||
|
||||
{history.location.state && (
|
||||
<FlashMessage state={history.location.state.modal} message={history.location.state.message} />
|
||||
)}
|
||||
{location.state && <FlashMessage state={location.state.modal} message={location.state.message} />}
|
||||
|
||||
{policy.protected ? (
|
||||
<Alert variant="info">
|
||||
|
||||
@ -2,7 +2,7 @@ import { FunctionComponent, useCallback, useEffect, useMemo } from 'react'
|
||||
|
||||
import { useApolloClient } from '@apollo/client'
|
||||
import classNames from 'classnames'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
import { RouteComponentProps, useLocation } from 'react-router'
|
||||
import { Subject } from 'rxjs'
|
||||
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
@ -73,7 +73,7 @@ const filters: FilteredConnectionFilter[] = [
|
||||
},
|
||||
]
|
||||
|
||||
export const CodeIntelIndexesPage: FunctionComponent<React.PropsWithChildren<CodeIntelIndexesPageProps>> = ({
|
||||
export const CodeIntelIndexesPage: FunctionComponent<CodeIntelIndexesPageProps> = ({
|
||||
authenticatedUser,
|
||||
repo,
|
||||
queryLsifIndexListByRepository = defaultQueryLsifIndexListByRepository,
|
||||
@ -81,9 +81,9 @@ export const CodeIntelIndexesPage: FunctionComponent<React.PropsWithChildren<Cod
|
||||
now,
|
||||
telemetryService,
|
||||
history,
|
||||
...props
|
||||
}) => {
|
||||
useEffect(() => telemetryService.logViewEvent('CodeIntelIndexes'), [telemetryService])
|
||||
const location = useLocation<{ message: string; modal: string }>()
|
||||
|
||||
const apolloClient = useApolloClient()
|
||||
const queryIndexes = useCallback(
|
||||
@ -109,15 +109,13 @@ export const CodeIntelIndexesPage: FunctionComponent<React.PropsWithChildren<Cod
|
||||
className="mb-3"
|
||||
/>
|
||||
|
||||
{repo && authenticatedUser?.siteAdmin && (
|
||||
{!!repo && !!authenticatedUser?.siteAdmin && (
|
||||
<Container className="mb-2">
|
||||
<EnqueueForm repoId={repo.id} querySubject={querySubject} />
|
||||
</Container>
|
||||
)}
|
||||
|
||||
{history.location.state && (
|
||||
<FlashMessage state={history.location.state.modal} message={history.location.state.message} />
|
||||
)}
|
||||
{!!location.state && <FlashMessage state={location.state.modal} message={location.state.message} />}
|
||||
|
||||
<Container>
|
||||
<div className="list-group position-relative">
|
||||
@ -132,7 +130,7 @@ export const CodeIntelIndexesPage: FunctionComponent<React.PropsWithChildren<Cod
|
||||
nodeComponentProps={{ now }}
|
||||
queryConnection={queryIndexes}
|
||||
history={history}
|
||||
location={props.location}
|
||||
location={location}
|
||||
cursorPaging={true}
|
||||
filters={filters}
|
||||
emptyElement={<EmptyAutoIndex />}
|
||||
|
||||
@ -2,7 +2,7 @@ import { FunctionComponent, useCallback, useEffect, useMemo, useState } from 're
|
||||
|
||||
import { useApolloClient } from '@apollo/client'
|
||||
import classNames from 'classnames'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
import { RouteComponentProps, useLocation } from 'react-router'
|
||||
import { of } from 'rxjs'
|
||||
|
||||
import { TelemetryProps } from '@sourcegraph/shared/src/telemetry/telemetryService'
|
||||
@ -102,6 +102,7 @@ export const CodeIntelUploadsPage: FunctionComponent<React.PropsWithChildren<Cod
|
||||
...props
|
||||
}) => {
|
||||
useEffect(() => telemetryService.logViewEvent('CodeIntelUploads'), [telemetryService])
|
||||
const location = useLocation<{ message: string; modal: string }>()
|
||||
|
||||
const apolloClient = useApolloClient()
|
||||
const queryLsifUploads = useCallback(
|
||||
@ -124,14 +125,14 @@ export const CodeIntelUploadsPage: FunctionComponent<React.PropsWithChildren<Cod
|
||||
|
||||
const [deleteStatus, setDeleteStatus] = useState({ isDeleting: false, message: '', state: '' })
|
||||
useEffect(() => {
|
||||
if (history.location.state) {
|
||||
if (location.state) {
|
||||
setDeleteStatus({
|
||||
isDeleting: true,
|
||||
message: history.location.state.message,
|
||||
state: history.location.state.modal,
|
||||
message: location.state.message,
|
||||
state: location.state.modal,
|
||||
})
|
||||
}
|
||||
}, [history.location.state])
|
||||
}, [location.state])
|
||||
|
||||
return (
|
||||
<div className="code-intel-uploads">
|
||||
|
||||
@ -1,11 +1,13 @@
|
||||
import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { EmbeddedWebApp } from './EmbeddedWebApp'
|
||||
|
||||
// It's important to have a root component in a separate file to create a react-refresh boundary and avoid page reload.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
render(<EmbeddedWebApp />, document.querySelector('#root'))
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(<EmbeddedWebApp />)
|
||||
})
|
||||
|
||||
@ -38,7 +38,7 @@ export const CodeInsightsCreationActions: FC<CodeInsightsCreationActionsProps> =
|
||||
)}
|
||||
|
||||
<div className="d-flex flex-wrap align-items-center">
|
||||
{errors && <ErrorAlert className="w-100" error={errors} />}
|
||||
{!!errors && <ErrorAlert className="w-100" error={errors} />}
|
||||
|
||||
<LoaderButton
|
||||
type="submit"
|
||||
|
||||
@ -49,7 +49,7 @@ const LivePreviewBanner: React.FunctionComponent<React.PropsWithChildren<unknown
|
||||
|
||||
interface LivePreviewChartProps extends React.ComponentProps<typeof ParentSize> {}
|
||||
|
||||
const LivePreviewChart: React.FunctionComponent<React.PropsWithChildren<LivePreviewChartProps>> = props => (
|
||||
const LivePreviewChart: React.FunctionComponent<LivePreviewChartProps> = props => (
|
||||
<ParentSize {...props} className={classNames(styles.chartBlock, props.className)} />
|
||||
)
|
||||
|
||||
|
||||
@ -20,7 +20,7 @@ function getDefaultInputStatus<T>({ meta }: useFieldAPI<T>): InputStatus {
|
||||
return InputStatus.initial
|
||||
}
|
||||
|
||||
function getDefaultInputError<T>({ meta }: useFieldAPI<T>): Pick<InputProps, 'error'> {
|
||||
function getDefaultInputError<T>({ meta }: useFieldAPI<T>): InputProps['error'] {
|
||||
return meta.touched && meta.error
|
||||
}
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import React, { memo, useCallback, useEffect, useState } from 'react'
|
||||
import React, { memo, useCallback, useLayoutEffect, useState } from 'react'
|
||||
|
||||
import { isEqual } from 'lodash'
|
||||
import { Layout, Layouts } from 'react-grid-layout'
|
||||
@ -32,7 +32,7 @@ export const SmartInsightsViewGrid: React.FunctionComponent<React.PropsWithChild
|
||||
const [layouts, setLayouts] = useState<Layouts>({})
|
||||
const [resizingView, setResizeView] = useState<Layout | null>(null)
|
||||
|
||||
useEffect(() => {
|
||||
useLayoutEffect(() => {
|
||||
setLayouts(insightLayoutGenerator(insights))
|
||||
}, [insights])
|
||||
|
||||
|
||||
@ -114,7 +114,12 @@ interface ToggleButtonProps {
|
||||
onClick: (value: SeriesSortOptionsInput) => void
|
||||
}
|
||||
|
||||
const ToggleButton: React.FunctionComponent<ToggleButtonProps> = ({ selected, value, children, onClick }) => (
|
||||
const ToggleButton: React.FunctionComponent<React.PropsWithChildren<ToggleButtonProps>> = ({
|
||||
selected,
|
||||
value,
|
||||
children,
|
||||
onClick,
|
||||
}) => (
|
||||
<Button variant="secondary" size="sm" className={getClasses(selected, value)} onClick={() => onClick(value)}>
|
||||
{children}
|
||||
</Button>
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { renderHook, act } from '@testing-library/react-hooks'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
import { Observable, ObservableInput, of } from 'rxjs'
|
||||
import { delay, map, switchMap, tap } from 'rxjs/operators'
|
||||
import sinon from 'sinon'
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import React from 'react'
|
||||
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
|
||||
import { CodeInsightsBackend, CodeInsightsBackendContext, CodeInsightsGqlBackend } from '../core'
|
||||
|
||||
|
||||
@ -44,9 +44,7 @@ export interface InsightsDashboardCreationContentProps {
|
||||
/**
|
||||
* Renders creation UI form content (fields, submit and cancel buttons).
|
||||
*/
|
||||
export const InsightsDashboardCreationContent: React.FunctionComponent<
|
||||
React.PropsWithChildren<InsightsDashboardCreationContentProps>
|
||||
> = props => {
|
||||
export const InsightsDashboardCreationContent: React.FunctionComponent<InsightsDashboardCreationContentProps> = props => {
|
||||
const { initialValues, owners, onSubmit, children } = props
|
||||
|
||||
const { UIFeatures } = useContext(CodeInsightsBackendContext)
|
||||
|
||||
@ -173,10 +173,8 @@ const triggerDashboardMenuItem = async (
|
||||
|
||||
const dashboardMenuItem = screen.getByTestId(testId)
|
||||
|
||||
// We're simulating keyboard navigation here to circumvent a bug in ReachUI
|
||||
// does not respond to programmatic click events on menu items
|
||||
dashboardMenuItem.focus()
|
||||
user.keyboard(' ')
|
||||
user.click(dashboardMenuItem)
|
||||
}
|
||||
|
||||
beforeEach(() => {
|
||||
|
||||
@ -41,7 +41,7 @@ interface ComputeLivePreviewProps {
|
||||
}[]
|
||||
}
|
||||
|
||||
export const ComputeLivePreview: React.FunctionComponent<React.PropsWithChildren<ComputeLivePreviewProps>> = props => {
|
||||
export const ComputeLivePreview: React.FunctionComponent<ComputeLivePreviewProps> = props => {
|
||||
// For the purposes of building out this component before the backend is ready
|
||||
// we are using the standard "line series" type data.
|
||||
// TODO after backend is merged, remove update the series value to use that structure
|
||||
|
||||
@ -42,9 +42,7 @@ interface LineChartLivePreviewProps {
|
||||
series: LivePreviewSeries[]
|
||||
}
|
||||
|
||||
export const LineChartLivePreview: React.FunctionComponent<
|
||||
React.PropsWithChildren<LineChartLivePreviewProps>
|
||||
> = props => {
|
||||
export const LineChartLivePreview: React.FunctionComponent<LineChartLivePreviewProps> = props => {
|
||||
const { disabled, repositories, stepValue, step, series, isAllReposMode, className } = props
|
||||
const { getInsightPreviewContent: getLivePreviewContent } = useContext(CodeInsightsBackendContext)
|
||||
const seriesToggleState = useSeriesToggle()
|
||||
|
||||
@ -35,9 +35,7 @@ export interface LangStatsInsightLivePreviewProps {
|
||||
* Displays live preview chart for creation UI with the latest insights settings
|
||||
* from creation UI form.
|
||||
*/
|
||||
export const LangStatsInsightLivePreview: React.FunctionComponent<
|
||||
React.PropsWithChildren<LangStatsInsightLivePreviewProps>
|
||||
> = props => {
|
||||
export const LangStatsInsightLivePreview: React.FunctionComponent<LangStatsInsightLivePreviewProps> = props => {
|
||||
const { repository = '', threshold, disabled = false, className } = props
|
||||
const { getLangStatsInsightContent } = useContext(CodeInsightsBackendContext)
|
||||
|
||||
|
||||
@ -7,12 +7,14 @@ import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import '../monitoring/initMonitoring'
|
||||
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { EnterpriseWebApp } from './EnterpriseWebApp'
|
||||
|
||||
// It's important to have a root component in a separate file to create a react-refresh boundary and avoid page reload.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
render(<EnterpriseWebApp />, document.querySelector('#root'))
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(<EnterpriseWebApp />)
|
||||
})
|
||||
|
||||
@ -1,5 +1,7 @@
|
||||
import { FunctionComponent, useCallback, useState } from 'react'
|
||||
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
|
||||
import { Form } from '@sourcegraph/branded/src/components/Form'
|
||||
import { gql, useLazyQuery, useMutation } from '@sourcegraph/http-client'
|
||||
import { IFeatureFlagOverride } from '@sourcegraph/shared/src/schema'
|
||||
@ -41,6 +43,9 @@ export const CREATE_FEATURE_FLAG_OVERRIDE = gql`
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export interface EarlyAccessOrgsCodeFormProps extends RouteComponentProps<{}> {}
|
||||
|
||||
/**
|
||||
* Form that sets a feature flag override for org-code flag, based on organization name.
|
||||
* This enables 2 screens - organization code host connections and organization repositories.
|
||||
@ -51,7 +56,7 @@ export const CREATE_FEATURE_FLAG_OVERRIDE = gql`
|
||||
* This implementation is a quick hack for making our lives easier while in early access
|
||||
* stage. IMO it's not worth a lot of effort as it is throwaway work once we are in GA.
|
||||
*/
|
||||
export const EarlyAccessOrgsCodeForm: FunctionComponent<any> = () => {
|
||||
export const EarlyAccessOrgsCodeForm: FunctionComponent<EarlyAccessOrgsCodeFormProps> = () => {
|
||||
const [name, setName] = useState<string>('')
|
||||
|
||||
const [updateFeatureFlag, { data, loading: flagLoading, error: flagError }] = useMutation<
|
||||
|
||||
@ -8,16 +8,16 @@ import styles from './ProductCertificate.module.scss'
|
||||
|
||||
interface Props {
|
||||
/** The title of the certificate. */
|
||||
title: React.ReactFragment
|
||||
title: React.ReactNode
|
||||
|
||||
/** The subtitle of the certificate. */
|
||||
subtitle?: React.ReactFragment | null
|
||||
subtitle?: React.ReactNode
|
||||
|
||||
/** The detail text of the certificate. */
|
||||
detail?: React.ReactFragment | null
|
||||
detail?: React.ReactNode
|
||||
|
||||
/** Rendered after the certificate body (usually consists of a Wildcard <CardFooter />). */
|
||||
footer?: React.ReactFragment | null
|
||||
footer?: React.ReactNode
|
||||
|
||||
className?: string
|
||||
}
|
||||
|
||||
@ -180,7 +180,7 @@ export const SearchContextForm: React.FunctionComponent<React.PropsWithChildren<
|
||||
const [hasRepositoriesConfigChanged, setHasRepositoriesConfigChanged] = useState(false)
|
||||
const [repositoriesConfig, setRepositoriesConfig] = useState('')
|
||||
const onRepositoriesConfigChange = useCallback(
|
||||
(config, isInitialValue) => {
|
||||
(config: string, isInitialValue?: boolean) => {
|
||||
setRepositoriesConfig(config)
|
||||
if (!isInitialValue && config !== repositoriesConfig) {
|
||||
setHasRepositoriesConfigChanged(true)
|
||||
|
||||
@ -80,7 +80,7 @@ interface Props extends ThemeProps {
|
||||
primaryButtonTextNoPaymentRequired?: string
|
||||
|
||||
/** A fragment to render below the form's primary button. */
|
||||
afterPrimaryButton?: React.ReactFragment
|
||||
afterPrimaryButton?: React.ReactNode
|
||||
|
||||
history: H.History
|
||||
}
|
||||
|
||||
@ -149,12 +149,16 @@ export class ExternalAccountNode extends React.PureComponent<ExternalAccountNode
|
||||
</small>
|
||||
</div>
|
||||
<div className="text-nowrap">
|
||||
{this.props.node.accountData && (
|
||||
{/*
|
||||
* Issue: This JSX tag's 'children' prop expects a single child of type 'ReactNode', but multiple children were provided
|
||||
* It seems that v18 requires explicit boolean value
|
||||
*/}
|
||||
{!!this.props.node.accountData && (
|
||||
<Button onClick={this.toggleShowData} variant="secondary">
|
||||
{this.state.showData ? 'Hide' : 'Show'} data
|
||||
</Button>
|
||||
)}{' '}
|
||||
{this.props.node.refreshURL && (
|
||||
{!!this.props.node.refreshURL && (
|
||||
<Button href={this.props.node.refreshURL} variant="secondary" as="a">
|
||||
Refresh
|
||||
</Button>
|
||||
|
||||
@ -24,7 +24,7 @@ interface ContributionGroup {
|
||||
title: string
|
||||
error?: ErrorLike
|
||||
columnHeaders: string[]
|
||||
rows: (React.ReactFragment | null)[][]
|
||||
rows: React.ReactNode[][]
|
||||
}
|
||||
|
||||
const ContributionsTable: React.FunctionComponent<
|
||||
|
||||
@ -49,7 +49,7 @@ const FeatureFlagsLocalOverrideAgent = React.memo(() => {
|
||||
})
|
||||
|
||||
const MINUTE = 60000
|
||||
export const FeatureFlagsProvider: React.FunctionComponent<FeatureFlagsProviderProps> = ({
|
||||
export const FeatureFlagsProvider: React.FunctionComponent<React.PropsWithChildren<FeatureFlagsProviderProps>> = ({
|
||||
isLocalOverrideEnabled = true,
|
||||
children,
|
||||
}) => {
|
||||
@ -77,11 +77,9 @@ interface MockedFeatureFlagsProviderProps {
|
||||
* <ComponentUsingFeatureFlag />
|
||||
* </MockedFeatureFlagsProvider>)
|
||||
*/
|
||||
export const MockedFeatureFlagsProvider: React.FunctionComponent<MockedFeatureFlagsProviderProps> = ({
|
||||
overrides,
|
||||
refetchInterval,
|
||||
children,
|
||||
}) => {
|
||||
export const MockedFeatureFlagsProvider: React.FunctionComponent<
|
||||
React.PropsWithChildren<MockedFeatureFlagsProviderProps>
|
||||
> = ({ overrides, refetchInterval, children }) => {
|
||||
const mockRequestGraphQL = useMemo(
|
||||
() => (
|
||||
query: string,
|
||||
|
||||
@ -1,4 +1,6 @@
|
||||
import { renderHook } from '@testing-library/react-hooks'
|
||||
import React from 'react'
|
||||
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
|
||||
import { FeatureFlagName } from './featureFlags'
|
||||
import { MockedFeatureFlagsProvider } from './FeatureFlagsProvider'
|
||||
@ -9,25 +11,49 @@ describe('useFeatureFlag', () => {
|
||||
const DISABLED_FLAG = 'disabled-flag' as FeatureFlagName
|
||||
const ERROR_FLAG = 'error-flag' as FeatureFlagName
|
||||
const NON_EXISTING_FLAG = 'non-existing-flag' as FeatureFlagName
|
||||
const setup = (initialFlagName: FeatureFlagName, defaultValue = false, refetchInterval?: number) =>
|
||||
renderHook(({ flagName }) => useFeatureFlag(flagName, defaultValue), {
|
||||
wrapper: function Wrapper({ children, overrides }) {
|
||||
return (
|
||||
<MockedFeatureFlagsProvider
|
||||
overrides={overrides as Partial<Record<FeatureFlagName, boolean>>}
|
||||
refetchInterval={refetchInterval}
|
||||
>
|
||||
{children}
|
||||
</MockedFeatureFlagsProvider>
|
||||
)
|
||||
},
|
||||
|
||||
const Wrapper: React.JSXElementConstructor<{
|
||||
children: React.ReactElement
|
||||
nextOverrides?: Partial<Record<FeatureFlagName, boolean | Error>>
|
||||
refetchInterval?: number
|
||||
}> = ({ nextOverrides, children, refetchInterval }) => {
|
||||
// New `renderHook` doesn't pass any props into Wrapper component like the old one
|
||||
// so couldn't find a way to reproduce `state.setRender(...)` with custom `overrides`
|
||||
// we have to use this state together with `nextOverrides` as an alternative.
|
||||
const [overrides, setOverrides] = React.useState({
|
||||
[ENABLED_FLAG]: true,
|
||||
[DISABLED_FLAG]: false,
|
||||
[ERROR_FLAG]: new Error('Some error'),
|
||||
})
|
||||
|
||||
React.useEffect(() => {
|
||||
setTimeout(() => {
|
||||
setOverrides(current => nextOverrides ?? current)
|
||||
}, refetchInterval)
|
||||
}, [nextOverrides, refetchInterval])
|
||||
|
||||
return (
|
||||
<MockedFeatureFlagsProvider overrides={overrides} refetchInterval={refetchInterval}>
|
||||
{children}
|
||||
</MockedFeatureFlagsProvider>
|
||||
)
|
||||
}
|
||||
|
||||
const setup = (
|
||||
initialFlagName: FeatureFlagName,
|
||||
defaultValue = false,
|
||||
refetchInterval?: number,
|
||||
nextOverrides?: Partial<Record<FeatureFlagName, boolean | Error>>
|
||||
) =>
|
||||
renderHook<
|
||||
ReturnType<typeof useFeatureFlag>,
|
||||
{
|
||||
flagName: FeatureFlagName
|
||||
}
|
||||
>(({ flagName }) => useFeatureFlag(flagName, defaultValue), {
|
||||
wrapper: props => <Wrapper refetchInterval={refetchInterval} nextOverrides={nextOverrides} {...props} />,
|
||||
initialProps: {
|
||||
flagName: initialFlagName,
|
||||
overrides: {
|
||||
[ENABLED_FLAG]: true,
|
||||
[DISABLED_FLAG]: false,
|
||||
[ERROR_FLAG]: new Error('Some error'),
|
||||
},
|
||||
},
|
||||
})
|
||||
|
||||
@ -37,20 +63,19 @@ describe('useFeatureFlag', () => {
|
||||
expect(state.result.current).toStrictEqual([false, 'initial', undefined])
|
||||
|
||||
// Loaded state
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([false, 'loaded', undefined])
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([false, 'loaded', undefined]))
|
||||
})
|
||||
|
||||
it('returns [defaultValue=true] correctly', async () => {
|
||||
const state = setup(NON_EXISTING_FLAG, true)
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toEqual(expect.arrayContaining([true, 'loaded']))
|
||||
|
||||
await waitFor(() => expect(state.result.current).toEqual(expect.arrayContaining([true, 'loaded'])))
|
||||
})
|
||||
|
||||
it('returns [defaultValue=false] correctly', async () => {
|
||||
const state = setup(NON_EXISTING_FLAG, false)
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toEqual(expect.arrayContaining([false, 'loaded']))
|
||||
|
||||
await waitFor(() => expect(state.result.current).toEqual(expect.arrayContaining([false, 'loaded'])))
|
||||
})
|
||||
|
||||
it('returns [true] value correctly', async () => {
|
||||
@ -59,51 +84,44 @@ describe('useFeatureFlag', () => {
|
||||
expect(state.result.current).toStrictEqual([false, 'initial', undefined])
|
||||
|
||||
// Loaded state
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([true, 'loaded', undefined])
|
||||
expect(state.result.all.length).toBe(2)
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([true, 'loaded', undefined]))
|
||||
})
|
||||
|
||||
it('updates on value change', async () => {
|
||||
const state = setup(ENABLED_FLAG, false, 100)
|
||||
const state = setup(ENABLED_FLAG, false, 100, { [ENABLED_FLAG]: false })
|
||||
// Initial state
|
||||
expect(state.result.current).toStrictEqual([false, 'initial', undefined])
|
||||
|
||||
// Loaded state
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([true, 'loaded', undefined])
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([true, 'loaded', undefined]))
|
||||
|
||||
// Rerender and wait for new state
|
||||
state.rerender({ overrides: { [ENABLED_FLAG]: false }, flagName: ENABLED_FLAG })
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([false, 'loaded', undefined])
|
||||
state.rerender({ flagName: ENABLED_FLAG })
|
||||
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([false, 'loaded', undefined]))
|
||||
})
|
||||
|
||||
it('updates when feature flag prop changes', async () => {
|
||||
const state = setup(ENABLED_FLAG)
|
||||
const state = setup(ENABLED_FLAG, false, undefined, {})
|
||||
// Initial state
|
||||
expect(state.result.all[0]).toStrictEqual([false, 'initial', undefined])
|
||||
expect(state.result.current).toStrictEqual([false, 'initial', undefined])
|
||||
// Loaded state
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([true, 'loaded', undefined])
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([true, 'loaded', undefined]))
|
||||
|
||||
// Rerender and wait for new state
|
||||
state.rerender({ overrides: {}, flagName: DISABLED_FLAG })
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toStrictEqual([false, 'loaded', undefined])
|
||||
state.rerender({ flagName: DISABLED_FLAG })
|
||||
await waitFor(() => expect(state.result.current).toStrictEqual([false, 'loaded', undefined]))
|
||||
})
|
||||
|
||||
it('returns "error" when no context parent', () => {
|
||||
const state = renderHook(() => useFeatureFlag(ENABLED_FLAG))
|
||||
// Initial state
|
||||
expect(state.result.all[0]).toStrictEqual([false, 'initial', undefined])
|
||||
// Loaded state
|
||||
|
||||
expect(state.result.current).toEqual(expect.arrayContaining([false, 'error']))
|
||||
})
|
||||
|
||||
it('returns "error" when unhandled error', async () => {
|
||||
const state = setup(ERROR_FLAG)
|
||||
await state.waitForNextUpdate()
|
||||
expect(state.result.current).toEqual(expect.arrayContaining([false, 'error']))
|
||||
|
||||
await waitFor(() => expect(state.result.current).toEqual(expect.arrayContaining([false, 'error'])))
|
||||
})
|
||||
})
|
||||
|
||||
@ -583,6 +583,7 @@ describe('Batches', () => {
|
||||
// TODO: SSBC has to go through accessibility audits before this can pass.
|
||||
it.skip('is styled correctly', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/batch-changes/create')
|
||||
await driver.page.waitForSelector('[data-testid="batch-spec-yaml-file"]')
|
||||
await percySnapshotWithVariants(driver.page, 'Create batch change')
|
||||
await accessibilityAudit(driver.page)
|
||||
})
|
||||
|
||||
@ -72,9 +72,9 @@ describe('Blob viewer', () => {
|
||||
describe('general layout for viewing a file', () => {
|
||||
it('populates editor content and FILES tab', async () => {
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/${repositoryName}/-/blob/${fileName}`)
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
const blobContent = await driver.page.evaluate(
|
||||
() => document.querySelector<HTMLElement>('.test-repo-blob')?.textContent
|
||||
() => document.querySelector<HTMLElement>('[data-testid="repo-blob"]')?.textContent
|
||||
)
|
||||
|
||||
// editor shows the return string content from Blob request
|
||||
@ -144,13 +144,13 @@ describe('Blob viewer', () => {
|
||||
|
||||
it('should redirect from line number hash to query parameter', async () => {
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/${repositoryName}/-/blob/${fileName}#2`)
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
await driver.assertWindowLocation(`/${repositoryName}/-/blob/${fileName}?L2`)
|
||||
})
|
||||
|
||||
it('should redirect from line range hash to query parameter', async () => {
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/${repositoryName}/-/blob/${fileName}#1-3`)
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
await driver.assertWindowLocation(`/${repositoryName}/-/blob/${fileName}?L1-3`)
|
||||
})
|
||||
})
|
||||
@ -271,7 +271,7 @@ describe('Blob viewer', () => {
|
||||
await driver.page.goto(
|
||||
`${driver.sourcegraphBaseUrl}/${repositoryName}/-/blob/this_is_a_long_file_path/apps/rest-showcase/src/main/java/org/demo/rest/example/OrdersController.java`
|
||||
)
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
await driver.page.waitForSelector('.test-breadcrumb')
|
||||
// Uncomment this snapshot once https://github.com/sourcegraph/sourcegraph/issues/15126 is resolved
|
||||
// await percySnapshot(driver.page, this.test!.fullTitle())
|
||||
@ -279,7 +279,7 @@ describe('Blob viewer', () => {
|
||||
|
||||
it.skip('shows a hover overlay from a hover provider when a token is hovered', async () => {
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/${repositoryName}/-/blob/${fileName}`)
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
// TODO
|
||||
})
|
||||
|
||||
@ -513,7 +513,7 @@ describe('Blob viewer', () => {
|
||||
response.type('application/javascript; charset=utf-8').send(extensionBundleString)
|
||||
})
|
||||
}
|
||||
const timeout = 5000
|
||||
const timeout = 10000
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/github.com/sourcegraph/test/-/blob/test.ts`)
|
||||
|
||||
// Wait for some line decoration attachment portal
|
||||
@ -785,6 +785,7 @@ describe('Blob viewer', () => {
|
||||
|
||||
const timeout = 5000
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/github.com/sourcegraph/test/-/blob/test.ts`)
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
|
||||
// File 1 (test.ts). Only line two contains 'word'
|
||||
try {
|
||||
@ -1072,7 +1073,7 @@ describe('Blob viewer', () => {
|
||||
await driver.page.waitForFunction(
|
||||
() =>
|
||||
document
|
||||
.querySelector('.test-repo-blob [data-line="1"]')
|
||||
.querySelector('[data-testid="repo-blob"] [data-line="1"]')
|
||||
?.nextElementSibling?.textContent?.includes('file path: test spaces.ts'),
|
||||
{ timeout: 5000 }
|
||||
)
|
||||
|
||||
@ -87,6 +87,8 @@ describe('GlobalNavbar', () => {
|
||||
|
||||
test('is highlighted on search page', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/search?q=test&patternType=regexp')
|
||||
await driver.page.waitForSelector('[data-test-id="/search"]')
|
||||
await driver.page.waitForSelector('[data-test-active="true"]')
|
||||
|
||||
const active = await driver.page.evaluate(() =>
|
||||
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
|
||||
@ -97,6 +99,8 @@ describe('GlobalNavbar', () => {
|
||||
|
||||
test('is highlighted on repo page', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/github.com/sourcegraph/sourcegraph')
|
||||
await driver.page.waitForSelector('[data-test-id="/search"]')
|
||||
await driver.page.waitForSelector('[data-test-active="true"]')
|
||||
|
||||
const active = await driver.page.evaluate(() =>
|
||||
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
|
||||
@ -107,6 +111,8 @@ describe('GlobalNavbar', () => {
|
||||
|
||||
test('is highlighted on repo file page', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/github.com/sourcegraph/sourcegraph/-/blob/README.md')
|
||||
await driver.page.waitForSelector('[data-test-id="/search"]')
|
||||
await driver.page.waitForSelector('[data-test-active="true"]')
|
||||
|
||||
const active = await driver.page.evaluate(() =>
|
||||
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
|
||||
@ -117,6 +123,8 @@ describe('GlobalNavbar', () => {
|
||||
|
||||
test('is not highlighted on notebook page', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/notebooks/id')
|
||||
await driver.page.waitForSelector('[data-test-id="/search"]')
|
||||
await driver.page.waitForSelector('[data-test-active="false"]')
|
||||
|
||||
const active = await driver.page.evaluate(() =>
|
||||
document.querySelector('[data-test-id="/search"]')?.getAttribute('data-test-active')
|
||||
|
||||
@ -61,9 +61,9 @@ describe('User profile page', () => {
|
||||
UpdateUser: () => ({ updateUser: { ...USER, displayName: 'Test2' } }),
|
||||
})
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/users/test/settings/profile')
|
||||
await driver.page.waitForSelector('[data-testid="user-profile-form-fields"]')
|
||||
await percySnapshotWithVariants(driver.page, 'User Profile Settings Page')
|
||||
await accessibilityAudit(driver.page)
|
||||
await driver.page.waitForSelector('[data-testid="user-profile-form-fields"]')
|
||||
await driver.replaceText({
|
||||
selector: '[data-testid="test-UserProfileFormFields__displayName"]',
|
||||
newText: 'Test2',
|
||||
|
||||
@ -82,6 +82,7 @@ describe('Repository', () => {
|
||||
const shortRepositoryName = 'sourcegraph/jsonrpc2'
|
||||
const repositoryName = `github.com/${shortRepositoryName}`
|
||||
const repositorySourcegraphUrl = `/${repositoryName}`
|
||||
const commitUrl = `${repositorySourcegraphUrl}/-/commit/15c2290dcb37731cc4ee5a2a1c1e5a25b4c28f81?visible=1`
|
||||
const clickedFileName = 'async.go'
|
||||
const clickedCommit = ''
|
||||
const fileEntries = ['jsonrpc2.go', clickedFileName]
|
||||
@ -136,10 +137,8 @@ describe('Repository', () => {
|
||||
'/github.com/sourcegraph/jsonrpc2/-/commit/9e615b1c32cc519130575e8d10d0d0fee8a5eb6c',
|
||||
},
|
||||
],
|
||||
url:
|
||||
'/github.com/sourcegraph/jsonrpc2/-/commit/15c2290dcb37731cc4ee5a2a1c1e5a25b4c28f81',
|
||||
canonicalURL:
|
||||
'/github.com/sourcegraph/jsonrpc2/-/commit/15c2290dcb37731cc4ee5a2a1c1e5a25b4c28f81',
|
||||
url: commitUrl,
|
||||
canonicalURL: commitUrl,
|
||||
externalURLs: [
|
||||
{
|
||||
url:
|
||||
@ -312,9 +311,8 @@ describe('Repository', () => {
|
||||
'/github.com/sourcegraph/jsonrpc2/-/commit/9e615b1c32cc519130575e8d10d0d0fee8a5eb6c',
|
||||
},
|
||||
],
|
||||
url: '/github.com/sourcegraph/jsonrpc2/-/commit/15c2290dcb37731cc4ee5a2a1c1e5a25b4c28f81',
|
||||
canonicalURL:
|
||||
'/github.com/sourcegraph/jsonrpc2/-/commit/15c2290dcb37731cc4ee5a2a1c1e5a25b4c28f81',
|
||||
url: commitUrl,
|
||||
canonicalURL: commitUrl,
|
||||
externalURLs: [
|
||||
{
|
||||
url:
|
||||
@ -414,7 +412,7 @@ describe('Repository', () => {
|
||||
})
|
||||
}, 'Blob')
|
||||
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
await driver.assertWindowLocation(`/${repositoryName}/-/blob/${clickedFileName}`)
|
||||
|
||||
// Assert breadcrumb order
|
||||
@ -436,7 +434,10 @@ describe('Repository', () => {
|
||||
selector: '[data-testid="git-commit-node-oid"]',
|
||||
action: 'click',
|
||||
})
|
||||
await driver.page.waitForSelector('[data-testid="repository-commit-page"]')
|
||||
await driver.page.waitForSelector('[data-testid="git-commit-node-message-subject"]')
|
||||
await driver.assertWindowLocation(commitUrl)
|
||||
|
||||
await assertSelectorHasText(
|
||||
'[data-testid="git-commit-node-message-subject"]',
|
||||
'update LSIF indexing CI workflow'
|
||||
@ -494,7 +495,7 @@ describe('Repository', () => {
|
||||
|
||||
// page.click() fails for some reason with Error: Node is either not visible or not an HTMLElement
|
||||
await driver.page.$eval('.test-tree-file-link', linkElement => (linkElement as HTMLElement).click())
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
|
||||
await driver.page.waitForSelector('.test-breadcrumb')
|
||||
const breadcrumbTexts = await driver.page.evaluate(() =>
|
||||
@ -522,7 +523,9 @@ describe('Repository', () => {
|
||||
"https://github.com/ggilmore/q-test/blob/master/Geoffrey's%20random%20queries.32r242442bf/%25%20token.4288249258.sql"
|
||||
)
|
||||
|
||||
const blobContent = await driver.page.evaluate(() => document.querySelector('.test-repo-blob')?.textContent)
|
||||
const blobContent = await driver.page.evaluate(
|
||||
() => document.querySelector('[data-testid="repo-blob"]')?.textContent
|
||||
)
|
||||
assert.strictEqual(blobContent, `content for: ${filePath}\nsecond line\nthird line`)
|
||||
})
|
||||
|
||||
@ -549,7 +552,7 @@ describe('Repository', () => {
|
||||
|
||||
// page.click() fails for some reason with Error: Node is either not visible or not an HTMLElement
|
||||
await driver.page.$eval('.test-tree-file-link', linkElement => (linkElement as HTMLElement).click())
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
|
||||
await driver.page.waitForSelector('.test-breadcrumb')
|
||||
const breadcrumbTexts = await driver.page.evaluate(() =>
|
||||
@ -576,7 +579,7 @@ describe('Repository', () => {
|
||||
|
||||
// page.click() fails for some reason with Error: Node is either not visible or not an HTMLElement
|
||||
await driver.page.$eval('.test-tree-file-link', linkElement => (linkElement as HTMLElement).click())
|
||||
await driver.page.waitForSelector('.test-repo-blob')
|
||||
await driver.page.waitForSelector('[data-testid="repo-blob"]')
|
||||
|
||||
await driver.page.waitForSelector('.test-breadcrumb')
|
||||
const breadcrumbTexts = await driver.page.evaluate(() =>
|
||||
@ -1047,7 +1050,7 @@ describe('Repository', () => {
|
||||
await driver.page.goto(`${driver.sourcegraphBaseUrl}/${repoName}`)
|
||||
|
||||
try {
|
||||
await driver.page.waitForSelector('.test-file-decoration-container', { timeout: 5000 })
|
||||
await driver.page.waitForSelector('.test-file-decoration-container', { timeout: 10000 })
|
||||
} catch {
|
||||
throw new Error('Expected to see file decorations')
|
||||
}
|
||||
|
||||
@ -235,6 +235,8 @@ describe('Search', () => {
|
||||
test('Is set from the URL query parameter when loading a search-related page', async () => {
|
||||
await driver.page.goto(driver.sourcegraphBaseUrl + '/search?q=foo')
|
||||
const editor = await createEditorAPI(driver, queryInputSelector)
|
||||
await editor.waitForIt()
|
||||
await driver.page.waitForSelector('[data-testid="results-info-bar"]')
|
||||
expect(await editor.getValue()).toStrictEqual('foo')
|
||||
// Field value is cleared when navigating to a non search-related page
|
||||
await driver.page.waitForSelector('a[href="/extensions"]')
|
||||
@ -243,6 +245,8 @@ describe('Search', () => {
|
||||
expect(await editor.getValue()).toStrictEqual(undefined)
|
||||
// Field value is restored when the back button is pressed
|
||||
await driver.page.goBack()
|
||||
await editor.waitForIt()
|
||||
await driver.page.waitForSelector('[data-testid="results-info-bar"]')
|
||||
expect(await editor.getValue()).toStrictEqual('foo')
|
||||
})
|
||||
|
||||
|
||||
@ -7,12 +7,14 @@ import '@sourcegraph/shared/src/polyfills'
|
||||
|
||||
import './monitoring/initMonitoring'
|
||||
|
||||
import { render } from 'react-dom'
|
||||
import { createRoot } from 'react-dom/client'
|
||||
|
||||
import { OpenSourceWebApp } from './OpenSourceWebApp'
|
||||
|
||||
// It's important to have a root component in a separate file to create a react-refresh boundary and avoid page reload.
|
||||
// https://github.com/pmmmwh/react-refresh-webpack-plugin/blob/main/docs/TROUBLESHOOTING.md#edits-always-lead-to-full-reload
|
||||
window.addEventListener('DOMContentLoaded', () => {
|
||||
render(<OpenSourceWebApp />, document.querySelector('#root'))
|
||||
const root = createRoot(document.querySelector('#root')!)
|
||||
|
||||
root.render(<OpenSourceWebApp />)
|
||||
})
|
||||
|
||||
@ -77,7 +77,7 @@ interface Props
|
||||
CodeInsightsProps,
|
||||
BatchChangesProps {
|
||||
history: H.History
|
||||
location: H.Location<{ query: string }>
|
||||
location: H.Location
|
||||
authenticatedUser: AuthenticatedUser | null
|
||||
authRequired: boolean
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
@ -73,11 +73,9 @@ const commonProps = (): UserNavItemProps => ({
|
||||
position: Position.bottomStart,
|
||||
})
|
||||
|
||||
const OpenByDefaultWrapper: React.FunctionComponent<
|
||||
React.PropsWithChildren<{
|
||||
children: React.FunctionComponent<React.PropsWithChildren<{ menuButtonRef: React.Ref<HTMLButtonElement> }>>
|
||||
}>
|
||||
> = ({ children }) => {
|
||||
const OpenByDefaultWrapper: React.FunctionComponent<{
|
||||
children: React.FunctionComponent<React.PropsWithChildren<{ menuButtonRef: React.Ref<HTMLButtonElement> }>>
|
||||
}> = ({ children }) => {
|
||||
const menuButtonReference = useRef<HTMLButtonElement>(null)
|
||||
|
||||
useEffect(() => {
|
||||
|
||||
@ -113,6 +113,7 @@ export interface BlobProps
|
||||
wrapCode: boolean
|
||||
/** The current text document to be rendered and provided to extensions */
|
||||
blobInfo: BlobInfo
|
||||
'data-testid'?: string
|
||||
|
||||
// Experimental reference panel
|
||||
disableStatusBar: boolean
|
||||
@ -196,7 +197,15 @@ const STATUS_BAR_VERTICAL_GAP_VAR = '--blob-status-bar-vertical-gap'
|
||||
* in this state, hovers can lead to errors like `DocumentNotFoundError`.
|
||||
*/
|
||||
export const Blob: React.FunctionComponent<React.PropsWithChildren<BlobProps>> = props => {
|
||||
const { location, isLightTheme, extensionsController, blobInfo, platformContext, settingsCascade } = props
|
||||
const {
|
||||
location,
|
||||
isLightTheme,
|
||||
extensionsController,
|
||||
blobInfo,
|
||||
platformContext,
|
||||
settingsCascade,
|
||||
'data-testid': dataTestId,
|
||||
} = props
|
||||
|
||||
const settingsChanges = useMemo(() => new BehaviorSubject<Settings | null>(null), [])
|
||||
useEffect(() => {
|
||||
@ -783,7 +792,12 @@ export const Blob: React.FunctionComponent<React.PropsWithChildren<BlobProps>> =
|
||||
|
||||
return (
|
||||
<>
|
||||
<div className={classNames(props.className, styles.blob)} ref={nextBlobElement} tabIndex={-1}>
|
||||
<div
|
||||
data-testid={dataTestId}
|
||||
className={classNames(props.className, styles.blob)}
|
||||
ref={nextBlobElement}
|
||||
tabIndex={-1}
|
||||
>
|
||||
<Code
|
||||
className={classNames('test-blob', styles.blobCode, props.wrapCode && styles.blobCodeWrapped)}
|
||||
ref={nextCodeViewElement}
|
||||
|
||||
@ -365,7 +365,8 @@ export const BlobPage: React.FunctionComponent<React.PropsWithChildren<Props>> =
|
||||
{/* Render the (unhighlighted) blob also in the case highlighting timed out */}
|
||||
{renderMode === 'code' && (
|
||||
<Blob
|
||||
className={classNames('test-repo-blob', styles.blob, styles.border)}
|
||||
data-testid="repo-blob"
|
||||
className={classNames(styles.blob, styles.border)}
|
||||
blobInfo={blobInfoOrError}
|
||||
wrapCode={wrapCode}
|
||||
platformContext={props.platformContext}
|
||||
|
||||
@ -88,8 +88,9 @@ const queryCommit = memoizeObservable(
|
||||
}
|
||||
if (!data.node.commit) {
|
||||
// Filter out any revision not found errors, they usually come in multiples when searching for a commit, we want to replace all of them with 1 "Commit not found" error
|
||||
// TODO: Figuring why should we use `errors` here since it is `undefined` in this place
|
||||
const errorsWithoutRevisionError = errors?.filter(
|
||||
error => !error.message.includes('revision not found')
|
||||
(error: { message: string | string[] }) => !error.message.includes('revision not found')
|
||||
)
|
||||
|
||||
const revisionErrorsFiltered =
|
||||
@ -258,7 +259,11 @@ export class RepositoryCommitPage extends React.Component<Props, State> {
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
return (
|
||||
<div className={classNames('p-3', styles.repositoryCommitPage)} ref={this.nextRepositoryCommitPageElement}>
|
||||
<div
|
||||
data-testid="repository-commit-page"
|
||||
className={classNames('p-3', styles.repositoryCommitPage)}
|
||||
ref={this.nextRepositoryCommitPageElement}
|
||||
>
|
||||
<PageTitle
|
||||
title={
|
||||
this.state.commitOrError && !isErrorLike(this.state.commitOrError)
|
||||
|
||||
@ -51,7 +51,7 @@ export interface GitCommitNodeProps {
|
||||
preferAbsoluteTimestamps?: boolean
|
||||
|
||||
/** Fragment to show at the end to the right of the SHA. */
|
||||
afterElement?: React.ReactFragment
|
||||
afterElement?: React.ReactNode
|
||||
|
||||
/** Determine the git diff visualization UI */
|
||||
diffMode?: DiffMode
|
||||
@ -99,7 +99,7 @@ export const GitCommitNode: React.FunctionComponent<React.PropsWithChildren<GitC
|
||||
DeprecatedTooltipController.forceUpdate()
|
||||
}, [flashCopiedToClipboardMessage])
|
||||
|
||||
const copyToClipboard = useCallback((oid): void => {
|
||||
const copyToClipboard = useCallback((oid: string): void => {
|
||||
eventLogger.log('CommitSHACopiedToClipboard')
|
||||
copy(oid)
|
||||
setFlashCopiedToClipboardMessage(true)
|
||||
|
||||
@ -66,9 +66,9 @@ class UpdateMirrorRepositoryActionContainer extends React.PureComponent<UpdateMi
|
||||
}
|
||||
|
||||
public render(): JSX.Element | null {
|
||||
let title: React.ReactFragment
|
||||
let description: React.ReactFragment
|
||||
let buttonLabel: React.ReactFragment
|
||||
let title: React.ReactNode
|
||||
let description: React.ReactNode
|
||||
let buttonLabel: React.ReactNode
|
||||
let buttonDisabled = false
|
||||
let info: React.ReactNode
|
||||
if (this.props.repo.mirrorInfo.cloneInProgress) {
|
||||
|
||||
@ -11,10 +11,10 @@ import styles from './ActionContainer.module.scss'
|
||||
|
||||
export const BaseActionContainer: React.FunctionComponent<
|
||||
React.PropsWithChildren<{
|
||||
title: React.ReactFragment
|
||||
description: React.ReactFragment
|
||||
action: React.ReactFragment
|
||||
details?: React.ReactFragment
|
||||
title: React.ReactNode
|
||||
description: React.ReactNode
|
||||
action: React.ReactNode
|
||||
details?: React.ReactNode
|
||||
className?: string
|
||||
}>
|
||||
> = ({ title, description, action, details, className }) => (
|
||||
@ -31,10 +31,10 @@ export const BaseActionContainer: React.FunctionComponent<
|
||||
)
|
||||
|
||||
interface Props {
|
||||
title: React.ReactFragment
|
||||
description: React.ReactFragment
|
||||
title: React.ReactNode
|
||||
description: React.ReactNode
|
||||
buttonClassName?: string
|
||||
buttonLabel: React.ReactFragment
|
||||
buttonLabel: React.ReactNode
|
||||
buttonSubtitle?: string
|
||||
buttonDisabled?: boolean
|
||||
info?: React.ReactNode
|
||||
|
||||
@ -3,7 +3,7 @@ import * as React from 'react'
|
||||
import { mdiMessageTextOutline, mdiCog, mdiDelete, mdiPlus } from '@mdi/js'
|
||||
import { VisuallyHidden } from '@reach/visually-hidden'
|
||||
import classNames from 'classnames'
|
||||
import { RouteComponentProps } from 'react-router'
|
||||
import { RouteComponentProps, useLocation } from 'react-router'
|
||||
import { Subject, Subscription } from 'rxjs'
|
||||
import { catchError, map, mapTo, startWith, switchMap } from 'rxjs/operators'
|
||||
import { useCallbackRef } from 'use-callback-ref'
|
||||
@ -23,7 +23,7 @@ import { eventLogger } from '../tracking/eventLogger'
|
||||
|
||||
import styles from './SavedSearchListPage.module.scss'
|
||||
|
||||
interface NodeProps extends RouteComponentProps<{}, {}, { description?: string }>, SearchPatternTypeProps {
|
||||
interface NodeProps extends RouteComponentProps, SearchPatternTypeProps {
|
||||
savedSearch: GQL.ISavedSearch
|
||||
onDelete: () => void
|
||||
linkRef: React.MutableRefObject<HTMLAnchorElement | null> | null
|
||||
@ -126,7 +126,7 @@ interface State {
|
||||
savedSearchesOrError?: GQL.ISavedSearch[] | ErrorLike
|
||||
}
|
||||
|
||||
interface Props extends RouteComponentProps<{}, {}, { description?: string }>, NamespaceProps {}
|
||||
interface Props extends RouteComponentProps, NamespaceProps {}
|
||||
|
||||
export class SavedSearchListPage extends React.Component<Props, State> {
|
||||
public subscriptions = new Subscription()
|
||||
@ -188,6 +188,7 @@ const SavedSearchListPageContent: React.FunctionComponent<React.PropsWithChildre
|
||||
savedSearchesOrError,
|
||||
...props
|
||||
}) => {
|
||||
const location = useLocation<{ description?: string }>()
|
||||
const searchPatternType = useNavbarQueryState(state => state.searchPatternType)
|
||||
const callbackReference = useCallbackRef<HTMLAnchorElement>(null, ref => ref?.focus())
|
||||
|
||||
@ -210,7 +211,7 @@ const SavedSearchListPageContent: React.FunctionComponent<React.PropsWithChildre
|
||||
{namespaceSavedSearches.map(search => (
|
||||
<SavedSearchNode
|
||||
key={search.id}
|
||||
linkRef={props.location.state?.description === search.description ? callbackReference : null}
|
||||
linkRef={location.state?.description === search.description ? callbackReference : null}
|
||||
{...props}
|
||||
patternType={searchPatternType}
|
||||
savedSearch={search}
|
||||
|
||||
@ -48,7 +48,7 @@ export function submitSearch({
|
||||
},
|
||||
{ source }
|
||||
)
|
||||
history.push(path, { ...history.location.state, query })
|
||||
history.push(path, { ...(typeof history.location.state === 'object' ? history.location.state : null), query })
|
||||
if (activation) {
|
||||
activation.update({ DidSearch: true })
|
||||
}
|
||||
|
||||
@ -47,7 +47,7 @@ export interface SiteAdminAreaRouteContext
|
||||
isSourcegraphDotCom: boolean
|
||||
|
||||
/** This property is only used by {@link SiteAdminOverviewPage}. */
|
||||
overviewComponents: readonly React.ComponentType<React.PropsWithChildren<unknown>>[]
|
||||
overviewComponents: readonly React.ComponentType<React.PropsWithChildren<{}>>[]
|
||||
}
|
||||
|
||||
export interface SiteAdminAreaRoute extends RouteDescriptor<SiteAdminAreaRouteContext> {}
|
||||
|
||||
@ -451,7 +451,7 @@ const FeatureFlagOverrideItem: FunctionComponent<
|
||||
const nsValue = orgID > 0 ? orgID : userID
|
||||
|
||||
const onError = useCallback(
|
||||
error => {
|
||||
(error: Error) => {
|
||||
setError(error)
|
||||
},
|
||||
[setError]
|
||||
|
||||
@ -4,7 +4,7 @@ import { mdiChartLineVariant } from '@mdi/js'
|
||||
|
||||
import { Badge, H1, Icon } from '@sourcegraph/wildcard'
|
||||
|
||||
export const AnalyticsPageTitle: React.FunctionComponent = ({ children }) => (
|
||||
export const AnalyticsPageTitle: React.FunctionComponent<React.PropsWithChildren<{}>> = ({ children }) => (
|
||||
<div className="d-flex flex-column justify-content-between align-items-start">
|
||||
<Badge variant="merged">Experimental</Badge>
|
||||
|
||||
|
||||
@ -1,4 +1,4 @@
|
||||
import { act } from '@testing-library/react-hooks'
|
||||
import { act } from '@testing-library/react'
|
||||
|
||||
import { SearchPatternType } from '@sourcegraph/shared/src/graphql-operations'
|
||||
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
// causes false positive on act()
|
||||
/* eslint-disable @typescript-eslint/no-floating-promises */
|
||||
import { act, renderHook } from '@testing-library/react-hooks'
|
||||
import { renderHook, act } from '@testing-library/react'
|
||||
|
||||
import { useThemeState } from './stores'
|
||||
import { ThemePreference } from './stores/themeState'
|
||||
|
||||
@ -23,7 +23,7 @@ interface TourContentProps {
|
||||
className?: string
|
||||
}
|
||||
|
||||
const Header: React.FunctionComponent<{ onClose: () => void; title?: string }> = ({
|
||||
const Header: React.FunctionComponent<React.PropsWithChildren<{ onClose: () => void; title?: string }>> = ({
|
||||
children,
|
||||
onClose,
|
||||
title = 'Quick start',
|
||||
@ -36,7 +36,7 @@ const Header: React.FunctionComponent<{ onClose: () => void; title?: string }> =
|
||||
</div>
|
||||
)
|
||||
|
||||
const Footer: React.FunctionComponent<{ completedCount: number; totalCount: number }> = ({
|
||||
const Footer: React.FunctionComponent<React.PropsWithChildren<{ completedCount: number; totalCount: number }>> = ({
|
||||
completedCount,
|
||||
totalCount,
|
||||
}) => (
|
||||
@ -50,7 +50,7 @@ const Footer: React.FunctionComponent<{ completedCount: number; totalCount: numb
|
||||
</Text>
|
||||
)
|
||||
|
||||
const CompletedItem: React.FunctionComponent = ({ children }) => (
|
||||
const CompletedItem: React.FunctionComponent<React.PropsWithChildren<{}>> = ({ children }) => (
|
||||
<li className="d-flex align-items-start">
|
||||
<Icon
|
||||
size="sm"
|
||||
@ -62,7 +62,7 @@ const CompletedItem: React.FunctionComponent = ({ children }) => (
|
||||
</li>
|
||||
)
|
||||
|
||||
export const TourContent: React.FunctionComponent<TourContentProps> = ({
|
||||
export const TourContent: React.FunctionComponent<React.PropsWithChildren<TourContentProps>> = ({
|
||||
onClose,
|
||||
tasks,
|
||||
variant,
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
import { cleanup } from '@testing-library/react'
|
||||
import { renderHook, WrapperComponent, act } from '@testing-library/react-hooks'
|
||||
import { renderHook, cleanup, act } from '@testing-library/react'
|
||||
import { WrapperComponent } from '@testing-library/react-hooks'
|
||||
|
||||
import { TemporarySettings } from '@sourcegraph/shared/src/settings/temporary/TemporarySettings'
|
||||
import { MockTemporarySettings } from '@sourcegraph/shared/src/settings/temporary/testUtils'
|
||||
@ -20,7 +20,7 @@ const getFieldsAsObject = (value: object): object =>
|
||||
const TourId = 'MockTour'
|
||||
|
||||
const setup = (settings: TemporarySettings['onboarding.quickStartTour'] = {}) => {
|
||||
const wrapper: WrapperComponent<{}> = ({ children }) => (
|
||||
const wrapper: WrapperComponent<React.PropsWithChildren<{}>> = ({ children }) => (
|
||||
<MockTemporarySettings settings={{ 'onboarding.quickStartTour': settings }}>{children}</MockTemporarySettings>
|
||||
)
|
||||
return renderHook(() => useTour(TourId), { wrapper })
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue
Block a user