Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

First class support for CRA #102

Merged
merged 26 commits into from
May 23, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
26 commits
Select commit Hold shift + click to select a range
873101f
feat: Make CRA use jest.config.js + add babelCRA
nvh95 Apr 26, 2022
bf0022b
feat: Use custom test.js in example. TODO: Move to jest-preview
nvh95 Apr 26, 2022
b77f928
feat: use babelTransform directly from CRA
nvh95 May 16, 2022
2e89229
chore: add note about how to handle scripts/test.js
nvh95 May 16, 2022
dc72664
docs: Add skeleton for cra post
nvh95 May 17, 2022
da23a81
feat: add CLI to add jest.config.js
nvh95 May 17, 2022
625f4fb
feat: Add CLI to create scripts/test.js
nvh95 May 17, 2022
ec099c9
docs: Updating docs
nvh95 May 18, 2022
2b754bf
feat: Make jest-preview accepts argument (use commander)
nvh95 May 18, 2022
d00f12f
feat: Update docs for first class support CRA
nvh95 May 18, 2022
7db9b84
chore: Fix typescript issue with HTMLCollection is not an array
nvh95 May 18, 2022
42f94af
chore: v0.2.1-alpha.0
nvh95 May 18, 2022
8f52c4a
feat: Update package.json by codemod as well
nvh95 May 18, 2022
9a0235a
fix: fix favicon.ico location
nvh95 May 18, 2022
7cab2e2
chore: release v0.2.1-alpha.1
nvh95 May 18, 2022
304a64a
feat: Add `jest-preview` script to codemod
nvh95 May 18, 2022
8de7b09
feat: Configure CRA in the codemod + try catch each step
nvh95 May 19, 2022
a0cdc42
chore: release v0.2.1-alpha.2
nvh95 May 19, 2022
fd79c7d
chore: add an instruction after config CRA
nvh95 May 19, 2022
4deda01
fix: make scripts dir if not existed
nvh95 May 19, 2022
1783529
chore: release v0.2.1-alpha.3
nvh95 May 19, 2022
d829df0
chore: try to delete a nested property in configCra
nvh95 May 21, 2022
bfdd733
chore: Update comments
nvh95 May 21, 2022
cd38dfc
chore: do not need to try catch to delete since we are targeting un-e…
nvh95 May 22, 2022
dc923dd
chore: Update docs
nvh95 May 22, 2022
45ff575
chore: Update release date of CRA support blog post
nvh95 May 23, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .vscode/launch.json
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
"request": "launch",
"name": "Debug Preview Server",
"skipFiles": ["<node_internals>/**"],
"program": "${workspaceFolder}/server/previewServer.js",
"program": "${workspaceFolder}/cli/index.js",
"outFiles": ["${workspaceFolder}/**/*.js"]
}
]
Expand Down
2 changes: 1 addition & 1 deletion CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -77,7 +77,7 @@ Whenever `preview.debug()` is triggered, or whenever a test fails, you will see
Following are brief descriptions of the repository architecture:

- [src](https://github.com/nvh95/jest-preview/tree/main/src/): contains most of the code of Jest Preview such as `debug` function, `jestPreviewConfigure`, all Jest Transformations, pre-configured presets, adapters, etc.
- [server](https://github.com/nvh95/jest-preview/tree/main/server/): contains Jest Preview server code, which is a web server that serves the preview page (Jest Preview Dashboard).
- [cli/server](https://github.com/nvh95/jest-preview/tree/main/cli/server): contains Jest Preview server code, which is a web server that serves the preview page (Jest Preview Dashboard).
- [demo](https://github.com/nvh95/jest-preview/tree/main/demo/): contains the demo app. You will work with this app most of the time when developing Jest Preview.
- [config/jest](https://github.com/nvh95/jest-preview/tree/main/config/jest/): jest configuration files for the demo app.
- [dist](https://github.com/nvh95/jest-preview/tree/main/dist/): Distribution code, which is bundled and processed by Rollup (previously: Vite Library Mode).
Expand Down
130 changes: 130 additions & 0 deletions cli/configCra.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,130 @@
#!/usr/bin/env node
// @ts-check
const path = require('path');
const fs = require('fs');
// Append current node_modules to the module search path, so require('react-scripts') can work.
module.paths.push(path.resolve(process.cwd(), './node_modules'));

// 1. Create `jest.config.js`
try {
// @ts-expect-error This is meant to run where react-scripts is installed
const createJestConfig = require('react-scripts/scripts/utils/createJestConfig.js');
const jestConfig = createJestConfig(
(/** @type {string} */ filePath) => path.posix.join('<rootDir>', filePath),
null,
true,
);
jestConfig.transform = {
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we add these keys to the existing transform instead of overwriting transform like this?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No. This is intentional. We would like to remove '^.+\\.css$'. Without removing this, it causes some issues that makes Sass not work in #101

'^.+\\.(js|jsx|mjs|cjs|ts|tsx)$':
'react-scripts/config/jest/babelTransform.js',
'^.+\\.(css|scss|sass)$': 'jest-preview/transforms/css',
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)':
'jest-preview/transforms/fileCRA',
};
jestConfig.transformIgnorePatterns =
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should we do a check first to ensure transformIgnorePatterns is an array and not nullish?

Copy link
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No need since we are targeting un-ejected CRA apps. See other comments for more details

jestConfig.transformIgnorePatterns.filter(
(/** @type {string} */ pattern) =>
pattern !== '^.+\\.module\\.(css|sass|scss)$',
);
delete jestConfig.moduleNameMapper['^.+\\.module\\.(css|sass|scss)$'];

const jestConfigFileContent = `module.exports = ${JSON.stringify(
jestConfig,
null,
2,
)}\n`;
fs.writeFileSync('jest.config.js', jestConfigFileContent);
console.log(`Added jest.config.js to the project.`);

// Try to prettier `jest.config.js`
const execSync = require('child_process').execSync;
try {
execSync('prettier jest.config.js --write');
} catch (error) {
// Just ignore if user doesn't have prettier installed
}
} catch (error) {
console.error(error);
}

// 2. Create `scripts/test.js`
// https://github.com/facebook/create-react-app/blob/f99167c014a728ec856bda14f87181d90b050813/packages/react-scripts/scripts/eject.js#L158-L162

try {
const testFile = path.resolve(
process.cwd(),
'./node_modules/react-scripts/scripts/test.js',
);
let content = fs.readFileSync(testFile, 'utf8');

content =
content
// Remove dead code from .js files on eject
.replace(
/\/\/ @remove-on-eject-begin([\s\S]*?)\/\/ @remove-on-eject-end/gm,
'',
)
// Require `env` direct from `react-scripts`
.replace(
`require('../config/env');`,
`require('react-scripts/config/env');`,
)
.trim() + '\n';
console.log(`Added scripts/test.js to the project.`);
if (!fs.existsSync('scripts')) {
fs.mkdirSync('scripts');
}
fs.writeFileSync(path.resolve(process.cwd(), 'scripts/test.js'), content);

// 3. Update `package.json`
const packageJson = require(path.resolve(process.cwd(), 'package.json'));
packageJson.scripts.test = 'node scripts/test.js';
if (!packageJson.scripts['jest-preview']) {
packageJson.scripts['jest-preview'] = 'jest-preview';
}
fs.writeFileSync(
path.resolve(process.cwd(), 'package.json'),
JSON.stringify(packageJson, null, 2) + '\n',
);
console.log(`Update test script in package.json.`);
} catch (error) {
console.error(error);
}

// 4. Configure Jest Preview using jestPreviewConfigure in `src/setupTests.ts` or `src/setupTests.js`
/**
* @param {string} filePath
* @param {string} content
*/
function injectToFileIfExisted(filePath, content) {
if (fs.existsSync(filePath)) {
fs.appendFileSync(filePath, content);
}
}

try {
const configToInject = `import { jestPreviewConfigure } from 'jest-preview'

jestPreviewConfigure({
// Opt-in to automatic mode to preview failed test case.
autoPreview: true,
// TODO: To add your global css here
externalCss: ['src/index.css'],
})
`;
injectToFileIfExisted(
path.resolve(process.cwd(), 'src/setupTests.ts'),
configToInject,
);
injectToFileIfExisted(
path.resolve(process.cwd(), 'src/setupTests.js'),
configToInject,
);
console.log(`Configured Jest Preview in src/setupTests.(ts|js).`);
} catch (error) {
console.error(error);
}

console.log(
'\nTo continue, run `npm run jest-preview` to open Jest Preview server, then `npm run test` to run Jest. It will preview any failed test in your browser.',
);
15 changes: 15 additions & 0 deletions cli/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#!/usr/bin/env node
const { program } = require('commander');

program
.command('config-cra')
.description('Integrate Jest Preview with CRA.')
.action(() => {
require('./configCra');
});

program.description('Start Jest Preview server.').action(() => {
require('./server/previewServer');
});

program.parse(process.argv);
File renamed without changes.
File renamed without changes.
2 changes: 1 addition & 1 deletion server/previewServer.js → cli/server/previewServer.js
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ const BODY_BASENAME = 'body.html';
const BODY_PATH = path.join(CACHE_DIRECTORY, BODY_BASENAME);
const PUBLIC_CONFIG_BASENAME = 'cache-public.config';
const PUBLIC_CONFIG_PATH = path.join(CACHE_DIRECTORY, PUBLIC_CONFIG_BASENAME);
const FAV_ICON_PATH = './node_modules/jest-preview/server/favicon.ico';
const FAV_ICON_PATH = './node_modules/jest-preview/cli/server/favicon.ico';

// Always set default public folder to `public` if not specified
let publicFolder = 'public';
Expand Down
File renamed without changes.
27 changes: 2 additions & 25 deletions examples/create-react-app/README.md
Original file line number Diff line number Diff line change
@@ -1,27 +1,4 @@
# Use with create-react-app

This example demonstrates how to use `jest-preview` with `create-react-app`.

## Setup jest with create-react-app

jest is setup with create-react-app by default, we don't need to do anything more

## Installation and Usage

Please refer to [Installation](../../README.md#installation) and [Usage](../../README.md#installation).
Except for step 2 of installation: **Configure jest's transform to transform CSS and files**

- Because `create-react-app` allows user to [use svg files as React components](https://create-react-app.dev/docs/adding-images-fonts-and-files/#adding-svgs), `jest-preview` therefore needs to support that, we update Jest's configuration in `package.json` as follow:

```json
{
"transform": {
// Other transforms
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "jest-preview/transforms/fileCRA"
}
}
```

## Caveats

Even though `jest-preview` itself supports CSS Modules, it doesn't support `create-react-app` without ejecting yet. The support will land in the next version.
See the documentation at [Create React App
example](https://www.jest-preview.com/docs/examples/create-react-app)
49 changes: 49 additions & 0 deletions examples/create-react-app/jest.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,49 @@
module.exports = {
roots: ['<rootDir>/src'],
collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}', '!src/**/*.d.ts'],
setupFiles: ['react-app-polyfill/jsdom'],
// Use `setupTests.js` if you use javascript
setupFilesAfterEnv: ['<rootDir>/src/setupTests.ts'],
testMatch: [
'<rootDir>/src/**/__tests__/**/*.{js,jsx,ts,tsx}',
'<rootDir>/src/**/*.{spec,test}.{js,jsx,ts,tsx}',
],
testEnvironment: 'jsdom',
testRunner: 'jest-circus/runner.js',
transform: {
'^.+\\.(js|jsx|mjs|cjs|ts|tsx)$':
'react-scripts/config/jest/babelTransform.js',
// Update the regex to support css and sass
'^.+\\.(css|scss|sass)$': 'jest-preview/transforms/css',
'^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)':
'jest-preview/transforms/fileCRA',
},
transformIgnorePatterns: [
'[/\\\\]node_modules[/\\\\].+\\.(js|jsx|mjs|cjs|ts|tsx)$',
// Remove to support CSS Modules
// '^.+\\.module\\.(css|sass|scss)$',
],
modulePaths: [],
moduleNameMapper: {
'^react-native$': 'react-native-web',
// Remove to support CSS Modules
// '^.+\\.module\\.(css|sass|scss)$': 'identity-obj-proxy',
},
moduleFileExtensions: [
'web.js',
'js',
'web.ts',
'ts',
'web.tsx',
'tsx',
'json',
'web.jsx',
'jsx',
'node',
],
watchPlugins: [
'jest-watch-typeahead/filename',
'jest-watch-typeahead/testname',
],
resetMocks: true,
};
11 changes: 3 additions & 8 deletions examples/create-react-app/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,11 +19,12 @@
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "react-scripts test",
"test": "node scripts/test.js",
"test:nc": "npm run test -- --no-cache",
"eject": "react-scripts eject",
"jest-preview": "jest-preview",
"test:debug": "npm-run-all -p test jest-preview"
"test:debug": "npm-run-all -p test jest-preview",
"config-cra": "jest-preview config-cra"
},
"eslintConfig": {
"extends": [
Expand All @@ -46,11 +47,5 @@
"devDependencies": {
"jest-preview": "file:../..",
"npm-run-all": "^4.1.5"
},
"jest": {
"transform": {
"^.+\\.css$": "jest-preview/transforms/css",
"^(?!.*\\.(js|jsx|mjs|cjs|ts|tsx|css|json)$)": "jest-preview/transforms/fileCRA"
}
}
}
52 changes: 52 additions & 0 deletions examples/create-react-app/scripts/test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
'use strict';

// Do this as the first thing so that any code reading it knows the right env.
process.env.BABEL_ENV = 'test';
process.env.NODE_ENV = 'test';
process.env.PUBLIC_URL = '';

// Makes the script crash on unhandled rejections instead of silently
// ignoring them. In the future, promise rejections that are not handled will
// terminate the Node.js process with a non-zero exit code.
process.on('unhandledRejection', (err) => {
throw err;
});

// Ensure environment variables are read.
// Read config from react-scripts directly
require('react-scripts/config/env');

const jest = require('jest');
const execSync = require('child_process').execSync;
let argv = process.argv.slice(2);

function isInGitRepository() {
try {
execSync('git rev-parse --is-inside-work-tree', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}

function isInMercurialRepository() {
try {
execSync('hg --cwd . root', { stdio: 'ignore' });
return true;
} catch (e) {
return false;
}
}

// Watch unless on CI or explicitly running all tests
if (
!process.env.CI &&
argv.indexOf('--watchAll') === -1 &&
argv.indexOf('--watchAll=false') === -1
) {
// https://github.com/facebook/create-react-app/issues/5210
const hasSourceControl = isInGitRepository() || isInMercurialRepository();
argv.push(hasSourceControl ? '--watch' : '--watchAll');
}

jest.run(argv);
2 changes: 2 additions & 0 deletions examples/create-react-app/src/App.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { useState } from 'react';
import { ReactComponent as Logo } from './logo.svg';
import logo from './logo.svg';
import logo2 from './assets/images/logo.svg';
import styles from './style.module.css';

import './App.css';
import './assets/css/App.css';
Expand All @@ -15,6 +16,7 @@ function App() {
<img src={logo} className="App-logo" alt="logo" />
<img src={logo2} className="logo2" alt="logo2" />
<p>Create React App example</p>
<p className={styles.textOrange}>Styled by CSS Modules</p>
<button
data-testid="increase"
type="button"
Expand Down
3 changes: 3 additions & 0 deletions examples/create-react-app/src/style.module.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
.textOrange {
color: orange;
}
Loading