Skip to content

Naming


Every name in the codebase tells a story. Consistent naming makes intent visible at a glance, speeds up code review, and prevents the ambiguity that breeds bugs.

Casing at a Glance

What you are namingConventionExample
Variables, refs, functionscamelCaseuserName, fetchOrders, isLoading
ConstantsSCREAMING_SNAKE_CASEAPI_BASE_URL, MAX_RETRY_COUNT
Types, interfaces, classes, enumsPascalCaseUserDTO, CartStore
Vue componentsPascalCaseProfileCard.vue, CartModal.vue
Other files (TS, JS, JSON)camelCaseuserService.ts, config.json
Test filesPascalCaseCartStore.spec.ts, Auth.e2e.ts
data-testid attributeskebab-casecheckout-submit-button

The sections below explain each rule in detail, with examples of what to do and what to avoid.

Casing

Rules

  • Variables, refs, functions: use camelCase.

    • userName, fetchOrders, isLoading
    • User_Name, GetOrders, loading_status
  • Constants: use SCREAMING_SNAKE_CASE.

    • MAX_RETRY_COUNT, API_BASE_URL
    • maxRetryCount, ApiBaseUrl
  • Enums: name the enum itself in PascalCase with PascalCase members.

    • export enum HttpStatus { Ok = 200, NotFound = 404 }
    • export enum http_status { ok = 200 }
  • Types, interfaces, classes: use PascalCase.

    • UserDTO, CartStore
    • userDto, cart_store
  • Files:

    • Vue components → PascalCase
      • ProfileCard.vue, CartModal.vue
      • profile-card.vue, cart_modal.vue
    • Other files (TS, JS, JSON, etc.) → camelCase
      • userService.ts, dateUtils.ts, config.json
      • UserService.ts, date-utils.ts, Config.JSON
    • API service modules live in src/services/api/<module>.ts (camelCase filename matching the domain)
      • src/services/api/checkout.ts, src/services/api/users.ts
      • src/services/api/CheckoutService.ts, src/services/api/user-service.ts
  • Test files: always PascalCase.

    • CartStore.spec.ts, Checkout.spec.ts, Auth.e2e.ts
    • cart-store.spec.ts, auth.e2e.ts

Acronyms

Acronyms like URL, HTTP, and API have their own casing rules.

Rules

  • In camelCase, capitalize only the first letter of the acronym.

    • fetchUrl, httpClient, apiKey
    • fetchURL, HTTPClient, APIKey
  • In PascalCase, capitalize only the first letter of the acronym.

    • HttpServer, ApiClient, UrlParser
    • HTTPServer, APIClient, URLParser
  • In SCREAMING_SNAKE_CASE, keep the acronym fully uppercase.

    • API_BASE_URL, HTTP_TIMEOUT_MS
    • Api_Base_Url, Http_Timeout_Ms

Booleans

Rules

  • Prefix with intent so the name reads as a statement:

    • is* → state or condition: isOpen, isLoading, isDirty
    • has* → possession or availability: hasError, hasAccess
    • can* → capability or permission: canSubmit, canEdit
    • should* → policy or decision: shouldPersist, shouldDebounce
    • needs* → requirement: needsUpdate, needsAuth
  • The name plus its value must form a readable sentence.

    • isLoading = true"is loading"
    • canSubmit = false"cannot submit"
  • Avoid vague flags.

    • loading, errorFlag, submitable
    • isLoading, hasError, canSubmit

Refs and Computeds

Rules

  • Refs: camelCase, no suffix.

    • count = ref(0)
    • count_ref, Count
  • Computed values: descriptive nouns or noun phrases in camelCase.

    • fullName, cartTotal, isFormValid
    • full_name, validForm
  • Prefer meaningful names that explain the reactive value's purpose.

Collections

Rules

  • Use plural names for arrays, sets, and maps.

    • users: User[], items: CartItem[]
    • userList, itemArray
  • Use singular names for single entities.

    • user: User, item: CartItem
  • When iterating, the loop variable should be the singular of the collection.

    • for (const user of users) { ... }

Functions

Rules

  • Use verbNoun style, action-oriented.

    • getUser, fetchOrders
    • userFetch, ordersList
  • Keep names short but precise. Prefer domain terms over abbreviations.

    • calculateDiscount, formatDate
    • calcDisc, fmtDt
  • For async operations, prefix with fetch, load, or update.

    • fetchUserProfile, updateCartItem
  • For pure transformations, prefix with map, to, or from.

    • mapUserDto, toUserModel, fromApiResponse
  • Do not append Async to function names. The async keyword and Promise return type already signal asynchrony.

    • getUser(id: string): Promise<User>
    • getUserAsync(id: string): Promise<User>

Constants, Types & Enums

Constants

  • Naming: SCREAMING_SNAKE_CASE.

    • API_BASE_URL, ONE_MINUTE_MS, MAX_RETRY_COUNT
    • apiBaseUrl, oneMinute, MaxRetryCount
  • Placement:

    • Shared/cross-feature: store domain-specific constants in src/config/<domain>.ts (e.g., src/config/api.ts, src/config/dates.ts).
    • Feature-local: keep them near usage as constants.ts within the feature's directory (e.g., src/pages/Checkout/constants.ts). Each feature should have only one such file.
  • Exports: named exports only, no default exports.

  • Grouping: for related constants, prefer as const objects:

    ts
    // src/config/api.ts
    export const Api = {
      BASE_URL: '/api',
      TIMEOUT_MS: 10_000,
    } as const;
    
    // src/config/dates.ts
    export const Time = {
      ONE_SECOND_MS: 1_000,
      ONE_MINUTE_MS: 60_000,
    } as const;

Types & Interfaces

Naming

  • Domain models (application-wide):

    • File names: camelCase, single short word.
      • user.ts, order.ts
      • User.ts, userModel.ts, user-types.ts
    • Interface/type names: PascalCase, no suffix.
      • export interface User { … }
      • export interface user_type { … }
  • API models (DTOs):

    • File names: camelCase, single word, matching the entity.
      • userResponse.ts, createUserRequest.ts
      • UserResponse.ts, user-response.ts
    • Interface/type names: must end with Response or Request.
      • export interface UserResponse { … }
      • export interface CreateUserRequest { … }
      • UserDTO, UserApiType
  • Nested/reused types inside DTOs:

    • Use PascalCase, no suffix.
      • Address inside userResponse.ts
      • AddressResponse for a nested field
  • Feature/module-local types:

    • File name: always types.ts in the module directory.
      • src/pages/checkout/types.ts
      • checkoutTypes.ts, Checkout.interfaces.ts

Exports

  • Always use named exports.
    • export interface User { … }
    • export default interface User { … }
  • Do not re-export from index.ts; consumers must import directly from the type file.

Interface vs Type

  • Use interface for object shapes that may be extended or merged.
  • Use type for unions, intersections, function signatures, or mapped/utility types.

Derived Types

  • Prefer TypeScript utilities (Pick, Omit, Partial, Readonly, etc.) to create variations of existing types.

Enums (and better alternatives)

  • Prefer union literal types for small closed sets:

    ts
    type Status = 'idle' | 'loading' | 'success' | 'error';
  • Use as const objects when you need both a map and a type:

    ts
    export const Status = {
      Idle: 'idle',
      Loading: 'loading',
      Success: 'success',
      Error: 'error',
    } as const;
    export type Status = typeof Status[keyof typeof Status];
  • Use enum only when you need numeric flags, reverse mapping, or interoperability with external enum APIs:

    ts
    export enum HttpMethod {
      Get = 'GET',
      Post = 'POST',
      Put = 'PUT',
      Delete = 'DELETE',
    }
  • Avoid const enum in shared libraries or mixed toolchains, as it can cause build issues.

Do / Don't

TopicDo ✅Don't ❌
Constants namingONE_MINUTE_MS, API_BASE_URLoneMinute, ApiBaseUrl
Constant groupingexport const Api = { BASE_URL: … } as constMixed unrelated constants in a single file
Types/Interfacesinterface User { … }, type UserRole = …type user = {}, interface user_role {}
File placementsrc/models/User.ts, api/models/UserResponse.tsRandomly defined types in components
EnumsUse union literals or as const objectsUse enum for simple string sets
ExportsNamed exports onlyDefault exports for types/constants

Rationale

  • Consistent casing makes intent clear at a glance.
  • Organized placement prevents "catch-all" files and improves discoverability.
  • Named exports avoid ambiguity and support tree-shaking and refactoring.
  • Unions and as const objects provide leaner JS output and safer typing than overusing enum.
  • DTO types may contain snake_case properties to mirror the API response format; app domain types must use PascalCase property names. This keeps UI/business logic consistent while correctly modeling the transport layer.

Environment Variables

  • Client-exposed variables must be prefixed with VITE_ so Vite bundles them into the client build.

    • VITE_API_BASE_URL, VITE_APP_TITLE
    • API_BASE_URL, APP_TITLE (server-only, invisible to the client)
  • Server-only variables do not need the VITE_ prefix.

    • DATABASE_URL, SESSION_SECRET
  • Use SCREAMING_SNAKE_CASE for all environment variable names.

    • viteApiUrl, api-base-url
  • Access client env vars through import.meta.env:

    ts
    const apiUrl = import.meta.env.VITE_API_BASE_URL

Props and Emits

Props

  • Naming in code: camelCase.

  • Naming in templates: automatically converted to kebab-case.

    • defineProps<{ userId: string }>()<MyComp user-id="123" />
    • <MyComp userId="123" />
  • Booleans: name positively, no negations.

    • isOpen, disabled
    • notVisible, noDisabled
  • Events as props (callbacks): never prefix with on.

    • submit?: (payload: FormData) => void
    • onSubmit?: (payload: FormData) => void

Emits

  • Use simple, action-like names in lowercase.

    • change, update, submit, save, delete, open, close
  • Always declare payload type using array syntax:

    ts
    const emit = defineEmits<{
      change: [id: number]
      close: []
    }>()

Rationale

  • Simple, predictable names make events easy to remember.
  • Lowercase-only keeps consistency with Vue's event system.
  • Array syntax makes payload expectations explicit.

Stores & Composables

Stores

  • Naming in code: always use<Name>Store in PascalCase.

    • useCartStore, useAuthStore
    • cartStore, auth_store
  • File naming: camelCase with .store.ts suffix.

    • cart.store.ts, auth.store.ts
    • CartStore.ts, authStore.ts
  • State keys: camelCase, descriptive nouns.

    • items, userProfile, isLoading
    • ItemsList, profile_data, loadingFlag

Composables

  • Naming in code: always use<Name> in camelCase.

    • useAuth, useCart, useProfileQuery
    • authComposable, CartHook
  • File naming: same as function, camelCase.

    • useAuth.ts, useCart.ts, useProfileQuery.ts
    • UseAuth.ts, auth.js
  • Variable assignment: the variable name must match the composable name without the use prefix.

    • const auth = useAuth()
    • const cart = useCart()
    • const authComposable = useAuth()
    • const myCart = useCart()
  • Return object keys: stable, predictable, grouped by type.

    • State → nouns: user, items, isLoading
    • Actions → verbs: login, logout, addItem

Rationale

  • The use* prefix makes reactive utilities immediately recognizable.
  • A consistent suffix for stores avoids confusion between store files and composables.
  • Matching variable names reduce cognitive load.
  • A clear return contract makes composables predictable.

API / DTOs

Location

  • DTO interfaces live under /api/models/.

    • File names: camelCase, single short word.
      • user.ts, order.ts
      • User.ts, userModel.ts, user-types.ts
    • The main response object must end with Response.
    • Nested or referenced types use regular PascalCase names without suffix.
      • api/models/UserResponse.ts, api/models/Address.ts
      • api/models/UserDTO.ts, api/models/AddressResponse.ts (nested type should not use Response)
  • Domain models also live under /api/models/.

    • File names: camelCase, single short word.
      • api/models/User.ts
      • api/models/userModel.ts
    • Interface name: clean, no suffix.
  • API functions live under /api/.

    • File name: camelCase, preferably a single word.
      • api/user.ts, api/order.ts
      • api/UserApi.ts, api/user-api.ts

Rules

  • DTOs

    • Main object mapping to an API response → must end with Response.
    • Nested objects/types inside the response → regular PascalCase names.
    • Example:
      ts
      // api/models/UserResponse.ts
      export interface Address {
        street: string
        city: string
        zip: string
      }
      
      export interface UserResponse {
        id: string
        full_name: string
        email_address: string
        created_at: string
        address: Address
      }
  • Domain models

    • Represent the cleaned, normalized shape used in the app.
    • No suffix in the interface name.
    • Example:
      ts
      // api/models/User.ts
      export interface Address {
        street: string
        city: string
        zip: string
      }
      
      export interface User {
        id: string
        name: string
        email: string
        createdAt: Date
        address: Address
      }
  • API functions

    • Name functions as verbNoun, scoped to the resource.
    • Return domain models only.
    • Mapping methods go at the end of the file, after API functions.
    • Example:
      ts
      // api/user.ts
      import type { UserResponse } from './models/UserResponse'
      import type { User } from './models/User'
      
      export async function getUser(id: string): Promise<User> {
        const res = await fetch(`/api/users/${id}`)
        if (!res.ok) throw new Error('Request failed')
        const data: UserResponse = await res.json()
        return toUser(data)
      }
      
      export async function createUser(user: User): Promise<User> {
        const res = await fetch('/api/users', {
          method: 'POST',
          headers: { 'Content-Type': 'application/json' },
          body: JSON.stringify(toUserResponse(user)),
        })
        if (!res.ok) throw new Error('Request failed')
        const data: UserResponse = await res.json()
        return toUser(data)
      }
      
      // Mapping functions are defined last
      function toUser(response: UserResponse): User {
        return {
          id: response.id,
          name: response.full_name,
          email: response.email_address,
          createdAt: new Date(response.created_at),
          address: response.address,
        }
      }
      
      function toUserResponse(user: User): UserResponse {
        return {
          id: user.id,
          full_name: user.name,
          email_address: user.email,
          created_at: user.createdAt.toISOString(),
          address: user.address,
        }
      }

Rationale

  • The Response suffix is reserved for top-level API response objects.
  • Nested types keep clean PascalCase naming since they may be reused.
  • Mapping functions at the end keep API operations easy to find at the top.
  • A domain-first approach ensures UI/business logic never consumes raw API responses.

Components & Icons

Components

  • File names: PascalCase.

    • ProfileCard.vue, CartModal.vue
    • profile-card.vue, cart_modal.vue
  • Base components (UI primitives): use the primitive name only.

    • Button.vue, Input.vue
    • BaseButton.vue, BaseInput.vue
  • Shared components: place in src/components/<domain>.

    • Group by domain:
      • src/components/layoutHeader.vue, Footer.vue, Sidebar.vue
      • src/components/navigationNavBar.vue, Breadcrumb.vue
      • src/components/formsFormField.vue, FormError.vue
      • src/components/feedbackToast.vue, Modal.vue
  • Page components: place in src/pages/<Feature> or src/pages/<Module>.

    • Use the feature/module name as the filename, in PascalCase.
      • src/pages/Checkout/Checkout.vue, src/pages/UserProfile/UserProfile.vue
      • src/pages/Checkout/Index.vue, src/pages/UserProfile/index.vue
  • Module/feature-specific components: place in src/pages/<Feature>/components/.

    • One components folder per module. No nested "children" folders.
      • src/pages/Checkout/components/CheckoutForm.vue
      • src/pages/Checkout/components/forms/CheckoutForm.vue
    • Component names must include the entity and be suffixed with intent.
      • UserForm.vue, UserList.vue, ProductCard.vue, OrderSummary.vue, PaymentModal.vue
    • Modal components must always end with Modal.
      • DeleteConfirmationModal.vue, PaymentModal.vue
      • DeleteConfirmation.vue

Icons

  • File names: PascalCase, suffixed with Icon.

    • SearchIcon.vue, CartIcon.vue
    • search.vue, carticon.vue
  • Placement: src/components/icons/.

    • src/components/icons/SearchIcon.vue
    • src/components/icons/search.vue
  • Imports: direct file import, not barrel export.

    • import SearchIcon from '@/components/icons/SearchIcon.vue'
    • import { SearchIcon } from '@/components/icons'

Rationale

  • PascalCase communicates that components are first-class Vue entities.
  • Base components map to primitives (Button, Input), keeping names short.
  • Domain grouping prevents a component "dumping ground."
  • Page components reflect their feature/module and avoid Index.vue.
  • Feature components are suffixed with intent (Form, List, Card, Summary, Modal) so their purpose is explicit.
  • Modal suffix makes role unambiguous.
  • Icons are imported directly by file path with the Icon suffix for clarity.

Queries/Mutations

Queries represent the single source of truth for fetching and caching server data, using the Query/Mutation layer as the data-fetching layer. It provides declarative, predictable async-state management with built-in caching, stale-data handling, background refetching, and request deduplication.

Centralizing query and mutation definitions under src/queries/ ensures consistent naming, predictable cache keys, and a clean separation between data fetching, UI logic, and state management.

Unlike a Pinia store (for client-side app state), queries are strictly for server state — data that lives on the backend and can be invalidated or refetched at any time.

Location & File Naming

  • All query and mutation definitions live under src/queries/.
  • File names: camelCase, ending with .query.ts.
    • products.query.ts, productLines.query.ts
    • productsQuery.ts, Products.query.ts

Interfaces & Types

  • Response interfaces: suffixed with Response.
    • ProductResponse, OrderResponse
  • Params (input) types: suffixed with Params.
    • GetProductsParams, FetchOrdersParams
  • Query key args interfaces: suffixed with QueryKeyArgs.
    • ProductLineRetrieveQueryKeyArgs, OrderListQueryKeyArgs
  • Hook args types: prefixed with Use<Entity><Scope>QueryArgs or Use<Entity><Action>MutationArgs.
    • UseProductLineQueryArgs, UseProductLinesQueryArgs
    • UseCreateProductLineMutationArgs, UseUpdateProductLineMutationArgs
  • DTOs: mirror API response fields (snake_case if the API returns it).
  • Domain types: PascalCase properties (camelCase inside the app).

Query Keys

  • Define query keys as arrays.
  • Use a query key factory per file.
    • Exported const named after the entity, suffixed with Keys.
    • First element: resource/entity name in camelCase.
    • Additional elements: sub-scope (list, detail, etc.) and params.
  • Pass params as part of the key for dynamic caching.
    • This ensures queries (via the Query/Mutation layer) invalidate and refetch correctly when inputs change.
ts
interface ProductLineRetrieveQueryKeyArgs {
  id: MaybeRefOrGetter<number>;
}

export const productLineKeys = {
  all: ['productLines'] as const,
  list: (params: GetProductLinesParams) => [...productLineKeys.all, 'list', params] as const,
  detail: ({ id }: ProductLineRetrieveQueryKeyArgs) =>
    [...productLineKeys.all, 'detail', id] as const,
};

Query & Mutation Args Types

  • Query args types extend Partial<UseQueryOptions<...>> and merge with a QueryKeyArgs interface.
  • Mutation args types extend MutationObserverOptions and merge with QueryKeyArgs when invalidation depends on filters or other params.
  • Naming:
    • ✅ Queries: Use<Entity><Scope>QueryArgs
    • ✅ Mutations: Use<Entity><Action>MutationArgs
ts
type UseProductLineQueryArgs =
  Partial<UseQueryOptions<UnifiedProductLine>> & ProductLineRetrieveQueryKeyArgs;

export const useProductLineQuery = ({ id, ...options }: UseProductLineQueryArgs) =>
  useQuery({
    queryKey: productLineKeys.detail({ id }),
    queryFn: () => getInventoryProductLine(toValue(id)),
    enabled: () => !!toValue(id),
    ...options,
  });
ts
type UseCreateProductLineMutationArgs =
  ProductLineQueryKeyArgs &
  MutationObserverOptions<UnifiedProductLine, DefaultError, ProductLineMutationArgs>;

export const useCreateProductLinesMutation = (
  { filters, ...options }: UseCreateProductLineMutationArgs = {}
) => {
  const queryClient = useQueryClient();

  return useMutation({
    ...options,
    mutationFn: async ({ data }) =>
      await addInventoryProductLine({
        unifiedProductLineCreateRequest: data as UnifiedProductLineCreateRequest,
      }),
    async onSuccess(...args) {
      const rawFilters = toValue(filters);
      await invalidateProductLinesQueryCache(queryClient, rawFilters);
      options?.onSuccess?.(...args);
    },
  });
};

Query Functions

  • Name functions as verb + entity.
    • fetchProducts, fetchProductById, fetchOrders
  • Must accept a typed Params or QueryKeyArgs object.
  • Return type must be a Response or mapped domain model.
  • Always export individually (no default export).
ts
export async function fetchProducts(
  params: GetProductsParams
): Promise<ProductResponse[]> {
  const { data } = await api.get('/products', { params })
  return data
}

Hook Wrappers

  • Prefix with use + entity + scope.
    • useProducts, useProductDetail, useOrders
  • Align hook names with query key factory methods.
  • Reuse the same QueryKeyArgs type used by the factory.
  • Declare args types (Use...QueryArgs / Use...MutationArgs).

Conventions Recap

  • Files: src/queries/*.query.ts, camelCase.
  • Interfaces: *Response, *Params, *QueryKeyArgs.
  • Hook args types: Use<Entity><Scope>QueryArgs / Use<Entity><Action>MutationArgs.
  • Query keys: factory per entity (entityKeys). Params must be part of the key for dynamic caching.
  • Query functions: fetch<Entity>[BySomething], typed with Params or QueryKeyArgs.
  • Hooks: use<Entity>[Scope], typed with the same QueryKeyArgs or Use...Args.
  • Consistency: always array keys, stable factories, typed responses.

Rationale

  • Centralization avoids scattering queries across features.
  • .query.ts suffix makes the file's purpose obvious.
  • Factory keys ensure stability, cache correctness, and prevent typos.
  • Dynamic cache keys with params guarantee queries refresh when inputs change.
  • Dedicated QueryKeyArgs interfaces unify typing across keys, functions, and hooks.
  • Use...Args types give hooks typed extension points.
  • Verb + entity functions communicate intent clearly.
  • Hook alignment with keys improves predictability.
  • Consistent typing (Response / Params / QueryKeyArgs / Use...Args) separates API shapes from domain logic.

Utilities

Location

  • All utilities live under src/utils/.
  • Feature/module-specific utilities go under src/utils/<feature>/.

✅ Examples:

text
src/utils/date.ts
src/utils/string.ts
src/utils/httpClient.ts
src/utils/auth/token.ts
src/utils/cart/price.ts

Naming

  • File names: intent-driven, single word if possible (date.ts, string.ts).

    • If one word is not enough, use camelCase (httpClient.ts, routeGuards.ts).
    • ❌ Avoid *-utils.ts or *-helpers.ts.
  • Functions: verbNoun (formatCurrency, parseIsoDate, isNonEmptyString).

  • Constants: SCREAMING_SNAKE_CASE.

  • Types/enums/interfaces: PascalCase.

ts
// date.ts
export const MS_IN_MINUTE = 60_000;

export function toStartOfDay(d: Date): Date {
  const out = new Date(d);
  out.setHours(0, 0, 0, 0);
  return out;
}

Rules

  • Exports: named exports only, no default exports.
  • Scope: utilities must be framework-agnostic (no Vue imports).
  • Flat only: do not nest deeper than one folder (src/utils/<feature>/).
  • Split files: only if intent diverges (date.ts vs dateFormat.ts).

Do / Don't

ScopeDo ✅Don't ❌
Filedate.ts, httpClient.tsdate-utils.ts, StringHelpers.ts
Exportexport function formatCurrency(...)export default function helper(...)
ConstantMS_IN_MINUTE = 60000minuteMs = 60000
FunctionisNonEmptyString(u): u is stringstringCheck(u)

Tests

1. Unit Tests

  • Naming: <TargetName>.spec.ts (PascalCase).
  • Placement: same folder as the script/component under test.
  • Rule: filename must mirror the target file.

✅ Examples:

text
src/components/ProfileCard.vue
src/components/ProfileCard.spec.ts

src/stores/CartStore.ts
src/stores/CartStore.spec.ts

❌ Bad:

text
src/tests/cart.test.ts
src/components/profile-card.spec.ts

2. Integration Tests

  • Naming: <PageName>.spec.ts (PascalCase).
  • Placement: same directory as the page component.

✅ Example:

text
src/pages/Checkout.vue
src/pages/Checkout.spec.ts

❌ Bad:

text
src/pages/checkout-page.spec.ts

3. E2E Tests

  • Naming: <ModuleName>.e2e.ts (PascalCase).
  • Placement: tests/e2e/<ModuleName>/.
  • Rule: never place E2E tests under src/.

✅ Examples:

text
tests/e2e/Cart/Cart.e2e.ts
tests/e2e/Auth/Auth.e2e.ts

❌ Bad:

text
src/pages/Cart.e2e.ts
tests/e2e/cart.e2e.ts

4. Fixtures & Mocks

  • Location: shared fixtures belong under tests/fixtures/<module>/. Project-specific fixtures may be colocated with the test that uses them; keep shared fixtures in the canonical directory.
  • Directories: lowercase (cart/, auth/).
  • JSON fixtures: PascalCase.
    • UserCredentials.json, CartItems.json
  • TS/JS factories: camelCase.
    • userFactory.ts, orderFactory.ts

✅ Example:

text
tests/fixtures/cart/UserCredentials.json
tests/fixtures/cart/userFactory.ts

❌ Bad for shared fixtures:

text
tests/fixtures/UserCredentials.json  # shared fixture without a module directory

Project-specific fixtures may live beside the test that owns them. For example, this is valid when the fixture is used only by the checkout E2E flow:

text
tests/e2e/Cart/Checkout.e2e.ts
tests/e2e/Cart/fixtures/CheckoutUser.json

Do not treat a colocated, project-specific fixture as a shared fixture. Shared fixtures belong under tests/fixtures/<module>/.


5. Identifiers

  • All data-testid attributes must use kebab-case.
    • data-testid="checkout-submit-button"
    • data-testid="checkoutSubmitBtn"

6. Do / Don't

ScopeDo ✅Don't ❌
UnitProfileCard.spec.ts colocated with ProfileCard.vueprofile-card.spec.ts or in /tests/
IntegrationCheckout.spec.ts colocated with Checkout.vuecheckout.spec.ts
E2Etests/e2e/Cart/Cart.e2e.tssrc/pages/Cart.e2e.ts
Fixtures JSONtests/fixtures/cart/UserCredentials.jsontests/fixtures/cart/data.json (too vague)
Factories TStests/fixtures/cart/userFactory.tstests/fixtures/cart/UserFactory.ts (wrong case)
Identifiersdata-testid="checkout-submit-button"data-testid="checkoutSubmitBtn"