valid-describe-callback

Try in Playground
jestjsError ProneWarning

0

No tags

No CWE or CVE

This rule validates that the second parameter of a describe() function is a callback function. This callback function:

  • should not be async
  • should not contain any parameters
  • should not contain any return statements

The following describe function aliases are also validated:

  • describe
  • describe.only
  • describe.skip
  • fdescribe
  • xdescribe

View a similar ESLint rule, valid-describe-callback

Ast Rule: function call


valid-describe-callback

How to write a rule
const DESCRIBES = [
  // astType = string
  "describe",
  "fdescribe",
  "xdescribe",
  // astType = member
  "describe.only",
  "describe.skip",
];

function visit(node, filename, code) {
  // only run on .spec. or .test. files
  if (!filename.includes(".spec.") && !filename.includes(".test.")) return;

  if (
    !node.functionName ||
    !node.arguments ||
    !node.arguments.values ||
    node.arguments.values.length === 0
  )
    return;

  function checkCallback(node) {
    if (node.arguments.values[1].value.astType === "string") {
      const error = buildError(
        node.arguments.values[1].value.start.line,
        node.arguments.values[1].value.start.col,
        node.arguments.values[1].value.end.line,
        node.arguments.values[1].value.end.col,
        "Second argument must be function",
        "WARNING",
        "BEST_PRACTICE"
      );
      addError(error);
      return true;
    }

    if (node.arguments.values[1].value.isAsync) {
      const error = buildError(
        node.arguments.values[1].value.start.line,
        node.arguments.values[1].value.start.col,
        node.arguments.values[1].value.end.line,
        node.arguments.values[1].value.end.col,
        "No async describe callback",
        "WARNING",
        "BEST_PRACTICE"
      );
      addError(error);
      return true;
    }

    if (node.arguments.values[1].value?.parameters?.values?.length > 0) {
      const error = buildError(
        node.arguments.values[1].value?.parameters.values[0].name.start.line,
        node.arguments.values[1].value?.parameters.values[0].name.start.col,
        node.arguments.values[1].value?.parameters.values[0].name.end.line,
        node.arguments.values[1].value?.parameters.values[0].name.end.col,
        "Unexpected argument(s) in describe callback",
        "WARNING",
        "BEST_PRACTICE"
      );
      addError(error);
      return true;
    }


    const content = node.arguments.values[1].value?.content;
    if (!content) return;

    // check if the callback has a return statement or is auto returned 
    if (content.elements) {
      for (let i = 0; i < content.elements.length; i++) {
        if (content.elements[i].astType === "return") {
          const error = buildError(
            content.elements[i].start.line,
            content.elements[i].start.col,
            content.elements[i].end.line,
            content.elements[i].end.col,
            "Unexpected return statement in describe callback",
            "WARNING",
            "BEST_PRACTICE"
          );
          addError(error);
          return true;
        }
      }
    } else {
      const error = buildError(
        node.arguments.values[1].value.start.line,
        node.arguments.values[1].value.start.col,
        node.arguments.values[1].value.end.line,
        node.arguments.values[1].value.end.col,
        "Unexpected return statement in describe callback",
        "WARNING",
        "BEST_PRACTICE"
      );
      addError(error);
      return true;
    }
    return false;
  }

  if (node.functionName.astType === "string") {
    const name = DESCRIBES.includes(node.functionName.value);
    if (!name) return;
    if (checkCallback(node)) return;
  }

  if (node.functionName.astType === "member") {
    const first = node.functionName?.parent?.value;
    if (!first) return;
    const second = node.functionName?.name?.value;
    if (!second) return;
    const name = DESCRIBES.includes(`${first}.${second}`);
    if (!name) return;
    if (checkCallback(node)) return;
  }
}

bad-with-aliases.spec.js

Expected test result: has error

The following are considered warning when you use aliases too.

// the second parameter should be a function
fdescribe('sdsdsd', "")

// Async callback functions are not allowed
xdescribe('myFunction()', async () => {
  // ...
});

// Callback function parameters are not allowed
describe.only('myFunction()', done => {
  // ...
});

describe.skip('myFunction', () => {
  // No return statements are allowed in block of a callback function
  return Promise.resolve().then(() => {
    it('breaks', () => {
      throw new Error('Fail');
    });
  });
});

// Returning a value from a describe block is not allowed
fdescribe('myFunction', () =>
  it('returns a truthy value', () => {
    expect(myFunction()).toBeTruthy();
  }));

bad.test.js

Expected test result: has error

The following are considered warnings

// the second parameter should be a function
describe('sdsdsd', "")

// Async callback functions are not allowed
describe('myFunction()', async () => {
  // ...
});

// Callback function parameters are not allowed
describe('myFunction()', done => {
  // ...
});

describe('myFunction', () => {
  // No return statements are allowed in block of a callback function
  return Promise.resolve().then(() => {
    it('breaks', () => {
      throw new Error('Fail');
    });
  });
});

// Returning a value from a describe block is not allowed
describe('myFunction', () =>
  it('returns a truthy value', () => {
    expect(myFunction()).toBeTruthy();
  }));

good.spec.js

Expected test result: no error

The following are not warnings

describe('myFunction()', () => {
  it('returns a truthy value', () => {
    expect(myFunction()).toBeTruthy();
  });
});
Add comment

Log in to add a comment


    Be the first one to leave a comment!

Codiga Logo
Codiga Hub
  • Rulesets
  • Playground
  • Snippets
  • Cookbooks
soc-2 icon

We are SOC-2 Compliance Certified

G2 high performer medal

Codiga – All rights reserved 2022.