Skip to content

Style Guide

This document outlines the style guide that applies to all of our projects.

1. Global Conventions

These sections apply to all code in any of our projects.

All code files should begin with a comment section containing the copyright information:

/**
* @copyright nhcarrigan
* @license Naomi's Public License
* @author Naomi Carrigan
*/

Note that copyright must be assigned to nhcarrigan, and our license must be applied, but when checking in new code you may attribute yourself as the author, or add yourself to the list for existing code.

2. JavaScript Projects

The following sections apply to our JavaScript/TypeScript projects. Style conventions are enforced through our custom ESLint package.

2.1. Main Rules

These rules apply to all TypeScript code, and will run on files in src/**/*.ts.

2.1.1. eslint Enforced Rules

  • Setters must have a corresponding getter.
  • Array methods which return a value must return a value in the callback.
  • Arrow functions should always use curly braces.
  • Variables must be used within the scope they are defined.
  • Comments should always start with a capital letter.
  • Avoid overly complex functions.
  • return statements must either never return a value, or always return a value.
  • if statements and similar control blocks should always use curly braces.
  • Switch statements must always have a default case, and the default case must always come last.
  • Never use loose equality.
  • A for loops iteration direction must match the conditional bound.
  • Functions assigned to a property must match the name of the property.
  • With the exception of callback functions, all functions should be assigned a name.
  • When using the function keyword, prefer function name() declarations over const name = function() expressions.
  • Getters should always return a value.
  • Getters and setters should be grouped together.
  • You should never use logical assignment operators.
  • You should never have more than one class per file.
  • Logical blocks should not be nested more than five levels deep. Prefer extracting into separate functions.
  • Files should not have more than 300 lines. Prefer extracting into modular functions.
  • Functions should not have more than 50 lines. Prefer breaking down into smaller logic.
  • Callbacks should not be nested more than two levels.
  • Functions should not have more than 20 logical statements/branches.
  • Constructor names should begin with a capital letter.
  • Do not use the alert API.
  • Promise executor functions should not be async.
  • Do not use await within loops.
  • Do not use bitwise operators.
  • Do not use arguments.caller or arguments.callee.
  • Do not declare variables within switch case statements.
  • Do not reassign class definitions.
  • Do not compare with -0.
  • Do not perform assignments in conditions e.g. if (name = "Naomi").
  • Do not use the console API, prefer our custom logger.
  • Avoid confusing operator combinations e.g. a + b ?? c.
  • Avoid constant conditions e.g. if(false) or while(true).
  • Constructors should not return a value. They implicitly return the instance.
  • Do not use control characters \x00 in regular expressions.
  • Do not use debugger statements.
  • Do not use delete on variables.
  • Do not start a regular expression with = (confusing with /= operator).
  • Functions should not have multiple parameters with the same name.
  • Classes should not have multiple members with the same name.
  • Objects should not have multiple keys with the same name.
  • If/else statements should not use the same condition.
  • Switch statements should not have duplicate case conditions.
  • Import statements should not have duplicate imports.
  • Prefer using early return over if/else.
  • Block statements should not be empty.
  • Character classes in regular expressions should not be empty.
  • Destructured values should not be empty.
  • Static class blocks should not be empty.
  • You should not use loose equality with null.
  • You should not use eval.
  • Do not reassign the error parameter in a catch block.
  • Do not extend native prototypes.
  • Do not call bind unnecessarily.
  • Do not cast to a boolean in conditions.
  • Do not use continue or break unnecessarily.
  • All case statements should have a break statement.
  • Do not reassign function declarations.
  • Do not reassign global values (e.g. window).
  • Do not use implicit coercion (e.g. !!bool, +num).
  • Do not declare global variables. All values should be scoped to a module.
  • Do not reassign to imported variables.
  • Do not declare functions in logical blocks.
  • Do not pass invalid regular expression strings to RegExp().
  • Do not use this outside of classes.
  • Do not use unusual space characters.
  • Do not access __iterator__.
  • Do not use loop labels.
  • Do not create unnecessary blocks.
  • Do not nest if within else, use else if.
  • Do not use multi-code characters (such as emotes) within character classes.
  • Do not chain assignment operators.
  • Do not use \ to create multi-line strings. Use concatenation or template strings.
  • if conditions should not be in the negative (e.g. !bool).
  • Do not nest ternary statements.
  • Do not use new outside of an assignment.
  • Do not use new with BigInt or Symbol.
  • Do not use new with String, Number, or Boolean.
  • Do not use octal escapes (e.g. \8, \2).
  • Do not call the Math, JSON, Reflect, Atomics, or Intl objects as functions or constructors.
  • Do not call new Object() without an argument.
  • Do not use octal literals e.g. 071.
  • Do not reassign function parameters.
  • Do not use the ++ operator.
  • Do not use return in promise executors.
  • Do not use the __proto__ property.
  • Prefer calling the Object prototype over builtins (e.g. Object.prototype.hasOwnProperty.call(foo, "bar") over foo.hasOwnProperty("bar")).
  • Do not use literal spaces in regular expressions, use the escape sequence \s.
  • Do not return assignment operations.
  • Do not use javascript:void URLs.
  • Do not assign a variable to itself.
  • Do not compare a variable with itself.
  • Do not use the comma operator to evaluate multiple expressions.
  • Setters should not return a value.
  • Do not allow sparse arrays.
  • Do not use ${foo} template interpolation in regular strings.
  • Do not reference this before calling super in constructors that call super.
  • Do not reference variables that are not declared.
  • Do not initialise variables to undefined.
  • With the exception of trailing underscores for unused variables, do not use dangling underscores.
  • Do not insert line breaks in confusing locations, such as the middle of a variable assignment.
  • Do not use a variable in a loop condition that is not modified within the loop.
  • Do not use a ternary if a simpler approach exists.
  • There should be no unreachable code.
  • Do not use control statements in finally blocks.
  • You must use parentheses when negating in or instanceOf, e.g. if (!(key in obj)).
  • Do not use optional chaining where an undefined value is not allowed.
  • Do not declare private class members that are not consumed by the class.
  • Do not declare variables that are unused.
  • Avoid backreferences in regular expressions when they would match nothing.
  • Do not use .call() or .apply() unnecessarily.
  • catch statements should handle an error, not turn around and throw the error.
  • Do not use computed-key syntax for literal keys e.g. {["a"]: "b"}.
  • Do not concatenate string literals without variables.
  • Do not escape characters unnecessarily.
  • Do not rename a reference to the same name e.g. let { a: a } = b;.
  • Do not use redundant return statements.
  • No use of the var keyword.
  • Do not merge TODO or FIX comments into the main branch.
  • Do not use the with statement.
  • An object must use either all long-form definitions {a : a} or all short-form definitions { a }, but never a mix of the two { a, b: c}.
  • Avoid shorthand operators +=, prefer the explicit x = x + y.
  • Callback functions should be anonymous arrow functions.
  • Use const for all variables that are not reassigned.
  • Capture groups should be named.
  • Prefer Object.hasOwn().
  • Prefer the spread operator over Object.assign.
  • Prefer regular expression literals over the RegExp constructor.
  • Prefer using the ... operator instead of arguments.
  • Prefer the spread operator over .apply().
  • Prefer template literals over string concatenation.
  • parseInt() should always be given a radix.
  • When an asynchronous function relies on an argument that might change (such as an object reference), care must be taken to avoid race conditions within that function.
  • Generator functions must use the yield keyword.
  • Object keys must be sorted alphabetically.
  • Variables within the same declaration block must be sorted alphabetically.
  • If using the Symbol function, you must include a description.
  • Do not compare to NaN, use Number.isNaN().
  • typeof comparators must be a valid return type of typeof.
  • Conditions should always start with the variable, not the constant.

2.1.2. typescript-eslint Enforced Rules

  • Do not use await on functions that do not return a Promise.
  • Exported type definitions should use the export type keyword.
  • Use dot notation for property access, unless such syntax would be invalid.
  • Do not use the delete keyword with arrays.
  • Do not use <Object>.toString() on objects. Prefer JSON.stringify().
  • Do not assign void values to variables, such as const log = console.log("Bad");.
  • Do not duplicate types (e.g. type myType = "A" | "A").
  • Promises should always be awaited.
  • Do not implicitly evaluate code, such as with new Function().
  • Do not use void with functions that do not return a value.
  • Do not use Promises in places that do not consume them (such as callbacks).
  • Do not combine string and number values in an enum.
  • Do not use unions/intersections with overriding values (such as any or unknown).
  • Do not use comparison operators with boolean values (e.g. variable === false).
  • Do not pass values to a conditional statement that are always truthy or falsy.
  • Do not use unnecessary namespace references.
  • Do not use template strings without interpolated values.
  • Do not pass type arguments to a function that match the default value of that parameter.
  • Do not define unused type parameters in a function.
  • Values with an any type should not be passed to function calls.
  • Values with an any type should not be assigned to variables.
  • Values which have an any type should not be called as functions.
  • Do not merge declarations (instead of interface Foo and class Foo, use interface Foo and class Bar implements Foo).
  • Do not compare enum values against arbitrary values.
  • Do not use the Function type. Functions should have a specific type signature.
  • Values with an any type cannot have properties, and such properties should not be accessed.
  • Functions should not return an any value.
  • The unary minus operator should only be used on numbers.
  • throw statements should only throw an Error instance.
  • Object properties should be destructured, rather than assigned to variables via direct access.
  • Use <Array>.find() instead of <Array>.filter()[0] to find an element in an array.
  • Use <Array>.includes() instead of <Array>.indexOf() === -1.
  • Use ?? instead of ||.
  • Use optional chaining when possible.
  • Promises that reject should always return an Error.
  • Class members that are not modified outside of the constructor should be readonly.
  • The .reduce() callback should have typed parameters.
  • When using a non-global regular expression, use <RegExp>.exec over <String>.match().
  • Class methods that return this should have this as the return type, not the class.
  • When testing the beginning or end of a string, use <String>.startsWith() or <String>.endsWith().
  • Functions which return a Promise must use the async keyword.
  • <Array>.sort() should always be given a compare function.
  • async functions must use await, or be rewritten without the promise.
  • The + operator can only be used with operands of the same type.
  • Values interpolated in a template string must be of type string.
  • Functions which return a promise should await that return value.
  • Conditions should not use nullable values - you must always explicitly compare against null or undefined.
  • Switch statements are not preferred, but when you must use them it should have exhaustive cases.
  • Unbound class methods must be called with the expected scope (e.g. myClass.method.bind(myClass)).
  • The error parameter in a catch statement is of type unknown.
  • Function overload signatures must be grouped together.
  • Array type definitions should use the generic Array<T> over T[].
  • @ts directives are disallowed, except for @ts-expect-error which requires a description.
  • Classes with static public properties should have a getter for each property.
  • Class methods which are not static must make use of this or be converted to static.
  • Prefer passing type arguments to a constructor instead of a type annotation.
  • Prefer Record<string, T> over { [key: string]: T}.
  • Do not use any type assertions (x as T).
  • Object types should be defined with interface over type.
  • Imported values which are only used as types should be imported with type.
  • Function parameters which are optional or have a default value should come after all mandatory parameters.
  • Functions should always have an explicit return type.
  • All class members should have an accessibility modifier (public, protected, private).
  • All exported functions and public class methods should have explicit parameter and return types.
  • Variables should always be initialised with a value.
  • Functions which take more than three arguments should take a single object instead.
  • Class members should be ordered as constructor, fields, statics, getters, setters, then methods.
  • Method types should use the property signature func: (arg: string) => string.
  • Variables should use camelCase, types and classes should use PascalCase. Only variables may use a leading underscore to indicate they are unused.
  • Never use the Array constructor.
  • Do not use non-null assertions.
  • Enums should not have duplicate values.
  • Do not use the delete operator on dynamic property accesses.
  • Functions, interfaces, and object types should never be empty.
  • Do not use any.
  • Classes should not consist solely of static members.
  • The for...in loop should not be used.
  • When all imported members are type imports, use import type { a, b } over import { type a, type b }.
  • Do not use type annotations for variables with a string, number, or boolean value. These can be inferred.
  • The void type should only be used as the sole return type of a function. (): void | string is invalid.
  • Do not reference unsafe values in a loop.
  • Do not use number literals which would lose precision due to memory constraints.
  • Classes should use constructor(), not new(). Interfaces for the class should use new(), not constructor().
  • Do not use namespaces.
  • Do not use require().
  • Do not declare variables in an inner scope that share a name with a variable in the outer scope.
  • Do not alias this.
  • Do not needlessly assign constructor parameters to class members of the same name.
  • Do not extends any or unknown.
  • Unused expressions should be removed.
  • Unused variables should be removed.
  • Do not access a variable before its definition.
  • Do not define empty constructors.
  • Do not define empty exports.
  • Do not use wrapper types (e.g. use boolean, not Boolean).
  • Use as const over literal types.
  • Enum values should always be explicitly initialised.
  • The for...of loop should be used over the for loop, when the index value is not needed.
  • Enum values should always be literal values.
  • Do not use TypeScript’s /// reference.
  • Do not use overloads when the same result can be achieved via union types or optional parameters.

2.1.3. eslint-comments Enforced Rules

  • Do not use blanket eslint-disable directives. All disable directives must target specific rules.
  • All disable directives must include a comment explaining why the linter is being bypassed.

2.1.4. deprecation Enforced Rules

  • Do not use deprecated methods/features, even if the package still supports them.

2.1.5. import Enforced Rules

  • Do not use default imports if the module does not have a default export.
  • Do not import modules simply to re-export them.
  • Exports should always be at the end of the file.
  • With the exception of packages, imports should always have a file extension.
  • Imports should always be at the beginning of the file.
  • If a module has multiple named exports, group them in a single export { a, b } declaration.
  • Import statements should be followed by a newline.
  • Import paths should never be absolute
  • You should not use AMD syntax
  • You should not use CJS syntax
  • You should not define cyclical imports (if file a imports file b, file b CANNOT import file a)
  • Modules should not use a default export.
  • A module should not import the same file multiple times.
  • You should not use dynamic require calls.
  • Named import blocks cannot be empty.
  • You should not import packages that are not included in the package.json file.
  • You cannot import a CommonJS module.
  • Exports must always be declared with const.
  • You cannot use a named export as the default export.
  • You cannot use a named export as the property of a default export.
  • You should not have orphaned modules (modules without any exports, or a module which is never imported).
  • You cannot use webpack loader syntax.
  • Imports must be sorted alphabetically, first grouped by: builtin node modules, external packages, internal packages, modules in parent directory, modules in current directory, type-only imports.
    • There should be no new lines between these groups.

2.1.6. jsdoc Enforced Rules

  • @access tags should be one of package, private, protected, or public.
  • Asterisks should be aligned.
  • Lines should not be improperly indented.
  • @param names should reflect the actual parameters.
  • @property names should reflect the actual properties.
  • Should not use = (GCC syntax).
  • All tags must be valid JSDoc tags.
  • @template names should be used in the @typedef.
  • @license tag MUST be set to Naomi's Public License.
  • Tags that do not expect content should not have content.
  • @implements should only be used on constructors or classes.
  • Tag descriptions should not be just a reflection of the name.
  • Description tags must be complete sentences.
  • JSDocs must be multiple lines. The first and last line cannot have text content.
  • JSDoc syntax must be valid.
  • JSDocs must have a description.
  • JSDocs cannot be empty.
  • A hyphen should separate the parameter name from the description.
  • JSDocs are required on all public functions, classes, and methods. They are encouraged on all public exports.
  • Parameters must have a name and description.
  • Properties must have a name and description.
  • If a function returns a value, it must be documented in @returns.
  • If a function throws an unhandled error intentionally, it must be documented in @throws.
  • If a generator yields a value, it must be documented in @yields.
  • Tags must be sorted alphabetically.
  • There should be no blank lines between tags.
  • Any types referenced must be valid types.

2.1.7. unicorn Enforced Rules

  • Regular expressions should use special shorthand where possible.
  • catch blocks should always have error as the parameter.
  • When destructuring an object, all consumed properties should be destructured. Do not perform direct property access on an object that is previously destructured.
  • When spreading a ternary in an array, both sides of the expression must be of the same type.
  • Functions should be declared in the highest scope possible.
  • Error() constructor calls must always be given a message.
  • Escape sequences must use uppercase text.
  • Files should be named in camelCase.
  • Do not pass function references as callbacks to array methods.
  • Do not use .forEach().
  • Do not use this in array methods.
  • Do not chain .push() calls.
  • Do not use .reduce().
  • Do not access properties directly from an await expression.
  • Do not use await in Promise methods such as .all().
  • Files should not be empty.
  • Do not use a for loop when you can use a for...of loop.
  • Use Unicode escape sequences over hexadecimal escape sequences.
  • Use Array.isArray() over instanceof Array.
  • GET or HEAD requests should never have a body.
  • Do not declare variables that start with new or class.
  • Do not use .length as the argument to .slice().
  • Use Buffer.from(), not new Buffer().
  • Do not use objects as default parameters.
  • Do not pass single-element arrays to Promise methods.
  • Do not define a class that only has static members.
  • Do not use .then().
  • Do not assign to this.
  • Do not use typeof x === "undefined".
  • await cannot be used on non-promise values.
  • You cannot ignore consecutive values when destructuring an array, e.g. [,,,, val].
  • You cannot use IIFEs.
  • Promises should not return .resolve() or .reject().
  • You should not spread an array into a new array without adding values.
  • You should not associate any case statements with the default statement.
  • Number literals cannot have trailing zeroes after the decimal place.
  • Number literals must use proper case.
  • Numbers over 9999 should use _ to separate each group of three digits, e.g. 10_000.
  • If you need to flatten an array, use Array.flat().
  • Prefer .flatMap() over .map().flat().
  • Prefer indexOf over findIndex.
  • Prefer .some() over .filter().length.
  • Prefer .at() for accessing array and string indices.
  • Prefer .codePointAt() over .charCodeAt().
  • Prefer Date.now() over new Date().getTime().
  • Use default parameters instead of reassigning undefined values.
  • Use Math.trunc to round numbers, not bitwise operators.
  • Use ESM modules, not CJS.
  • Use coercion functions such as String() directly.
  • Use a negative index instead of .legnth - 1.
  • Use the node: protocol when importing built-in packages.
  • Use Number properties instead of global helpers e.g. Number.isNaN() over isNaN().
  • Use Object.fromEntries to create an object from key-value pairs.
  • If a catch block does not use the error parameter, the parameter should be omitted.
  • Prefer calling prototype methods over instance methods.
  • Prefer using Set.has() over Array.includes to check for existence.
  • Prefer using Set.size over Array.length.
  • Prefer using the spread operator over Array.concat() or String.split("").
  • Prefer using String.replaceAll() over regex searches.
  • Prefer String.slice() over String.substr[ing]().
  • Prefer String.startsWith() or .endsWith() over RegExp.test().
  • Prefer String.trim[Start|End]() over String.trim[Left|Right]().
  • Use structuredClone to create a deep clone of an object.
  • Use top-level await instead of IIFEs.
  • Variable names should not be abbreviated.
  • Array.join() must always be given a separator.
  • Number.toFixed() must always be given a digits argument.

2.2. React Rules

These rules apply to TSX, and will run on files in src/**/*.tsx.

  • Boolean properties should be named accordingly, such as isEnabled instead of enabled.
  • Buttons must always have a type attribute.
  • If a radio button or checkbox is checked, it must be readonly or have an onChange handler.
  • Default properties should match the property types.
  • Properties, state, and context should always be destructured.
  • Components must always have a name.
  • A component cannot consume another component’s propTypes.
  • PropTypes cannot use any, array, or object.
  • forwardRef components must use ref.
  • Components should always be defined as named arrow functions.
  • State should have matching names, e.g. [color, setColor].
  • iframes should always use the sandbox attribute.
  • Boolean properties should always be explicitly written: <Component prop={true}>.
  • Spaces between JSX elements must be explicitly written: {' '}.
  • Files that contain JSX must have the extension .jsx or .tsx.
  • Fragments should always use the shorthand syntax.
  • Event handlers must be appropriately named: onChange={this.handleChange}
  • Iterators must always have a key prop, and it should never be the index.
  • You should not use .bind() in component props
  • You should not insert comments as text nodes.
  • Contexts should not use mutable values (e.g. objects), to prevent re-renders.
  • Components should not have duplicate properties.
  • Avoid leaking conditional values. Prefer condition ? <Component> : null over condition && <Component>.
  • Element text should always be wrapped in JSX containers.
  • You should never use javascript: urls.
  • target=_blank must always be accompanied with rel=noreferrer.
  • You should not use variables that are not declared.
  • You should not use fragments unless there is no singular wrapping element.
  • Components should use PascalCase.
  • You should not spread props multiple times.
  • Props should be sorted alphabetically.
  • You should not access this.state inside any setState functions.
  • You should not use arrow functions for lifecycle methods.
  • Children cannot be passed as props.
  • You cannot use dangerous properties (dangerouslySetInnerHTML).
  • You cannot use deprecated methods.
  • You cannot use setState in componentDidMount or componentDidUpdate or componentWillUpdate.
  • You cannot directly mutate this.state.
  • You cannot use findDOMNode.
  • You cannot use isMounted.
  • Each file should have only one component.
  • ReactDOM.render’s return value should not be used.
  • You should not use string references.
  • this cannot be used in stateless functional components.
  • HTML entities must be escaped.
  • You cannot use unknown HTML properties.
  • You cannot use unsafe lifecycle methods.
  • You cannot define a component within another component.
  • Class components should not have unused methods.
  • Components should not have unused propTypes or state
  • Class components should use the ES6 class Component extends React.Component syntax.
  • Props should be readonly.
  • Stateless components should be pure functions.
  • Property accesses must be reflected in the propTypes.
  • Any optional property must have a default value.
  • Classes using the render method MUST return a value.
  • Components without children must still have a separate closing tag.
  • State initialisation must always happen in the constructor.
  • Static properties should be defined as static name = .
  • The style property must always be an object.
  • Void elements img, br etc. must never have children.

2.3. Playwright Rules

These files apply to Playwright End-to-end tests, and will run on e2e/**/*.spec.ts.

  • All tests must have at least one expect assertion.
  • describe calls cannot be nested more than two levels deep.
  • Playwright APIs must be awaited.
  • Tests cannot be commented out.
  • expect cannot be called conditionally.
  • Tests cannot contain conditional logic.
  • Setup and teardown hooks cannot be duplicated.
  • You cannot use element handles (page.$).
  • You cannot use page.$eval().
  • Tests cannot be focused with .only().
  • Tests cannot be forced with { force: true }.
  • You cannot use getByTitle().
  • You cannot use the networkidle option.
  • You cannot use first(), lat(), or nth().
  • You cannot use page.pause().
  • You cannot use page.locator(). Prefer specific methods like page.getByRole().
  • Tests cannot be skipped with .skip().
  • expect can only be called within test blocks.
  • You cannot reference variables in page.evaluate().
  • Some Playwright methods are synchronous - these cannot be awaited.
  • You cannot use .not() when a specific matcher exists.
  • You cannot use page.waitForSelector() or page.waitForTimeout().
  • Prefer .toBeGreaterThan() over (x > 5).toBe(true).
  • Prefer .toBe(5) over (x === 5).toBe(true)
  • Hooks should be ordered as Playwright calls them: beforeAll, beforeEach, afterEach, afterAll.
  • Hooks should be at the top of the test.
  • Test names should be lowercase.
  • Use .toStrictEqual() over .toEqual().
  • Use .toBe() over .toStrictEqual() for primitive values.
  • Use .toContain() over .includes().
  • Use .toHaveCount() over locator.count() and .toHaveLength() over .length.
  • Prefer assertions like .toBeVisible() over (locator.isVisible()).toBe(true).
  • Setup and teardown code must be in a hook.
  • .toThrow() assertions require a message.
  • Tests must be within a describe block.
  • All expect() calls must have a custom message.
  • Promises that contain an expect must be awaited.
  • Tests must have a title that is not empty.

2.4. Vitest Rules

These files apply to Vitest unit tests, and will run on test/**/*.spec.ts.

We also mandate the use of describe, it, and expect over suite, test, and assert.

  • Files must have .spec.ts extension.
  • consistent-test-it
  • Tests must have at least one expect.
  • describe calls cannot be nested more than two levels deep.
  • Prefer .toHaveBeenCalled() over .toBeCalled().
  • Tests cannot be commented out.
  • expect cannot be called conditionally.
  • Tests cannot contain conditional logic.
  • Tests cannot be run conditionally.
  • Tests cannot be disabled.
  • You cannot use the done() callback.
  • Setup and teardown hooks cannot be duplicated.
  • Tests cannot be focused with .only().
  • Test titles must be unique.
  • node:test cannot be imported.
  • Snapshots cannot use string interpolation.
  • expect must be within it or test.
  • Tests cannot use return.
  • Prefer toHaveBeenCalledWith() over toHaveBeenCalled().
  • Prefer .toBeGreaterThan() over (x > 5).toBe(true).
  • Use describe.each instead of manual loops.
  • Prefer .toBe(5) over (x === 5).toBe(true).
  • All tests must start with expect.assertions(number).
  • Use expect.resolves(fn) over expect(await fn).
  • Hooks should be ordered as Vitest calls them: beforeAll, beforeEach, afterEach, afterAll.
  • Hooks should be at the top of the test.
  • Test titles should be lowercase.
  • Prefer vi.fn().mockResolvedValue(val) over vi.fn().mockImplementation(() => Promise.resolve(val)).
  • Prefer vi.spyOn(Date, "now") instead of overwriting the global Date.now = vi.fn().
  • Use .toStrictEqual() over .toEqual().
  • Use .toBe() over .toStrictEqual() for primitive values.
  • Use .toBeFalsy() over .toBe(false).
  • Use .toBeObject() over .toBeInstanceOf(Object).
  • Use .toBeTruthy() over .toBe(true).
  • Use .toContain() over .includes().
  • Use .toHaveLength() over .length.
  • Use test.todo() instead of test.skip().
  • Setup and teardown code must be in a hook.
  • .toThrow() assertions require a message.
  • Tests must be within a describe block.
  • The describe callback should not have any parameters and cannot use return.
  • All expect() calls must have a custom message.
  • Tests must have a title that is not empty.