Skip to content

Commit 8de3593

Browse files
authored
fix(prefer-to-have-style): properly handle index access of style property (#266)
* fix(prefer-to-have-style): handle indexed style access * fix(prefer-to-have-style): support computed properties * fix(prefer-to-have-style): handle number assertions properly * fix(prefer-to-have-style): handle regexp literals
1 parent 9b48d90 commit 8de3593

File tree

2 files changed

+82
-22
lines changed

2 files changed

+82
-22
lines changed

src/__tests__/lib/rules/prefer-to-have-style.js

+35
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,11 @@ ruleTester.run("prefer-to-have-style", rule, {
1010
`expect().toBe(true)`,
1111
`expect(el).toHaveStyle({foo:"bar"})`,
1212
`expect(el.style).toMatchSnapshot()`,
13+
`expect(el.style).toEqual(1)`,
1314
`expect(el.style).toEqual(foo)`,
15+
`expect(el.style[1]).toEqual([])`,
16+
`expect(el.style[1]).toEqual({})`,
17+
`expect(element.style[0]).toBe(new RegExp('reg'));`,
1418
`expect(el).toHaveAttribute("style")`,
1519
`React.useLayoutEffect(() => {
1620
if (foo) {
@@ -132,5 +136,36 @@ ruleTester.run("prefer-to-have-style", rule, {
132136
errors,
133137
output: `expect(imageElement).not.toHaveStyle(\`box-shadow: inset 0px 0px 0px 400px 40px\`)`,
134138
},
139+
{
140+
code: `expect(element.style[1]).toEqual('padding');`,
141+
errors,
142+
output: `expect(element).toHaveStyle({padding: expect.anything()});`,
143+
},
144+
{
145+
code: `expect(element.style[1]).toBe(\`padding\`);`,
146+
errors,
147+
output: `expect(element).toHaveStyle({[\`padding\`]: expect.anything()});`,
148+
},
149+
{
150+
code: `expect(element.style[1]).not.toEqual('padding');`,
151+
errors,
152+
},
153+
{
154+
code: `expect(element.style[1]).not.toBe(\`padding\`);`,
155+
errors,
156+
},
157+
{
158+
code: `expect(element.style[1]).toBe(x);`,
159+
errors,
160+
output: `expect(element).toHaveStyle({[x]: expect.anything()});`,
161+
},
162+
{
163+
code: `expect(element.style[0]).toBe(1);`,
164+
errors,
165+
},
166+
{
167+
code: `expect(element.style[0]).toBe(/RegExp/);`,
168+
errors,
169+
},
135170
],
136171
});

src/rules/prefer-to-have-style.js

+47-22
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,13 @@ export const meta = {
1818
};
1919

2020
export const create = (context) => {
21+
function getReplacementObjectProperty(styleName) {
22+
if (styleName.type === "Literal") {
23+
return camelCase(styleName.value);
24+
}
25+
26+
return `[${context.getSourceCode().getText(styleName)}]`;
27+
}
2128
function getReplacementStyleParam(styleName, styleValue) {
2229
return styleName.type === "Literal"
2330
? `{${camelCase(styleName.value)}: ${context
@@ -148,7 +155,7 @@ export const create = (context) => {
148155
},
149156

150157
//expect(el.style["foo-bar"]).toBe("baz")
151-
[`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.arguments.0.type=/(Template)?Literal/][parent.parent.callee.name=expect]`](
158+
[`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.arguments.0.type=/((Template)?Literal|Identifier)/][parent.parent.callee.name=expect]`](
152159
node
153160
) {
154161
const styleName = node.parent.property;
@@ -157,10 +164,14 @@ export const create = (context) => {
157164
const startOfStyleMemberExpression = node.object.range[1];
158165
const endOfStyleMemberExpression =
159166
node.parent.parent.arguments[0].range[1];
160-
context.report({
161-
node: node.property,
162-
message: "Use toHaveStyle instead of asserting on element style",
163-
fix(fixer) {
167+
168+
let fix = null;
169+
170+
if (
171+
typeof styleValue.value !== "number" &&
172+
!(styleValue.value instanceof RegExp)
173+
) {
174+
fix = (fixer) => {
164175
return [
165176
fixer.removeRange([
166177
startOfStyleMemberExpression,
@@ -169,10 +180,20 @@ export const create = (context) => {
169180
fixer.replaceText(matcher, "toHaveStyle"),
170181
fixer.replaceText(
171182
styleValue,
172-
getReplacementStyleParam(styleName, styleValue)
183+
typeof styleName.value === "number"
184+
? `{${getReplacementObjectProperty(
185+
styleValue
186+
)}: expect.anything()}`
187+
: getReplacementStyleParam(styleName, styleValue)
173188
),
174189
];
175-
},
190+
};
191+
}
192+
193+
context.report({
194+
node: node.property,
195+
message: "Use toHaveStyle instead of asserting on element style",
196+
fix,
176197
});
177198
},
178199
//expect(el.style["foo-bar"]).not.toBe("baz")
@@ -185,10 +206,10 @@ export const create = (context) => {
185206
const endOfStyleMemberExpression =
186207
node.parent.parent.arguments[0].range[1];
187208

188-
context.report({
189-
node: node.property,
190-
message: "Use toHaveStyle instead of asserting on element style",
191-
fix(fixer) {
209+
let fix = null;
210+
211+
if (typeof styleName.value !== "number") {
212+
fix = (fixer) => {
192213
return [
193214
fixer.removeRange([
194215
node.object.range[1],
@@ -200,7 +221,13 @@ export const create = (context) => {
200221
getReplacementStyleParam(styleName, styleValue)
201222
),
202223
];
203-
},
224+
};
225+
}
226+
227+
context.report({
228+
node: node.property,
229+
message: "Use toHaveStyle instead of asserting on element style",
230+
fix,
204231
});
205232
},
206233
//expect(foo.style).toHaveProperty("foo", "bar")
@@ -225,9 +252,9 @@ export const create = (context) => {
225252
fixer.replaceText(matcher, "toHaveStyle"),
226253
fixer.replaceTextRange(
227254
[styleName.range[0], styleValue.range[1]],
228-
`{${camelCase(
229-
styleName.value
230-
)}: ${context.getSourceCode().getText(styleValue)}}`
255+
`{${camelCase(styleName.value)}: ${context
256+
.getSourceCode()
257+
.getText(styleValue)}}`
231258
),
232259
];
233260
},
@@ -238,10 +265,8 @@ export const create = (context) => {
238265
[`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`](
239266
node
240267
) {
241-
const [
242-
styleName,
243-
styleValue,
244-
] = node.parent.parent.parent.parent.arguments;
268+
const [styleName, styleValue] =
269+
node.parent.parent.parent.parent.arguments;
245270
const matcher = node.parent.parent.parent.property;
246271

247272
context.report({
@@ -259,9 +284,9 @@ export const create = (context) => {
259284
fixer.replaceText(matcher, "toHaveStyle"),
260285
fixer.replaceTextRange(
261286
[styleName.range[0], styleValue.range[1]],
262-
`{${camelCase(
263-
styleName.value
264-
)}: ${context.getSourceCode().getText(styleValue)}}`
287+
`{${camelCase(styleName.value)}: ${context
288+
.getSourceCode()
289+
.getText(styleValue)}}`
265290
),
266291
];
267292
},

0 commit comments

Comments
 (0)