tests: co-locate every Vitest suite next to its subject under src/

Eliminate the parallel `tests/` tree. Each `*.test.ts` / `*.test.tsx`
now sits in the same directory as the module it covers, mirroring its
filename (`Foo.ts` ↔ `Foo.test.ts`). The renderer-SDK mock used by
component / hook tests moves to `src/__mocks__/nitro-renderer.ts` and
the Vitest setup file becomes `src/test-setup.ts` — both still wired
through `vitest.config.mts` exactly as before, only the paths changed.

All 13 suites + 178/178 cases still pass. The production build is
unaffected: rollup only follows imports from `src/index.tsx` and never
crosses into `.test.ts` files, so test code is naturally tree-shaken
out of the bundle. `yarn build` output is byte-for-byte the same on
the user-facing chunks.

tsconfig drops the now-redundant `tests` include entry. CLAUDE.md
'Layout convention' replaces the old `tests/` row with three rows
documenting the new co-located convention, the `__mocks__/` directory
and the `test-setup.ts` entry; ARCHITECTURE.md picks up the same
update. The 'DO NOT CHANGE' qualifier on the layout is preserved —
this rewrite IS the change, decided deliberately to make tests a
first-class part of the source tree rather than a sibling project.
This commit is contained in:
simoleo89
2026-05-16 11:35:03 +02:00
parent eb8d87969d
commit 8b4308af16
19 changed files with 47 additions and 46 deletions
+8 -5
View File
@@ -106,7 +106,9 @@ src/hooks/<area>/<feature?>/ → hooks, FLAT files, no per-feature s
src/api/ → cross-cutting helpers (LocalizeText, composers, formatters) src/api/ → cross-cutting helpers (LocalizeText, composers, formatters)
src/common/ → reusable UI primitives + error boundary src/common/ → reusable UI primitives + error boundary
src/state/ → Zustand stores (cross-feature only) src/state/ → Zustand stores (cross-feature only)
tests/ → Vitest suites (mirror filename of subject) src/**/*.test.{ts,tsx} → Vitest suites co-located next to their subject (e.g. `Foo.ts` + `Foo.test.ts`)
src/__mocks__/ → hand-written renderer-SDK stub for tests (aliased over `@nitrots/nitro-renderer`)
src/test-setup.ts → Vitest setupFiles entry (jest-dom matchers, etc.)
``` ```
When splitting a god-hook the convention is **3 files, all flat in the When splitting a god-hook the convention is **3 files, all flat in the
@@ -261,7 +263,7 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` | | God-hook split (state + actions + shim) | `doorbell`, `poll`, `furni-chooser`, `user-chooser`, `friend-request`, `chat-input` |
| God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends`, `catalog` (three-way: `useCatalogData` / `useCatalogUiState` / `useCatalogActions` — all 48 consumers migrated, deprecated `useCatalog` shim removed) | | God-hook split (`useBetween` singleton + state filter + actions filter + shim) | `wired-tools`, `translation`, `notification`, `friends`, `catalog` (three-way: `useCatalogData` / `useCatalogUiState` / `useCatalogActions` — all 48 consumers migrated, deprecated `useCatalog` shim removed) |
| `WidgetErrorBoundary` | `RoomWidgetsView` umbrella + per-widget wrap on all 13 room widgets and all 20 furniture widgets (so a crash in one widget no longer takes down its siblings) | | `WidgetErrorBoundary` | `RoomWidgetsView` umbrella + per-widget wrap on all 13 room widgets and all 20 furniture widgets (so a crash in one widget no longer takes down its siblings) |
| Vitest | 178/178 cases — pure helpers + 2 Zustand store suites (`navigatorRoomCreatorStore`, `wiredCreatorToolsUiStore`) + 2 component-/hook-level pilots (WidgetErrorBoundary, useDoorbellState) on top of the renderer-SDK mock at `tests/mocks/renderer-mock.ts`, 34 cases on the catalog pure helpers, 4 contract cases on the catalog filters | | Vitest | 178/178 cases — pure helpers + 2 Zustand store suites (`navigatorRoomCreatorStore`, `wiredCreatorToolsUiStore`) + 2 component-/hook-level pilots (WidgetErrorBoundary, useDoorbellState) on top of the renderer-SDK mock at `src/__mocks__/nitro-renderer.ts`, 34 cases on the catalog pure helpers, 4 contract cases on the catalog filters. **Tests are co-located** under `src/`, alongside their subject. |
| Form Actions | Login / Register / Forgot (LoginView.tsx) | | Form Actions | Login / Register / Forgot (LoginView.tsx) |
| Cherry-picked from `duckietm` PR #126 | `UserAccountSettingsView` (reset password / email / username under user settings), plus the wear-badge popup `canShowWearButton` gating | | Cherry-picked from `duckietm` PR #126 | `UserAccountSettingsView` (reset password / email / username under user settings), plus the wear-badge popup `canShowWearButton` gating |
@@ -270,7 +272,7 @@ into `configurePreviewServer` so `yarn preview` keeps working.
| Split `useChatWidget` / `useAvatarInfoWidget` | Both state-driven via events with no clean imperative actions to extract — skip-motivated. Already touched today for the InfoStand listener move. | | Split `useChatWidget` / `useAvatarInfoWidget` | Both state-driven via events with no clean imperative actions to extract — skip-motivated. Already touched today for the InfoStand listener move. |
| Split `usePetPackageWidget` / `useWordQuizWidget` / `useChatCommandSelector` | Their "actions" mutate internal state or are tightly interdependent — skip-motivated. | | Split `usePetPackageWidget` / `useWordQuizWidget` / `useChatCommandSelector` | Their "actions" mutate internal state or are tightly interdependent — skip-motivated. |
| Hoist Wired Creator Tools **derived** state to the Zustand slice | UI-only flags are already hoisted (`useWiredCreatorToolsUiStore`). What's left is the event-driven derived state — `selectedFurni` / `selectedUser` / `monitorSnapshot` / `variableHighlightOverlays` — which can only move alongside their listener effects (multi-session refactor). | | Hoist Wired Creator Tools **derived** state to the Zustand slice | UI-only flags are already hoisted (`useWiredCreatorToolsUiStore`). What's left is the event-driven derived state — `selectedFurni` / `selectedUser` / `monitorSnapshot` / `variableHighlightOverlays` — which can only move alongside their listener effects (multi-session refactor). |
| Widen the component / hook test coverage | Mock layer is in place (`tests/mocks/renderer-mock.ts`) and the first 2 pilots pass. Good follow-up targets: other `*State` hooks built on event reducers, `LoginView` Form Actions happy/error paths, OfferView with `useNitroQuery`. | | Widen the component / hook test coverage | Mock layer is in place (`src/__mocks__/nitro-renderer.ts`) and the first 2 pilots pass. Good follow-up targets: other `*State` hooks built on event reducers, `LoginView` Form Actions happy/error paths, OfferView with `useNitroQuery`. |
## Known open logic bugs ## Known open logic bugs
@@ -310,7 +312,8 @@ Fix shapes documented; both are reasonable PRs on their own.
- Architecture doc: `docs/ARCHITECTURE.md` - Architecture doc: `docs/ARCHITECTURE.md`
- Test runner config: `vitest.config.mts` (separate from `vite.config.mjs`) - Test runner config: `vitest.config.mts` (separate from `vite.config.mjs`)
- Test setup: `tests/setup.ts` - Test setup: `src/test-setup.ts`
- Test convention: co-located under `src/` next to the subject (`src/<path>/Foo.ts``src/<path>/Foo.test.ts`). No separate `tests/` tree.
- React Query adapter: `src/api/nitro-query/createNitroQuery.ts` - React Query adapter: `src/api/nitro-query/createNitroQuery.ts`
- Zustand factory: `src/state/createNitroStore.ts` - Zustand factory: `src/state/createNitroStore.ts`
- Error boundary: `src/common/error-boundary/WidgetErrorBoundary.tsx` - Error boundary: `src/common/error-boundary/WidgetErrorBoundary.tsx`
@@ -333,7 +336,7 @@ Fix shapes documented; both are reasonable PRs on their own.
`useCatalogUiState` / `useCatalogActions` in `useCatalogUiState` / `useCatalogActions` in
`src/hooks/catalog/useCatalog.ts` (all 48 consumers migrated; `src/hooks/catalog/useCatalog.ts` (all 48 consumers migrated;
deprecated `useCatalog` shim removed) deprecated `useCatalog` shim removed)
- Renderer-SDK mock for Vitest: `tests/mocks/renderer-mock.ts` - Renderer-SDK mock for Vitest: `src/__mocks__/nitro-renderer.ts`
(aliased over `@nitrots/nitro-renderer` via `vitest.config.mts`). (aliased over `@nitrots/nitro-renderer` via `vitest.config.mts`).
Hosts the explicit `NitroLogger` mock, the `mockEventDispatcher` / Hosts the explicit `NitroLogger` mock, the `mockEventDispatcher` /
`clearMockEventDispatcher` helpers used by hook tests, the `clearMockEventDispatcher` helpers used by hook tests, the
+4 -4
View File
@@ -528,7 +528,7 @@ Pure helpers in `useCatalog.helpers.ts`:
visitors) and passes the resulting `visitorCount` into the helper. visitors) and passes the resulting `visitorCount` into the helper.
`useCatalog.ts` now imports these instead of defining them inline `useCatalog.ts` now imports these instead of defining them inline
(net **75 LOC**). Test file `tests/useCatalog.helpers.test.ts` covers (net **75 LOC**). Co-located test file `src/hooks/catalog/useCatalog.helpers.test.ts` covers
all six helpers with 34 cases (tree depth + offerId mapping, all six helpers with 34 cases (tree depth + offerId mapping,
node lookups including root exclusion, the limit-reached / guild-admin node lookups including root exclusion, the limit-reached / guild-admin
fallback / visitors-in-room paths of the placement helper, and the fallback / visitors-in-room paths of the placement helper, and the
@@ -538,7 +538,7 @@ empty-map / partial-bucket branches of the offer lookup).
- Vitest 3 + jsdom + `@testing-library/react` + `@testing-library/jest-dom` - Vitest 3 + jsdom + `@testing-library/react` + `@testing-library/jest-dom`
configured. Separate `vitest.config.mts` so the runner doesn't drag in configured. Separate `vitest.config.mts` so the runner doesn't drag in
the renderer SDK aliases from `vite.config.mjs`. the renderer SDK aliases from `vite.config.mjs`.
- **163 cases passing** across 12 test files. Pure-module suites: - **178 cases passing** across 13 test files, **co-located under `src/`** next to each subject (no separate `tests/` tree). Pure-module suites:
- `WiredCreatorTools.helpers.test.ts` (18) — formatters + snapshot - `WiredCreatorTools.helpers.test.ts` (18) — formatters + snapshot
factory. factory.
- `navigatorRoomCreatorStore.test.ts` (4) — Zustand store invariants - `navigatorRoomCreatorStore.test.ts` (4) — Zustand store invariants
@@ -580,7 +580,7 @@ empty-map / partial-bucket branches of the offer lookup).
`DOORBELL`, dedup duplicates, remove on `RSDE_ACCEPTED` / `DOORBELL`, dedup duplicates, remove on `RSDE_ACCEPTED` /
`RSDE_REJECTED`, ignore stale events, unsubscribe on unmount. `RSDE_REJECTED`, ignore stale events, unsubscribe on unmount.
- **Renderer-SDK mock at `tests/mocks/renderer-mock.ts`** — - **Renderer-SDK mock at `src/__mocks__/nitro-renderer.ts`** —
`vitest.config.mts` aliases `@nitrots/nitro-renderer` over this file `vitest.config.mts` aliases `@nitrots/nitro-renderer` over this file
so jsdom-hosted tests never load Pixi or the message so jsdom-hosted tests never load Pixi or the message
parser/composer registry. The mock exports: parser/composer registry. The mock exports:
@@ -734,7 +734,7 @@ Remaining order of value/risk for the next contributor:
each tab. A slice at `src/components/wired-tools/wiredToolsStore.ts` each tab. A slice at `src/components/wired-tools/wiredToolsStore.ts`
would make each tab subscribe to the keys it needs. would make each tab subscribe to the keys it needs.
4. **Widen the component/hook Vitest coverage.** The renderer-SDK 4. **Widen the component/hook Vitest coverage.** The renderer-SDK
mock layer is in place (`tests/mocks/renderer-mock.ts`) and the mock layer is in place (`src/__mocks__/nitro-renderer.ts`) and the
first two pilots — `WidgetErrorBoundary` and `useDoorbellState` first two pilots — `WidgetErrorBoundary` and `useDoorbellState`
pass. Good follow-up targets: other `*State` hooks built on event pass. Good follow-up targets: other `*State` hooks built on event
reducers (`useFurniChooserState`, `useUserChooserState`, reducers (`useFurniChooserState`, `useUserChooserState`,
@@ -1,5 +1,5 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { dedupeBadges } from '../src/api/avatar/dedupeBadges'; import { dedupeBadges } from './dedupeBadges';
describe('dedupeBadges', () => describe('dedupeBadges', () =>
{ {
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { ColorUtils } from '../src/api/utils/ColorUtils'; import { ColorUtils } from './ColorUtils';
import { FixedSizeStack } from '../src/api/utils/FixedSizeStack'; import { FixedSizeStack } from './FixedSizeStack';
import { LocalizeFormattedNumber } from '../src/api/utils/LocalizeFormattedNumber'; import { LocalizeFormattedNumber } from './LocalizeFormattedNumber';
describe('LocalizeFormattedNumber', () => describe('LocalizeFormattedNumber', () =>
{ {
@@ -1,10 +1,10 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { CloneObject } from '../src/api/utils/CloneObject'; import { CloneObject } from './CloneObject';
import { ConvertSeconds } from '../src/api/utils/ConvertSeconds'; import { ConvertSeconds } from './ConvertSeconds';
import { LocalizeShortNumber } from '../src/api/utils/LocalizeShortNumber'; import { LocalizeShortNumber } from './LocalizeShortNumber';
import { GetWiredTimeLocale } from '../src/api/wired/GetWiredTimeLocale'; import { GetWiredTimeLocale } from '../wired/GetWiredTimeLocale';
import { WiredDateToString } from '../src/api/wired/WiredDateToString'; import { WiredDateToString } from '../wired/WiredDateToString';
import { getPrefixFontStyle, parsePrefixColors, PRESET_PREFIX_FONTS } from '../src/api/utils/PrefixUtils'; import { getPrefixFontStyle, parsePrefixColors, PRESET_PREFIX_FONTS } from './PrefixUtils';
describe('ConvertSeconds', () => describe('ConvertSeconds', () =>
{ {
@@ -5,12 +5,12 @@ import { describe, expect, it, vi } from 'vitest';
* with a deterministic stub. The stub returns `key|amount` so each test * with a deterministic stub. The stub returns `key|amount` so each test
* can assert both the bucket FriendlyTime chose AND the value it computed. * can assert both the bucket FriendlyTime chose AND the value it computed.
*/ */
vi.mock('../src/api/utils/LocalizeText', () => ({ vi.mock('./LocalizeText', () => ({
LocalizeText: (key: string, _params?: string[], replacements?: string[]) => LocalizeText: (key: string, _params?: string[], replacements?: string[]) =>
`${ key }|${ replacements?.[0] ?? '' }` `${ key }|${ replacements?.[0] ?? '' }`
})); }));
import { FriendlyTime } from '../src/api/utils/FriendlyTime'; import { FriendlyTime } from './FriendlyTime';
const MINUTE = 60; const MINUTE = 60;
const HOUR = 60 * MINUTE; const HOUR = 60 * MINUTE;
@@ -4,10 +4,10 @@ import { NitroLogger } from '@nitrots/nitro-renderer';
import { cleanup, render, screen } from '@testing-library/react'; import { cleanup, render, screen } from '@testing-library/react';
import { FC } from 'react'; import { FC } from 'react';
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { WidgetErrorBoundary } from '../src/common/error-boundary/WidgetErrorBoundary'; import { WidgetErrorBoundary } from './WidgetErrorBoundary';
// `import { NitroLogger } from '@nitrots/nitro-renderer'` resolves to // `import { NitroLogger } from '@nitrots/nitro-renderer'` resolves to
// `tests/mocks/renderer-mock.ts` via the alias in vitest.config.mts. // `src/__mocks__/nitro-renderer.ts` via the alias in vitest.config.mts.
// The SUT imports the same path, so both reach the same vi.fn instance. // The SUT imports the same path, so both reach the same vi.fn instance.
describe('WidgetErrorBoundary', () => describe('WidgetErrorBoundary', () =>
@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest'; import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
import { useRoomCreatorStore } from '../src/components/navigator/views/navigatorRoomCreatorStore'; import { useRoomCreatorStore } from './navigatorRoomCreatorStore';
describe('useRoomCreatorStore', () => describe('useRoomCreatorStore', () =>
{ {
@@ -6,7 +6,7 @@ import {
formatMonitorSource, formatMonitorSource,
formatVariableTimestamp, formatVariableTimestamp,
normalizeMonitorReason normalizeMonitorReason
} from '../src/components/wired-tools/WiredCreatorTools.helpers'; } from './WiredCreatorTools.helpers';
describe('WiredCreatorTools helpers', () => describe('WiredCreatorTools helpers', () =>
{ {
@@ -1,5 +1,5 @@
import { beforeEach, describe, expect, it } from 'vitest'; import { beforeEach, describe, expect, it } from 'vitest';
import { useWiredCreatorToolsUiStore } from '../src/components/wired-tools/wiredCreatorToolsUiStore'; import { useWiredCreatorToolsUiStore } from './wiredCreatorToolsUiStore';
const INITIAL = { const INITIAL = {
isVisible: false, isVisible: false,
@@ -75,7 +75,7 @@ vi.mock('use-between', () => ({
// Import AFTER the mock is set up. The hooks resolve `useBetween` at // Import AFTER the mock is set up. The hooks resolve `useBetween` at
// import time via the module graph, so the order matters. // import time via the module graph, so the order matters.
import { useCatalogActions, useCatalogData, useCatalogUiState } from '../src/hooks/catalog/useCatalog'; import { useCatalogActions, useCatalogData, useCatalogUiState } from './useCatalog';
describe('useCatalog filter contract', () => describe('useCatalog filter contract', () =>
{ {
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { BuilderFurniPlaceableStatus } from '../src/api/catalog/BuilderFurniPlaceableStatus'; import { BuilderFurniPlaceableStatus } from '../../api/catalog/BuilderFurniPlaceableStatus';
import { CatalogType } from '../src/api/catalog/CatalogType'; import { CatalogType } from '../../api/catalog/CatalogType';
import { import {
buildCatalogNodeTree, buildCatalogNodeTree,
findNodeById, findNodeById,
@@ -9,7 +9,7 @@ import {
getOfferProductKeys, getOfferProductKeys,
normalizeCatalogType, normalizeCatalogType,
resolveBuilderFurniPlaceableStatus resolveBuilderFurniPlaceableStatus
} from '../src/hooks/catalog/useCatalog.helpers'; } from './useCatalog.helpers';
// --------------------------------------------------------------------------- // ---------------------------------------------------------------------------
// normalizeCatalogType // normalizeCatalogType
@@ -1,6 +1,6 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { CatalogType } from '../src/api/catalog/CatalogType'; import { CatalogType } from '../../api/catalog/CatalogType';
import { getOffersStorageKey, getPagesStorageKey, normalizeCatalogType, parseOffers, parsePages, STORAGE_KEY_OFFERS_BUILDER, STORAGE_KEY_OFFERS_NORMAL, STORAGE_KEY_PAGES_BUILDER, STORAGE_KEY_PAGES_NORMAL } from '../src/hooks/catalog/useCatalogFavorites.helpers'; import { getOffersStorageKey, getPagesStorageKey, normalizeCatalogType, parseOffers, parsePages, STORAGE_KEY_OFFERS_BUILDER, STORAGE_KEY_OFFERS_NORMAL, STORAGE_KEY_PAGES_BUILDER, STORAGE_KEY_PAGES_NORMAL } from './useCatalogFavorites.helpers';
describe('normalizeCatalogType', () => describe('normalizeCatalogType', () =>
{ {
@@ -1,7 +1,7 @@
import { describe, expect, it } from 'vitest'; import { describe, expect, it } from 'vitest';
import { AvatarInfoUser } from '../src/api/room/widgets/AvatarInfoUser'; import { AvatarInfoUser } from '../../../api/room/widgets/AvatarInfoUser';
import type { IAvatarInfo } from '../src/api/room/widgets/IAvatarInfo'; import type { IAvatarInfo } from '../../../api/room/widgets/IAvatarInfo';
import { applyFavouriteGroupUpdate, applyUserBadgesUpdate, applyUserFigureUpdate } from '../src/hooks/rooms/widgets/avatarInfo.reducers'; import { applyFavouriteGroupUpdate, applyUserBadgesUpdate, applyUserFigureUpdate } from './avatarInfo.reducers';
/** /**
* Pure reducers for the InfoStand pilot. They take the inspected * Pure reducers for the InfoStand pilot. They take the inspected
@@ -3,8 +3,8 @@
import { RoomSessionDoorbellEvent } from '@nitrots/nitro-renderer'; import { RoomSessionDoorbellEvent } from '@nitrots/nitro-renderer';
import { act, cleanup, renderHook } from '@testing-library/react'; import { act, cleanup, renderHook } from '@testing-library/react';
import { afterEach, beforeEach, describe, expect, it } from 'vitest'; import { afterEach, beforeEach, describe, expect, it } from 'vitest';
import { useDoorbellState } from '../src/hooks/rooms/widgets/useDoorbellState'; import { useDoorbellState } from './useDoorbellState';
import { clearMockEventDispatcher, mockEventDispatcher } from './mocks/renderer-mock'; import { clearMockEventDispatcher, mockEventDispatcher } from '../../../__mocks__/nitro-renderer';
// Server push helper — mirrors the renderer wire by emitting the same // Server push helper — mirrors the renderer wire by emitting the same
// constants the SUT listens to. The real constructor takes a session // constants the SUT listens to. The real constructor takes a session
-1
View File
@@ -30,7 +30,6 @@
}, },
"include": [ "include": [
"src", "src",
"tests",
"node_modules/@nitrots/nitro-renderer/src/**/*.ts" "node_modules/@nitrots/nitro-renderer/src/**/*.ts"
] ]
} }
+7 -8
View File
@@ -6,23 +6,22 @@ import { resolve } from 'path';
* dev/build config wires up the renderer SDK via filesystem aliases that * dev/build config wires up the renderer SDK via filesystem aliases that
* point at sibling working trees (`../renderer`, `../Nitro_Render_V3`). * point at sibling working trees (`../renderer`, `../Nitro_Render_V3`).
* *
* Test files were originally written against pure modules (helpers, * Tests live next to their subject under `src/` (`foo.ts` + `foo.test.ts`).
* stores) that don't pull in the renderer. We now also support * The renderer SDK is aliased to a hand-written stub at
* component-level tests by aliasing `@nitrots/nitro-renderer` to a * `src/__mocks__/nitro-renderer.ts` so jsdom doesn't try to evaluate
* hand-written stub at `tests/mocks/renderer-mock.ts` so jsdom doesn't * Pixi + the full message parser/composer registry at import time.
* try to evaluate Pixi + the full message parser/composer registry.
*/ */
export default defineConfig({ export default defineConfig({
test: { test: {
environment: 'jsdom', environment: 'jsdom',
globals: false, globals: false,
include: [ 'tests/**/*.test.ts', 'tests/**/*.test.tsx' ], include: [ 'src/**/*.test.ts', 'src/**/*.test.tsx' ],
setupFiles: [ './tests/setup.ts' ], setupFiles: [ './src/test-setup.ts' ],
css: false css: false
}, },
resolve: { resolve: {
alias: { alias: {
'@nitrots/nitro-renderer': resolve(__dirname, 'tests/mocks/renderer-mock.ts'), '@nitrots/nitro-renderer': resolve(__dirname, 'src/__mocks__/nitro-renderer.ts'),
'@': resolve(__dirname, 'src') '@': resolve(__dirname, 'src')
} }
} }