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

Rhys/codemod #829

Merged
merged 50 commits into from
Sep 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
7a92325
feat: started work on a codemod script
wheresrhys Aug 29, 2024
a76cee9
feat:set up basic codemods package structure
wheresrhys Aug 31, 2024
4725251
test: added tests for first codemod
wheresrhys Aug 31, 2024
0c3debf
fix: defined types for codemod
wheresrhys Aug 31, 2024
5577416
test: test suite for identifying fetch-mock instances
wheresrhys Aug 31, 2024
f981e4d
test: sketched out some more test requirements
wheresrhys Aug 31, 2024
6ec5575
feat: first codemod for editing an option implemented
wheresrhys Aug 31, 2024
f790205
feat: progress on codemod to alter global options
wheresrhys Aug 31, 2024
23201b0
feat: more option codemods
wheresrhys Aug 31, 2024
e523711
feat: can identify fetch mock in esm
wheresrhys Aug 31, 2024
eeda85b
build: stop using ts for the codemods
wheresrhys Aug 31, 2024
298a9e2
refactor: `modularise codemods a little
wheresrhys Aug 31, 2024
a115a27
feat: lastUrl() codemod implemented
wheresrhys Sep 1, 2024
300e0b8
feat: lastOptions() and lastResponse()
wheresrhys Sep 1, 2024
1ef70ec
feat: support converting getAny() etc
wheresrhys Sep 1, 2024
3b7daf7
feat: use get("*") as a better replacement for getAny()
wheresrhys Sep 1, 2024
e976e7e
feat: remiving options from methods more or less works
wheresrhys Sep 2, 2024
3c29acf
test: tests for getAnyOnce ->getAny with deprecated optiosn pass
wheresrhys Sep 2, 2024
3b0786f
test: add tests for removing multiple options at once
wheresrhys Sep 14, 2024
c272f65
feat: codemod fro fallbackToNetwork option
wheresrhys Sep 14, 2024
fd4ff41
test: added tests for resetting method codemods
wheresrhys Sep 15, 2024
5f1203e
feat: codemod resetHistory to clearHistory
wheresrhys Sep 15, 2024
a96f62b
feat: codemod reset()
wheresrhys Sep 15, 2024
21e54af
feat: codemod restore()
wheresrhys Sep 15, 2024
e9d59df
feat: finished implementing reset method codemods
wheresrhys Sep 15, 2024
62fc48c
feat: add an error to warn of lastCall() signature change
wheresrhys Sep 15, 2024
5bf6f67
feat: add an error to warn of calls() signature change
wheresrhys Sep 15, 2024
c4b3871
test: add tests for replacing sandbox() with fetchHandler
wheresrhys Sep 15, 2024
dbb0b43
feat: midway through implementing codemods for sandbox
wheresrhys Sep 15, 2024
a42817b
fix: avoided converting jest.mock() to jest.route()
wheresrhys Sep 16, 2024
c0adc0c
test: scaffolded out some more tests for identifying references to fe…
wheresrhys Sep 16, 2024
9a06c1e
feat: remove codemods for sandbox() method
wheresrhys Sep 16, 2024
4d64a53
test: skip tests for multiple instances of fetch-mock
wheresrhys Sep 16, 2024
f4a3c39
docs: start documenting usage of codemods
wheresrhys Sep 16, 2024
70dd5b2
test: started scaffollding integration tests
wheresrhys Sep 18, 2024
517a6ac
docs: document how to use transform with jodeshift
wheresrhys Sep 18, 2024
317ede7
feat: allow manually specifying extra variables that hold fetch-mock …
wheresrhys Sep 18, 2024
90445a1
test: more work on integration tests
wheresrhys Sep 18, 2024
aad042b
refactor(codemod): Convert codemods to commonjs (for compatibility wi…
wheresrhys Sep 18, 2024
376d5c3
feat: converted codemods to use tsx parser for greater reuse
wheresrhys Sep 19, 2024
78c2e35
fix: changed to use j.ObjectProperty instead of j.Property
wheresrhys Sep 19, 2024
3a610b7
fix: reinstated codemods for options on all methods
wheresrhys Sep 19, 2024
5914d5f
test: finished integration tests
wheresrhys Sep 19, 2024
47da836
Merge pull request #834 from wheresrhys/rhys/codemod-tsx
wheresrhys Sep 19, 2024
4e35b53
chore: fix type-check error
wheresrhys Sep 19, 2024
623b43b
Merge pull request #835 from wheresrhys/rhys/codemod-tsx
wheresrhys Sep 19, 2024
8d06df2
test: fix type checking for fetch-mock package
wheresrhys Sep 19, 2024
9661ceb
chore: get ready to release to npm
wheresrhys Sep 19, 2024
dca98de
chore: fix inconsistent use of require/import in tests
wheresrhys Sep 19, 2024
cacb67c
test: avoid running codemod tests in browser
wheresrhys Sep 19, 2024
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
5 changes: 4 additions & 1 deletion eslint.config.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,12 +11,15 @@ export default [
ignores: [
'docs/**/*.js',
'packages/*/test/fixtures/*',
'packages/**/__tests__/fixtures/*',
'**/dist/**/*',
'packages/fetch-mock/types/index.test-d.ts',
],
},
eslint.configs.recommended,
...tseslint.configs.recommended,
...tseslint.configs.recommended.map((config) => {
return { ...config, ignores: ['packages/codemods/**'] };
}),
eslintConfigPrettier,
{
rules: {
Expand Down
2,741 changes: 1,559 additions & 1,182 deletions package-lock.json

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
"test": "vitest . --ui --exclude=packages/jest/src/__tests__/*",
"test:ci": "vitest . --reporter=junit --outputFile=test-results/junit.xml --coverage.provider=istanbul --exclude=packages/jest/src/__tests__/*",
"test:jest": "node --experimental-vm-modules node_modules/jest/bin/jest.js packages/jest",
"test:browser": "vitest . --browser.enabled --browser.name chromium --browser.provider playwright --exclude=packages/jest/src/__tests__/*",
"test:browser": "vitest . --browser.enabled --browser.name chromium --browser.provider playwright --exclude=packages/{jest,codemods}/src/__tests__/*",
"coverage:send": "cat ./coverage/lcov.info | coveralls",
"compat:module": "npm run compat:module -w import-compat"
},
Expand Down
1 change: 1 addition & 0 deletions packages/codemods/.npmignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
try.js
75 changes: 75 additions & 0 deletions packages/codemods/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
# @fetch-mock/codemods

A tool for helping upgrade to fetch-mock@12.

## Usage

1. Install `npm i -D @fetch-mock/codemods jscodeshift`;
2. Manually modify any code using `.sandbox()` according to the example above.
3. Run `jscodeshift -t node_modules/@fetch-mock/codemods/wrc/index.jscodeshift .` to run over your entire project, or replace `.` with the directory/file paths you wish to modify. [The jscodeshift CLI has many options](https://jscodeshift.com/run/cli/) - adjust them to suit your project. **Note that the parser option should not be used as @fetch-mock/codemods forces use of the TSX parser in order to ensure compatibility with teh greatest range of projects**.
4. For scenarios where the codemod is unable to detect which variable contains a fetch-mock instance (e.g. when it is required in a global set up file, or when using `jest.mock()`) you may pass in one or more variable names using the `FM_VARIABLES` environment variable e.g. `FM_VARIABLES=fm,fetch`
5. After the codemods have executed, run your tests, correcting all the errors thrown. The notes on what is in/out of scope will help guide you.
6. Once all your tests are fixed you should be able to uninstall the codemods: `npm uninstall @fetch-mock/codemods jscodeshift`

## Features

- Identifies instances of fetch-mock imported using `require` or `import`
- Rewrites `.mock()` to `.route()`
- Rewrites `.reset()`, `.restore()`, `.resetBehavior()` and `.resetHistory()` to their equivalents in fetch-mock@12
- Rewrites `.lastUrl()`, `.lastOptions()` and `lastResponse()` to their equivalents in fetch-mock@12
- Adds an informative error whenever `.lastCall()` or `.calls()` are used with advice on how to manually correct these
- Converts `.getOnce()`, `.getAnyOnce()`, `.postOnce()` etc... - which have been removed - to calls to the underlying `.get()` method with additional options passed in.
- Removes uses of the deprecated options `overwriteRoutes`, `warnOnFallback`, `sendAsJson`
- Removes uses of the deprecated `fallbackToNetwork` option, and adds an informative error with details of how to replace with the `spyGlobal()` method

## Limitations/Out of scope

- Javascript is a language with multiple ways to do the same thing. While these codemods attempt to cover a few different patterns, it's likely that they don't cover all the ways fetch-mock is being used. Please raise an issue if you think they can be improved.
- fetch-mock@12 no longer has the `.mock()` method which combines defining a route _and_ setting up global mocking. All calls to `.mock()` are replaced by `.route()`.
If using global `fetch` you will also need to call `.mockGlobal()` at least once per test suite.
- The `.sandbox()` method previously created a `fetch` function that also had all the fetch-mock methods attached. This is no longer the case, but pulling it apart is complex and deliberately left out of scope for this codemod.
- Any uses of fetch-mock prior to assigning to a variable will not be modified e.g. `require('fetch-mock').mock('a', 'b')` will not be converted to `require('fetch-mock').route('a', 'b')`
- When using a pattern such as `jest.mock('node-fetch', () => require('fetch-mock').sandbox())`, the codemod is unable to identify that `require('node-fetch')` will be an instance of fetch-mock.
- On the server side fetch-mock was previously built around node-fetch's classes, but now uses native `fetch`. In most cases, even if your application code still uses node-fetch, your mocks will still work. However, if you explicitly create instances of `Request` or `Headers` using node-fetch's classes, you may need to provide these to fetch-mock.

Taking the last 4 points together, this example illustrates the kind of manual modifications required:

### Before

```js
jest.mock('node-fetch', () => require('fetch-mock').sandbox());

const nodeFetch = require('node-fetch');

it('runs a test', () => {
nodeFetch.get('http://a.com', 200);
myAPI.call();
expect(nodeFetch.called()).toBe(true);
});
```

### After

```js
const fetchMock = require('fetch-mock');

jest.mock('node-fetch', () => {
const nodeFetch = jest.requireActual('node-fetch');
// only needed if your application makes use of Response, Request
// or Headers classes directly
Object.assign(fetchMock.config, {
fetch: nodeFetch,
Response: nodeFetch.Response,
Request: nodeFetch.Request,
Headers: nodeFetch.Headers,
});
return fetchMock.fetchHandler;
});
const nodeFetch = require('node-fetch');

it('runs a test', () => {
fetchMock.get('http://a.com', 200);
myAPI.call();
expect(fetchMock.called()).toBe(true);
});
```
27 changes: 27 additions & 0 deletions packages/codemods/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "@fetch-mock/codemods",
"description": "Codemods for upgrading fetch-mock",
"version": "0.0.0",
"main": "./src/index.js",
"type": "commonjs",
"engines": {
"node": ">=18.11.0"
},
"repository": {
"directory": "packages/codemods",
"type": "git",
"url": "git+https://github.com/wheresrhys/fetch-mock.git"
},
"scripts": {
"build": "echo 'no build'"
},
"license": "MIT",
"author": "Rhys Evans",
"bugs": {
"url": "https://github.com/wheresrhys/fetch-mock/issues"
},
"homepage": "http://www.wheresrhys.co.uk/fetch-mock",
"dependencies": {
"jscodeshift": "^17.0.0"
}
}
4 changes: 4 additions & 0 deletions packages/codemods/src/__tests__/fixtures/extra-vars.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
const fetchMock = require('fetch-mock');
fetchMock.mock('blah', 200);
fm1.mock('blah', 200);
fm2.mock('blah', 200);
2 changes: 2 additions & 0 deletions packages/codemods/src/__tests__/fixtures/jsx.jsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
import fetchMock from 'fetch-mock';
fetchMock.mock("blah", <div>Content</div>);
4 changes: 4 additions & 0 deletions packages/codemods/src/__tests__/fixtures/tsx.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import fetchMock from 'fetch-mock';
function helper (res: number): void {
fetchMock.mock("blah", <div>Content</div>);
};
4 changes: 4 additions & 0 deletions packages/codemods/src/__tests__/fixtures/typescript.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
import fetchMock from 'fetch-mock';
function helper (res: number): void {
fetchMock.mock("blah", res);
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import { describe, it, expect } from 'vitest';
import { codemod } from '../index';
import jscodeshift from 'jscodeshift';

function expectCodemodResult(src, expected) {
expect(codemod(src, jscodeshift)).toEqual(expected);
}

describe('identifying fetch-mock instances', () => {
it('cjs require() named fetchMock', () => {
expectCodemodResult(
`
const fetchMock = require('fetch-mock');
fetchMock.mock("blah", 200)
`,
`
const fetchMock = require('fetch-mock');
fetchMock.route("blah", 200)
`,
);
});
it('cjs require() named something else', () => {
expectCodemodResult(
`
const fetchNot = require('fetch-mock');
fetchNot.mock("blah", 200)
`,
`
const fetchNot = require('fetch-mock');
fetchNot.route("blah", 200)
`,
);
});
it('esm import named fetchMock', () => {
expectCodemodResult(
`
import fetchMock from 'fetch-mock';
fetchMock.mock("blah", 200)
`,
`
import fetchMock from 'fetch-mock';
fetchMock.route("blah", 200)
`,
);
});
it('esm import named something else', () => {
expectCodemodResult(
`
import fetchNot from 'fetch-mock';
fetchNot.mock("blah", 200)
`,
`
import fetchNot from 'fetch-mock';
fetchNot.route("blah", 200)
`,
);
});
it.skip('unassigned instances of require("fetch-mock")', () => {
expectCodemodResult(
`require('fetch-mock').mock("blah", 200)`,
`require('fetch-mock').route("blah", 200)`,
);
});
it.skip('sandbox() instances', () => {
expectCodemodResult(
`
const fetchMock = require('fetch-mock');
const fm = fetchMock.sandbox();
fm.mock("blah", 200)
`,
`
const fetchMock = require('fetch-mock');
const fm = fetchMock.sandbox();
fm.route("blah", 200)
`,
);
});
it.skip('sandbox() instances used by jest / vitest.mock', () => {});
it.skip('identify multiple instances on a page e.g. fetchMock and fm=fetchMock.sandbox()', () => {});
it.skip('identify when a fm instance with lots of chained methods is assigned to a new variable', () => {
expectCodemodResult(
`
const fetchMock = require('fetch-mock');
const fm = fetchMock.get('a', 'b').get('a', 'b');
fm.mock("blah", 200)
`,
`
const fetchMock = require('fetch-mock');
const fm = fetchMock.get('a', 'b').get('a', 'b');
fm.route("blah", 200)
`,
);
});
});
42 changes: 42 additions & 0 deletions packages/codemods/src/__tests__/integration.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
import { it, describe, expect } from 'vitest';
import { promisify } from 'node:util';
import { exec as callbackExec } from 'node:child_process';
const exec = promisify(callbackExec);

describe('integration', () => {
it('can operate on typescript', async () => {
const { stdout } = await exec(
'jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/typescript.ts',
);
expect(stdout).toContain(`import fetchMock from 'fetch-mock';
function helper (res: number): void {
fetchMock.route("blah", res);
}`);
});
it('can operate on jsx', async () => {
const { stdout } = await exec(
'jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/jsx.jsx',
);
expect(stdout).toContain(`import fetchMock from 'fetch-mock';
fetchMock.route("blah", <div>Content</div>);`);
});

it('can operate on tsx', async () => {
const { stdout } = await exec(
'jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/tsx.tsx',
);
expect(stdout).toContain(`import fetchMock from 'fetch-mock';
function helper (res: number): void {
fetchMock.route("blah", <div>Content</div>);
}`);
});
it('allow passing in one or more additional variable names for fetch-mock', async () => {
const { stdout } = await exec(
'FM_VARIABLES=fm1,fm2 jscodeshift --parser ts -p -d -t ./packages/codemods/src/index.js ./packages/codemods/src/__tests__/fixtures/extra-vars.js',
);
expect(stdout).toContain(`const fetchMock = require('fetch-mock');
fetchMock.route('blah', 200);
fm1.route('blah', 200);
fm2.route('blah', 200);`);
});
});
Loading