@aamini/config 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/msw.d.mts +3 -0
- package/dist/msw.mjs +3 -0
- package/dist/playwright.shared.config.d.mts +8 -0
- package/dist/playwright.shared.config.mjs +47 -0
- package/dist/playwright.shared.config.mjs.map +1 -0
- package/dist/setup/styles.d.mts +1 -0
- package/dist/setup/styles.mjs +3 -0
- package/dist/test/browser.d.mts +15 -0
- package/dist/test/browser.mjs +26 -0
- package/dist/test/browser.mjs.map +1 -0
- package/dist/test/database.d.mts +22 -0
- package/dist/test/database.mjs +18 -0
- package/dist/test/database.mjs.map +1 -0
- package/dist/test/db.d.mts +21 -0
- package/dist/test/db.mjs +53 -0
- package/dist/test/db.mjs.map +1 -0
- package/dist/test/playwright.d.mts +7 -0
- package/dist/test/playwright.mjs +15 -0
- package/dist/test/playwright.mjs.map +1 -0
- package/dist/test/postgres.d.mts +14 -0
- package/dist/test/postgres.mjs +25 -0
- package/dist/test/postgres.mjs.map +1 -0
- package/dist/test/server.d.mts +15 -0
- package/dist/test/server.mjs +23 -0
- package/dist/test/server.mjs.map +1 -0
- package/dist/test/visual-page.d.mts +36 -0
- package/dist/test/visual-page.mjs +30 -0
- package/dist/test/visual-page.mjs.map +1 -0
- package/dist/vite.shared.d.mts +14 -0
- package/dist/vite.shared.mjs +232 -0
- package/dist/vite.shared.mjs.map +1 -0
- package/dist/vitest.shared.d.mts +12 -0
- package/dist/vitest.shared.mjs +122 -0
- package/dist/vitest.shared.mjs.map +1 -0
- package/msw.ts +5 -0
- package/package.json +99 -0
- package/playwright.shared.config.ts +71 -0
- package/setup/styles.ts +4 -0
- package/test/browser.ts +31 -0
- package/test/database.ts +38 -0
- package/test/db.ts +80 -0
- package/test/playwright.ts +17 -0
- package/test/postgres.ts +43 -0
- package/test/server.ts +31 -0
- package/test/visual-page.ts +57 -0
- package/tsconfig/tsconfig.astro.json +34 -0
- package/tsconfig/tsconfig.lib.json +19 -0
- package/tsconfig/tsconfig.react.json +16 -0
- package/tsconfig/tsconfig.strict.json +16 -0
- package/tsconfig/tsconfig.tanstack.json +17 -0
- package/virtual-modules.d.ts +6 -0
- package/vite.config.ts +53 -0
- package/vite.shared.ts +298 -0
- package/vitest.shared.ts +183 -0
package/test/db.ts
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// oxlint-disable typescript-eslint/triple-slash-reference
|
|
2
|
+
/// <reference path="../virtual-modules.d.ts" />
|
|
3
|
+
// oxlint-disable no-empty-pattern
|
|
4
|
+
import handlers from '@test/handlers'
|
|
5
|
+
import { setupServer, SetupServer } from 'msw/node'
|
|
6
|
+
import { resolve } from 'node:path'
|
|
7
|
+
import { test as baseTest } from 'vitest'
|
|
8
|
+
import type { Database } from './database'
|
|
9
|
+
import { seedDatabase } from './database'
|
|
10
|
+
|
|
11
|
+
const DEFAULT_SCHEMA_PATH = 'src/db/tables.ts'
|
|
12
|
+
const DEFAULT_MIGRATIONS_PATH = 'src/db/migrations'
|
|
13
|
+
const DEFAULT_IMAGE = 'postgres:17'
|
|
14
|
+
|
|
15
|
+
export interface DbFixture {
|
|
16
|
+
server: SetupServer
|
|
17
|
+
_cleanup: void
|
|
18
|
+
db: Database
|
|
19
|
+
seedFunction: (db: Database) => Promise<void>
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const mswServer = setupServer(...handlers)
|
|
23
|
+
|
|
24
|
+
export const test = baseTest.extend<DbFixture>({
|
|
25
|
+
// MSW Server fixture
|
|
26
|
+
server: [
|
|
27
|
+
async ({}, use) => {
|
|
28
|
+
mswServer.listen({ onUnhandledRequest: 'bypass' })
|
|
29
|
+
await use(mswServer)
|
|
30
|
+
mswServer.close()
|
|
31
|
+
},
|
|
32
|
+
{ auto: true, scope: 'worker' },
|
|
33
|
+
],
|
|
34
|
+
_cleanup: [
|
|
35
|
+
async ({ server }, use) => {
|
|
36
|
+
await use()
|
|
37
|
+
server.resetHandlers()
|
|
38
|
+
},
|
|
39
|
+
{ auto: true },
|
|
40
|
+
],
|
|
41
|
+
|
|
42
|
+
// Database fixtures
|
|
43
|
+
seedFunction: [async ({}, use) => use(async () => {}), { scope: 'file' }],
|
|
44
|
+
db: [
|
|
45
|
+
async ({ seedFunction }: Pick<DbFixture, 'seedFunction'>, use) => {
|
|
46
|
+
const { PostgreSqlContainer } = await import('@testcontainers/postgresql')
|
|
47
|
+
const { drizzle } = await import('drizzle-orm/node-postgres')
|
|
48
|
+
const { Wait } = await import('testcontainers')
|
|
49
|
+
const { Pool } = await import('pg')
|
|
50
|
+
|
|
51
|
+
// Auto-discover schema
|
|
52
|
+
const schemaPath = resolve(process.cwd(), DEFAULT_SCHEMA_PATH)
|
|
53
|
+
|
|
54
|
+
// Resolve migrations folder relative to cwd (app root)
|
|
55
|
+
const migrationsFolder = resolve(process.cwd(), DEFAULT_MIGRATIONS_PATH)
|
|
56
|
+
|
|
57
|
+
const container = await new PostgreSqlContainer(DEFAULT_IMAGE)
|
|
58
|
+
.withWaitStrategy(Wait.forHealthCheck())
|
|
59
|
+
.start()
|
|
60
|
+
const db = drizzle({
|
|
61
|
+
client: new Pool({ connectionString: container.getConnectionUri() }),
|
|
62
|
+
}) as Database
|
|
63
|
+
|
|
64
|
+
try {
|
|
65
|
+
await seedDatabase(db, { schemaPath, migrationsFolder, seedFunction })
|
|
66
|
+
await use(db)
|
|
67
|
+
} finally {
|
|
68
|
+
await db.$client.end()
|
|
69
|
+
await container.stop()
|
|
70
|
+
}
|
|
71
|
+
},
|
|
72
|
+
{ scope: 'file' },
|
|
73
|
+
],
|
|
74
|
+
})
|
|
75
|
+
|
|
76
|
+
export function initDb(seedFunction: (db: Database) => Promise<void>) {
|
|
77
|
+
test.override({
|
|
78
|
+
seedFunction: [async ({}, use) => use(seedFunction), { scope: 'file' }],
|
|
79
|
+
})
|
|
80
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
import { readFile } from 'node:fs/promises'
|
|
2
|
+
import type { Page } from '@playwright/test'
|
|
3
|
+
|
|
4
|
+
export async function mockJsonRoute(
|
|
5
|
+
page: Page,
|
|
6
|
+
url: string | RegExp,
|
|
7
|
+
fixtureUrl: URL,
|
|
8
|
+
) {
|
|
9
|
+
await page.route(url, async (route) => {
|
|
10
|
+
const body = await readFile(fixtureUrl, 'utf8')
|
|
11
|
+
|
|
12
|
+
await route.fulfill({
|
|
13
|
+
contentType: 'application/json',
|
|
14
|
+
body,
|
|
15
|
+
})
|
|
16
|
+
})
|
|
17
|
+
}
|
package/test/postgres.ts
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import {
|
|
2
|
+
PostgreSqlContainer,
|
|
3
|
+
type StartedPostgreSqlContainer,
|
|
4
|
+
} from '@testcontainers/postgresql'
|
|
5
|
+
import { Wait } from 'testcontainers'
|
|
6
|
+
|
|
7
|
+
type State = {
|
|
8
|
+
container: StartedPostgreSqlContainer
|
|
9
|
+
databaseUrl: string
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
let state: State | undefined
|
|
13
|
+
|
|
14
|
+
export async function ensurePostgresContainer() {
|
|
15
|
+
const databaseUrl = process.env.DATABASE_URL
|
|
16
|
+
if (databaseUrl) {
|
|
17
|
+
return {
|
|
18
|
+
databaseUrl,
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
if (!state) {
|
|
23
|
+
const container = await new PostgreSqlContainer('postgres:17')
|
|
24
|
+
.withDatabase('imdbgraph')
|
|
25
|
+
.withUsername('imdbgraph')
|
|
26
|
+
.withPassword('imdbgraph')
|
|
27
|
+
.withWaitStrategy(Wait.forHealthCheck())
|
|
28
|
+
.start()
|
|
29
|
+
|
|
30
|
+
state = {
|
|
31
|
+
container,
|
|
32
|
+
databaseUrl: container.getConnectionUri(),
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
return state
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function stopPostgresContainer() {
|
|
40
|
+
if (!state) return
|
|
41
|
+
await state.container.stop()
|
|
42
|
+
state = undefined
|
|
43
|
+
}
|
package/test/server.ts
ADDED
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// oxlint-disable typescript-eslint/triple-slash-reference
|
|
2
|
+
/// <reference path="../virtual-modules.d.ts" />
|
|
3
|
+
// oxlint-disable no-empty-pattern
|
|
4
|
+
import handlers from '@test/handlers'
|
|
5
|
+
import { SetupServer, setupServer } from 'msw/node'
|
|
6
|
+
import { test as baseTest } from 'vitest'
|
|
7
|
+
|
|
8
|
+
export interface MswServerFixture {
|
|
9
|
+
server: SetupServer
|
|
10
|
+
_cleanup: void
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
const mswServer = setupServer(...handlers)
|
|
14
|
+
|
|
15
|
+
export const test = baseTest.extend<MswServerFixture>({
|
|
16
|
+
server: [
|
|
17
|
+
async ({}, use) => {
|
|
18
|
+
mswServer.listen({ onUnhandledRequest: 'bypass' })
|
|
19
|
+
await use(mswServer)
|
|
20
|
+
mswServer.close()
|
|
21
|
+
},
|
|
22
|
+
{ auto: true, scope: 'worker' },
|
|
23
|
+
],
|
|
24
|
+
_cleanup: [
|
|
25
|
+
async ({ server }, use) => {
|
|
26
|
+
await use()
|
|
27
|
+
server.resetHandlers()
|
|
28
|
+
},
|
|
29
|
+
{ auto: true },
|
|
30
|
+
],
|
|
31
|
+
})
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
import { createElement, type ComponentType, type ReactNode } from 'react'
|
|
2
|
+
import { expect } from 'vitest'
|
|
3
|
+
import { page, type Locator } from 'vitest/browser'
|
|
4
|
+
import { render, type RenderResult } from 'vitest-browser-react'
|
|
5
|
+
|
|
6
|
+
interface Viewport {
|
|
7
|
+
width: number
|
|
8
|
+
height: number
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
interface ExpectComponentScreenshotOptions {
|
|
12
|
+
component: ComponentType
|
|
13
|
+
name: string
|
|
14
|
+
waitFor: (screen: RenderResult) => Locator
|
|
15
|
+
prepare?: ((screen: RenderResult) => void | Promise<void>) | undefined
|
|
16
|
+
target?: ((screen: RenderResult) => Locator) | undefined
|
|
17
|
+
setup?: (() => void | Promise<void>) | undefined
|
|
18
|
+
wrapper?: ComponentType<{ children: ReactNode }> | undefined
|
|
19
|
+
viewport?: Viewport | undefined
|
|
20
|
+
fullPage?: boolean | undefined
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export async function expectComponentScreenshot({
|
|
24
|
+
component,
|
|
25
|
+
name,
|
|
26
|
+
waitFor,
|
|
27
|
+
prepare,
|
|
28
|
+
target = (screen) => screen.locator,
|
|
29
|
+
setup,
|
|
30
|
+
wrapper,
|
|
31
|
+
viewport = { width: 1280, height: 720 },
|
|
32
|
+
fullPage = true,
|
|
33
|
+
}: ExpectComponentScreenshotOptions) {
|
|
34
|
+
localStorage.clear()
|
|
35
|
+
document.body.replaceChildren()
|
|
36
|
+
await setup?.()
|
|
37
|
+
await page.viewport(viewport.width, viewport.height)
|
|
38
|
+
|
|
39
|
+
const container = document.body.appendChild(document.createElement('div'))
|
|
40
|
+
container.style.width = '100vw'
|
|
41
|
+
container.style.minHeight = '100vh'
|
|
42
|
+
|
|
43
|
+
const screen = await render(
|
|
44
|
+
createElement(component),
|
|
45
|
+
wrapper ? { container, wrapper } : { container },
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
await prepare?.(screen)
|
|
49
|
+
await expect.element(waitFor(screen)).toBeVisible()
|
|
50
|
+
await document.fonts.ready
|
|
51
|
+
if (fullPage) {
|
|
52
|
+
await page.viewport(viewport.width, Math.ceil(container.scrollHeight))
|
|
53
|
+
}
|
|
54
|
+
await expect.element(target(screen)).toMatchScreenshot(`${name}.png`, {
|
|
55
|
+
screenshotOptions: { scale: 'css' },
|
|
56
|
+
})
|
|
57
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
{
|
|
2
|
+
// TODO: remove
|
|
3
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
4
|
+
"extends": "./tsconfig.strict.json",
|
|
5
|
+
"compilerOptions": {
|
|
6
|
+
// Enable top-level await, and other modern ESM features.
|
|
7
|
+
"target": "ESNext",
|
|
8
|
+
"module": "ESNext",
|
|
9
|
+
// Enable module resolution without file extensions on relative paths, for things like npm package imports.
|
|
10
|
+
"moduleResolution": "Bundler",
|
|
11
|
+
// Allow importing TypeScript files using their native extension (.ts(x)).
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
// Enable JSON imports.
|
|
14
|
+
"resolveJsonModule": true,
|
|
15
|
+
// Enforce the usage of type-only imports when needed, which helps avoiding bundling issues.
|
|
16
|
+
"verbatimModuleSyntax": true,
|
|
17
|
+
// Ensure that each file can be transpiled without relying on other imports.
|
|
18
|
+
// This is redundant with the previous option, however it ensures that it's on even if someone disable `verbatimModuleSyntax`
|
|
19
|
+
"isolatedModules": true,
|
|
20
|
+
// Astro directly run TypeScript code, no transpilation needed.
|
|
21
|
+
"noEmit": true,
|
|
22
|
+
// Report an error when importing a file using a casing different from another import of the same file.
|
|
23
|
+
"forceConsistentCasingInFileNames": true,
|
|
24
|
+
// Properly support importing CJS modules in ESM
|
|
25
|
+
"esModuleInterop": true,
|
|
26
|
+
// Skip typechecking libraries and .d.ts files
|
|
27
|
+
"skipLibCheck": true,
|
|
28
|
+
// Allow JavaScript files to be imported
|
|
29
|
+
"allowJs": true,
|
|
30
|
+
// Allow JSX files (or files that are internally considered JSX, like Astro files) to be imported inside `.js` and `.ts` files.
|
|
31
|
+
"jsx": "react-jsx",
|
|
32
|
+
"jsxImportSource": "react"
|
|
33
|
+
}
|
|
34
|
+
}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./tsconfig.strict.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"noEmit": true,
|
|
6
|
+
"declaration": true,
|
|
7
|
+
"declarationMap": true,
|
|
8
|
+
"esModuleInterop": true,
|
|
9
|
+
"incremental": false,
|
|
10
|
+
"isolatedModules": true,
|
|
11
|
+
"lib": ["es2022", "DOM", "DOM.Iterable"],
|
|
12
|
+
"module": "NodeNext",
|
|
13
|
+
"moduleDetection": "force",
|
|
14
|
+
"moduleResolution": "NodeNext",
|
|
15
|
+
"resolveJsonModule": true,
|
|
16
|
+
"skipLibCheck": true,
|
|
17
|
+
"target": "ES2022"
|
|
18
|
+
}
|
|
19
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./tsconfig.strict.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
9
|
+
|
|
10
|
+
/* Bundler mode */
|
|
11
|
+
"moduleResolution": "bundler",
|
|
12
|
+
"allowImportingTsExtensions": true,
|
|
13
|
+
"verbatimModuleSyntax": false,
|
|
14
|
+
"noEmit": true
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"compilerOptions": {
|
|
3
|
+
"skipLibCheck": true,
|
|
4
|
+
"strict": true,
|
|
5
|
+
"noUnusedLocals": true,
|
|
6
|
+
"noUnusedParameters": true,
|
|
7
|
+
"noFallthroughCasesInSwitch": true,
|
|
8
|
+
"noUncheckedSideEffectImports": true,
|
|
9
|
+
"noImplicitOverride": true,
|
|
10
|
+
"noImplicitReturns": true,
|
|
11
|
+
"noUncheckedIndexedAccess": true,
|
|
12
|
+
"exactOptionalPropertyTypes": true,
|
|
13
|
+
"allowUnreachableCode": false,
|
|
14
|
+
"allowUnusedLabels": false
|
|
15
|
+
}
|
|
16
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://json.schemastore.org/tsconfig",
|
|
3
|
+
"extends": "./tsconfig.strict.json",
|
|
4
|
+
"compilerOptions": {
|
|
5
|
+
"target": "ES2022",
|
|
6
|
+
"jsx": "react-jsx",
|
|
7
|
+
"module": "ESNext",
|
|
8
|
+
"lib": ["ES2022", "DOM", "DOM.Iterable"],
|
|
9
|
+
"types": ["vite/client"],
|
|
10
|
+
|
|
11
|
+
/* Bundler mode */
|
|
12
|
+
"moduleResolution": "bundler",
|
|
13
|
+
"allowImportingTsExtensions": true,
|
|
14
|
+
"verbatimModuleSyntax": false,
|
|
15
|
+
"noEmit": true
|
|
16
|
+
}
|
|
17
|
+
}
|
package/vite.config.ts
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
import { defineConfig } from 'vite-plus'
|
|
2
|
+
|
|
3
|
+
export default defineConfig({
|
|
4
|
+
pack: {
|
|
5
|
+
entry: [
|
|
6
|
+
'vite.shared.ts',
|
|
7
|
+
'vitest.shared.ts',
|
|
8
|
+
'playwright.shared.config.ts',
|
|
9
|
+
'msw.ts',
|
|
10
|
+
'test/browser.ts',
|
|
11
|
+
'test/database.ts',
|
|
12
|
+
'test/db.ts',
|
|
13
|
+
'test/playwright.ts',
|
|
14
|
+
'test/postgres.ts',
|
|
15
|
+
'test/server.ts',
|
|
16
|
+
'test/visual-page.ts',
|
|
17
|
+
'setup/styles.ts',
|
|
18
|
+
],
|
|
19
|
+
dts: true,
|
|
20
|
+
format: ['esm'],
|
|
21
|
+
sourcemap: true,
|
|
22
|
+
deps: {
|
|
23
|
+
skipNodeModulesBundle: true,
|
|
24
|
+
neverBundle: [
|
|
25
|
+
'@playwright/test',
|
|
26
|
+
'@rolldown/plugin-babel',
|
|
27
|
+
'@tailwindcss/vite',
|
|
28
|
+
'@tanstack/devtools-vite',
|
|
29
|
+
'@tanstack/react-start',
|
|
30
|
+
'@test/handlers',
|
|
31
|
+
'@test/styles',
|
|
32
|
+
'@testing-library/jest-dom',
|
|
33
|
+
'@testcontainers/postgresql',
|
|
34
|
+
'@vitejs/plugin-react',
|
|
35
|
+
'@vitest/browser-playwright',
|
|
36
|
+
'@voidzero-dev/vite-plus-core',
|
|
37
|
+
'@voidzero-dev/vite-plus-test',
|
|
38
|
+
'drizzle-orm',
|
|
39
|
+
'drizzle-seed',
|
|
40
|
+
'msw',
|
|
41
|
+
'nitro',
|
|
42
|
+
'pg',
|
|
43
|
+
'react',
|
|
44
|
+
'testcontainers',
|
|
45
|
+
'vite',
|
|
46
|
+
'vite-plus',
|
|
47
|
+
'vite-plugin-svgr',
|
|
48
|
+
'vitest',
|
|
49
|
+
'vitest-browser-react',
|
|
50
|
+
],
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
})
|
package/vite.shared.ts
ADDED
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { nitro } from 'nitro/vite'
|
|
2
|
+
import { devtools } from '@tanstack/devtools-vite'
|
|
3
|
+
import { tanstackStart } from '@tanstack/react-start/plugin/vite'
|
|
4
|
+
import viteReact, { reactCompilerPreset } from '@vitejs/plugin-react'
|
|
5
|
+
import tailwindcss from '@tailwindcss/vite'
|
|
6
|
+
import babel from '@rolldown/plugin-babel'
|
|
7
|
+
import svgr from 'vite-plugin-svgr'
|
|
8
|
+
import { existsSync } from 'node:fs'
|
|
9
|
+
import { dirname, resolve } from 'node:path'
|
|
10
|
+
import { fileURLToPath } from 'node:url'
|
|
11
|
+
import type { Plugin } from 'vite'
|
|
12
|
+
import {
|
|
13
|
+
defineConfig as defineVitestConfig,
|
|
14
|
+
mergeConfig,
|
|
15
|
+
type TestProjectConfiguration,
|
|
16
|
+
type TestProjectInlineConfiguration,
|
|
17
|
+
} from 'vitest/config'
|
|
18
|
+
import { defineConfig } from 'vite'
|
|
19
|
+
import { playwright } from 'vite-plus/test/browser-playwright'
|
|
20
|
+
|
|
21
|
+
const HANDLER_LOCATIONS = [
|
|
22
|
+
'__mocks__/handlers.ts',
|
|
23
|
+
'__mocks__/handlers.js',
|
|
24
|
+
'src/mocks/handlers.ts',
|
|
25
|
+
'src/mocks/handlers.js',
|
|
26
|
+
]
|
|
27
|
+
|
|
28
|
+
const STYLES_LOCATIONS = ['src/styles.css', 'src/index.css', 'src/app.css']
|
|
29
|
+
|
|
30
|
+
function mswHandlersPlugin(root: string): Plugin {
|
|
31
|
+
const virtualModuleId = '@test/handlers'
|
|
32
|
+
const resolvedVirtualModuleId = '\0' + virtualModuleId
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
name: 'msw-handlers-resolver',
|
|
36
|
+
resolveId(id) {
|
|
37
|
+
if (id === virtualModuleId) {
|
|
38
|
+
return resolvedVirtualModuleId
|
|
39
|
+
}
|
|
40
|
+
return undefined
|
|
41
|
+
},
|
|
42
|
+
load(id) {
|
|
43
|
+
if (id === resolvedVirtualModuleId) {
|
|
44
|
+
for (const location of HANDLER_LOCATIONS) {
|
|
45
|
+
const fullPath = resolve(root, location)
|
|
46
|
+
if (existsSync(fullPath)) {
|
|
47
|
+
return `export { default } from '${fullPath}'`
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
throw new Error(
|
|
51
|
+
`No MSW handlers found. Expected one of:\n${HANDLER_LOCATIONS.map((l) => ` - ${resolve(root, l)}`).join('\n')}`,
|
|
52
|
+
)
|
|
53
|
+
}
|
|
54
|
+
return undefined
|
|
55
|
+
},
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
function stylesPlugin(root: string): Plugin {
|
|
60
|
+
const virtualModuleId = '@test/styles'
|
|
61
|
+
const resolvedVirtualModuleId = '\0' + virtualModuleId
|
|
62
|
+
|
|
63
|
+
return {
|
|
64
|
+
name: 'styles-resolver',
|
|
65
|
+
resolveId(id) {
|
|
66
|
+
if (id === virtualModuleId) {
|
|
67
|
+
return resolvedVirtualModuleId
|
|
68
|
+
}
|
|
69
|
+
return undefined
|
|
70
|
+
},
|
|
71
|
+
load(id) {
|
|
72
|
+
if (id === resolvedVirtualModuleId) {
|
|
73
|
+
for (const location of STYLES_LOCATIONS) {
|
|
74
|
+
const fullPath = resolve(root, location)
|
|
75
|
+
if (existsSync(fullPath)) {
|
|
76
|
+
return `import '${fullPath}'`
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
throw new Error(
|
|
80
|
+
`No styles file found. Expected one of:\n${STYLES_LOCATIONS.map((l) => ` - ${resolve(root, l)}`).join('\n')}`,
|
|
81
|
+
)
|
|
82
|
+
}
|
|
83
|
+
return undefined
|
|
84
|
+
},
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const stylesSetup = new URL('setup/styles.ts', import.meta.url).pathname
|
|
89
|
+
|
|
90
|
+
function mergeProjectConfig(
|
|
91
|
+
base: TestProjectConfiguration,
|
|
92
|
+
overrides: TestProjectConfiguration = {},
|
|
93
|
+
): TestProjectConfiguration {
|
|
94
|
+
return mergeConfig(
|
|
95
|
+
base as Record<string, any>,
|
|
96
|
+
overrides as Record<string, any>,
|
|
97
|
+
) as TestProjectConfiguration
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
interface ProjectOverrides {
|
|
101
|
+
server?: TestProjectConfiguration
|
|
102
|
+
browser?: TestProjectConfiguration
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
function createBaseConfig(
|
|
106
|
+
root: string,
|
|
107
|
+
overrides: TestProjectInlineConfiguration = {},
|
|
108
|
+
projectOverrides: ProjectOverrides = {},
|
|
109
|
+
) {
|
|
110
|
+
const localSetup = resolve(root, 'vitest.setup.ts')
|
|
111
|
+
const hasLocalSetup = existsSync(localSetup)
|
|
112
|
+
|
|
113
|
+
return mergeConfig(
|
|
114
|
+
defineVitestConfig({
|
|
115
|
+
root,
|
|
116
|
+
resolve: {
|
|
117
|
+
tsconfigPaths: true,
|
|
118
|
+
dedupe: ['react', 'react-dom'],
|
|
119
|
+
alias: [
|
|
120
|
+
{ find: '@/mocks', replacement: resolve(root, '__mocks__') },
|
|
121
|
+
{ find: '@', replacement: resolve(root, 'src') },
|
|
122
|
+
],
|
|
123
|
+
},
|
|
124
|
+
plugins: [
|
|
125
|
+
tailwindcss(),
|
|
126
|
+
viteReact(),
|
|
127
|
+
svgr({
|
|
128
|
+
include: '**/*.svg',
|
|
129
|
+
svgrOptions: { exportType: 'default' },
|
|
130
|
+
}),
|
|
131
|
+
],
|
|
132
|
+
test: {
|
|
133
|
+
passWithNoTests: true,
|
|
134
|
+
exclude: [
|
|
135
|
+
'**/node_modules/**',
|
|
136
|
+
'**/dist/**',
|
|
137
|
+
'**/.output/**',
|
|
138
|
+
'**/.vercel/**',
|
|
139
|
+
'**/cypress/**',
|
|
140
|
+
'**/.{idea,git,cache,output,temp}/**',
|
|
141
|
+
'**/{karma,rollup,webpack,vite,vitest,jest,ava,babel,nyc,cypress,tsup,build}.config.*',
|
|
142
|
+
'**/e2e/**',
|
|
143
|
+
],
|
|
144
|
+
projects: [
|
|
145
|
+
{
|
|
146
|
+
extends: true,
|
|
147
|
+
test: {
|
|
148
|
+
name: 'unit',
|
|
149
|
+
include: ['src/**/*.test.unit.ts'],
|
|
150
|
+
},
|
|
151
|
+
},
|
|
152
|
+
mergeProjectConfig(
|
|
153
|
+
{
|
|
154
|
+
extends: true,
|
|
155
|
+
plugins: [mswHandlersPlugin(root)],
|
|
156
|
+
test: {
|
|
157
|
+
name: 'server',
|
|
158
|
+
include: ['src/**/*.test.ts'],
|
|
159
|
+
exclude: ['src/**/*.test.unit.ts', 'src/**/*.test.tsx'],
|
|
160
|
+
testTimeout: 30_000,
|
|
161
|
+
fileParallelism: false,
|
|
162
|
+
setupFiles: hasLocalSetup ? [localSetup] : [],
|
|
163
|
+
},
|
|
164
|
+
} satisfies TestProjectConfiguration,
|
|
165
|
+
projectOverrides.server ?? {},
|
|
166
|
+
),
|
|
167
|
+
mergeProjectConfig(
|
|
168
|
+
{
|
|
169
|
+
extends: true,
|
|
170
|
+
plugins: [mswHandlersPlugin(root), stylesPlugin(root)],
|
|
171
|
+
test: {
|
|
172
|
+
name: 'browser',
|
|
173
|
+
include: ['src/**/*.test.tsx', 'tests/**/*.test.tsx'],
|
|
174
|
+
setupFiles: [stylesSetup],
|
|
175
|
+
browser: {
|
|
176
|
+
instances: [
|
|
177
|
+
{
|
|
178
|
+
browser: 'chromium',
|
|
179
|
+
},
|
|
180
|
+
],
|
|
181
|
+
provider: playwright(),
|
|
182
|
+
enabled: true,
|
|
183
|
+
headless: true,
|
|
184
|
+
},
|
|
185
|
+
},
|
|
186
|
+
} satisfies TestProjectConfiguration,
|
|
187
|
+
projectOverrides.browser ?? {},
|
|
188
|
+
),
|
|
189
|
+
],
|
|
190
|
+
},
|
|
191
|
+
}),
|
|
192
|
+
overrides,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
const isVitest = process.env.VITEST === 'true'
|
|
197
|
+
|
|
198
|
+
function createFrameworkConfig(root: string) {
|
|
199
|
+
return defineConfig({
|
|
200
|
+
root,
|
|
201
|
+
resolve: {
|
|
202
|
+
tsconfigPaths: true,
|
|
203
|
+
dedupe: ['react', 'react-dom'],
|
|
204
|
+
alias: [
|
|
205
|
+
{ find: '@/mocks', replacement: resolve(root, '__mocks__') },
|
|
206
|
+
{ find: '@', replacement: resolve(root, 'src') },
|
|
207
|
+
],
|
|
208
|
+
},
|
|
209
|
+
ssr: {
|
|
210
|
+
noExternal: ['recharts'],
|
|
211
|
+
},
|
|
212
|
+
plugins: [
|
|
213
|
+
...(isVitest ? [] : [devtools(), nitro(), tanstackStart()]),
|
|
214
|
+
tailwindcss(),
|
|
215
|
+
viteReact(),
|
|
216
|
+
babel({ presets: [reactCompilerPreset()] }),
|
|
217
|
+
svgr({
|
|
218
|
+
include: '**/*.svg',
|
|
219
|
+
svgrOptions: { exportType: 'default' },
|
|
220
|
+
}),
|
|
221
|
+
],
|
|
222
|
+
})
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
// Creates a full app config with customized test project overrides
|
|
226
|
+
export function createAppConfig(
|
|
227
|
+
root: string,
|
|
228
|
+
projectOverrides: ProjectOverrides = {},
|
|
229
|
+
overrides: TestProjectInlineConfiguration = {},
|
|
230
|
+
) {
|
|
231
|
+
return mergeConfig(
|
|
232
|
+
createFrameworkConfig(root),
|
|
233
|
+
createBaseConfig(root, overrides, projectOverrides),
|
|
234
|
+
)
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
// Helper to create config from import.meta.url
|
|
238
|
+
export function baseConfigFor(configUrl: string | URL) {
|
|
239
|
+
const root = dirname(fileURLToPath(configUrl))
|
|
240
|
+
return createAppConfig(root)
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
// Workspace-level shared config (lint, fmt, staged)
|
|
244
|
+
// Cast to any because Vite+ extensions (fmt, lint, staged) aren't in standard Vite types
|
|
245
|
+
export const workspaceConfig = defineConfig({
|
|
246
|
+
fmt: {
|
|
247
|
+
singleQuote: true,
|
|
248
|
+
semi: false,
|
|
249
|
+
useTabs: true,
|
|
250
|
+
printWidth: 80,
|
|
251
|
+
proseWrap: 'always',
|
|
252
|
+
ignorePatterns: [
|
|
253
|
+
'**/.vercel',
|
|
254
|
+
'**/.output',
|
|
255
|
+
'**/env.d.ts',
|
|
256
|
+
'pnpm-lock.yaml',
|
|
257
|
+
'routeTree.gen.ts',
|
|
258
|
+
'**/charts/**',
|
|
259
|
+
'**/*.yaml',
|
|
260
|
+
'**/*.yml',
|
|
261
|
+
],
|
|
262
|
+
},
|
|
263
|
+
lint: {
|
|
264
|
+
plugins: ['typescript', 'react', 'unicorn'],
|
|
265
|
+
options: {
|
|
266
|
+
typeAware: true,
|
|
267
|
+
typeCheck: true,
|
|
268
|
+
},
|
|
269
|
+
rules: {
|
|
270
|
+
'no-console': ['error', { allow: ['warn', 'error'] }],
|
|
271
|
+
},
|
|
272
|
+
overrides: [
|
|
273
|
+
{
|
|
274
|
+
files: ['apps/**'],
|
|
275
|
+
plugins: ['typescript', 'react'],
|
|
276
|
+
rules: {
|
|
277
|
+
'react/self-closing-comp': 'error',
|
|
278
|
+
},
|
|
279
|
+
},
|
|
280
|
+
{
|
|
281
|
+
files: ['**/*.test.ts', '**/*.test.tsx'],
|
|
282
|
+
plugins: ['typescript', 'vitest'],
|
|
283
|
+
rules: {
|
|
284
|
+
'@typescript-eslint/no-explicit-any': 'off',
|
|
285
|
+
},
|
|
286
|
+
},
|
|
287
|
+
{
|
|
288
|
+
files: ['**/scripts/**', '**/src/lib/imdb/**'],
|
|
289
|
+
rules: {
|
|
290
|
+
'no-console': 'off',
|
|
291
|
+
},
|
|
292
|
+
},
|
|
293
|
+
],
|
|
294
|
+
},
|
|
295
|
+
staged: {
|
|
296
|
+
'*.{js,ts,tsx}': 'vp check --fix',
|
|
297
|
+
},
|
|
298
|
+
} as any)
|