-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
762feaf
commit cf79b97
Showing
5 changed files
with
433 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
# Checks that Wrapper methods are called with appropriate selectors. (vue-test-utils/no-deprecated-selectors) | ||
|
||
The `--fix` option on the command line can automatically fix some of the problems reported by this rule. | ||
|
||
## Rule Details | ||
|
||
This rule reports `Wrapper` `find*` and `get*` calls which are using improper selectors for their return types. For example, `find` should be called with a CSS selector and should be expected to return a DOM element, and `findComponent` should be called with a component selector and should be expected to return a Vue component. | ||
|
||
Addiitonally, this rule reports `wrapper.vm` usages which are chained off an improper selector function. For example, `wrapper.find('div')` always returns a DOM element in VTU 2, making `wrapper.find('div').vm` an incorrect usage. | ||
|
||
### Options | ||
|
||
This rule has an object option: | ||
|
||
- `wrapperNames` can be set to an array of variable names that are checked for deprecated function calls. | ||
|
||
Examples of **incorrect** code for this rule: | ||
|
||
```js | ||
/* eslint vue-test-utils/no-deprecated-selectors: "error" */ | ||
import MyComponent from './MyComponent.vue'; | ||
|
||
const wrapper = mount(MyComponent); | ||
|
||
wrapper.get('div').vm.$emit('click'); | ||
wrapper.get(MyComponent).setProps(/* ... */); | ||
expect(wrapper.findAll(FooComponent)).at(0)).toBeTruthy(); | ||
``` | ||
|
||
Examples of **correct** code for this rule: | ||
|
||
```js | ||
/* eslint vue-test-utils/no-deprecated-selectors: "error" */ | ||
import MyComponent from './MyComponent.vue'; | ||
|
||
const wrapper = mount(MyComponent); | ||
|
||
wrapper.getComponent(DivComponent).vm.$emit('click'); | ||
wrapper.getComponent(MyComponent).setProps(/* ... */); | ||
expect(wrapper.findAllComponents(FooComponent).at(0)).toBeTruthy(); | ||
``` | ||
|
||
Examples of **incorrect** code with the `{ "wrapperName": ["component"] }` option: | ||
|
||
```js | ||
/* eslint vue-test-utils/no-deprecated-selectors: ["error", { "wrapperName": ["component"] }] */ | ||
import MyComponent from './MyComponent.vue'; | ||
|
||
const component = mount(MyComponent); | ||
|
||
component.get('div').vm.$emit('click'); | ||
component.get(MyComponent).setProps(/* ... */); | ||
expect(component.findAll(FooComponent).at(0)).toBeTruthy(); | ||
``` | ||
|
||
Examples of **correct** code with the `{ "wrapperName": ["component"] }` option: | ||
|
||
```js | ||
/* eslint vue-test-utils/no-deprecated-selectors: ["error", { "wrapperName": ["component"] }] */ | ||
import MyComponent from './MyComponent.vue'; | ||
|
||
const component = mount(MyComponent); | ||
|
||
component.getComponent(DivComponent).vm.$emit('click'); | ||
component.getComponent(MyComponent).setProps(/* ... */); | ||
|
||
const wrapper = mount(MyComponent); | ||
|
||
// not reported because `wrapper` is not in the list of `wrapperName`s | ||
wrapper.get(MyComponent).vm.$emit('click'); | ||
``` | ||
|
||
## Limitations | ||
|
||
- This rule cannot detect wrappers if they are not stored into a local variable with a name matching one of the names in the `wrapperNames` option (eg, `mount(Foo).get(MyComponent)` will never error) | ||
|
||
## When Not To Use It | ||
|
||
- Never | ||
|
||
## Further Reading | ||
|
||
- [VTU 1 docs](https://vue-test-utils.vuejs.org/api/wrapper/#find) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,137 @@ | ||
const { get } = require('lodash'); | ||
const path = require('path'); | ||
const isVtuVersionAtLeast = require('./checkVtuVersion'); | ||
const { VTU_PLUGIN_SETTINGS_KEY } = require('./constants'); | ||
const { nodeIsCalledFromWrapper, nodeCalleeReturnsWrapper, isComponentSelector } = require('./utils'); | ||
const { detectVtuVersion } = isVtuVersionAtLeast; | ||
|
||
const DEFAULT_WRAPPER_VARIABLES = ['wrapper']; | ||
|
||
/** | ||
* @type {import('eslint').Rule.RuleModule} | ||
*/ | ||
module.exports = { | ||
meta: { | ||
type: 'problem', | ||
docs: { | ||
description: 'disallow deprecated selector usage', | ||
url: path.join(__dirname, '../../docs/rules/no-deprecated-selectors.md'), | ||
}, | ||
fixable: 'code', | ||
schema: [ | ||
{ | ||
type: 'object', | ||
properties: { | ||
wrapperNames: { | ||
description: 'List of variable names to which wrappers are typically assigned', | ||
type: 'array', | ||
items: { | ||
type: 'string', | ||
}, | ||
}, | ||
}, | ||
}, | ||
], // Add a schema if the rule has options | ||
messages: { | ||
deprecatedComponentSelector: | ||
'Calling {{ functionName }} with a component selector is deprecated and will be removed in VTU 2.', | ||
memberUsageFromDeprecatedSelector: | ||
'{{ functionName }} will no longer return `wrapper.{{ missingMemberName }}` in VTU 2. Use {{ alternateFunctionName }} with a component selector instead.', | ||
}, | ||
}, | ||
|
||
create(context) { | ||
const wrapperNames = (context.options[0] && context.options[0].wrapperNames) || DEFAULT_WRAPPER_VARIABLES; | ||
const vtuVersion = get(context.settings, [VTU_PLUGIN_SETTINGS_KEY, 'version']) || detectVtuVersion(); | ||
|
||
const componentOnlyWrapperMembers = new Set(['vm', 'props', 'setData', 'setProps', 'emitted']); | ||
|
||
const deprecatedComponentSelectorFunctions = { | ||
// functionName => preferred name | ||
find: 'findComponent', | ||
findAll: 'findAllComponents', | ||
get: 'getComponent', | ||
}; | ||
|
||
const canChainComponentsFromCssWrappers = isVtuVersionAtLeast(vtuVersion, '1.3.0'); | ||
|
||
return { | ||
CallExpression(node) { | ||
if (node.callee.type !== 'MemberExpression' || node.callee.property.type !== 'Identifier') { | ||
return; | ||
} | ||
|
||
if ( | ||
node.callee.property.name in deprecatedComponentSelectorFunctions && | ||
nodeIsCalledFromWrapper(node.callee.object, wrapperNames) | ||
) { | ||
// these functions should always have strings passed to them, never objects or components | ||
if (node.arguments[0] && isComponentSelector(node.arguments[0], context)) { | ||
let isSuccessiveWrapperChain = false; | ||
let wrapperSelectorCall = node.callee.object; | ||
while (nodeCalleeReturnsWrapper(wrapperSelectorCall)) { | ||
if (wrapperSelectorCall.callee.property.name in deprecatedComponentSelectorFunctions) { | ||
// cannot autofix in versions before 1.3 because this is a chain like `get('div').get(SomeComponent)`. | ||
// Autofixing to `get('div').getComponent(SomeComponent)` will cause an error on those versions | ||
isSuccessiveWrapperChain = true; | ||
break; | ||
} | ||
wrapperSelectorCall = wrapperSelectorCall.callee.object; | ||
} | ||
|
||
context.report({ | ||
messageId: 'deprecatedComponentSelector', | ||
node: node.arguments[0], | ||
data: { | ||
functionName: node.callee.property.name, | ||
}, | ||
fix: | ||
(isSuccessiveWrapperChain && !canChainComponentsFromCssWrappers) | ||
? undefined | ||
: fixer => { | ||
return fixer.replaceText( | ||
node.callee.property, | ||
deprecatedComponentSelectorFunctions[node.callee.property.name] | ||
); | ||
}, | ||
}); | ||
return; | ||
} | ||
} | ||
}, | ||
MemberExpression(node) { | ||
if ( | ||
node.property.type === 'Identifier' && | ||
componentOnlyWrapperMembers.has(node.property.name) && | ||
nodeIsCalledFromWrapper(node.object, wrapperNames) && | ||
nodeCalleeReturnsWrapper(node.object) // if object isn't a call which returns wrapper, then member usage is rooted directly off wrapper which is safe | ||
) { | ||
// the member usage should be not be chained immediately after a non-component selector function | ||
// (okay if previous calls don't return components as long as the last one does) | ||
let lastWrapperCall = node.object; | ||
if ( | ||
lastWrapperCall.callee.property.name === 'at' && | ||
nodeCalleeReturnsWrapper(lastWrapperCall.callee.object) | ||
) { | ||
// special handling for findAll().at().foo - need to make sure we're looking at the 'findAll', | ||
// not the 'at' | ||
lastWrapperCall = lastWrapperCall.callee.object; | ||
} | ||
if (lastWrapperCall.callee.property.name in deprecatedComponentSelectorFunctions) { | ||
context.report({ | ||
messageId: 'memberUsageFromDeprecatedSelector', | ||
node, | ||
data: { | ||
functionName: lastWrapperCall.callee.property.name, | ||
missingMemberName: node.property.name, | ||
alternateFunctionName: | ||
deprecatedComponentSelectorFunctions[lastWrapperCall.callee.property.name], | ||
}, | ||
}); | ||
return; | ||
} | ||
} | ||
}, | ||
}; | ||
}, | ||
}; |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.