diff --git a/smoke-test/repositories.json b/smoke-test/repositories.json index 3defa57..10c53ac 100644 --- a/smoke-test/repositories.json +++ b/smoke-test/repositories.json @@ -248,6 +248,7 @@ "Twistbioscience/DesignerComponents", "bopen/react-jsonschema-form-field-geolocation", "chuntley/dom-testing-extended", + "testing-library/dom-testing-library", "frankieyan/custom-meta-input", "villeheikkila/fullstackopen", "kentcdodds/learn-react", diff --git a/src/__tests__/lib/rules/prefer-in-document.js b/src/__tests__/lib/rules/prefer-in-document.js index 6b659f6..9f053f7 100644 --- a/src/__tests__/lib/rules/prefer-in-document.js +++ b/src/__tests__/lib/rules/prefer-in-document.js @@ -46,51 +46,158 @@ const valid = [ expect(foo).toHaveLength(1);`, `expect(screen.notAQuery('foo-bar')).toHaveLength(1)`, `expect(screen.getAllByText('foo-bar')).toHaveLength(2)`, + `import foo from "./foo"; + it('should be defined', () => { + expect(useBoolean).toBeDefined(); + })`, + `const span = foo('foo') as HTMLSpanElement`, + `const rtl = render() + const stars = rtl.container.querySelector('div').children + + expect(rtl.container.children).toHaveLength(1) + expect(stars).toHaveLength(5)`, + ` let content = container.querySelector('p') + + expect(content).not.toBeNull() + + fireEvent.click(closeButton) + + await waitExpect( + () => { + content = container.querySelector('p') + expect(content).toBeNull() + } + )`, ]; const invalid = [ // Invalid cases that applies to all variants - ...["getByText", "getAllByRole"].map((q) => [ - invalidCase( - `expect(screen.${q}('foo')).toHaveLength(1)`, - `expect(screen.${q}('foo')).toBeInTheDocument()` - ), - invalidCase( - `expect(${q}('foo')).toHaveLength(1)`, - `expect(${q}('foo')).toBeInTheDocument()` - ), - invalidCase( - `expect(wrapper.${q}('foo')).toHaveLength(1)`, - `expect(wrapper.${q}('foo')).toBeInTheDocument()` - ), - invalidCase( - `const foo = screen.${q}('foo'); - expect(foo).toHaveLength(1);`, - `const foo = screen.${q}('foo'); - expect(foo).toBeInTheDocument();` - ), - invalidCase( - `const foo = ${q}('foo'); - expect(foo).toHaveLength(1);`, - `const foo = ${q}('foo'); - expect(foo).toBeInTheDocument();` - ), - invalidCase( - `let foo; - foo = ${q}('foo'); - expect(foo).toHaveLength(1);`, - `let foo; - foo = ${q}('foo'); - expect(foo).toBeInTheDocument();` - ), - invalidCase( - `let foo; - foo = screen.${q}('foo'); - expect(foo).toHaveLength(1);`, - `let foo; - foo = screen.${q}('foo'); - expect(foo).toBeInTheDocument();` - ), - ]), + + invalidCase( + `expect(screen.getByText('foo')).toHaveLength(1)`, + `expect(screen.getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(getByText('foo')).toHaveLength(1)`, + `expect(getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(wrapper.getByText('foo')).toHaveLength(1)`, + `expect(wrapper.getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `const foo = screen.getByText('foo'); + expect(foo).toHaveLength(1);`, + `const foo = screen.getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `const foo = getByText('foo'); + expect(foo).toHaveLength(1);`, + `const foo = getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = getByText('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = screen.getByText('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = screen.getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `expect(screen.getAllByRole('foo')).toHaveLength(1)`, + `expect(screen.getByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(await screen.findAllByRole('foo')).toHaveLength(1)`, + `expect(await screen.findByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(getAllByRole('foo')).toHaveLength(1)`, + `expect(getByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(wrapper.getAllByRole('foo')).toHaveLength(1)`, + `expect(wrapper.getByRole('foo')).toBeInTheDocument()` + ), + invalidCase( + `const foo = screen.getAllByRole('foo'); + expect(foo).toHaveLength(1);`, + `const foo = screen.getByRole('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `const foo = getAllByRole('foo'); + expect(foo).toHaveLength(1);`, + `const foo = getByRole('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = getAllByRole('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = getByRole('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = screen.getAllByRole('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = screen.getByRole('foo'); + expect(foo).toBeInTheDocument();` + ), + + invalidCase( + `expect(screen.getByText('foo')).toHaveLength(1)`, + `expect(screen.getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(getByText('foo')).toHaveLength(1)`, + `expect(getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `expect(wrapper.getByText('foo')).toHaveLength(1)`, + `expect(wrapper.getByText('foo')).toBeInTheDocument()` + ), + invalidCase( + `const foo = screen.getByText('foo'); + expect(foo).toHaveLength(1);`, + `const foo = screen.getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `const foo = getByText('foo'); + expect(foo).toHaveLength(1);`, + `const foo = getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = getByText('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + invalidCase( + `let foo; + foo = screen.getByText('foo'); + expect(foo).toHaveLength(1);`, + `let foo; + foo = screen.getByText('foo'); + expect(foo).toBeInTheDocument();` + ), + // Invalid cases that applies to queryBy* and queryAllBy* ...["queryByText", "queryAllByText"].map((q) => [ invalidCase( @@ -172,6 +279,7 @@ const invalid = [ expect(await screen.findByText(/Compressing video/)).not.toBeInTheDocument(); })` ), + invalidCase( `it("foo", async () => { const compressingFeedback = await screen.findByText(/Compressing video/); @@ -216,9 +324,38 @@ const invalid = [ expect(compressingFeedback).not.toBeInTheDocument(); });` ), + invalidCase( + `const span = getByText('foo') as HTMLSpanElement + expect(span).not.toBeNull()`, + `const span = getByText('foo') as HTMLSpanElement + expect(span).toBeInTheDocument()` + ), + invalidCase( + `const span = await findByText('foo') as HTMLSpanElement + expect(span).not.toBeNull()`, + `const span = await findByText('foo') as HTMLSpanElement + expect(span).toBeInTheDocument()` + ), + invalidCase( + `let span; + span = getByText('foo') as HTMLSpanElement + expect(span).not.toBeNull()`, + `let span; + span = getByText('foo') as HTMLSpanElement + expect(span).toBeInTheDocument()` + ), + invalidCase( + `const things = screen.getAllByText("foo"); + expect(things).toHaveLength(1);`, + `const things = screen.getByText("foo"); + expect(things).toBeInTheDocument();` + ), ]; -const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2017 } }); +const ruleTester = new RuleTester({ + parser: require.resolve("@typescript-eslint/parser"), + parserOptions: { ecmaVersion: 2020, sourceType: "module" }, +}); ruleTester.run("prefer-in-document", rule, { valid: [].concat(...valid), invalid: [].concat(...invalid), diff --git a/src/__tests__/lib/rules/prefer-to-have-style.js b/src/__tests__/lib/rules/prefer-to-have-style.js index af5a90d..9c8c21a 100644 --- a/src/__tests__/lib/rules/prefer-to-have-style.js +++ b/src/__tests__/lib/rules/prefer-to-have-style.js @@ -16,12 +16,26 @@ ruleTester.run("prefer-to-have-style", rule, { document.body.setAttribute("style", "foo"); } }, [foo]);`, + `expect(collapse.style).not.toContain( + expect.objectContaining({ + display: 'none', + height: '0px', + }) + )`, ], invalid: [ { code: `expect(a.style).toHaveProperty('transform')`, errors, }, + { + code: `expect(a.style).not.toHaveProperty('transform')`, + errors, + }, + { + code: `expect(a.style).not.toHaveProperty(\`\${foo}\`)`, + errors, + }, { code: `expect(el.style.foo).toBe("bar")`, errors, @@ -72,6 +86,16 @@ ruleTester.run("prefer-to-have-style", rule, { errors, output: `expect(el).toHaveStyle({backgroundColor: expect.anything()})`, }, + { + code: `expect(el.style).toContain(\`background-color\`)`, + errors, + output: `expect(el).toHaveStyle(\`background-color\`)`, + }, + { + code: `expect(el.style).not.toContain(\`background-color\`)`, + errors, + output: `expect(el).not.toHaveStyle(\`background-color\`)`, + }, { code: `expect(el.style).not.toContain("background-color")`, errors, @@ -82,5 +106,30 @@ ruleTester.run("prefer-to-have-style", rule, { errors, output: `expect(el).toHaveStyle("background-color: green; border-width: 10px; color: blue;")`, }, + { + code: `expect(imageElement.style[\`box-shadow\`]).toBe(\`inset 0px 0px 0px 400px \${c}\`)`, + errors, + output: `expect(imageElement).toHaveStyle(\`box-shadow: inset 0px 0px 0px 400px \${c}\`)`, + }, + { + code: `expect(imageElement.style[\`box-shadow\` ]).toBe( \`inset 0px 0px 0px 400px \${c}\`)`, + errors, + output: `expect(imageElement).toHaveStyle( \`box-shadow: inset 0px 0px 0px 400px \${c}\`)`, + }, + { + code: `expect(imageElement.style[\`box-\${shadow}\`]).toBe("inset 0px 0px 0px 400px 40px")`, + errors, + output: `expect(imageElement).toHaveStyle(\`box-\${shadow}: inset 0px 0px 0px 400px 40px\`)`, + }, + { + code: `expect(imageElement.style[\`box-shadow\`]).not.toBe(\`inset 0px 0px 0px 400px \${c}\`)`, + errors, + output: `expect(imageElement).not.toHaveStyle(\`box-shadow: inset 0px 0px 0px 400px \${c}\`)`, + }, + { + code: `expect(imageElement.style[\`box-shadow\`]).not.toBe("inset 0px 0px 0px 400px 40px")`, + errors, + output: `expect(imageElement).not.toHaveStyle(\`box-shadow: inset 0px 0px 0px 400px 40px\`)`, + }, ], }); diff --git a/src/rules/prefer-in-document.js b/src/rules/prefer-in-document.js index 3c62cbc..dbc104c 100644 --- a/src/rules/prefer-in-document.js +++ b/src/rules/prefer-in-document.js @@ -56,6 +56,18 @@ export const create = (context) => { for (const argument of Array.from(matcherArguments)) { operations.push(fixer.remove(argument)); } + if ( + matcherNode.name === "toHaveLength" && + matcherArguments[0].value === 1 && + query.indexOf("All") > 0 + ) { + operations.push( + fixer.replaceText( + queryNode.property || queryNode, + query.replace("All", "") + ) + ); + } // Flip the .not if necessary if (isAntonymMatcher(matcherNode, matcherArguments)) { if (negatedMatcher) { @@ -81,17 +93,23 @@ export const create = (context) => { } } + function getQueryNodeFrom(expression) { + return expression.type === "TSAsExpression" + ? getQueryNodeFrom(expression.expression) + : expression.type === "AwaitExpression" + ? getQueryNodeFrom(expression.argument) + : expression.callee; + } + function getQueryNodeFromAssignment(identifierName) { const variable = context.getScope().set.get(identifierName); + if (!variable) return; const init = variable.defs[0].node.init; let queryNode; if (init) { // let foo = screen.(); - queryNode = - init.type === "AwaitExpression" - ? init.argument.callee.property - : init.callee.property || init.callee; + queryNode = getQueryNodeFrom(init); } else { // let foo; // foo = screen.(); @@ -101,12 +119,8 @@ export const create = (context) => { if (!assignmentRef) { return; } - queryNode = - assignmentRef.writeExpr.type === "AwaitExpression" - ? assignmentRef.writeExpr.argument.callee - : assignmentRef.writeExpr.type === "CallExpression" - ? assignmentRef.writeExpr.callee - : assignmentRef.writeExpr; + const assignment = assignmentRef.writeExpr; + queryNode = getQueryNodeFrom(assignment); } return queryNode; } @@ -130,7 +144,6 @@ export const create = (context) => { expect, }); }, - // // const foo = expect(foo).not. [`MemberExpression[object.object.callee.name=expect][object.property.name=not][property.name=${alternativeMatchers}][object.object.arguments.0.type=Identifier]`]( node diff --git a/src/rules/prefer-to-have-style.js b/src/rules/prefer-to-have-style.js index 98d338b..fa7d0ae 100644 --- a/src/rules/prefer-to-have-style.js +++ b/src/rules/prefer-to-have-style.js @@ -17,221 +17,255 @@ export const meta = { fixable: "code", }; -export const create = (context) => ({ - //expect(el.style.foo).toBe("bar"); - [`MemberExpression[property.name=style][parent.computed=false][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.callee.name=expect]`]( - node - ) { - const styleName = node.parent.property; - const [styleValue] = node.parent.parent.parent.parent.arguments; - const matcher = node.parent.parent.parent.property; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], styleName.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleValue, - `{${styleName.name}:${context.getSourceCode().getText(styleValue)}}` - ), - ]; - }, - }); - }, - //expect(el.style.foo).not.toBe("bar"); - [`MemberExpression[property.name=style][parent.computed=false][parent.parent.parent.property.name=not][parent.parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.callee.name=expect]`]( - node - ) { - const styleName = node.parent.property; - const styleValue = node.parent.parent.parent.parent.parent.arguments[0]; - const matcher = node.parent.parent.parent.parent.property; +export const create = (context) => { + function getReplacementStyleParam(styleName, styleValue) { + return styleName.type === "Literal" + ? `{${camelCase(styleName.value)}: ${context + .getSourceCode() + .getText(styleValue)}}` + : `${context.getSourceCode().getText(styleName).slice(0, -1)}: ${ + styleValue.type === "TemplateLiteral" + ? context.getSourceCode().getText(styleValue).substring(1) + : `${styleValue.value}\`` + }`; + } - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], styleName.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleValue, - `{${styleName.name}:${context.getSourceCode().getText(styleValue)}}` - ), - ]; - }, - }); - }, - // expect(el.style).toContain("foo-bar") - [`MemberExpression[property.name=style][parent.parent.property.name=toContain][parent.callee.name=expect]`]( - node - ) { - const [styleName] = node.parent.parent.parent.arguments; - const matcher = node.parent.parent.property; + return { + //expect(el.style.foo).toBe("bar"); + [`MemberExpression[property.name=style][parent.computed=false][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.arguments.0.type=/(Template)?Literal/][parent.parent.callee.name=expect]`]( + node + ) { + const styleName = node.parent.property; + const [styleValue] = node.parent.parent.parent.parent.arguments; + const matcher = node.parent.parent.parent.property; + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([node.object.range[1], styleName.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleValue, + `{${styleName.name}:${context + .getSourceCode() + .getText(styleValue)}}` + ), + ]; + }, + }); + }, + //expect(el.style.foo).not.toBe("bar"); + [`MemberExpression[property.name=style][parent.computed=false][parent.parent.parent.property.name=not][parent.parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.parent.arguments.0.type=/(Template)?Literal$/][parent.parent.callee.name=expect]`]( + node + ) { + const styleName = node.parent.property; + const styleValue = node.parent.parent.parent.parent.parent.arguments[0]; + const matcher = node.parent.parent.parent.parent.property; + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([node.object.range[1], styleName.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleValue, + `{${styleName.name}:${context + .getSourceCode() + .getText(styleValue)}}` + ), + ]; + }, + }); + }, + // expect(el.style).toContain("foo-bar") + [`MemberExpression[property.name=style][parent.parent.property.name=toContain][parent.parent.parent.arguments.0.type=/(Template)?Literal$/][parent.callee.name=expect]`]( + node + ) { + const [styleName] = node.parent.parent.parent.arguments; + const matcher = node.parent.parent.property; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], node.property.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleName, - `{${camelCase(styleName.value)}: expect.anything()}` - ), - ]; - }, - }); - }, - // expect(el.style).not.toContain("foo-bar") - [`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toContain]`]( - node - ) { - const [styleName] = node.parent.parent.parent.parent.arguments; - const matcher = node.parent.parent.parent.property; + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([node.object.range[1], node.property.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleName, + styleName.type === "Literal" + ? `{${camelCase(styleName.value)}: expect.anything()}` + : context.getSourceCode().getText(styleName) + ), + ]; + }, + }); + }, + // expect(el.style).not.toContain("foo-bar") + [`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toContain][parent.parent.parent.parent.arguments.0.type=/(Template)?Literal$/]`]( + node + ) { + const [styleName] = node.parent.parent.parent.parent.arguments; + const matcher = node.parent.parent.parent.property; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], node.property.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleName, - `{${camelCase(styleName.value)}: expect.anything()}` - ), - ]; - }, - }); - }, + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([node.object.range[1], node.property.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleName, + styleName.type === "Literal" + ? `{${camelCase(styleName.value)}: expect.anything()}` + : context.getSourceCode().getText(styleName) + ), + ]; + }, + }); + }, - //expect(el).toHaveAttribute("style", "foo: bar"); - [`CallExpression[callee.property.name=toHaveAttribute][arguments.0.value=style][arguments.1][callee.object.callee.name=expect]`]( - node - ) { - context.report({ - node: node.arguments[0], - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.replaceText(node.callee.property, "toHaveStyle"), - fixer.removeRange([ - node.arguments[0].range[0], - node.arguments[1].range[0], - ]), - ]; - }, - }); - }, + //expect(el).toHaveAttribute("style", "foo: bar"); + [`CallExpression[callee.property.name=toHaveAttribute][arguments.0.value=style][arguments.1][callee.object.callee.name=expect]`]( + node + ) { + context.report({ + node: node.arguments[0], + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.replaceText(node.callee.property, "toHaveStyle"), + fixer.removeRange([ + node.arguments[0].range[0], + node.arguments[1].range[0], + ]), + ]; + }, + }); + }, - //expect(el.style["foo-bar"]).toBe("baz") - [`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=/toBe$|to(Strict)?Equal/][parent.parent.callee.name=expect]`]( - node - ) { - const styleName = node.parent.property; - const [styleValue] = node.parent.parent.parent.parent.arguments; - const matcher = node.parent.parent.parent.property; - const startOfStyleMemberExpression = node.object.range[1]; - const endOfStyleMemberExpression = node.parent.parent.arguments[0].range[1]; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([ - startOfStyleMemberExpression, - endOfStyleMemberExpression, - ]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleValue, - `{${camelCase(styleName.value)}: ${context - .getSourceCode() - .getText(styleValue)}}` - ), - ]; - }, - }); - }, - //expect(el.style["foo-bar"]).not.toBe("baz") - [`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=not][parent.parent.parent.parent.parent.callee.property.name=/toBe$|to(Strict)?Equal/][parent.parent.callee.name=expect]`]( - node - ) { - const styleName = node.parent.property; - const [styleValue] = node.parent.parent.parent.parent.parent.arguments; - const matcher = node.parent.parent.parent.parent.property; - const endOfStyleMemberExpression = node.parent.parent.arguments[0].range[1]; + //expect(el.style["foo-bar"]).toBe("baz") + [`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]`]( + node + ) { + const styleName = node.parent.property; + const [styleValue] = node.parent.parent.parent.parent.arguments; + const matcher = node.parent.parent.parent.property; + const startOfStyleMemberExpression = node.object.range[1]; + const endOfStyleMemberExpression = + node.parent.parent.arguments[0].range[1]; + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([ + startOfStyleMemberExpression, + endOfStyleMemberExpression, + ]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleValue, + getReplacementStyleParam(styleName, styleValue) + ), + ]; + }, + }); + }, + //expect(el.style["foo-bar"]).not.toBe("baz") + [`MemberExpression[property.name=style][parent.computed=true][parent.parent.parent.property.name=not][parent.parent.parent.parent.parent.callee.property.name=/toBe$|to(Strict)?Equal/][parent.parent.parent.parent.parent.arguments.0.type=/(Template)?Literal/][parent.parent.callee.name=expect]`]( + node + ) { + const styleName = node.parent.property; + const [styleValue] = node.parent.parent.parent.parent.parent.arguments; + const matcher = node.parent.parent.parent.parent.property; + const endOfStyleMemberExpression = + node.parent.parent.arguments[0].range[1]; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], endOfStyleMemberExpression]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceText( - styleValue, - `{${camelCase(styleName.value)}: ${context - .getSourceCode() - .getText(styleValue)}}` - ), - ]; - }, - }); - }, - //expect(foo.style).toHaveProperty("foo", "bar") - [`MemberExpression[property.name=style][parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`]( - node - ) { - const [styleName, styleValue] = node.parent.parent.parent.arguments; - const matcher = node.parent.parent.property; + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + return [ + fixer.removeRange([ + node.object.range[1], + endOfStyleMemberExpression, + ]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceText( + styleValue, + getReplacementStyleParam(styleName, styleValue) + ), + ]; + }, + }); + }, + //expect(foo.style).toHaveProperty("foo", "bar") + [`MemberExpression[property.name=style][parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`]( + node + ) { + const [styleName, styleValue] = node.parent.parent.parent.arguments; + const matcher = node.parent.parent.property; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - if (!styleValue) { - return null; - } - return [ - fixer.removeRange([node.object.range[1], node.property.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceTextRange( - [styleName.range[0], styleValue.range[1]], - `{${camelCase(styleName.value)}: ${context - .getSourceCode() - .getText(styleValue)}}` - ), - ]; - }, - }); - }, + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + if ( + !styleValue || + !["Literal", "TemplateLiteral"].includes(styleValue.type) + ) { + return null; + } + return [ + fixer.removeRange([node.object.range[1], node.property.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceTextRange( + [styleName.range[0], styleValue.range[1]], + `{${camelCase( + styleName.value + )}: ${context.getSourceCode().getText(styleValue)}}` + ), + ]; + }, + }); + }, - //expect(foo.style).not.toHaveProperty("foo", "bar") - [`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`]( - node - ) { - const [styleName, styleValue] = node.parent.parent.parent.parent.arguments; - const matcher = node.parent.parent.parent.property; + //expect(foo.style).not.toHaveProperty("foo", "bar") + [`MemberExpression[property.name=style][parent.parent.property.name=not][parent.parent.parent.property.name=toHaveProperty][parent.callee.name=expect]`]( + node + ) { + const [ + styleName, + styleValue, + ] = node.parent.parent.parent.parent.arguments; + const matcher = node.parent.parent.parent.property; - context.report({ - node: node.property, - message: "Use toHaveStyle instead of asserting on element style", - fix(fixer) { - return [ - fixer.removeRange([node.object.range[1], node.property.range[1]]), - fixer.replaceText(matcher, "toHaveStyle"), - fixer.replaceTextRange( - [styleName.range[0], styleValue.range[1]], - `{${camelCase(styleName.value)}: ${context - .getSourceCode() - .getText(styleValue)}}` - ), - ]; - }, - }); - }, -}); + context.report({ + node: node.property, + message: "Use toHaveStyle instead of asserting on element style", + fix(fixer) { + if ( + !styleValue || + !["Literal", "TemplateLiteral"].includes(styleValue.type) + ) { + return null; + } + return [ + fixer.removeRange([node.object.range[1], node.property.range[1]]), + fixer.replaceText(matcher, "toHaveStyle"), + fixer.replaceTextRange( + [styleName.range[0], styleValue.range[1]], + `{${camelCase( + styleName.value + )}: ${context.getSourceCode().getText(styleValue)}}` + ), + ]; + }, + }); + }, + }; +};