Skip to content

Commit

Permalink
feat(no-deprecated-functions): support jest version setting (#564)
Browse files Browse the repository at this point in the history
* feat(no-deprecated-functions): support jest `version` setting

* chore(no-deprecated-functions): cache jest version
  • Loading branch information
G-Rath authored May 9, 2020
1 parent 0d9dce0 commit 05f20b8
Show file tree
Hide file tree
Showing 6 changed files with 360 additions and 51 deletions.
17 changes: 17 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,23 @@ You can also whitelist the environment variables provided by Jest by doing:
}
```

The behaviour of some rules (specifically `no-deprecated-functions`) change
depending on the version of `jest` being used.

This setting is detected automatically based off the version of the `jest`
package installed in `node_modules`, but it can also be provided explicitly if
desired:

```json
{
"settings": {
"jest": {
"version": 26
}
}
}
```

## Shareable configurations

### Recommended
Expand Down
22 changes: 11 additions & 11 deletions docs/rules/no-deprecated-functions.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,20 @@ of majors, eventually they are removed completely.
## Rule details

This rule warns about calls to deprecated functions, and provides details on
what to replace them with.
what to replace them with, based on the version of Jest that is installed.

This rule can also autofix a number of these deprecations for you.

### `jest.resetModuleRegistry`

This function was renamed to `resetModules` in Jest 15, and is scheduled for
removal in Jest 27.

### `jest.addMatchers`

This function was replaced with `expect.extend` in Jest 17, and is scheduled for
removal in Jest 27.

### `require.requireActual` & `require.requireMock`

These functions were replaced in Jest 21 and removed in Jest 26.
Expand All @@ -25,16 +35,6 @@ for type checkers to handle, and their use via `require` deprecated. Finally,
the release of Jest 26 saw them removed from the `require` function all
together.

### `jest.addMatchers`

This function was replaced with `expect.extend` in Jest 17, and is scheduled for
removal in Jest 27.

### `jest.resetModuleRegistry`

This function was renamed to `resetModules` in Jest 15, and is scheduled for
removal in Jest 27.

### `jest.runTimersToTime`

This function was renamed to `advanceTimersByTime` in Jest 22, and is scheduled
Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
"@babel/preset-typescript": "^7.3.3",
"@commitlint/cli": "^8.2.0",
"@commitlint/config-conventional": "^8.2.0",
"@schemastore/package": "^0.0.5",
"@semantic-release/changelog": "^3.0.5",
"@semantic-release/git": "^7.0.17",
"@types/eslint": "^6.1.3",
Expand Down
284 changes: 250 additions & 34 deletions src/rules/__tests__/no-deprecated-functions.test.ts
Original file line number Diff line number Diff line change
@@ -1,49 +1,265 @@
import * as fs from 'fs';
import * as os from 'os';
import * as path from 'path';
import { JSONSchemaForNPMPackageJsonFiles } from '@schemastore/package';
import { TSESLint } from '@typescript-eslint/experimental-utils';
import rule from '../no-deprecated-functions';
import rule, {
JestVersion,
_clearCachedJestVersion,
} from '../no-deprecated-functions';

const ruleTester = new TSESLint.RuleTester();

[
['require.requireMock', 'jest.requireMock'],
['require.requireActual', 'jest.requireActual'],
['jest.addMatchers', 'expect.extend'],
['jest.resetModuleRegistry', 'jest.resetModules'],
['jest.runTimersToTime', 'jest.advanceTimersByTime'],
['jest.genMockFromModule', 'jest.createMockFromModule'],
].forEach(([deprecation, replacement]) => {
/**
* Makes a new temp directory, prefixed with `eslint-plugin-jest-`
*
* @return {Promise<string>}
*/
const makeTempDir = async () =>
fs.mkdtempSync(path.join(os.tmpdir(), 'eslint-plugin-jest-'));

/**
* Sets up a fake project with a `package.json` file located in
* `node_modules/jest` whose version is set to the given `jestVersion`.
*
* @param {JestVersion} jestVersion
*
* @return {Promise<string>}
*/
const setupFakeProjectDirectory = async (
jestVersion: JestVersion,
): Promise<string> => {
const jestPackageJson: JSONSchemaForNPMPackageJsonFiles = {
name: 'jest',
version: `${jestVersion}.0.0`,
};

const tempDir = await makeTempDir();
const jestPackagePath = path.join(tempDir, 'node_modules', 'jest');

// todo: remove in node@10 & replace with { recursive: true }
fs.mkdirSync(path.join(tempDir, 'node_modules'));

fs.mkdirSync(jestPackagePath);
await fs.writeFileSync(
path.join(jestPackagePath, 'package.json'),
JSON.stringify(jestPackageJson),
);

return tempDir;
};

const generateValidCases = (
jestVersion: JestVersion | undefined,
functionCall: string,
): Array<TSESLint.ValidTestCase<never>> => {
const [name, func] = functionCall.split('.');
const settings = { jest: { version: jestVersion } } as const;

return [
{ settings, code: `${functionCall}()` },
{ settings, code: `${functionCall}` },
{ settings, code: `${name}['${func}']()` },
{ settings, code: `${name}['${func}']` },
];
};

const generateInvalidCases = (
jestVersion: JestVersion | undefined,
deprecation: string,
replacement: string,
): Array<TSESLint.InvalidTestCase<'deprecatedFunction', never>> => {
const [deprecatedName, deprecatedFunc] = deprecation.split('.');
const [replacementName, replacementFunc] = replacement.split('.');
const settings = { jest: { version: jestVersion } };
const errors: [TSESLint.TestCaseError<'deprecatedFunction'>] = [
{ messageId: 'deprecatedFunction', data: { deprecation, replacement } },
];

return [
{
code: `${deprecation}()`,
output: `${replacement}()`,
settings,
errors,
},
{
code: `${deprecatedName}['${deprecatedFunc}']()`,
output: `${replacementName}['${replacementFunc}']()`,
settings,
errors,
},
];
};

describe('the jest version cache', () => {
beforeEach(async () => process.chdir(await setupFakeProjectDirectory(17)));

// change the jest version *after* each test case
afterEach(async () => {
const jestPackageJson: JSONSchemaForNPMPackageJsonFiles = {
name: 'jest',
version: '24.0.0',
};

const tempDir = process.cwd();

await fs.writeFileSync(
path.join(tempDir, 'node_modules', 'jest', 'package.json'),
JSON.stringify(jestPackageJson),
);
});

ruleTester.run('no-deprecated-functions', rule, {
valid: [
'require("fs")', // this will cause jest version to be read & cached
'jest.requireActual()', // deprecated after jest 17
],
invalid: [],
});
});

ruleTester.run(`${deprecation} -> ${replacement}`, rule, {
// contains the cache-clearing beforeEach so we can test the cache too
describe('the rule', () => {
beforeEach(() => _clearCachedJestVersion());

// a few sanity checks before doing our massive loop
ruleTester.run('no-deprecated-functions', rule, {
valid: [
'jest',
'require("fs")',
`${replacement}()`,
replacement,
`${replacementName}['${replacementFunc}']()`,
`${replacementName}['${replacementFunc}']`,
...generateValidCases(14, 'jest.resetModuleRegistry'),
...generateValidCases(17, 'require.requireActual'),
...generateValidCases(25, 'jest.genMockFromModule'),
],
invalid: [
{
code: `${deprecation}()`,
output: `${replacement}()`,
errors: [
{
messageId: 'deprecatedFunction',
data: { deprecation, replacement },
},
],
},
{
code: `${deprecatedName}['${deprecatedFunc}']()`,
output: `${replacementName}['${replacementFunc}']()`,
errors: [
{
messageId: 'deprecatedFunction',
data: { deprecation, replacement },
},
],
},
...generateInvalidCases(
21,
'jest.resetModuleRegistry',
'jest.resetModules',
),
...generateInvalidCases(24, 'jest.addMatchers', 'expect.extend'),
...generateInvalidCases(
26,
'jest.genMockFromModule',
'jest.createMockFromModule',
),
],
});

describe.each<JestVersion>([
14,
15,
16,
17,
18,
19,
20,
21,
22,
23,
24,
25,
26,
27,
])('when using jest version %i', jestVersion => {
beforeEach(async () =>
process.chdir(await setupFakeProjectDirectory(jestVersion)),
);

const allowedFunctions: string[] = [];
const deprecations = ([
[15, 'jest.resetModuleRegistry', 'jest.resetModules'],
[17, 'jest.addMatchers', 'expect.extend'],
[21, 'require.requireMock', 'jest.requireMock'],
[21, 'require.requireActual', 'jest.requireActual'],
[22, 'jest.runTimersToTime', 'jest.advanceTimersByTime'],
[26, 'jest.genMockFromModule', 'jest.createMockFromModule'],
] as const).filter(deprecation => {
if (deprecation[0] > jestVersion) {
allowedFunctions.push(deprecation[1]);

return false;
}

return true;
});

ruleTester.run('explict jest version', rule, {
valid: [
'jest',
'require("fs")',
...allowedFunctions
.map(func => generateValidCases(jestVersion, func))
.reduce((acc, arr) => acc.concat(arr), []),
],
invalid: deprecations
.map(([, deprecation, replacement]) =>
generateInvalidCases(jestVersion, deprecation, replacement),
)
.reduce((acc, arr) => acc.concat(arr), []),
});

ruleTester.run('detected jest version', rule, {
valid: [
'jest',
'require("fs")',
...allowedFunctions
.map(func => generateValidCases(undefined, func))
.reduce((acc, arr) => acc.concat(arr), []),
],
invalid: deprecations
.map(([, deprecation, replacement]) =>
generateInvalidCases(undefined, deprecation, replacement),
)
.reduce((acc, arr) => acc.concat(arr), []),
});
});

describe('when no jest version is provided', () => {
describe('when the jest package.json is missing the version property', () => {
beforeEach(async () => {
const tempDir = await setupFakeProjectDirectory(1);

await fs.writeFileSync(
path.join(tempDir, 'node_modules', 'jest', 'package.json'),
JSON.stringify({}),
);

process.chdir(tempDir);
});

it('requires the version to be set explicitly', () => {
expect(() => {
const linter = new TSESLint.Linter();

linter.defineRule('no-deprecated-functions', rule);

linter.verify('jest.resetModuleRegistry()', {
rules: { 'no-deprecated-functions': 'error' },
});
}).toThrow(
'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly',
);
});
});

describe('when the jest package.json is not found', () => {
beforeEach(async () => process.chdir(await makeTempDir()));

it('requires the version to be set explicitly', () => {
expect(() => {
const linter = new TSESLint.Linter();

linter.defineRule('no-deprecated-functions', rule);

linter.verify('jest.resetModuleRegistry()', {
rules: { 'no-deprecated-functions': 'error' },
});
}).toThrow(
'Unable to detect Jest version - please ensure jest package is installed, or otherwise set version explicitly',
);
});
});
});
});
Loading

0 comments on commit 05f20b8

Please sign in to comment.