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

feat(no-deprecated-functions): support jest version setting #564

Merged
merged 2 commits into from May 9, 2020
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
17 changes: 17 additions & 0 deletions README.md
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
Expand Up @@ -9,10 +9,20 @@ of majors, eventually they are removed completely.
## Rule details
Copy link
Member

Choose a reason for hiding this comment

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

Should this mention that it respects/varies based on jest version?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

You mean something like this?

(But could be improved by mentioning the actual setting, or at least linking back to that section in the readme)

Copy link
Member

Choose a reason for hiding this comment

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

Linking back or something yeah, so people can be explicit


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
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
251 changes: 216 additions & 35 deletions src/rules/__tests__/no-deprecated-functions.test.ts
@@ -1,49 +1,230 @@
import * as fs from 'fs';
G-Rath marked this conversation as resolved.
Show resolved Hide resolved
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 } 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`,
Copy link
Member

Choose a reason for hiding this comment

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

we might add new "better" versions in minors, but this is fine for now 👍

};

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,
},
];
};

ruleTester.run(`${deprecation} -> ${replacement}`, rule, {
// a few sanity checks before doing our massive loop
ruleTester.run('no-deprecated-functions', rule, {
valid: [
'jest',
'require("fs")',
...generateValidCases(14, 'jest.resetModuleRegistry'),
...generateValidCases(17, 'require.requireActual'),
...generateValidCases(25, 'jest.genMockFromModule'),
],
invalid: [
...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")',
`${replacement}()`,
replacement,
`${replacementName}['${replacementFunc}']()`,
`${replacementName}['${replacementFunc}']`,
...allowedFunctions
.map(func => generateValidCases(jestVersion, func))
.reduce((acc, arr) => acc.concat(arr), []),
],
invalid: [
{
code: `${deprecation}()`,
output: `${replacement}()`,
errors: [
{
messageId: 'deprecatedFunction',
data: { deprecation, replacement },
},
],
},
{
code: `${deprecatedName}['${deprecatedFunc}']()`,
output: `${replacementName}['${replacementFunc}']()`,
errors: [
{
messageId: 'deprecatedFunction',
data: { deprecation, replacement },
},
],
},
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',
);
});
});
});