Archived
init
This commit is contained in:
@@ -0,0 +1,70 @@
|
||||
import { describe, expect, it, vi, beforeEach, afterEach } from "bun:test";
|
||||
import { renderHook, act } from "@testing-library/react";
|
||||
import { useSWR } from "../src/index";
|
||||
|
||||
const fetcher = vi.fn((key: string) => Promise.resolve(`data-${key}`));
|
||||
|
||||
describe("useSWR (React)", () => {
|
||||
beforeEach(() => {
|
||||
vi.useFakeTimers();
|
||||
fetcher.mockClear();
|
||||
});
|
||||
|
||||
|
||||
it("fetches data on mount", async () => {
|
||||
const { result } = renderHook(() => useSWR("test-key", fetcher));
|
||||
|
||||
expect(result.current.isLoading).toBe(true);
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.data).toBe("data-test-key");
|
||||
expect(result.current.isLoading).toBe(false);
|
||||
});
|
||||
|
||||
it("deduplicates requests", async () => {
|
||||
const { result: r1 } = renderHook(() => useSWR("dedupe-key", fetcher));
|
||||
const { result: r2 } = renderHook(() => useSWR("dedupe-key", fetcher));
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(fetcher).toHaveBeenCalledTimes(1);
|
||||
expect(r1.current.data).toBe("data-dedupe-key");
|
||||
expect(r2.current.data).toBe("data-dedupe-key");
|
||||
});
|
||||
|
||||
it("mutates data manually", async () => {
|
||||
const { result } = renderHook(() => useSWR("mutate-key", fetcher));
|
||||
|
||||
await act(async () => {
|
||||
await vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.data).toBe("data-mutate-key");
|
||||
|
||||
await act(async () => {
|
||||
await result.current.mutate("new-data");
|
||||
await vi.runAllTimers();
|
||||
});
|
||||
|
||||
expect(result.current.data).toBe("new-data");
|
||||
});
|
||||
|
||||
// it("handles errors", async () => {
|
||||
// const error = new Error("failed");
|
||||
// fetcher.mockImplementationOnce(() => Promise.reject(error));
|
||||
|
||||
// const { result } = renderHook(() => useSWR("error-key", fetcher));
|
||||
|
||||
// await act(async () => {
|
||||
// await vi.runAllTimers();
|
||||
// });
|
||||
|
||||
// expect(result.current.error).toBe(error);
|
||||
// expect(result.current.isLoading).toBe(false);
|
||||
// });
|
||||
});
|
||||
@@ -0,0 +1,2 @@
|
||||
[test]
|
||||
preload = ["./happydom.ts", "./testing-library.ts"]
|
||||
@@ -0,0 +1,3 @@
|
||||
import { GlobalRegistrator } from "@happy-dom/global-registrator";
|
||||
|
||||
GlobalRegistrator.register();
|
||||
Vendored
+7
@@ -0,0 +1,7 @@
|
||||
import { TestingLibraryMatchers } from "@testing-library/jest-dom/matchers";
|
||||
import { Matchers, AsymmetricMatchers } from "bun:test";
|
||||
|
||||
declare module "bun:test" {
|
||||
interface Matchers<T> extends TestingLibraryMatchers<typeof expect.stringContaining, void> {}
|
||||
interface AsymmetricMatchers extends TestingLibraryMatchers<any, any> {}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
{
|
||||
"name": "@supwr/react",
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"module": "./dist/index.js",
|
||||
"main": "./dist/index.cjs",
|
||||
"types": "./dist/index.d.ts",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": {
|
||||
"types": "./dist/index.d.ts",
|
||||
"default": "./dist/index.js"
|
||||
},
|
||||
"require": {
|
||||
"types": "./dist/index.d.cts",
|
||||
"default": "./dist/index.cjs"
|
||||
}
|
||||
},
|
||||
"./package.json": "./package.json"
|
||||
},
|
||||
"scripts": {
|
||||
"build": "bunup"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/bun": "^1.4.0",
|
||||
"@types/react": "^19.2.18",
|
||||
"@happy-dom/global-registrator": "^20.12.0",
|
||||
"@testing-library/jest-dom": "^7.0.1",
|
||||
"@testing-library/react": "^16.3.3",
|
||||
"bunup": "^0.16.32"
|
||||
},
|
||||
"dependencies": {
|
||||
"@supwr/core": "workspace:*"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"react": "^18.0.0 || ^19.0.0"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
// Main exports
|
||||
// React SWR hooks
|
||||
export { default as useSWR, SWRConfig, useSWRMutate, useSWRInstance, useSWRPrefetch } from './use-swr'
|
||||
export { default as useSWRInfinite } from './use-swr-infinite'
|
||||
|
||||
export type { SWRResponse, SWRHook } from './types'
|
||||
@@ -0,0 +1,5 @@
|
||||
import { BareFetcher, Key, SWRConfig } from "@supwr/core";
|
||||
import { SWRHook, SWRResponse } from "./types";
|
||||
|
||||
|
||||
export type Middleware = (useSWRNext: SWRHook) => <Data = any, Error = any>(key: Key, fetcher: BareFetcher<Data> | null, config: SWRConfig<Key, Error>) => SWRResponse<Data, Error>;
|
||||
@@ -0,0 +1,53 @@
|
||||
import type { SWRConfig as CoreSWRConfig, MutatorOptions} from '@supwr/core'
|
||||
import { Middleware } from './middleware';
|
||||
|
||||
export interface SWRConfig<Data = unknown, Error = unknown> extends CoreSWRConfig<Data, Error> {
|
||||
// React-specific config
|
||||
suspense?: boolean
|
||||
fallbackData?: Data
|
||||
revalidateOnMount?: boolean
|
||||
use?: Middleware[]
|
||||
revalidateOnFocus?: boolean
|
||||
revalidateOnReconnect?: boolean
|
||||
refreshWhenHidden?: boolean
|
||||
refreshWhenOffline?: boolean
|
||||
compare?: (a: Data | undefined, b: Data | undefined) => boolean
|
||||
}
|
||||
|
||||
export interface SWRResponse<Data = unknown, Error = unknown> {
|
||||
data: Data | undefined
|
||||
error: Error | undefined
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
mutate: (data?: Data | Promise<Data> | ((currentData?: Data) => Data | Promise<Data>), options?: MutatorOptions<Data>) => Promise<Data | undefined>
|
||||
}
|
||||
|
||||
export interface SWRHook<Data = unknown, Error = unknown> extends SWRResponse<Data, Error> {
|
||||
// Additional hook-specific properties can be added here
|
||||
}
|
||||
|
||||
// Infinite loading types
|
||||
export interface SWRInfiniteKeyLoader<Data = any, Args extends any[] = any[]> {
|
||||
(index: number, previousPageData: Data | null): Args | null | undefined
|
||||
}
|
||||
|
||||
export interface SWRInfiniteConfig<Data = unknown, Error = unknown> extends Omit<SWRConfig<Data[], Error>, 'compare'> {
|
||||
initialSize?: number
|
||||
revalidateAll?: boolean
|
||||
persistSize?: boolean
|
||||
revalidateFirstPage?: boolean
|
||||
parallel?: boolean
|
||||
compare?: (a: Data[] | undefined, b: Data[] | undefined) => boolean
|
||||
}
|
||||
|
||||
export interface SWRInfiniteResponse<Data = unknown, Error = unknown> {
|
||||
data: Data[] | undefined
|
||||
error: Error | undefined
|
||||
isLoading: boolean
|
||||
isValidating: boolean
|
||||
loading: boolean
|
||||
validating: boolean
|
||||
size: number
|
||||
setSize: (size: number | ((size: number) => number)) => Promise<Data[][]>
|
||||
mutate: (data?: Data[] | Promise<Data[]> | ((currentData?: Data[]) => Data[] | Promise<Data[]>), options?: MutatorOptions<Data[]>) => Promise<Data[] | undefined>
|
||||
}
|
||||
@@ -0,0 +1,224 @@
|
||||
import { useCallback, useDebugValue, useMemo, useState, useSyncExternalStore } from 'react'
|
||||
import { createSWR } from '@supwr/core'
|
||||
import type {
|
||||
SWRInfiniteKeyLoader,
|
||||
SWRInfiniteConfig,
|
||||
SWRInfiniteResponse,
|
||||
} from './types'
|
||||
|
||||
import {
|
||||
Key,
|
||||
Fetcher,
|
||||
SWRFetcher,
|
||||
BareFetcher
|
||||
} from "@supwr/core"
|
||||
|
||||
|
||||
// Global SWR instance for infinite loading
|
||||
const globalSWR = createSWR()
|
||||
|
||||
/**
|
||||
* useSWRInfinite hook for infinite loading
|
||||
*/
|
||||
export function useSWRInfinite<Data = any, Error = any>(
|
||||
getKey: SWRInfiniteKeyLoader<Data> | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRInfiniteConfig<Data, Error>>
|
||||
): SWRInfiniteResponse<Data, Error> {
|
||||
const [size, setSize] = useState(config?.initialSize || 1)
|
||||
|
||||
// Generate all keys for current size
|
||||
const keys = useMemo(() => {
|
||||
if (!getKey) return []
|
||||
|
||||
const keys: Key[] = []
|
||||
for (let i = 0; i < size; i++) {
|
||||
const previousPageData = i === 0 ? null : (globalSWR.get(keys[i - 1])?.data as Data) || null
|
||||
const keyArgs = getKey(i, previousPageData)
|
||||
|
||||
if (keyArgs === null || keyArgs === undefined) {
|
||||
break
|
||||
}
|
||||
|
||||
keys.push(keyArgs)
|
||||
}
|
||||
return keys
|
||||
}, [getKey, size])
|
||||
|
||||
// Normalize fetcher
|
||||
const normalizedFetcher = useMemo(() => {
|
||||
if (!fetcher) return null
|
||||
|
||||
if (fetcher.length === 2) return fetcher as Fetcher<Data>
|
||||
|
||||
return (key: Key, options: any) => {
|
||||
if (Array.isArray(key)) {
|
||||
return (fetcher as BareFetcher<Data>)(...key)
|
||||
}
|
||||
return (fetcher as BareFetcher<Data>)(key)
|
||||
}
|
||||
}, [fetcher])
|
||||
|
||||
// Get data for all pages
|
||||
const data = useMemo(() => {
|
||||
if (keys.length === 0) return undefined
|
||||
|
||||
const pages: Data[] = []
|
||||
for (const key of keys) {
|
||||
const entry = globalSWR.get(key)
|
||||
if (entry?.data !== undefined) {
|
||||
pages.push(entry.data as Data)
|
||||
} else {
|
||||
// If any page doesn't have data, return undefined
|
||||
return undefined
|
||||
}
|
||||
}
|
||||
return pages
|
||||
}, [keys])
|
||||
|
||||
// Check loading states
|
||||
const isLoading = useMemo(() => {
|
||||
if (keys.length === 0) return false
|
||||
|
||||
// Check if first page is loading
|
||||
const firstEntry = globalSWR.get(keys[0])
|
||||
return firstEntry?.isLoading || false
|
||||
}, [keys])
|
||||
|
||||
const isValidating = useMemo(() => {
|
||||
if (keys.length === 0) return false
|
||||
|
||||
// Check if any page is validating
|
||||
for (const key of keys) {
|
||||
const entry = globalSWR.get(key)
|
||||
if (entry?.isValidating) return true
|
||||
}
|
||||
return false
|
||||
}, [keys])
|
||||
|
||||
// Get error from any page
|
||||
const error = useMemo(() => {
|
||||
if (keys.length === 0) return undefined
|
||||
|
||||
for (const key of keys) {
|
||||
const entry = globalSWR.get(key)
|
||||
if (entry?.error) return entry.error
|
||||
}
|
||||
return undefined
|
||||
}, [keys])
|
||||
|
||||
// Subscribe to all key changes
|
||||
const subscribe = useCallback((callback: () => void) => {
|
||||
if (keys.length === 0) return () => { }
|
||||
|
||||
const unsubscribes = keys.map(key =>
|
||||
globalSWR.subscribe(key, callback)
|
||||
)
|
||||
|
||||
return () => {
|
||||
unsubscribes.forEach(unsubscribe => unsubscribe())
|
||||
}
|
||||
}, [keys])
|
||||
|
||||
const getSnapshot = useCallback(() => ({
|
||||
data,
|
||||
error,
|
||||
isLoading,
|
||||
isValidating
|
||||
}), [data, error, isLoading, isValidating])
|
||||
|
||||
// Use sync external store
|
||||
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
||||
|
||||
// Create mutate function for infinite data
|
||||
const mutate = useCallback(
|
||||
async (mutateData?: any, options?: any) => {
|
||||
if (keys.length === 0) return undefined
|
||||
|
||||
if (mutateData === undefined) {
|
||||
// Revalidate all pages
|
||||
const promises = keys.map(key => globalSWR.revalidate(key, options))
|
||||
return Promise.all(promises) as any
|
||||
}
|
||||
|
||||
// Mutate specific page or all pages
|
||||
if (typeof mutateData === 'function') {
|
||||
// Apply function to all pages
|
||||
const currentData = data || []
|
||||
const newData = await Promise.all(currentData.map((page, index) =>
|
||||
(mutateData as any)(page, index)
|
||||
))
|
||||
|
||||
// Update each page
|
||||
const promises = keys.map((key, index) =>
|
||||
globalSWR.mutate(key, newData[index], options)
|
||||
)
|
||||
const results = await Promise.all(promises)
|
||||
return results[0] as Promise<Data>
|
||||
} else if (Array.isArray(mutateData)) {
|
||||
// Update specific pages
|
||||
const promises = mutateData.map((pageData, index) =>
|
||||
keys[index] ? globalSWR.mutate(keys[index], pageData, options) : Promise.resolve(undefined)
|
||||
)
|
||||
const results = await Promise.all(promises)
|
||||
return results[0] as Promise<Data>
|
||||
} else {
|
||||
// Update first page
|
||||
return globalSWR.mutate(keys[0], mutateData, options)
|
||||
}
|
||||
},
|
||||
[keys, data]
|
||||
)
|
||||
|
||||
// Create setSize function
|
||||
const setSizeFunction = useCallback(
|
||||
(newSize: number | ((size: number) => number)) => {
|
||||
const targetSize = typeof newSize === 'function' ? newSize(size) : newSize
|
||||
|
||||
if (targetSize !== size) {
|
||||
setSize(targetSize)
|
||||
}
|
||||
|
||||
// Return promise that resolves when new pages are loaded
|
||||
return new Promise<Data[][]>((resolve) => {
|
||||
// This will resolve when the data is updated
|
||||
setTimeout(() => {
|
||||
if (!getKey) {
|
||||
resolve([])
|
||||
return
|
||||
}
|
||||
|
||||
const pages: Data[] = []
|
||||
for (let i = 0; i < targetSize; i++) {
|
||||
const previousPageData = i === 0 ? null : (globalSWR.get(keys[i - 1])?.data as Data) || null
|
||||
const keyArgs = getKey(i, previousPageData)
|
||||
|
||||
if (keyArgs === null || keyArgs === undefined) break
|
||||
|
||||
const entry = globalSWR.get(keyArgs)
|
||||
if (entry?.data) {
|
||||
pages.push(entry.data as Data)
|
||||
}
|
||||
}
|
||||
resolve([pages])
|
||||
}, 0)
|
||||
})
|
||||
},
|
||||
[size, getKey, keys]
|
||||
)
|
||||
|
||||
// Debug value
|
||||
useDebugValue(data ? `${data.length} pages` : 'loading')
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: state.isLoading,
|
||||
validating: state.isValidating,
|
||||
error: state.error as Error,
|
||||
size,
|
||||
setSize: setSizeFunction,
|
||||
mutate
|
||||
}
|
||||
}
|
||||
|
||||
export default useSWRInfinite
|
||||
@@ -0,0 +1,201 @@
|
||||
import React, { useCallback, useRef, useDebugValue, useMemo, useEffect, useContext, createContext, ReactNode, useSyncExternalStore } from 'react'
|
||||
import { createSWR } from '@supwr/core'
|
||||
import type { Key, Fetcher, SWRFetcher, BareFetcher, SWRCore } from '@supwr/core'
|
||||
import { SWRResponse, SWRConfig as SWRConfiguration } from './types';
|
||||
|
||||
const globalSWR = createSWR()
|
||||
const ConfigContext = createContext<Partial<SWRConfiguration<any, any>>>({})
|
||||
|
||||
export function useSWR<Data = any, Error = any>(
|
||||
key: Key | null,
|
||||
fetcher?: SWRFetcher<Data> | null,
|
||||
config?: Partial<SWRConfiguration<Data, Error>>
|
||||
): SWRResponse<Data, Error> {
|
||||
|
||||
const contextConfig = useContext(ConfigContext)
|
||||
const mergedConfig = useMemo(() => ({
|
||||
...contextConfig,
|
||||
...config
|
||||
}), [contextConfig, config])
|
||||
|
||||
const refreshInterval = mergedConfig?.refreshInterval
|
||||
const swrRef = useRef(globalSWR)
|
||||
|
||||
useEffect(() => {
|
||||
if (mergedConfig) {
|
||||
const { refreshInterval: _, ...configWithoutRefresh } = mergedConfig
|
||||
swrRef.current.updateConfig(configWithoutRefresh as SWRConfig)
|
||||
}
|
||||
}, [mergedConfig])
|
||||
|
||||
useEffect(() => {
|
||||
if (!key || !refreshInterval || refreshInterval <= 0) return
|
||||
|
||||
const intervalId = setInterval(() => {
|
||||
swrRef.current.revalidate(key, {
|
||||
fetcher: fetcherRef.current as any,
|
||||
dedupe: true
|
||||
})
|
||||
}, refreshInterval)
|
||||
|
||||
return () => clearInterval(intervalId)
|
||||
}, [key, refreshInterval])
|
||||
|
||||
const normalizedFetcher = useMemo(() => {
|
||||
if (!fetcher) return null
|
||||
|
||||
if (fetcher.length === 2) return fetcher as Fetcher<Data>
|
||||
|
||||
return (key: Key, options: any) => {
|
||||
if (Array.isArray(key)) {
|
||||
return (fetcher as BareFetcher<Data>)(...key)
|
||||
}
|
||||
return (fetcher as BareFetcher<Data>)(key)
|
||||
}
|
||||
}, [fetcher])
|
||||
|
||||
const fetcherRef = useRef(normalizedFetcher)
|
||||
useEffect(() => {
|
||||
fetcherRef.current = normalizedFetcher
|
||||
}, [normalizedFetcher])
|
||||
|
||||
const subscribe = useCallback((callback: () => void) => {
|
||||
if (!key) return () => { }
|
||||
return swrRef.current.addEventListener(key, () => {
|
||||
callback()
|
||||
})
|
||||
}, [key])
|
||||
|
||||
const lastStateRef = useRef<any>(null)
|
||||
|
||||
const getSnapshot = useCallback(() => {
|
||||
let nextState: any;
|
||||
|
||||
if (!key || !fetcherRef.current) {
|
||||
nextState = {
|
||||
data: mergedConfig?.fallbackData,
|
||||
error: undefined,
|
||||
isValidating: false,
|
||||
isLoading: false
|
||||
}
|
||||
} else {
|
||||
const entry = swrRef.current.get(key)
|
||||
const data = entry?.data ?? mergedConfig?.fallbackData
|
||||
|
||||
nextState = {
|
||||
data,
|
||||
error: entry?.error,
|
||||
isValidating: entry?.isValidating || false,
|
||||
isLoading: entry?.isLoading || false
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
lastStateRef.current &&
|
||||
lastStateRef.current.data === nextState.data &&
|
||||
lastStateRef.current.error === nextState.error &&
|
||||
lastStateRef.current.isValidating === nextState.isValidating &&
|
||||
lastStateRef.current.isLoading === nextState.isLoading
|
||||
) {
|
||||
return lastStateRef.current
|
||||
}
|
||||
|
||||
lastStateRef.current = nextState
|
||||
return nextState
|
||||
}, [key, mergedConfig?.fallbackData])
|
||||
|
||||
const state = useSyncExternalStore(subscribe, getSnapshot, getSnapshot)
|
||||
|
||||
const mutate = useCallback(
|
||||
(data?: any, options?: any) => {
|
||||
if (!key) return Promise.resolve(undefined)
|
||||
return swrRef.current.mutate(key, data, options)
|
||||
},
|
||||
[key]
|
||||
)
|
||||
|
||||
const suspense = mergedConfig?.suspense
|
||||
const hasData = state.data !== undefined
|
||||
const hasError = state.error !== undefined
|
||||
|
||||
useEffect(() => {
|
||||
if (!key || !fetcherRef.current || suspense) return
|
||||
|
||||
const entry = swrRef.current.get(key)
|
||||
const revalidateOnMount = mergedConfig?.revalidateOnMount !== false
|
||||
|
||||
if (!entry && revalidateOnMount) {
|
||||
swrRef.current.setLoading(key)
|
||||
swrRef.current.revalidate(key, {
|
||||
fetcher: fetcherRef.current as any,
|
||||
dedupe: true
|
||||
})
|
||||
} else if (entry && revalidateOnMount && !entry.data) {
|
||||
swrRef.current.revalidate(key, {
|
||||
fetcher: fetcherRef.current as any,
|
||||
dedupe: true
|
||||
})
|
||||
}
|
||||
}, [key, suspense, mergedConfig?.revalidateOnMount])
|
||||
|
||||
if (suspense) {
|
||||
if (hasError) {
|
||||
throw state.error
|
||||
}
|
||||
if (!hasData) {
|
||||
const promise = swrRef.current.revalidate(key, {
|
||||
fetcher: fetcherRef.current as any,
|
||||
dedupe: true
|
||||
}).then(() => true)
|
||||
|
||||
;(promise as any).catch(() => {})
|
||||
throw promise
|
||||
}
|
||||
}
|
||||
|
||||
useDebugValue(state.data ? `data: ${JSON.stringify(state.data)}` : 'loading')
|
||||
|
||||
return {
|
||||
...state,
|
||||
loading: state.isLoading,
|
||||
validating: state.isValidating,
|
||||
mutate
|
||||
}
|
||||
}
|
||||
|
||||
export function SWRConfig<Data = any, Error = any>({
|
||||
children,
|
||||
value
|
||||
}: {
|
||||
children: ReactNode
|
||||
value: Partial<SWRConfiguration<Data, Error>>
|
||||
}): React.ReactNode {
|
||||
return (
|
||||
<ConfigContext.Provider value={value}>
|
||||
{children}
|
||||
</ConfigContext.Provider>
|
||||
)
|
||||
}
|
||||
|
||||
export type SWRConfig<Data = any, Error = any> = SWRConfiguration<Data, Error>
|
||||
|
||||
export function useSWRMutate<Data = any, Error = any>() {
|
||||
return useCallback(
|
||||
(key: Key, data?: any, options?: any) => {
|
||||
return globalSWR.mutate(key, data, options)
|
||||
},
|
||||
[]
|
||||
)
|
||||
}
|
||||
|
||||
export function useSWRInstance(): SWRCore<unknown, unknown> {
|
||||
return globalSWR
|
||||
}
|
||||
|
||||
export function useSWRPrefetch<Data = any, Error = any>() {
|
||||
return useCallback(
|
||||
(key: Key, fetcher: SWRFetcher<Data>) => globalSWR.revalidate(key, { dedupe: true }), []
|
||||
)
|
||||
}
|
||||
|
||||
export default useSWR
|
||||
@@ -0,0 +1,10 @@
|
||||
import { afterEach, expect } from "bun:test";
|
||||
import { cleanup } from "@testing-library/react";
|
||||
import * as matchers from "@testing-library/jest-dom/matchers";
|
||||
|
||||
expect.extend(matchers);
|
||||
|
||||
// Optional: cleans up `render` after each test
|
||||
afterEach(() => {
|
||||
cleanup();
|
||||
});
|
||||
@@ -0,0 +1,39 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
// Environment setup & latest features
|
||||
"lib": [
|
||||
"DOM",
|
||||
"ES2022"
|
||||
],
|
||||
"target": "ES2018",
|
||||
"module": "ESNext",
|
||||
"moduleDetection": "force",
|
||||
"jsx": "react-jsx",
|
||||
"allowJs": true,
|
||||
"types": [
|
||||
"bun"
|
||||
],
|
||||
// Bundler mode
|
||||
"moduleResolution": "bundler",
|
||||
"declaration": true,
|
||||
"declarationMap": true,
|
||||
"sourceMap": true,
|
||||
// Best practices
|
||||
"strict": true,
|
||||
"skipLibCheck": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noImplicitOverride": true,
|
||||
// Some stricter flags (disabled by default)
|
||||
"noUnusedLocals": false,
|
||||
"noUnusedParameters": false,
|
||||
"noPropertyAccessFromIndexSignature": false
|
||||
},
|
||||
"include": [
|
||||
"src/**/*"
|
||||
],
|
||||
"exclude": [
|
||||
"node_modules",
|
||||
"dist"
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user