docs/src/content/docs/dev/style.md
Naomi Carrigan 691b5390bf feat: write style guide (#52)
Reviewed-on: https://codeberg.org/nhcarrigan/docs/pulls/52
Co-authored-by: Naomi Carrigan <commits@nhcarrigan.com>
Co-committed-by: Naomi Carrigan <commits@nhcarrigan.com>
2025-01-06 03:05:31 +00:00

596 lines
31 KiB
Markdown

---
title: 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.
### 1.1. Copyright Notice
All code files should begin with a comment section containing the copyright information:
```js
/**
* @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](/projects/eslint).
:::caution
We do _not_ use Prettier in any of our projects. Instead, our linter package also handles code styling.
If you are using VSCode, you can add this to your `.vscode/settings.json` file in the project directory to enable auto-formatting on save.
```json
{
"editor.codeActionsOnSave": {
"source.fixAll.eslint": "explicit"
},
"eslint.validate": ["typescript"]
}
```
Because of this, our style guide will not cover the formatting section of our linter 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 vaild 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 nmae.
- 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 `await`ed.
- 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 `await`ed.
- 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`.
:::warn
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.