Migrate away from CRA (#155)

* Migrate away from CRA
Fixes #128

* Add vitest

* Fix github client, still issues with kittycad

* Working e2e tests when kittycad.ts functions don't call node-fetch

* Remove cross-fetch that was causing issues in the browser (not sure why)

* Node 18, rm custom kittycad.ts, replace node-fetch

* Clean up

* Clean up and lint
This commit is contained in:
Pierre Jacquier
2023-05-16 18:20:48 -04:00
committed by GitHub
parent f25ef8bd4e
commit 28eb6ec3f3
23 changed files with 3244 additions and 21616 deletions

View File

@ -20,7 +20,7 @@ jobs:
- uses: actions/setup-node@v3.6.0 - uses: actions/setup-node@v3.6.0
with: with:
node-version: '16' node-version: '18'
cache: 'yarn' cache: 'yarn'
- run: yarn install - run: yarn install

14081
.pnp.cjs generated

File diff suppressed because it is too large Load Diff

View File

@ -4,9 +4,7 @@ Injects @kittycad/lib powered visual diffs for supported CAD files in GitHub Pul
## Available Scripts ## Available Scripts
The project was setup as a Create-React-App boilerplate, with Node 16, yarn 3 as package manager and TypeScript. The project uses Vite, with Node 18, yarn 3 as package manager and TypeScript.
https://craco.js.org/ is used to extend the default CRA configs.
From the project directory: From the project directory:
@ -22,32 +20,24 @@ yarn dlx @yarnpkg/sdks vscode
### `yarn build` ### `yarn build`
Builds the app for production to the `build` folder.\ Builds the extension for production to the `build` folder.\
It correctly bundles React in production mode and optimizes the build for the best performance. It correctly bundles React in production mode and optimizes the build for the best performance.
The generated `build` directory may then be added to Chrome with the **Load unpacked** button at [chrome://extensions](). This needs to be done everytime there's a change. The generated `build` directory may then be added to Chrome with the **Load unpacked** button at [chrome://extensions](). This needs to be done everytime there's a change.
### `yarn start` ### `yarn start`
Runs the app in the development mode.\ Runs the extension in the development mode.
Open [http://localhost:3000](http://localhost:3000) to view it in the browser.
**This will only show the current settings popup in a tab, that won't be able to act as a Chrome extension, so it may only be used for pure UI work.** The generated `build` directory may then be added to Chrome with the **Load unpacked** button at [chrome://extensions](). Background/content scripts and React views should reload as changes are made.
### `yarn test` ### `yarn test`
Launches the test runner in the interactive watch mode.\ Launches the unit tests runner in the interactive watch mode.
See the section about [running tests](https://facebook.github.io/create-react-app/docs/running-tests) for more information.
### `yarn eject` ### `yarn e2e`
**Note: this is a one-way operation. Once you `eject`, you cant go back!** Builds the extension and runs end-to-end tests through an automated Chromium instance.
If you arent satisfied with the build tool and configuration choices, you can `eject` at any time. This command will remove the single build dependency from your project.
Instead, it will copy all the configuration files and the transitive dependencies (webpack, Babel, ESLint, etc) right into your project so you have full control over them. All of the commands except `eject` will still work, but they will point to the copied scripts so you can tweak them. At this point youre on your own.
You dont have to ever use `eject`. The curated feature set is suitable for small and middle deployments, and you shouldnt feel obligated to use this feature. However we understand that this tool wouldnt be useful if you couldnt customize it when you are ready for it.
## Release a new version ## Release a new version

View File

@ -1,28 +0,0 @@
module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
return {
...webpackConfig,
entry: {
main: [
env === 'development' &&
require.resolve(
'react-dev-utils/webpackHotDevClient'
),
paths.appIndexJs,
].filter(Boolean),
content: './src/chrome/content.ts',
background: './src/chrome/background.ts',
},
output: {
...webpackConfig.output,
filename: 'static/js/[name].js',
},
optimization: {
...webpackConfig.optimization,
runtimeChunk: false,
},
}
},
},
}

10
index.html Normal file
View File

@ -0,0 +1,10 @@
<!DOCTYPE html>
<html lang="en">
<head>
<title>KittyCAD Diff Viewer Extension</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/index.tsx"></script>
</body>
</html>

View File

@ -12,9 +12,7 @@
"48": "logo192.png", "48": "logo192.png",
"128": "logo192.png" "128": "logo192.png"
}, },
"permissions": [ "permissions": ["storage"],
"storage"
],
"host_permissions": [ "host_permissions": [
"https://github.com/", "https://github.com/",
"https://api.github.com/", "https://api.github.com/",
@ -23,18 +21,14 @@
], ],
"content_scripts": [ "content_scripts": [
{ {
"matches": [ "matches": ["https://github.com/*"],
"https://github.com/*" "js": ["src/chrome/content.ts"],
],
"js": [
"./static/js/content.js"
],
"all_frames": false, "all_frames": false,
"run_at": "document_end" "run_at": "document_end"
} }
], ],
"background": { "background": {
"service_worker": "./static/js/background.js", "service_worker": "src/chrome/background.ts",
"type": "module" "type": "module"
} }
} }

View File

@ -3,7 +3,6 @@
"version": "0.1.5", "version": "0.1.5",
"private": true, "private": true,
"dependencies": { "dependencies": {
"@craco/craco": "^7.0.0",
"@dicebear/avatars": "^4.10.8", "@dicebear/avatars": "^4.10.8",
"@dicebear/avatars-bottts-sprites": "^4.10.8", "@dicebear/avatars-bottts-sprites": "^4.10.8",
"@kittycad/lib": "^0.0.19", "@kittycad/lib": "^0.0.19",
@ -27,27 +26,24 @@
"@types/three": "^0.150.2", "@types/three": "^0.150.2",
"buffer": "^6.0.3", "buffer": "^6.0.3",
"github-injection": "^1.1.0", "github-injection": "^1.1.0",
"isomorphic-fetch": "^3.0.0",
"react": "^18.2.0", "react": "^18.2.0",
"react-dom": "^18.2.0", "react-dom": "^18.2.0",
"react-is": "^18.2.0", "react-is": "^18.2.0",
"react-scripts": "5.0.1",
"styled-components": "^5.3.10", "styled-components": "^5.3.10",
"three": "^0.152.2", "three": "^0.152.2",
"three-mesh-bvh": "^0.5.23", "three-mesh-bvh": "^0.5.23",
"typescript": "^4.9.5" "typescript": "^4.9.5"
}, },
"scripts": { "scripts": {
"start": "react-scripts start", "start": "vite",
"build": "INLINE_RUNTIME_CHUNK=false craco build", "build": "vite build",
"test": "react-scripts test", "test": "vitest",
"e2e": "yarn build && yarn playwright test", "e2e": "yarn build && yarn playwright test",
"eject": "react-scripts eject",
"bump": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 4)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' public/manifest.json --indent 4)\" > public/manifest.json" "bump": "echo \"$(jq --arg v \"$VERSION\" '.version=$v' package.json --indent 4)\" > package.json && echo \"$(jq --arg v \"$VERSION\" '.version=$v' public/manifest.json --indent 4)\" > public/manifest.json"
}, },
"eslintConfig": { "eslintConfig": {
"extends": [ "extends": [
"react-app",
"react-app/jest",
"prettier" "prettier"
] ]
}, },
@ -77,12 +73,17 @@
}, },
"devDependencies": { "devDependencies": {
"@babel/runtime": "^7.21.0", "@babel/runtime": "^7.21.0",
"@crxjs/vite-plugin": "^1.0.14",
"@playwright/test": "^1.32.3", "@playwright/test": "^1.32.3",
"@vitejs/plugin-react": "^4.0.0",
"dotenv": "^16.0.3", "dotenv": "^16.0.3",
"eslint": "^8.39.0", "eslint": "^8.39.0",
"eslint-config-prettier": "^8.8.0", "eslint-config-prettier": "^8.8.0",
"eslint-config-react-app": "^7.0.1",
"eslint-plugin-react": "^7.32.2", "eslint-plugin-react": "^7.32.2",
"prettier": "^2.8.7" "jsdom": "^22.0.0",
"prettier": "^2.8.7",
"vite": "^4.3.5",
"vite-plugin-node-polyfills": "^0.8.2",
"vitest": "^0.31.0"
} }
} }

Binary file not shown.

Before

Width:  |  Height:  |  Size: 5.3 KiB

View File

@ -1,43 +0,0 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="icon" href="%PUBLIC_URL%/favicon.ico" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<meta name="theme-color" content="#000000" />
<meta
name="description"
content="Web site created using create-react-app"
/>
<link rel="apple-touch-icon" href="%PUBLIC_URL%/logo192.png" />
<!--
manifest.json provides metadata used when your web app is installed on a
user's mobile device or desktop. See https://developers.google.com/web/fundamentals/web-app-manifest/
-->
<link rel="manifest" href="%PUBLIC_URL%/manifest.json" />
<!--
Notice the use of %PUBLIC_URL% in the tags above.
It will be replaced with the URL of the `public` folder during the build.
Only files inside the `public` folder can be referenced from the HTML.
Unlike "/favicon.ico" or "favicon.ico", "%PUBLIC_URL%/favicon.ico" will
work correctly both with client-side routing and a non-root public URL.
Learn how to configure a non-root public URL by running `npm run build`.
-->
<title>KittyCAD Diff Viewer Extension</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
<!--
This HTML file is a template.
If you open it directly in the browser, you will see an empty page.
You can add webfonts, meta tags, or analytics to this file.
The build step will place the bundled scripts into the <body> tag.
To begin the development, run `npm start` or `yarn start`.
To create a production bundle, use `npm run build` or `yarn build`.
-->
</body>
</html>

View File

@ -1,3 +0,0 @@
# https://www.robotstxt.org/robotstxt.html
User-agent: *
Disallow:

View File

@ -7,11 +7,7 @@ import {
getGithubCommitUrlParams, getGithubCommitUrlParams,
createReactRoot, createReactRoot,
} from './web' } from './web'
import gitHubInjection from 'github-injection'
// https://github.com/OctoLinker/injection
// maintained by octolinker, makes sure pages that are loaded through pjax are available for injection
// no ts support
const gitHubInjection = require('github-injection')
const root = createReactRoot(document) const root = createReactRoot(document)

View File

@ -1,12 +1,6 @@
import React, { useEffect, useState } from 'react' import React, { useEffect, useState } from 'react'
import '@react-three/fiber' import '@react-three/fiber'
import { import { Box, useTheme, Text, TabNav, StyledOcticon } from '@primer/react'
Box,
useTheme,
Text,
TabNav,
StyledOcticon,
} from '@primer/react'
import { FileDiff } from '../../chrome/types' import { FileDiff } from '../../chrome/types'
import { Viewer3D } from './Viewer3D' import { Viewer3D } from './Viewer3D'
import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader' import { OBJLoader } from 'three/examples/jsm/loaders/OBJLoader'

View File

@ -1,9 +1,10 @@
import { fireEvent, render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { SourceRichToggle } from './SourceRichToggle' import { SourceRichToggle } from './SourceRichToggle'
import { vi } from 'vitest'
it('renders a source-rich diff toggle and checks its callbacks', async () => { it('renders a source-rich diff toggle and checks its callbacks', async () => {
const callbackSource = jest.fn() const callbackSource = vi.fn()
const callbackRich = jest.fn() const callbackRich = vi.fn()
render( render(
<SourceRichToggle <SourceRichToggle

View File

@ -1,16 +1,17 @@
import { fireEvent, render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { TokenForm } from './TokenForm' import { TokenForm } from './TokenForm'
import { vi } from 'vitest'
it('renders a token form and checks its callback', () => { it('renders a token form and checks its callback', () => {
const service = 'service' const service = 'service'
const token = 'token' const token = 'token'
const callback = jest.fn() const callback = vi.fn()
render(<TokenForm service={service} onToken={callback} />) render(<TokenForm service={service} onToken={callback} />)
expect(screen.getByText(`Enter a ${service} token`)).toBeInTheDocument() expect(screen.getByText(`Enter a ${service} token`)).toBeInTheDocument()
// TODO: understand why screen.getByRole started to hang // TODO: understand why screen.getByRole started to hang
const field = screen.getByAltText("Text input for token") const field = screen.getByAltText('Text input for token')
fireEvent.change(field, { target: { value: token } }) fireEvent.change(field, { target: { value: token } })
// TODO: understand why screen.getByRole started to hang // TODO: understand why screen.getByRole started to hang

View File

@ -6,7 +6,11 @@ export type TokenFormProps = {
onToken: (token: string) => void onToken: (token: string) => void
} }
export function TokenForm({ service, onToken, children }: PropsWithChildren<TokenFormProps>) { export function TokenForm({
service,
onToken,
children,
}: PropsWithChildren<TokenFormProps>) {
const [token, setToken] = useState('') const [token, setToken] = useState('')
return ( return (

View File

@ -1,10 +1,11 @@
import { fireEvent, render, screen } from '@testing-library/react' import { fireEvent, render, screen } from '@testing-library/react'
import { UserCard } from './UserCard' import { UserCard } from './UserCard'
import { vi } from 'vitest'
it('renders a user card and checks its callback button', () => { it('renders a user card and checks its callback button', () => {
const login = 'login' const login = 'login'
const avatar = 'avatar' const avatar = 'avatar'
const callback = jest.fn() const callback = vi.fn()
render( render(
<UserCard <UserCard

2
src/github-injection.d.ts vendored Normal file
View File

@ -0,0 +1,2 @@
// npm module that doesn't have TS types, used for proper injection timing on github.com
declare module 'github-injection'

View File

@ -1 +0,0 @@
/// <reference types="react-scripts" />

View File

@ -1,13 +1,15 @@
// jest-dom adds custom jest matchers for asserting on DOM nodes.
// allows you to do things like:
// expect(element).toHaveTextContent(/react/i)
// learn more: https://github.com/testing-library/jest-dom
import '@testing-library/jest-dom' import '@testing-library/jest-dom'
import matchers from '@testing-library/jest-dom/matchers'
import { cleanup } from '@testing-library/react'
import { vi } from 'vitest'
// extends Vitest's expect method with methods from react-testing-library
expect.extend(matchers)
// From https://github.com/primer/react/blob/5dd4bb1f7f92647197160298fc1f521b23b4823b/src/utils/test-helpers.tsx#L12 // From https://github.com/primer/react/blob/5dd4bb1f7f92647197160298fc1f521b23b4823b/src/utils/test-helpers.tsx#L12
global.CSS = { global.CSS = {
escape: jest.fn(), escape: vi.fn(),
supports: jest.fn().mockImplementation(() => { supports: vi.fn().mockImplementation(() => {
return false return false
}), }),
} }
@ -15,12 +17,20 @@ global.CSS = {
// TODO: improve/replace chrome mocks // TODO: improve/replace chrome mocks
global.chrome = { global.chrome = {
runtime: { runtime: {
sendMessage: jest.fn(), // @ts-ignore TS2322
sendMessage: vi.fn(),
}, },
storage: { storage: {
local: { local: {
set: jest.fn(), // @ts-ignore TS2322
get: jest.fn(), set: vi.fn(),
// @ts-ignore TS2322
get: vi.fn(),
}, },
}, },
} }
// runs a cleanup after each test case (e.g. clearing jsdom)
afterEach(() => {
cleanup()
})

View File

@ -1,10 +1,11 @@
{ {
"compilerOptions": { "compilerOptions": {
"target": "ES2015", "target": "ESNext",
"useDefineForClassFields": true,
"lib": ["dom", "dom.iterable", "esnext"], "lib": ["dom", "dom.iterable", "esnext"],
"allowJs": true, "allowJs": false,
"skipLibCheck": true, "skipLibCheck": true,
"esModuleInterop": true, "esModuleInterop": false,
"allowSyntheticDefaultImports": true, "allowSyntheticDefaultImports": true,
"strict": true, "strict": true,
"forceConsistentCasingInFileNames": true, "forceConsistentCasingInFileNames": true,
@ -17,5 +18,6 @@
"jsx": "react-jsx", "jsx": "react-jsx",
"types": ["chrome"] "types": ["chrome"]
}, },
"include": ["src"] "include": ["src"],
"references": [{ "path": "./tsconfig.node.json" }]
} }

9
tsconfig.node.json Normal file
View File

@ -0,0 +1,9 @@
{
"compilerOptions": {
"composite": true,
"module": "ESNext",
"moduleResolution": "Node",
"allowSyntheticDefaultImports": true
},
"include": ["vite.config.ts"]
}

27
vite.config.js Normal file
View File

@ -0,0 +1,27 @@
import { defineConfig } from 'vite'
import { nodePolyfills } from 'vite-plugin-node-polyfills'
import { configDefaults } from 'vitest/config'
import react from '@vitejs/plugin-react'
import { crx } from '@crxjs/vite-plugin'
import manifest from './manifest.json'
export default defineConfig(() => {
return {
build: {
outDir: 'build',
},
plugins: [react(), crx({ manifest }), nodePolyfills()],
resolve: {
alias: {
// Replaces node-fetch in kittycad.ts, cross-fetch wouldn't work
'node-fetch': 'isomorphic-fetch',
},
},
test: {
globals: true,
environment: 'jsdom',
setupFiles: 'src/setupTests.ts',
exclude: [...configDefaults.exclude, 'tests/*'],
},
}
})

10524
yarn.lock

File diff suppressed because it is too large Load Diff