mirror of
https://github.com/sourcegraph/sourcegraph.git
synced 2026-02-06 12:51:55 +00:00
Code Navigation: different icons for different file types (#58421)
Each item in the tree view now has an icon matching the file extension/language.
This commit is contained in:
parent
882d5674a8
commit
70c9eb357e
@ -1841,6 +1841,7 @@ ts_project(
|
||||
"//:node_modules/react-circular-progressbar",
|
||||
"//:node_modules/react-dom",
|
||||
"//:node_modules/react-grid-layout",
|
||||
"//:node_modules/react-icons",
|
||||
"//:node_modules/react-router-dom",
|
||||
"//:node_modules/react-spring",
|
||||
"//:node_modules/react-sticky-box",
|
||||
|
||||
@ -1,5 +1,54 @@
|
||||
.icon-container {
|
||||
display: flex;
|
||||
flex-direction: row;
|
||||
align-items: center;
|
||||
}
|
||||
|
||||
.icon {
|
||||
flex-shrink: 0;
|
||||
width: 1rem;
|
||||
height: 1rem;
|
||||
}
|
||||
|
||||
.test-indicator {
|
||||
border-radius: 100%;
|
||||
width: 0.5rem;
|
||||
height: 0.5rem;
|
||||
margin-left: -0.65rem;
|
||||
margin-bottom: 0.15rem;
|
||||
margin-right: 0.15rem;
|
||||
background-color: var(--gray-05);
|
||||
align-self: end;
|
||||
}
|
||||
|
||||
.blue {
|
||||
color: var(--blue);
|
||||
}
|
||||
|
||||
.pink {
|
||||
color: var(--pink);
|
||||
}
|
||||
|
||||
.yellow {
|
||||
color: var(--yellow);
|
||||
}
|
||||
|
||||
.red {
|
||||
color: var(--red);
|
||||
}
|
||||
|
||||
.gray {
|
||||
color: var(--gray-05);
|
||||
}
|
||||
|
||||
.green {
|
||||
color: var(--green);
|
||||
}
|
||||
|
||||
.cyan {
|
||||
color: var(--cyan);
|
||||
}
|
||||
|
||||
.default-icon {
|
||||
color: var(--gray-07);
|
||||
}
|
||||
|
||||
@ -27,7 +27,9 @@ import {
|
||||
|
||||
import type { FileTreeEntriesResult, FileTreeEntriesVariables } from '../graphql-operations'
|
||||
|
||||
import { FILE_ICONS, FileExtension } from './constants'
|
||||
import { FocusableTree, type FocusableTreeProps } from './RepoRevisionSidebarFocusableTree'
|
||||
import { getFileInfo } from './utils'
|
||||
|
||||
import styles from './RepoRevisionSidebarFileTree.module.scss'
|
||||
|
||||
@ -389,6 +391,8 @@ function renderNode({
|
||||
const { entry, error, dotdot, name } = element
|
||||
const submodule = entry?.submodule
|
||||
const url = entry?.url
|
||||
const fileInfo = getFileInfo(name)
|
||||
const fileIcon = FILE_ICONS.get(fileInfo.extension)
|
||||
|
||||
if (error) {
|
||||
return <ErrorAlert {...props} className={classNames(props.className, 'm-0')} variant="note" error={error} />
|
||||
@ -470,12 +474,25 @@ function renderNode({
|
||||
}
|
||||
}}
|
||||
>
|
||||
<Icon
|
||||
svgPath={isBranch ? (isExpanded ? mdiFolderOpenOutline : mdiFolderOutline) : mdiFileDocumentOutline}
|
||||
className={classNames('mr-1', styles.icon)}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
{name}
|
||||
<div className={styles.iconContainer}>
|
||||
{fileInfo.extension !== FileExtension.DEFAULT ? (
|
||||
<Icon
|
||||
as={fileIcon?.icon}
|
||||
className={classNames('mr-1', styles.icon, fileIcon?.iconClass)}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
) : (
|
||||
<Icon
|
||||
svgPath={
|
||||
isBranch ? (isExpanded ? mdiFolderOpenOutline : mdiFolderOutline) : mdiFileDocumentOutline
|
||||
}
|
||||
className={classNames('mr-1', styles.icon)}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
)}
|
||||
{fileInfo.isTest && <div className={classNames(styles.testIndicator)} />}
|
||||
{name}
|
||||
</div>
|
||||
</Link>
|
||||
)
|
||||
}
|
||||
|
||||
@ -1,3 +1,54 @@
|
||||
import { ComponentType } from 'react'
|
||||
|
||||
import { CiSettings, CiTextAlignLeft, CiWarning } from 'react-icons/ci'
|
||||
import { FaCss3Alt, FaJava, FaSass } from 'react-icons/fa'
|
||||
import { GoDatabase, GoTerminal } from 'react-icons/go'
|
||||
import { PiFilePngLight } from 'react-icons/pi'
|
||||
import {
|
||||
SiApachegroovy,
|
||||
SiAssemblyscript,
|
||||
SiC,
|
||||
SiClojure,
|
||||
SiCoffeescript,
|
||||
SiCplusplus,
|
||||
SiCsharp,
|
||||
SiDart,
|
||||
SiDocker,
|
||||
SiFortran,
|
||||
SiFsharp,
|
||||
SiGit,
|
||||
SiGo,
|
||||
SiGraphql,
|
||||
SiHaskell,
|
||||
SiHtml5,
|
||||
SiJavascript,
|
||||
SiJpeg,
|
||||
SiJulia,
|
||||
SiKotlin,
|
||||
SiLua,
|
||||
SiMarkdown,
|
||||
SiNixos,
|
||||
SiNpm,
|
||||
SiOcaml,
|
||||
SiPerl,
|
||||
SiPhp,
|
||||
SiPython,
|
||||
SiR,
|
||||
SiReact,
|
||||
SiRuby,
|
||||
SiRust,
|
||||
SiScala,
|
||||
SiSvelte,
|
||||
SiSvg,
|
||||
SiSwift,
|
||||
SiTypescript,
|
||||
SiVisualbasic,
|
||||
SiZig,
|
||||
} from 'react-icons/si'
|
||||
import { VscJson } from 'react-icons/vsc'
|
||||
|
||||
import styles from './RepoRevisionSidebarFileTree.module.scss'
|
||||
|
||||
export const LogsPageTabs = {
|
||||
COMMANDS: 0,
|
||||
SYNCLOGS: 1,
|
||||
@ -13,3 +64,160 @@ export enum CodeHostType {
|
||||
AZUREDEVOPS = 'azureDevOps',
|
||||
OTHER = 'other',
|
||||
}
|
||||
|
||||
export enum FileExtension {
|
||||
ASSEMBLY = 'asm',
|
||||
BASH = 'sh',
|
||||
BASIC = 'vb',
|
||||
C = 'c',
|
||||
CLOJURE_CLJ = 'clj',
|
||||
CLOJURE_CLJS = 'cljs',
|
||||
CLOJURE_CLJR = 'cljr',
|
||||
CLOJURE_CLJC = 'cljc',
|
||||
CLOJURE_EDN = 'edn',
|
||||
COFFEE = 'coffee',
|
||||
CPP = 'cc',
|
||||
CSHARP = 'cs',
|
||||
CSS = 'css',
|
||||
DART = 'dart',
|
||||
DEFAULT = 'default',
|
||||
DOCKERFILE = 'Dockerfile',
|
||||
DOCKERIGNORE = 'dockerignore',
|
||||
FORTRAN_F = 'f',
|
||||
FORTRAN_FOR = 'for',
|
||||
FORTRAN_FTN = 'ftn',
|
||||
FSHARP = 'fs',
|
||||
FSI = 'fsi',
|
||||
FSX = 'fsx',
|
||||
GITIGNORE = 'gitignore',
|
||||
GITATTRIBUTES = 'gitattributes',
|
||||
GO = 'go',
|
||||
GOMOD = 'mod',
|
||||
GOSUM = 'sum',
|
||||
GRAPHQL = 'graphql',
|
||||
GROOVY = 'groovy',
|
||||
HASKELL = 'hs',
|
||||
HTML = 'html',
|
||||
JAVA = 'java',
|
||||
JAVASCRIPT = 'js',
|
||||
JPG = 'jpg',
|
||||
JPEG = 'jpeg',
|
||||
JSON = 'json',
|
||||
JSX = 'jsx',
|
||||
JULIA = 'jl',
|
||||
KOTLIN = 'kt',
|
||||
LOCKFILE = 'lock',
|
||||
LUA = 'lua',
|
||||
MARKDOWN = 'md',
|
||||
NCL = 'ncl',
|
||||
NIX = 'nix',
|
||||
NPM = 'npmrc',
|
||||
OCAML = 'ml',
|
||||
PHP = 'php',
|
||||
PERL = 'pl',
|
||||
PERL_PM = 'pm',
|
||||
PNG = 'png',
|
||||
POWERSHELL_PS1 = 'ps1',
|
||||
POWERSHELL_PSM1 = 'psm1',
|
||||
PYTHON = 'py',
|
||||
R = 'r',
|
||||
R_CAP = 'R',
|
||||
RUBY = 'rb',
|
||||
RUST = 'rs',
|
||||
SCALA = 'scala',
|
||||
SASS = 'scss',
|
||||
SQL = 'sql',
|
||||
SVELTE = 'svelte',
|
||||
SVG = 'svg',
|
||||
SWIFT = 'swift',
|
||||
TEST = 'test',
|
||||
TYPESCRIPT = 'ts',
|
||||
TSX = 'tsx',
|
||||
TEXT = 'txt',
|
||||
YAML = 'yaml',
|
||||
YML = 'yml',
|
||||
ZIG = 'zig',
|
||||
}
|
||||
|
||||
type CustomIcon = ComponentType<{ className?: string }>
|
||||
interface IconInfo {
|
||||
icon: CustomIcon
|
||||
iconClass: string
|
||||
}
|
||||
/*
|
||||
* We use the react-icons package instead of material design icons for two reasons:
|
||||
* 1) Many of mdi's programming language icons will be deprecated soon.
|
||||
* 2) They are missing quite a few icons that are needed when displaying file types.
|
||||
*/
|
||||
export const FILE_ICONS: Map<FileExtension, IconInfo> = new Map([
|
||||
[FileExtension.ASSEMBLY, { icon: SiAssemblyscript, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.BASH, { icon: GoTerminal, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.BASIC, { icon: SiVisualbasic, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.C, { icon: SiC, iconClass: styles.blue }],
|
||||
[FileExtension.CLOJURE_CLJ, { icon: SiClojure, iconClass: styles.blue }],
|
||||
[FileExtension.CLOJURE_CLJC, { icon: SiClojure, iconClass: styles.blue }],
|
||||
[FileExtension.CLOJURE_CLJR, { icon: SiClojure, iconClass: styles.blue }],
|
||||
[FileExtension.CLOJURE_CLJS, { icon: SiClojure, iconClass: styles.blue }],
|
||||
[FileExtension.CLOJURE_EDN, { icon: SiClojure, iconClass: styles.blue }],
|
||||
[FileExtension.COFFEE, { icon: SiCoffeescript, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.CPP, { icon: SiCplusplus, iconClass: styles.blue }],
|
||||
[FileExtension.CSHARP, { icon: SiCsharp, iconClass: styles.blue }],
|
||||
[FileExtension.CSS, { icon: FaCss3Alt, iconClass: styles.blue }],
|
||||
[FileExtension.DART, { icon: SiDart, iconClass: styles.blue }],
|
||||
[FileExtension.DEFAULT, { icon: CiWarning, iconClass: styles.red }],
|
||||
[FileExtension.DOCKERFILE, { icon: SiDocker, iconClass: styles.blue }],
|
||||
[FileExtension.DOCKERIGNORE, { icon: SiDocker, iconClass: styles.blue }],
|
||||
[FileExtension.FORTRAN_F, { icon: SiFortran, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.FORTRAN_FOR, { icon: SiFortran, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.FORTRAN_FTN, { icon: SiFortran, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.FSHARP, { icon: SiFsharp, iconClass: styles.blue }],
|
||||
[FileExtension.FSI, { icon: SiFsharp, iconClass: styles.blue }],
|
||||
[FileExtension.FSX, { icon: SiFsharp, iconClass: styles.blue }],
|
||||
[FileExtension.GITIGNORE, { icon: SiGit, iconClass: styles.red }],
|
||||
[FileExtension.GITATTRIBUTES, { icon: SiGit, iconClass: styles.red }],
|
||||
[FileExtension.GO, { icon: SiGo, iconClass: styles.blue }],
|
||||
[FileExtension.GOMOD, { icon: SiGo, iconClass: styles.pink }],
|
||||
[FileExtension.GOSUM, { icon: SiGo, iconClass: styles.pink }],
|
||||
[FileExtension.GROOVY, { icon: SiApachegroovy, iconClass: styles.blue }],
|
||||
[FileExtension.GRAPHQL, { icon: SiGraphql, iconClass: styles.pink }],
|
||||
[FileExtension.HASKELL, { icon: SiHaskell, iconClass: styles.blue }],
|
||||
[FileExtension.HTML, { icon: SiHtml5, iconClass: styles.blue }],
|
||||
[FileExtension.JAVA, { icon: FaJava, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.JAVASCRIPT, { icon: SiJavascript, iconClass: styles.yellow }],
|
||||
[FileExtension.JPG, { icon: SiJpeg, iconClass: styles.yellow }],
|
||||
[FileExtension.JPEG, { icon: SiJpeg, iconClass: styles.yellow }],
|
||||
[FileExtension.JSX, { icon: SiReact, iconClass: styles.yellow }],
|
||||
[FileExtension.JSON, { icon: VscJson, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.JULIA, { icon: SiJulia, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.KOTLIN, { icon: SiKotlin, iconClass: styles.green }],
|
||||
[FileExtension.LOCKFILE, { icon: VscJson, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.LUA, { icon: SiLua, iconClass: styles.blue }],
|
||||
[FileExtension.MARKDOWN, { icon: SiMarkdown, iconClass: styles.blue }],
|
||||
[FileExtension.NCL, { icon: CiSettings, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.NIX, { icon: SiNixos, iconClass: styles.gray }],
|
||||
[FileExtension.NPM, { icon: SiNpm, iconClass: styles.red }],
|
||||
[FileExtension.OCAML, { icon: SiOcaml, iconClass: styles.yellow }],
|
||||
[FileExtension.PHP, { icon: SiPhp, iconClass: styles.cyan }],
|
||||
[FileExtension.PERL, { icon: SiPerl, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.PERL_PM, { icon: SiPerl, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.PNG, { icon: PiFilePngLight, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.POWERSHELL_PS1, { icon: GoTerminal, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.POWERSHELL_PSM1, { icon: GoTerminal, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.PYTHON, { icon: SiPython, iconClass: styles.blue }],
|
||||
[FileExtension.R, { icon: SiR, iconClass: styles.red }],
|
||||
[FileExtension.R_CAP, { icon: SiR, iconClass: styles.red }],
|
||||
[FileExtension.RUBY, { icon: SiRuby, iconClass: styles.red }],
|
||||
[FileExtension.RUST, { icon: SiRust, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.SCALA, { icon: SiScala, iconClass: styles.red }],
|
||||
[FileExtension.SASS, { icon: FaSass, iconClass: styles.pink }],
|
||||
[FileExtension.SQL, { icon: GoDatabase, iconClass: styles.blue }],
|
||||
[FileExtension.SVELTE, { icon: SiSvelte, iconClass: styles.red }],
|
||||
[FileExtension.SVG, { icon: SiSvg, iconClass: styles.blue }],
|
||||
[FileExtension.SWIFT, { icon: SiSwift, iconClass: styles.blue }],
|
||||
[FileExtension.TYPESCRIPT, { icon: SiTypescript, iconClass: styles.blue }],
|
||||
[FileExtension.TSX, { icon: SiReact, iconClass: styles.blue }],
|
||||
[FileExtension.TEXT, { icon: CiTextAlignLeft, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.YAML, { icon: CiSettings, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.YML, { icon: CiSettings, iconClass: styles.defaultIcon }],
|
||||
[FileExtension.ZIG, { icon: SiZig, iconClass: styles.yellow }],
|
||||
])
|
||||
|
||||
@ -25,6 +25,8 @@ import type { BlobFileFields, TreeHistoryFields } from '../../graphql-operations
|
||||
import { fetchBlob } from '../blob/backend'
|
||||
import { RenderedFile } from '../blob/RenderedFile'
|
||||
import { CommitMessageWithLinks } from '../commit/CommitMessageWithLinks'
|
||||
import { FILE_ICONS, FileExtension } from '../constants'
|
||||
import { getFileInfo } from '../utils'
|
||||
|
||||
import styles from './TreePagePanels.module.scss'
|
||||
|
||||
@ -176,54 +178,67 @@ export const FilesCard: FC<FilePanelProps> = ({ entries, historyEntries, classNa
|
||||
</CardHeader>
|
||||
</thead>
|
||||
<tbody>
|
||||
{entries.map(entry => (
|
||||
<tr key={entry.name}>
|
||||
<td className={styles.fileName}>
|
||||
<LinkOrSpan
|
||||
to={entry.url}
|
||||
className={classNames(
|
||||
'test-page-file-decorable',
|
||||
entry.isDirectory && 'font-weight-bold',
|
||||
`test-tree-entry-${entry.isDirectory ? 'directory' : 'file'}`
|
||||
)}
|
||||
title={entry.path}
|
||||
data-testid="tree-entry"
|
||||
>
|
||||
<Icon
|
||||
className="mr-1"
|
||||
svgPath={entry.isDirectory ? mdiFolderOutline : mdiFileDocumentOutline}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
{entry.name}
|
||||
{entry.isDirectory && '/'}
|
||||
</LinkOrSpan>
|
||||
</td>
|
||||
{fileHistoryByPath[entry.path] && (
|
||||
<>
|
||||
<td className={styles.commitMessage}>
|
||||
<span
|
||||
title={fileHistoryByPath[entry.path].subject}
|
||||
data-testid="git-commit-message-with-links"
|
||||
>
|
||||
<CommitMessageWithLinks
|
||||
to={fileHistoryByPath[entry.path].canonicalURL}
|
||||
message={fileHistoryByPath[entry.path].subject}
|
||||
className="text-muted"
|
||||
externalURLs={fileHistoryByPath[entry.path].externalURLs}
|
||||
{entries.map(entry => {
|
||||
const fileInfo = getFileInfo(entry.name)
|
||||
const fileIcon = FILE_ICONS.get(fileInfo.extension)
|
||||
|
||||
return (
|
||||
<tr key={entry.name}>
|
||||
<td className={styles.fileName}>
|
||||
<LinkOrSpan
|
||||
to={entry.url}
|
||||
className={classNames(
|
||||
'test-page-file-decorable',
|
||||
entry.isDirectory && 'font-weight-bold',
|
||||
`test-tree-entry-${entry.isDirectory ? 'directory' : 'file'}`
|
||||
)}
|
||||
title={entry.path}
|
||||
data-testid="tree-entry"
|
||||
>
|
||||
{fileInfo.extension !== FileExtension.DEFAULT ? (
|
||||
<Icon
|
||||
as={fileIcon?.icon}
|
||||
className={classNames('mr-1', fileIcon?.iconClass)}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
<td className={classNames(styles.commitDate, 'text-muted')}>
|
||||
<Timestamp
|
||||
noAbout={true}
|
||||
noAgo={true}
|
||||
date={getCommitDate(fileHistoryByPath[entry.path])}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
))}
|
||||
) : (
|
||||
<Icon
|
||||
svgPath={entry.isDirectory ? mdiFolderOutline : mdiFileDocumentOutline}
|
||||
className={classNames('mr-1')}
|
||||
aria-hidden={true}
|
||||
/>
|
||||
)}
|
||||
{entry.name}
|
||||
{entry.isDirectory && '/'}
|
||||
</LinkOrSpan>
|
||||
</td>
|
||||
{fileHistoryByPath[entry.path] && (
|
||||
<>
|
||||
<td className={styles.commitMessage}>
|
||||
<span
|
||||
title={fileHistoryByPath[entry.path].subject}
|
||||
data-testid="git-commit-message-with-links"
|
||||
>
|
||||
<CommitMessageWithLinks
|
||||
to={fileHistoryByPath[entry.path].canonicalURL}
|
||||
message={fileHistoryByPath[entry.path].subject}
|
||||
className="text-muted"
|
||||
externalURLs={fileHistoryByPath[entry.path].externalURLs}
|
||||
/>
|
||||
</span>
|
||||
</td>
|
||||
<td className={classNames(styles.commitDate, 'text-muted')}>
|
||||
<Timestamp
|
||||
noAbout={true}
|
||||
noAgo={true}
|
||||
date={getCommitDate(fileHistoryByPath[entry.path])}
|
||||
/>
|
||||
</td>
|
||||
</>
|
||||
)}
|
||||
</tr>
|
||||
)
|
||||
})}
|
||||
</tbody>
|
||||
</Card>
|
||||
)
|
||||
|
||||
@ -1,6 +1,99 @@
|
||||
import { describe, expect, it } from 'vitest'
|
||||
|
||||
import { getInitialSearchTerm } from './utils'
|
||||
import { FileExtension } from './constants'
|
||||
import { containsTest, getFileInfo, getInitialSearchTerm } from './utils'
|
||||
|
||||
describe('containsTest', () => {
|
||||
const tests: {
|
||||
name: string
|
||||
file: string
|
||||
expected: boolean
|
||||
}[] = [
|
||||
{
|
||||
name: 'returns true if "test_" exists in file name',
|
||||
file: 'test_myfile.go',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'returns true if "_test" exists in file name',
|
||||
file: 'myfile_test.go',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'returns true if "_spec" exists in file name',
|
||||
file: 'myfile_spec.go',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'returns true if "spec_" exists in file name',
|
||||
file: 'spec_myfile.go',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'works with sub-extensions',
|
||||
file: 'myreactcomponent.test.tsx',
|
||||
expected: true,
|
||||
},
|
||||
{
|
||||
name: 'returns false if not a test file',
|
||||
file: 'mytestcomponent.java',
|
||||
expected: false,
|
||||
},
|
||||
]
|
||||
|
||||
for (const t of tests) {
|
||||
it(t.name, () => {
|
||||
expect(containsTest(t.file)).toBe(t.expected)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('getFileInfo', () => {
|
||||
const tests: {
|
||||
name: string
|
||||
file: string
|
||||
isDirectory: boolean
|
||||
expectedExtension: FileExtension
|
||||
expectedIsTest: boolean
|
||||
}[] = [
|
||||
{
|
||||
name: 'works with simple file name',
|
||||
file: 'my-file.js',
|
||||
isDirectory: false,
|
||||
expectedExtension: 'js' as FileExtension,
|
||||
expectedIsTest: false,
|
||||
},
|
||||
{
|
||||
name: 'works with complex file name',
|
||||
file: 'my-file.module.scss',
|
||||
isDirectory: false,
|
||||
expectedExtension: 'scss' as FileExtension,
|
||||
expectedIsTest: false,
|
||||
},
|
||||
{
|
||||
name: 'returns isTest as true if file name contains test',
|
||||
file: 'my-file.test.tsx',
|
||||
isDirectory: false,
|
||||
expectedExtension: 'tsx' as FileExtension,
|
||||
expectedIsTest: true,
|
||||
},
|
||||
{
|
||||
name: 'returns isTest as true if file name contains test',
|
||||
file: '.eslintrc',
|
||||
isDirectory: false,
|
||||
expectedExtension: 'default' as FileExtension,
|
||||
expectedIsTest: false,
|
||||
},
|
||||
]
|
||||
|
||||
for (const t of tests) {
|
||||
it(t.name, () => {
|
||||
const fileInfo = getFileInfo(t.file)
|
||||
expect(fileInfo.extension).toBe(t.expectedExtension)
|
||||
expect(fileInfo.isTest).toBe(t.expectedIsTest)
|
||||
})
|
||||
}
|
||||
})
|
||||
|
||||
describe('getInitialSearchTerm', () => {
|
||||
const tests: {
|
||||
|
||||
@ -1,6 +1,6 @@
|
||||
import { type GitCommitFields, RepositoryType } from '../graphql-operations'
|
||||
|
||||
import { CodeHostType } from './constants'
|
||||
import { CodeHostType, FileExtension } from './constants'
|
||||
|
||||
export const isPerforceChangelistMappingEnabled = (): boolean =>
|
||||
window.context.experimentalFeatures.perforceChangelistMapping === 'enabled'
|
||||
@ -45,3 +45,42 @@ export const stringToCodeHostType = (codeHostType: string): CodeHostType => {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface FileInfo {
|
||||
extension: FileExtension
|
||||
isTest: boolean
|
||||
}
|
||||
|
||||
export const getFileInfo = (file: string): FileInfo => {
|
||||
const extension = file.split('.').at(-1)
|
||||
const isValidExtension = Object.values(FileExtension).includes(extension as FileExtension)
|
||||
|
||||
if (extension && isValidExtension) {
|
||||
return {
|
||||
extension: extension as FileExtension,
|
||||
isTest: containsTest(file),
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
extension: 'default' as FileExtension,
|
||||
isTest: false,
|
||||
}
|
||||
}
|
||||
|
||||
export const containsTest = (file: string): boolean => {
|
||||
const f = file.split('.')
|
||||
// To account for other test file path structures
|
||||
// adjust this regular expression.
|
||||
const isTest = /^(test|spec|tests)(\b|_)|(\b|_)(test|spec|tests)$/
|
||||
|
||||
for (const i of f) {
|
||||
if (i === 'test') {
|
||||
return true
|
||||
}
|
||||
if (isTest.test(i)) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
@ -387,6 +387,7 @@
|
||||
"react-dom": "18.1.0",
|
||||
"react-focus-lock": "^2.7.1",
|
||||
"react-grid-layout": "1.3.4",
|
||||
"react-icons": "^4.12.0",
|
||||
"react-resizable": "^3.0.4",
|
||||
"react-router-dom": "^6.8.1",
|
||||
"react-spring": "^9.4.2",
|
||||
|
||||
@ -367,6 +367,9 @@ importers:
|
||||
react-grid-layout:
|
||||
specifier: 1.3.4
|
||||
version: 1.3.4(react-dom@18.1.0)(react@18.1.0)
|
||||
react-icons:
|
||||
specifier: ^4.12.0
|
||||
version: 4.12.0(react@18.1.0)
|
||||
react-resizable:
|
||||
specifier: ^3.0.4
|
||||
version: 3.0.4(react-dom@18.1.0)(react@18.1.0)
|
||||
@ -21188,6 +21191,14 @@ packages:
|
||||
react-resizable: 3.0.4(react-dom@18.1.0)(react@18.1.0)
|
||||
dev: false
|
||||
|
||||
/react-icons@4.12.0(react@18.1.0):
|
||||
resolution: {integrity: sha512-IBaDuHiShdZqmfc/TwHu6+d6k2ltNCf3AszxNmjJc1KUfXdEeRJOKyNvLmAHaarhzGmTSVygNdyu8/opXv2gaw==}
|
||||
peerDependencies:
|
||||
react: '*'
|
||||
dependencies:
|
||||
react: 18.1.0
|
||||
dev: false
|
||||
|
||||
/react-inspector@6.0.2(react@18.1.0):
|
||||
resolution: {integrity: sha512-x+b7LxhmHXjHoU/VrFAzw5iutsILRoYyDq97EDYdFpPLcvqtEzk4ZSZSQjnFPbr5T57tLXnHcqFYoN1pI6u8uQ==}
|
||||
peerDependencies:
|
||||
|
||||
Loading…
Reference in New Issue
Block a user