diff --git a/.changeset/real-hairs-provide.md b/.changeset/real-hairs-provide.md
new file mode 100644
index 000000000..d7a17794a
--- /dev/null
+++ b/.changeset/real-hairs-provide.md
@@ -0,0 +1,6 @@
+---
+"@suspensive/react-await": patch
+"@suspensive/react": patch
+---
+
+refactor: add @suspensive/test-utils as dev dependency to remove unnecessary repetitive code
diff --git a/configs/test-utils/.eslintrc.cjs b/configs/test-utils/.eslintrc.cjs
new file mode 100644
index 000000000..7d0206f78
--- /dev/null
+++ b/configs/test-utils/.eslintrc.cjs
@@ -0,0 +1,6 @@
+/** @type {import('eslint').Linter.Config} */
+module.exports = {
+ root: true,
+ extends: ['@suspensive/eslint-config/react-ts'],
+ ignorePatterns: ['*.js*', 'dist', 'coverage'],
+}
diff --git a/configs/test-utils/package.json b/configs/test-utils/package.json
new file mode 100644
index 000000000..27914cd1c
--- /dev/null
+++ b/configs/test-utils/package.json
@@ -0,0 +1,28 @@
+{
+ "name": "@suspensive/test-utils",
+ "version": "0.0.0",
+ "private": true,
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/manudeli"
+ },
+ "license": "MIT",
+ "author": {
+ "name": "Jonghyeon Ko",
+ "email": "manudeli.ko@gmail.com"
+ },
+ "sideEffects": false,
+ "type": "module",
+ "main": "src/index.ts",
+ "scripts": {
+ "lint": "eslint \"**/*.ts*\"",
+ "type:check": "tsc --noEmit"
+ },
+ "devDependencies": {
+ "@types/react": "^18.2.0",
+ "react": "^18.2.0"
+ },
+ "peerDependencies": {
+ "react": "^16.8 || ^17 || ^18"
+ }
+}
diff --git a/packages/react-await/src/utils/toTest.tsx b/configs/test-utils/src/Suspend.tsx
similarity index 63%
rename from packages/react-await/src/utils/toTest.tsx
rename to configs/test-utils/src/Suspend.tsx
index a3cb70c02..c1b0a2042 100644
--- a/packages/react-await/src/utils/toTest.tsx
+++ b/configs/test-utils/src/Suspend.tsx
@@ -16,10 +16,3 @@ export const Suspend = ({ during, toShow }: SuspendProps) => {
Suspend.reset = () => {
suspendIsNeed.current = true
}
-
-export const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve('done'), ms))
-
-export const TEXT = 'TEXT' as const
-export const ERROR_MESSAGE = 'ERROR_MESSAGE' as const
-export const FALLBACK = 'FALLBACK' as const
-export const MS_100 = 100 as const
diff --git a/configs/test-utils/src/ThrowError.tsx b/configs/test-utils/src/ThrowError.tsx
new file mode 100644
index 000000000..5e919c2b5
--- /dev/null
+++ b/configs/test-utils/src/ThrowError.tsx
@@ -0,0 +1,33 @@
+import type { PropsWithChildren } from 'react'
+import { useEffect, useState } from 'react'
+
+const useSetTimeout = (fn: (...args: []) => void, delay: number) =>
+ useEffect(() => {
+ const timeout = setTimeout(fn, delay)
+ return () => clearTimeout(timeout)
+ }, [fn, delay])
+
+const throwErrorIsNeed = { current: false }
+type ThrowErrorProps = PropsWithChildren<{ message: string; after?: number }>
+export const ThrowError = ({ message, after = 0, children }: ThrowErrorProps) => {
+ const [isNeedError, setIsNeedError] = useState(after === 0 ? true : throwErrorIsNeed.current)
+ if (isNeedError) {
+ throw new Error(message)
+ }
+ useSetTimeout(() => setIsNeedError(true), after)
+ return <>{children}>
+}
+
+type ThrowNullProps = PropsWithChildren<{ after: number }>
+export const ThrowNull = ({ after, children }: ThrowNullProps) => {
+ const [isNeedError, setIsNeedError] = useState(throwErrorIsNeed.current)
+ if (isNeedError) {
+ throw null
+ }
+ useSetTimeout(() => setIsNeedError(true), after)
+ return <>{children}>
+}
+
+ThrowError.reset = () => {
+ throwErrorIsNeed.current = false
+}
diff --git a/configs/test-utils/src/delay.ts b/configs/test-utils/src/delay.ts
new file mode 100644
index 000000000..de834c18e
--- /dev/null
+++ b/configs/test-utils/src/delay.ts
@@ -0,0 +1 @@
+export const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve('done'), ms))
diff --git a/configs/test-utils/src/index.ts b/configs/test-utils/src/index.ts
new file mode 100644
index 000000000..d4b8e7837
--- /dev/null
+++ b/configs/test-utils/src/index.ts
@@ -0,0 +1,7 @@
+export { Suspend } from './Suspend'
+export { ThrowError, ThrowNull } from './ThrowError'
+export { delay } from './delay'
+export const TEXT = 'TEXT' as const
+export const ERROR_MESSAGE = 'ERROR_MESSAGE' as const
+export const FALLBACK = 'FALLBACK' as const
+export const MS_100 = 100 as const
diff --git a/configs/test-utils/tsconfig.json b/configs/test-utils/tsconfig.json
new file mode 100644
index 000000000..4f99717c1
--- /dev/null
+++ b/configs/test-utils/tsconfig.json
@@ -0,0 +1,7 @@
+{
+ "extends": "@suspensive/tsconfig/react-library.json",
+ "include": ["."],
+ "compilerOptions": {
+ "types": ["@testing-library/jest-dom"]
+ }
+}
diff --git a/packages/react-await/package.json b/packages/react-await/package.json
index 7874c72c3..68688d654 100644
--- a/packages/react-await/package.json
+++ b/packages/react-await/package.json
@@ -60,6 +60,7 @@
},
"devDependencies": {
"@suspensive/react": "workspace:*",
+ "@suspensive/test-utils": "workspace:*",
"@suspensive/tsup": "workspace:*",
"@suspensive/vitest": "workspace:*",
"@types/node": "^18.16.2",
diff --git a/packages/react-await/src/Await.spec.tsx b/packages/react-await/src/Await.spec.tsx
index bf990a4de..5837b89ea 100644
--- a/packages/react-await/src/Await.spec.tsx
+++ b/packages/react-await/src/Await.spec.tsx
@@ -1,7 +1,7 @@
import { ErrorBoundary, Suspense } from '@suspensive/react'
+import { ERROR_MESSAGE, FALLBACK, MS_100, TEXT, delay } from '@suspensive/test-utils'
import { act, render, screen, waitFor } from '@testing-library/react'
import { vi } from 'vitest'
-import { ERROR_MESSAGE, FALLBACK, MS_100, TEXT, delay } from './utils/toTest'
import { Await, awaitClient, useAwait } from '.'
const key = (id: number) => ['key', id] as const
diff --git a/packages/react/package.json b/packages/react/package.json
index 72063c211..a3f1b981b 100644
--- a/packages/react/package.json
+++ b/packages/react/package.json
@@ -56,6 +56,7 @@
"type:check": "tsc --noEmit"
},
"devDependencies": {
+ "@suspensive/test-utils": "workspace:*",
"@suspensive/tsup": "workspace:*",
"@suspensive/vitest": "workspace:*",
"@types/node": "^18.16.2",
diff --git a/packages/react/src/AsyncBoundary.spec.tsx b/packages/react/src/AsyncBoundary.spec.tsx
index 1aafdecfc..5cf5e017b 100644
--- a/packages/react/src/AsyncBoundary.spec.tsx
+++ b/packages/react/src/AsyncBoundary.spec.tsx
@@ -1,9 +1,9 @@
+import { ERROR_MESSAGE, FALLBACK, MS_100, Suspend, TEXT, ThrowError } from '@suspensive/test-utils'
import { act, render, waitFor } from '@testing-library/react'
import type { ComponentProps } from 'react'
import { createElement } from 'react'
import { createRoot } from 'react-dom/client'
import { vi } from 'vitest'
-import { ERROR_MESSAGE, FALLBACK, MS_100, Suspend, TEXT, ThrowError } from './utils/toTest'
import { AsyncBoundary, withAsyncBoundary } from '.'
let container = document.createElement('div')
diff --git a/packages/react/src/Delay.spec.tsx b/packages/react/src/Delay.spec.tsx
index eb91b20ee..c24da63c5 100644
--- a/packages/react/src/Delay.spec.tsx
+++ b/packages/react/src/Delay.spec.tsx
@@ -1,6 +1,6 @@
+import { MS_100, TEXT } from '@suspensive/test-utils'
import { act, render, screen, waitFor } from '@testing-library/react'
import { vi } from 'vitest'
-import { MS_100, TEXT } from './utils/toTest'
import { Delay, withDelay } from '.'
describe('', () => {
diff --git a/packages/react/src/ErrorBoundary.spec.tsx b/packages/react/src/ErrorBoundary.spec.tsx
index 657ee46c8..aa0c50aff 100644
--- a/packages/react/src/ErrorBoundary.spec.tsx
+++ b/packages/react/src/ErrorBoundary.spec.tsx
@@ -1,3 +1,4 @@
+import { ERROR_MESSAGE, FALLBACK, MS_100, TEXT, ThrowError, ThrowNull } from '@suspensive/test-utils'
import { act, render } from '@testing-library/react'
import type { ComponentProps, ComponentRef } from 'react'
import { createElement, createRef } from 'react'
@@ -5,7 +6,6 @@ import { createRoot } from 'react-dom/client'
import { vi } from 'vitest'
import { useSetTimeout } from './hooks'
import { assert } from './utils'
-import { ERROR_MESSAGE, FALLBACK, MS_100, TEXT, ThrowError, ThrowNull } from './utils/toTest'
import { ErrorBoundary, useErrorBoundary, useErrorBoundaryFallbackProps, withErrorBoundary } from '.'
let container = document.createElement('div')
diff --git a/packages/react/src/ErrorBoundaryGroup.spec.tsx b/packages/react/src/ErrorBoundaryGroup.spec.tsx
index 18e0e63b6..3bf0d7221 100644
--- a/packages/react/src/ErrorBoundaryGroup.spec.tsx
+++ b/packages/react/src/ErrorBoundaryGroup.spec.tsx
@@ -1,8 +1,8 @@
+import { ERROR_MESSAGE, MS_100, TEXT, ThrowError } from '@suspensive/test-utils'
import { act, render, screen } from '@testing-library/react'
import { createElement } from 'react'
import { vi } from 'vitest'
import { assert } from './utils'
-import { ERROR_MESSAGE, MS_100, TEXT, ThrowError } from './utils/toTest'
import { ErrorBoundary, ErrorBoundaryGroup, useErrorBoundaryGroup, withErrorBoundaryGroup } from '.'
const innerErrorBoundaryCount = 3
diff --git a/packages/react/src/Suspense.spec.tsx b/packages/react/src/Suspense.spec.tsx
index 18ae20920..129141ed2 100644
--- a/packages/react/src/Suspense.spec.tsx
+++ b/packages/react/src/Suspense.spec.tsx
@@ -1,7 +1,7 @@
+import { FALLBACK, MS_100, Suspend, TEXT } from '@suspensive/test-utils'
import { act, render, screen, waitFor } from '@testing-library/react'
import { createElement } from 'react'
import { vi } from 'vitest'
-import { FALLBACK, MS_100, Suspend, TEXT } from './utils/toTest'
import { Suspense, withSuspense } from '.'
describe('', () => {
diff --git a/packages/react/src/SuspensiveProvider.spec.tsx b/packages/react/src/SuspensiveProvider.spec.tsx
index a48a4d9f4..683e70cd4 100644
--- a/packages/react/src/SuspensiveProvider.spec.tsx
+++ b/packages/react/src/SuspensiveProvider.spec.tsx
@@ -1,6 +1,6 @@
+import { FALLBACK, MS_100, Suspend, TEXT } from '@suspensive/test-utils'
import { act, render, screen, waitFor } from '@testing-library/react'
import { vi } from 'vitest'
-import { FALLBACK, MS_100, Suspend, TEXT } from './utils/toTest'
import { Delay, Suspense, Suspensive, SuspensiveProvider } from '.'
const FALLBACK_GLOBAL = 'FALLBACK_GLOBAL'
diff --git a/packages/react/src/hooks/useSetTimeout.spec.ts b/packages/react/src/hooks/useSetTimeout.spec.ts
index 9f5467a82..f18f8d453 100644
--- a/packages/react/src/hooks/useSetTimeout.spec.ts
+++ b/packages/react/src/hooks/useSetTimeout.spec.ts
@@ -1,6 +1,6 @@
+import { MS_100 } from '@suspensive/test-utils'
import { act, renderHook } from '@testing-library/react'
import { describe, expect, it, vi } from 'vitest'
-import { MS_100 } from '../utils/toTest'
import { useSetTimeout } from '.'
vi.useFakeTimers()
diff --git a/packages/react/src/utils/toTest.tsx b/packages/react/src/utils/toTest.tsx
deleted file mode 100644
index 91be62fb3..000000000
--- a/packages/react/src/utils/toTest.tsx
+++ /dev/null
@@ -1,52 +0,0 @@
-import type { PropsWithChildren, ReactNode } from 'react'
-import { useState } from 'react'
-import { useSetTimeout } from '../hooks'
-
-const suspendIsNeed = { current: true }
-type SuspendProps = { during: number; toShow?: ReactNode }
-export const Suspend = ({ during, toShow }: SuspendProps) => {
- if (suspendIsNeed.current) {
- throw new Promise((resolve) =>
- setTimeout(() => {
- suspendIsNeed.current = false
- resolve('resolved')
- }, during)
- )
- }
- return <>{toShow}>
-}
-Suspend.reset = () => {
- suspendIsNeed.current = true
-}
-
-const throwErrorIsNeed = { current: false }
-type ThrowErrorProps = PropsWithChildren<{ message: string; after?: number }>
-export const ThrowError = ({ message, after = 0, children }: ThrowErrorProps) => {
- const [isNeedError, setIsNeedError] = useState(after === 0 ? true : throwErrorIsNeed.current)
- if (isNeedError) {
- throw new Error(message)
- }
- useSetTimeout(() => setIsNeedError(true), after)
- return <>{children}>
-}
-
-type ThrowNullProps = PropsWithChildren<{ after: number }>
-export const ThrowNull = ({ after, children }: ThrowNullProps) => {
- const [isNeedError, setIsNeedError] = useState(throwErrorIsNeed.current)
- if (isNeedError) {
- throw null
- }
- useSetTimeout(() => setIsNeedError(true), after)
- return <>{children}>
-}
-
-ThrowError.reset = () => {
- throwErrorIsNeed.current = false
-}
-
-export const delay = (ms: number) => new Promise((resolve) => setTimeout(() => resolve('done'), ms))
-
-export const TEXT = 'TEXT' as const
-export const ERROR_MESSAGE = 'ERROR_MESSAGE' as const
-export const FALLBACK = 'FALLBACK' as const
-export const MS_100 = 100 as const
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index 4a8c236a8..c10f5f666 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -81,7 +81,7 @@ importers:
version: 7.1.0(ts-node@10.9.1)(typescript@5.1.6)
turbo:
specifier: latest
- version: 1.10.16
+ version: 1.10.15
typescript:
specifier: ^5.1.6
version: 5.1.6
@@ -164,6 +164,15 @@ importers:
specifier: ^4.2.1
version: 4.2.1(eslint-config-prettier@8.3.0)(eslint@8.42.0)(prettier@2.8.8)
+ configs/test-utils:
+ devDependencies:
+ '@types/react':
+ specifier: ^18.2.0
+ version: 18.2.0
+ react:
+ specifier: ^18.2.0
+ version: 18.2.0
+
configs/tsconfig: {}
configs/tsup: {}
@@ -221,6 +230,9 @@ importers:
packages/react:
devDependencies:
+ '@suspensive/test-utils':
+ specifier: workspace:*
+ version: link:../../configs/test-utils
'@suspensive/tsup':
specifier: workspace:*
version: link:../../configs/tsup
@@ -252,6 +264,9 @@ importers:
'@suspensive/react':
specifier: workspace:*
version: link:../react
+ '@suspensive/test-utils':
+ specifier: workspace:*
+ version: link:../../configs/test-utils
'@suspensive/tsup':
specifier: workspace:*
version: link:../../configs/tsup
@@ -9589,64 +9604,64 @@ packages:
yargs: 17.7.2
dev: true
- /turbo-darwin-64@1.10.16:
- resolution: {integrity: sha512-+Jk91FNcp9e9NCLYlvDDlp2HwEDp14F9N42IoW3dmHI5ZkGSXzalbhVcrx3DOox3QfiNUHxzWg4d7CnVNCuuMg==}
+ /turbo-darwin-64@1.10.15:
+ resolution: {integrity: sha512-Sik5uogjkRTe1XVP9TC2GryEMOJCaKE2pM/O9uLn4koQDnWKGcLQv+mDU+H+9DXvKLnJnKCD18OVRkwK5tdpoA==}
cpu: [x64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
- /turbo-darwin-arm64@1.10.16:
- resolution: {integrity: sha512-jqGpFZipIivkRp/i+jnL8npX0VssE6IAVNKtu573LXtssZdV/S+fRGYA16tI46xJGxSAivrZ/IcgZrV6Jk80bw==}
+ /turbo-darwin-arm64@1.10.15:
+ resolution: {integrity: sha512-xwqyFDYUcl2xwXyGPmHkmgnNm4Cy0oNzMpMOBGRr5x64SErS7QQLR4VHb0ubiR+VAb8M+ECPklU6vD1Gm+wekg==}
cpu: [arm64]
os: [darwin]
requiresBuild: true
dev: true
optional: true
- /turbo-linux-64@1.10.16:
- resolution: {integrity: sha512-PpqEZHwLoizQ6sTUvmImcRmACyRk9EWLXGlqceogPZsJ1jTRK3sfcF9fC2W56zkSIzuLEP07k5kl+ZxJd8JMcg==}
+ /turbo-linux-64@1.10.15:
+ resolution: {integrity: sha512-dM07SiO3RMAJ09Z+uB2LNUSkPp3I1IMF8goH5eLj+d8Kkwoxd/+qbUZOj9RvInyxU/IhlnO9w3PGd3Hp14m/nA==}
cpu: [x64]
os: [linux]
requiresBuild: true
dev: true
optional: true
- /turbo-linux-arm64@1.10.16:
- resolution: {integrity: sha512-TMjFYz8to1QE0fKVXCIvG/4giyfnmqcQIwjdNfJvKjBxn22PpbjeuFuQ5kNXshUTRaTJihFbuuCcb5OYFNx4uw==}
+ /turbo-linux-arm64@1.10.15:
+ resolution: {integrity: sha512-MkzKLkKYKyrz4lwfjNXH8aTny5+Hmiu4SFBZbx+5C0vOlyp6fV5jZANDBvLXWiDDL4DSEAuCEK/2cmN6FVH1ow==}
cpu: [arm64]
os: [linux]
requiresBuild: true
dev: true
optional: true
- /turbo-windows-64@1.10.16:
- resolution: {integrity: sha512-+jsf68krs0N66FfC4/zZvioUap/Tq3sPFumnMV+EBo8jFdqs4yehd6+MxIwYTjSQLIcpH8KoNMB0gQYhJRLZzw==}
+ /turbo-windows-64@1.10.15:
+ resolution: {integrity: sha512-3TdVU+WEH9ThvQGwV3ieX/XHebtYNHv9HARHauPwmVj3kakoALkpGxLclkHFBLdLKkqDvmHmXtcsfs6cXXRHJg==}
cpu: [x64]
os: [win32]
requiresBuild: true
dev: true
optional: true
- /turbo-windows-arm64@1.10.16:
- resolution: {integrity: sha512-sKm3hcMM1bl0B3PLG4ifidicOGfoJmOEacM5JtgBkYM48ncMHjkHfFY7HrJHZHUnXM4l05RQTpLFoOl/uIo2HQ==}
+ /turbo-windows-arm64@1.10.15:
+ resolution: {integrity: sha512-l+7UOBCbfadvPMYsX08hyLD+UIoAkg6ojfH+E8aud3gcA1padpjCJTh9gMpm3QdMbKwZteT5uUM+wyi6Rbbyww==}
cpu: [arm64]
os: [win32]
requiresBuild: true
dev: true
optional: true
- /turbo@1.10.16:
- resolution: {integrity: sha512-2CEaK4FIuSZiP83iFa9GqMTQhroW2QryckVqUydmg4tx78baftTOS0O+oDAhvo9r9Nit4xUEtC1RAHoqs6ZEtg==}
+ /turbo@1.10.15:
+ resolution: {integrity: sha512-mKKkqsuDAQy1wCCIjCdG+jOCwUflhckDMSRoeBPcIL/CnCl7c5yRDFe7SyaXloUUkt4tUR0rvNIhVCcT7YeQpg==}
hasBin: true
optionalDependencies:
- turbo-darwin-64: 1.10.16
- turbo-darwin-arm64: 1.10.16
- turbo-linux-64: 1.10.16
- turbo-linux-arm64: 1.10.16
- turbo-windows-64: 1.10.16
- turbo-windows-arm64: 1.10.16
+ turbo-darwin-64: 1.10.15
+ turbo-darwin-arm64: 1.10.15
+ turbo-linux-64: 1.10.15
+ turbo-linux-arm64: 1.10.15
+ turbo-windows-64: 1.10.15
+ turbo-windows-arm64: 1.10.15
dev: true
/typanion@3.12.1:
diff --git a/turbo.json b/turbo.json
index 186e5466b..6b041de86 100644
--- a/turbo.json
+++ b/turbo.json
@@ -5,9 +5,9 @@
"build:watch": { "outputs": ["dist/**"] },
"npm:publish": { "dependsOn": ["^build"] },
"lint": { "cache": false },
- "lint:attw": { "dependsOn": ["^build"], "cache": false },
+ "lint:attw": { "dependsOn": ["^build"], "cache": false },
"lint:pub": { "cache": false },
- "test": { "outputs": ["coverage/**"], "cache": false },
+ "test": { "dependsOn": ["^build"], "outputs": ["coverage/**"], "cache": false },
"test:tsd": {},
"test:watch": { "cache": false },
"prepack": { "dependsOn": ["^prepack"], "cache": false },