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/layout → Header.vue, Footer.vue, Sidebar.vue
      • src/components/navigation → NavBar.vue, Breadcrumb.vue
      • src/components/forms → FormField.vue, FormError.vue
      • src/components/feedback → Toast.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"