diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000..224b0495 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,20 @@ +module.exports = { + root: true, + parser: '@typescript-eslint/parser', + extends: ['react-app'], + plugins: ['prettier', '@emotion'], + rules: { + 'prettier/prettier': 'error', + 'jsx-a11y/accessible-emoji': 'off', + 'import/no-anonymous-default-export': 'off', + 'react-hooks/exhaustive-deps': 'off', + }, + overrides: [ + { + files: ['src/__tests__/**/*'], + env: { + jest: true, + }, + }, + ], +} diff --git a/.github/workflows/push.yml b/.github/workflows/push.yml index ae171e6c..786fde18 100644 --- a/.github/workflows/push.yml +++ b/.github/workflows/push.yml @@ -46,6 +46,9 @@ jobs: - name: Run lint run: yarn lint + - name: Run typecheck + run: yarn typecheck + test: needs: [setup, install] runs-on: ubuntu-latest diff --git a/@types/emotion.d.ts b/@types/emotion.d.ts new file mode 100644 index 00000000..e7ca2344 --- /dev/null +++ b/@types/emotion.d.ts @@ -0,0 +1,9 @@ +import '@emotion/react' + +import { Theme as EmotionTheme } from '../src/theme' + +export {} + +declare module '@emotion/react' { + export interface Theme extends EmotionTheme {} +} diff --git a/@types/global.d.ts b/@types/global.d.ts new file mode 100644 index 00000000..054694fb --- /dev/null +++ b/@types/global.d.ts @@ -0,0 +1,8 @@ +interface Window {} + +interface Process { + env: { + PUBLIC_URL: string + NODE_ENV: 'development' | 'production' + } +} diff --git a/config-overrides.js b/config-overrides.js index afb293aa..0c5eef26 100644 --- a/config-overrides.js +++ b/config-overrides.js @@ -1,3 +1,13 @@ const { override, addBabelPreset } = require('customize-cra') -module.exports = override(addBabelPreset('@emotion/babel-preset-css-prop')) +const ignoreWarnings = (value) => (config) => { + config.ignoreWarnings = value + return config +} + +module.exports = override( + addBabelPreset('@emotion/babel-preset-css-prop'), + + // Ignore warnings about the react-diff-view sourcemap files. + ignoreWarnings([/Failed to parse source map/]) +) diff --git a/jest.config.js b/jest.config.js new file mode 100644 index 00000000..2e5802c1 --- /dev/null +++ b/jest.config.js @@ -0,0 +1,10 @@ +module.exports = { + preset: 'ts-jest', + testEnvironment: 'node', + transform: { + '^.+\\.(js|jsx|ts|tsx)?$': 'ts-jest', + }, + transformIgnorePatterns: ['/node_modules/'], + testMatch: ['>/__tests__/**/*.spec.(js|jsx|ts|tsx)'], + setupFilesAfterEnv: ['/jest.setup.js'], +} diff --git a/package.json b/package.json index 96943def..d327a21f 100644 --- a/package.json +++ b/package.json @@ -5,27 +5,28 @@ "homepage": "https://react-native-community.github.io/upgrade-helper", "scripts": { "build": "EXTEND_ESLINT=true react-app-rewired build", - "docker-test-e2e": "yarn start-and-wait && react-app-rewired test --watchAll=false --onlyChanged=false --testPathPattern='/__tests__/.*(\\.|).e2e.spec.js?$'", + "docker-test-e2e": "yarn start-and-wait && react-app-rewired test --watchAll=false --onlyChanged=false --testPathPattern='/__tests__/.*(\\.|).e2e.spec.(js|jsx|ts|tsx)?$'", "lint": "eslint . --cache --report-unused-disable-directives", + "typecheck": "tsc --noEmit", "prepare": "husky install", "start": "EXTEND_ESLINT=true react-app-rewired start", "start-and-wait": "yarn start & wget --retry-connrefused --waitretry=1 --read-timeout=20 --timeout=15 -t 30 http://localhost:3000/", - "test": "react-app-rewired test --watchAll=false --onlyChanged=false --testPathPattern='/__tests__/((?!e2e).)*.spec.js?$'", + "test": "react-app-rewired test --watchAll=false --onlyChanged=false --testPathPattern='/__tests__/((?!e2e).)*.spec.(js|jsx|ts|tsx)?$'", "test-e2e": "docker-compose run tests" }, "dependencies": { - "@ant-design/icons": "4.0.3", - "@emotion/react": "^11.10.6", - "@emotion/styled": "^11.10.6", - "antd": "5.2.3", + "@ant-design/icons": "^5.3.0", + "@emotion/react": "^11.11.3", + "@emotion/styled": "^11.11.0", + "antd": "^5.14.0", "date-fns": "^2.29.3", - "framer-motion": "^2.9.5", + "framer-motion": "^11.0.3", "markdown-to-jsx": "7.1.9", "query-string": "8.1.0", "react": "18.2.0", "react-content-loader": "6.2.0", "react-copy-to-clipboard": "5.1.0", - "react-diff-view": "2.4.2", + "react-diff-view": "^3.2.0", "react-dom": "18.2.0", "react-dom-confetti": "0.2.0", "react-ga": "3.3.1", @@ -35,19 +36,33 @@ "use-persisted-state": "^0.3.3" }, "devDependencies": { - "@emotion/babel-preset-css-prop": "^11.10.0", - "@emotion/eslint-plugin": "^11.10.0", + "@emotion/babel-preset-css-prop": "^11.11.0", + "@emotion/eslint-plugin": "^11.11.0", + "@jest/globals": "^29.7.0", "@testing-library/react": "^14.0.0", + "@types/jest": "^29.5.12", + "@types/jest-image-snapshot": "^6.4.0", + "@types/markdown-to-jsx": "^7.0.1", + "@types/node": "^20.11.16", + "@types/react": "18.2.0", + "@types/react-copy-to-clipboard": "^5.0.7", + "@types/react-dom": "^18.2.18", + "@types/semver": "^7.5.6", + "@types/use-persisted-state": "^0.3.4", + "@typescript-eslint/eslint-plugin": "^6.20.0", + "@typescript-eslint/parser": "^6.20.0", "customize-cra": "^1.0.0", "eslint": "^8.35.0", "eslint-plugin-prettier": "^4.2.1", "gh-pages": "5.0.0", "husky": "8.0.3", - "jest-image-snapshot": "6.1.0", + "jest-image-snapshot": "6.4.0", "prettier": "2.8.4", "pretty-quick": "3.1.3", "puppeteer": "10.0.0", - "react-app-rewired": "^2.2.1" + "react-app-rewired": "^2.2.1", + "ts-jest": "^29.1.2", + "typescript": "^5.3.3" }, "browserslist": { "production": [ @@ -61,33 +76,10 @@ "last 1 safari version" ] }, - "eslintConfig": { - "extends": [ - "react-app" - ], - "plugins": [ - "prettier", - "@emotion" - ], - "rules": { - "prettier/prettier": "error", - "jsx-a11y/accessible-emoji": "off", - "import/no-anonymous-default-export": "off", - "react-hooks/exhaustive-deps": "off" - } - }, "husky": { "hooks": { - "pre-commit": "pretty-quick --staged --pattern \"src/**/*.*(js|jsx)\"", + "pre-commit": "pretty-quick --staged --pattern \"src/**/*.*(js|jsx|ts|tsx)\"", "pre-push": "yarn run lint" } - }, - "jest": { - "testMatch": [ - "**/__tests__/**/*.spec.js" - ], - "setupFilesAfterEnv": [ - "/jest.setup.js" - ] } } diff --git a/src/App.jsx b/src/App.tsx similarity index 100% rename from src/App.jsx rename to src/App.tsx diff --git a/src/__tests__/Home.e2e.spec.js b/src/__tests__/Home.e2e.spec.ts similarity index 100% rename from src/__tests__/Home.e2e.spec.js rename to src/__tests__/Home.e2e.spec.ts diff --git a/src/__tests__/components/common/CompletedFilesCounter.spec.js b/src/__tests__/components/common/CompletedFilesCounter.spec.tsx similarity index 59% rename from src/__tests__/components/common/CompletedFilesCounter.spec.js rename to src/__tests__/components/common/CompletedFilesCounter.spec.tsx index e657e831..95553e67 100644 --- a/src/__tests__/components/common/CompletedFilesCounter.spec.js +++ b/src/__tests__/components/common/CompletedFilesCounter.spec.tsx @@ -1,21 +1,33 @@ import React from 'react' import { render } from '@testing-library/react' import CompletedFilesCounter from '../../../components/common/CompletedFilesCounter' +import { lightTheme } from '../../../theme' it('renders without crashing', () => { - const { container } = render() + const { container } = render( + + ) expect(container).toMatchInlineSnapshot(`
+ > + 10 + / + 11
` display: flex; justify-content: space-between; align-items: center; @@ -22,8 +30,10 @@ const BinaryRow = styled.div` padding: 10px 15px; border-bottom: 1px solid ${({ theme }) => theme.border}; ` - -const Popover = styled(({ className, ...props }) => ( +interface PopoverProps extends Omit { + className?: string +} +const Popover = styled(({ className, ...props }: PopoverProps) => ( ))` .ant-popover-inner-content { @@ -31,29 +41,53 @@ const Popover = styled(({ className, ...props }) => ( } ` -const BinaryList = ({ binaryFiles, toVersion, appName, packageName }) => - binaryFiles.map(({ newPath }, index) => { - return ( - - {removeAppPathPrefix(newPath, appName)} +interface BinaryListProps { + binaryFiles: File[] + toVersion: string + appName: string + packageName: string +} - - - ) - }) +const BinaryList: React.FC = ({ + binaryFiles, + toVersion, + appName, + packageName, +}) => { + return ( + <> + {binaryFiles.map(({ newPath }, index) => { + return ( + + {removeAppPathPrefix(newPath, appName)} + + + ) + })} + + ) +} + +interface BinaryDownloadProps { + diff: File[] + fromVersion: string + toVersion: string + appName: string + packageName: string +} const BinaryDownload = ({ diff, fromVersion, toVersion, appName, packageName, -}) => { +}: BinaryDownloadProps) => { const binaryFiles = diff.filter( ({ hunks, type }) => hunks.length === 0 && type !== 'delete' ) diff --git a/src/components/common/CompletedFilesCounter.js b/src/components/common/CompletedFilesCounter.tsx similarity index 82% rename from src/components/common/CompletedFilesCounter.js rename to src/components/common/CompletedFilesCounter.tsx index de6f6d09..ed40affd 100644 --- a/src/components/common/CompletedFilesCounter.js +++ b/src/components/common/CompletedFilesCounter.tsx @@ -3,6 +3,7 @@ import styled from '@emotion/styled' import { keyframes, css } from '@emotion/react' import Confetti from 'react-dom-confetti' import { Popover } from 'antd' +import type { Theme } from '../../theme' const shake = keyframes` 0% { @@ -26,8 +27,23 @@ const shake = keyframes` } ` +interface CompletedFilesCounterProps + extends React.HTMLAttributes { + completed: number + total: number + popoverContent: string + popoverCursorType: React.CSSProperties['cursor'] + theme?: Theme +} + const CompletedFilesCounter = styled( - ({ completed, total, popoverContent, popoverCursorType, ...props }) => ( + ({ + completed, + total, + popoverContent, + popoverCursorType, + ...props + }: CompletedFilesCounterProps) => (
{ + ({ + open, + version, + path, + packageName, + appName, + appPackage, + ...props + }: CopyFileButtonProps) => { const [popoverContent, setPopoverContent] = useState( popoverContentOpts.default ) @@ -45,7 +63,6 @@ const CopyFileButton = styled( diff --git a/src/components/common/Diff/Diff.js b/src/components/common/Diff/Diff.tsx similarity index 64% rename from src/components/common/Diff/Diff.js rename to src/components/common/Diff/Diff.tsx index 08e2c65d..018af59e 100644 --- a/src/components/common/Diff/Diff.js +++ b/src/components/common/Diff/Diff.tsx @@ -1,22 +1,31 @@ -import React, { useState, useCallback } from 'react' +import React, { useState, useCallback, Fragment } from 'react' import styled from '@emotion/styled' import { Diff as RDiff, + DiffProps as RDiffProps, Hunk, markEdits, tokenize, Decoration as DiffDecoration, + HunkData, + ViewType, + DiffType, + HunkTokens, + TokenNode, } from 'react-diff-view' import DiffHeader from './DiffHeader' import { getComments } from './DiffComment' import { replaceAppDetails } from '../../../utils' +import type { Theme } from '../../../theme' +import type { ChangeEventArgs } from 'react-diff-view' +import type { DefaultRenderToken } from 'react-diff-view/types/context' const copyPathPopoverContentOpts = { default: 'Click to copy file path', copied: 'File path copied!', } -const Container = styled.div` +const Container = styled.div<{ theme?: Theme }>` border: 1px solid ${({ theme }) => theme.border}; border-radius: 3px; margin-bottom: 16px; @@ -29,12 +38,15 @@ const More = styled.div` padding-left: 4px; ` -const Decoration = styled(DiffDecoration)` +const Decoration = styled(DiffDecoration)<{ theme?: Theme }>` background-color: ${({ theme }) => theme.diff.decorationContentBackground}; color: ${({ theme }) => theme.diff.decorationContent}; ` -const DiffView = styled(RDiff)` +interface DiffViewProps extends RDiffProps { + theme?: Theme +} +const DiffView = styled(RDiff)` .diff-gutter-col { width: 30px; } @@ -45,7 +57,7 @@ const DiffView = styled(RDiff)` } td.diff-gutter .diff-line-normal { - background-color: ${({ theme }) => theme.gutterInsertBackground}; + background-color: ${({ theme }) => theme.diff.gutterInsertBackground}; border-color: ${({ theme }) => theme.greenBorder}; } @@ -70,26 +82,25 @@ const DiffView = styled(RDiff)` // From diff global .diff { - background-color: ${({ theme }) => theme.diff.backgroundColor}; - color: ${({ theme }) => theme.diff.text}; + background-color: ${({ theme }) => theme.background}; + color: ${({ theme }) => theme.text}; tab-size: 4; hyphens: none; } .diff::selection { - background-color: ${({ theme }) => theme.diff.selectionMackground}; + background-color: ${({ theme }) => theme.diff.selectionBackground}; } .diff-decoration { line-height: 2; font-family: SFMono-Regular, Consolas, 'Liberation Mono', Menlo, Courier, monospace; - background-color: ${({ theme }) => theme.diff.decorationBackground}; } .diff-decoration-content { padding-left: 0.5em; - background-color: ${({ theme }) => theme.diff.contentBackground}; + background-color: ${({ theme }) => theme.diff.decorationContentBackground}; color: ${({ theme }) => theme.diff.decorationContent}; } @@ -143,8 +154,54 @@ const DiffView = styled(RDiff)` ` // Diff will be collapsed by default if the file has been deleted or has more than 5 hunks -const isDiffCollapsedByDefault = ({ type, hunks }) => - type === 'delete' || hunks.length > 5 ? true : undefined +const isDiffCollapsedByDefault = ({ + type, + hunks, +}: { + type: DiffType + hunks: HunkData[] +}) => (type === 'delete' || hunks.length > 5 ? true : undefined) + +const renderToken = ( + token: TokenNode, + renderDefault: DefaultRenderToken, + index: number +) => { + switch (token.type) { + case 'space': + console.log(token) + return ( + + {token.children && + token.children.map((token, index) => + renderToken(token, renderDefault, index) + )} + + ) + default: + return renderDefault(token, index) + } +} + +interface DiffProps { + packageName: string + oldPath: string + newPath: string + type: DiffType + hunks: HunkData[] + fromVersion: string + toVersion: string + diffKey: string + isDiffCompleted: boolean + onCompleteDiff: (diffKey: string) => void + selectedChanges: string[] + onToggleChangeSelection: (args: ChangeEventArgs) => void + areAllCollapsed?: boolean + setAllCollapsed: (collapse: boolean | undefined) => void + diffViewStyle: ViewType + appName: string + appPackage: string +} const Diff = ({ packageName, @@ -164,9 +221,9 @@ const Diff = ({ diffViewStyle, appName, appPackage, -}) => { - const [isDiffCollapsed, setIsDiffCollapsed] = useState( - isDiffCollapsedByDefault({ type, hunks }) +}: DiffProps) => { + const [isDiffCollapsed, setIsDiffCollapsed] = useState( + isDiffCollapsedByDefault({ type, hunks }) || false ) const [copyPathPopoverContent, setCopyPathPopoverContent] = useState( @@ -184,7 +241,7 @@ const Diff = ({ } const getHunksWithAppName = useCallback( - (originalHunks) => { + (originalHunks: HunkData[]) => { if (!appName && !appPackage) { // No patching of rn-diff-purge output required. return originalHunks @@ -213,9 +270,17 @@ const Diff = ({ newPath, fromVersion, toVersion, - appName, }) + const updatedHunks = React.useMemo(() => getHunksWithAppName(hunks), [hunks]) + const tokens: HunkTokens = React.useMemo( + () => + tokenize(hunks, { + enhancers: [markEdits(updatedHunks)], + }), + [hunks, updatedHunks] + ) + return ( 0} isDiffCollapsed={isDiffCollapsed} - setIsDiffCollapsed={(collapse, altKey) => { + setIsDiffCollapsed={(collapsed: boolean, altKey?: boolean) => { if (altKey) { - return setAllCollapsed(collapse) + return setAllCollapsed(collapsed) } setAllCollapsed(undefined) - setIsDiffCollapsed(collapse) + setIsDiffCollapsed(collapsed) }} isDiffCompleted={isDiffCompleted} onCopyPathToClipboard={handleCopyPathToClipboard} @@ -251,31 +316,29 @@ const Diff = ({ viewType={diffViewStyle} diffType={type} hunks={hunks} + renderToken={renderToken} + tokens={tokens} widgets={diffComments} optimizeSelection={true} selectedChanges={selectedChanges} > - {(originalHunks) => { - const updatedHunks = getHunksWithAppName(originalHunks) - - const options = { - enhancers: [markEdits(updatedHunks)], - } - - const tokens = tokenize(updatedHunks, options) - - return updatedHunks.map((hunk) => [ - - {hunk.content} - , - , - ]) - }} + {(hunks: HunkData[]) => + hunks.map((hunk) => ( + + {updatedHunks.map((hunk) => [ + + {hunk.content} + , + , + ])} + + )) + } )} @@ -286,7 +349,7 @@ const Diff = ({ Return true if passing `nextProps` to render would return the same result as passing prevProps to render, otherwise return false */ -const arePropsEqual = (prevProps, nextProps) => +const arePropsEqual = (prevProps: DiffProps, nextProps: DiffProps) => prevProps.isDiffCompleted === nextProps.isDiffCompleted && prevProps.areAllCollapsed === nextProps.areAllCollapsed && prevProps.diffViewStyle === nextProps.diffViewStyle && diff --git a/src/components/common/Diff/DiffComment.js b/src/components/common/Diff/DiffComment.tsx similarity index 55% rename from src/components/common/Diff/DiffComment.js rename to src/components/common/Diff/DiffComment.tsx index 9807b89a..e18f8073 100644 --- a/src/components/common/Diff/DiffComment.js +++ b/src/components/common/Diff/DiffComment.tsx @@ -1,30 +1,45 @@ import React, { useState } from 'react' import styled from '@emotion/styled' -import { motion } from 'framer-motion' +import { HTMLMotionProps, motion } from 'framer-motion' import { removeAppPathPrefix, getVersionsContentInDiff } from '../../../utils' import Markdown from '../Markdown' +import type { Theme } from '../../../theme' +import type { + LineChangeT, + ReleaseCommentT, + ReleaseT, +} from '../../../releases/types' + +interface ContainerProps + extends React.PropsWithChildren> { + isCommentOpen: boolean + lineChangeType: LineChangeT + theme?: Theme +} -const Container = styled(({ isCommentOpen, children, ...props }) => { - return ( - -
- - ) -})` +const Container = styled( + ({ isCommentOpen, children, ...props }: ContainerProps) => { + return ( + +
{children}
+
+ ) + } +)` overflow: hidden; & > div { @@ -34,6 +49,7 @@ const Container = styled(({ isCommentOpen, children, ...props }) => { const colorMap = { add: theme.diff.codeInsertBackground, delete: theme.diff.codeDeleteBackground, + neutral: undefined, } return colorMap[lineChangeType] || theme.background @@ -42,7 +58,10 @@ const Container = styled(({ isCommentOpen, children, ...props }) => { } ` -const ContentContainer = styled.div` +interface ContentContainerProps extends React.HTMLAttributes { + theme?: Theme +} +const ContentContainer = styled.div` flex: 1; position: relative; padding: 16px; @@ -51,7 +70,12 @@ const ContentContainer = styled.div` user-select: none; ` -const ShowButton = styled(({ isCommentOpen, ...props }) => ( +interface ShowButtonProps extends HTMLMotionProps<'div'> { + isCommentOpen: boolean + theme?: Theme +} + +const ShowButton = styled(({ isCommentOpen, ...props }: ShowButtonProps) => ( - `${LINE_CHANGE_TYPES[lineChangeType.toUpperCase()]}${lineNumber}` - -const getComments = ({ packageName, newPath, fromVersion, toVersion }) => { +const getLineNumberWithType = ({ + lineChangeType, + lineNumber, +}: { + lineChangeType: LineChangeT + lineNumber: number +}) => + `${ + LINE_CHANGE_TYPES[ + lineChangeType.toUpperCase() as keyof typeof LINE_CHANGE_TYPES + ] + }${lineNumber}` + +const getComments = ({ + packageName, + newPath, + fromVersion, + toVersion, +}: { + packageName: string + newPath: string + fromVersion: string + toVersion: string +}) => { const newPathSanitized = removeAppPathPrefix(newPath) const versionsInDiff = getVersionsContentInDiff({ @@ -103,15 +147,18 @@ const getComments = ({ packageName, newPath, fromVersion, toVersion }) => { fromVersion, toVersion, }).filter( - ({ comments }) => + ({ comments }: ReleaseT) => comments && comments.length > 0 && comments.some(({ fileName }) => fileName === newPathSanitized) ) - return versionsInDiff.reduce((allComments, version) => { - const comments = version.comments.reduce( - (versionComments, { fileName, lineChangeType, lineNumber, content }) => { + return versionsInDiff.reduce((allComments, version: ReleaseT) => { + const comments = version.comments?.reduce( + ( + versionComments, + { fileName, lineChangeType, lineNumber, content }: ReleaseCommentT + ) => { if (fileName !== newPathSanitized) { return versionComments } @@ -133,8 +180,14 @@ const getComments = ({ packageName, newPath, fromVersion, toVersion }) => { }, {}) } -const DiffComment = ({ content, lineChangeType }) => { - const [isCommentOpen, setIsCommentOpen] = useState(true) +const DiffComment = ({ + content, + lineChangeType, +}: { + content: any + lineChangeType: LineChangeT +}) => { + const [isCommentOpen, setIsCommentOpen] = useState(true) return ( { + comments: ReleaseCommentT[] + isDiffCollapsed: boolean + uncollapseDiff: () => void + theme?: Theme +} const DiffCommentReminder = styled( - ({ comments, isDiffCollapsed, uncollapseDiff, ...props }) => { + ({ + comments, + isDiffCollapsed, + uncollapseDiff, + ...props + }: DiffCommentReminderProps) => { const numberOfComments = Object.keys(comments).length const isOpen = isDiffCollapsed && numberOfComments > 0 @@ -16,7 +30,7 @@ const DiffCommentReminder = styled( open: { opacity: 1, cursor: 'pointer' }, closed: { opacity: 0, cursor: 'initial' }, }} - animate={isOpen > 0 ? 'open' : 'closed'} + animate={isOpen ? 'open' : 'closed'} transition={{ duration: getTransitionDuration(0.5), }} diff --git a/src/components/common/Diff/DiffHeader.js b/src/components/common/Diff/DiffHeader.tsx similarity index 71% rename from src/components/common/Diff/DiffHeader.js rename to src/components/common/Diff/DiffHeader.tsx index 40d33349..3900f4fe 100644 --- a/src/components/common/Diff/DiffHeader.js +++ b/src/components/common/Diff/DiffHeader.tsx @@ -1,6 +1,7 @@ import React from 'react' import styled from '@emotion/styled' import { Tag, Button, Popover } from 'antd' +import type { ButtonProps, TagProps } from 'antd' import { CheckOutlined, DownOutlined, @@ -15,12 +16,17 @@ import DiffCommentReminder from './DiffCommentReminder' import DownloadFileButton from '../DownloadFileButton' import ViewFileButton from '../ViewFileButton' import CopyFileButton from '../CopyFileButton' +import type { Theme } from '../../../theme' +import type { ReleaseCommentT } from '../../../releases/types' +import type { DiffType } from 'react-diff-view' export const testIDs = { collapseClickableArea: 'collapseClickableArea', } - -const Wrapper = styled.div` +interface WrapperProps extends React.HTMLAttributes { + theme?: Theme +} +const Wrapper = styled.div` display: flex; justify-content: space-between; font-family: SFMono-Regular, Consolas, Liberation Mono, Menlo, Courier, @@ -43,7 +49,15 @@ const FileRenameArrow = styled(RightOutlined)({ color: '#f78206', }) -const FileName = ({ oldPath, newPath, type, appName }) => { +const FileName = ({ + oldPath, + newPath, + type, +}: { + oldPath: string + newPath: string + type: DiffType +}) => { if (type === 'delete') { return {oldPath} } @@ -59,7 +73,7 @@ const FileName = ({ oldPath, newPath, type, appName }) => { return {newPath} } -function generatePathId(oldPath, newPath) { +function generatePathId(oldPath: string, newPath: string) { const isMoved = oldPath !== newPath if (newPath === '/dev/null') { newPath = 'deleted' @@ -68,7 +82,12 @@ function generatePathId(oldPath, newPath) { return encodeURIComponent(path.replace(/[/\\]/g, '-')) } -const FileStatus = ({ type, ...props }) => { +const FileStatus = ({ + type, + ...props +}: { + type: DiffType +} & TagProps) => { const colors = { add: 'blue', modify: 'green', @@ -84,13 +103,16 @@ const FileStatus = ({ type, ...props }) => { } return ( - - {labels[type]} + + {labels[type as keyof typeof labels]} ) } -const BinaryBadge = ({ open, ...props }) => +interface BinaryBadgeProps extends TagProps { + open: boolean +} +const BinaryBadge = ({ open, ...props }: BinaryBadgeProps) => open ? ( BINARY @@ -101,22 +123,20 @@ const defaultIconButtonStyle = ` font-size: 13px; ` -const CompleteDiffButton = styled(({ open, onClick, ...props }) => - open ? ( -