Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feature/RHTL-68 – server-side-rendering #510

Merged
merged 60 commits into from Jan 7, 2021
Merged
Show file tree
Hide file tree
Changes from 54 commits
Commits
Show all changes
60 commits
Select commit Hold shift + click to select a range
3841db2
chore: move current config (react-test-renderer) to src/native
joshuaellis Dec 6, 2020
40991cb
change: add eslint to avoid conflict & fix lint errors
joshuaellis Dec 10, 2020
e3b9104
feat: migrate previous server work
joshuaellis Dec 11, 2020
8b11562
merge master to branch
joshuaellis Dec 29, 2020
b34000e
Merge branch 'beta' into feature/RHTL-68
joshuaellis Dec 30, 2020
2cd41cb
feat: add global type file
joshuaellis Dec 31, 2020
d34245a
change: seperate helper funcs
joshuaellis Dec 31, 2020
85b9fa2
feat: add types to createRenderer & createRenderHook
joshuaellis Dec 31, 2020
add0639
tests: fix tests with type assertions etc.
joshuaellis Dec 31, 2020
785aa5c
fix: type assertion with RenderResult
joshuaellis Dec 31, 2020
b553441
test: fix errorHook type error.
joshuaellis Dec 31, 2020
7edd623
change: try to solve WrapperComponent PropTypes for useContext test
joshuaellis Dec 31, 2020
100c77c
fix: Wrapper type was breaking with spread operator
joshuaellis Dec 31, 2020
b5f6ced
feat: add server engine in typescript
joshuaellis Jan 1, 2021
56be770
test: get coverage to 100
joshuaellis Jan 2, 2021
8d35cac
change: remove rtr act & use own written
joshuaellis Jan 2, 2021
a4329dd
change: use relative paths & add react-dom to renderers
joshuaellis Jan 2, 2021
0e296b7
feat: add custom renderer option
joshuaellis Jan 2, 2021
fdfe432
feat: add dom renderer
joshuaellis Jan 3, 2021
af079a4
change: extract toRender to be shared
joshuaellis Jan 3, 2021
ec732c5
feat: add dom types
joshuaellis Jan 3, 2021
53a339e
test: formatting
joshuaellis Jan 3, 2021
7be2c80
change: create overload for createRenderHook
joshuaellis Jan 3, 2021
9b57e0d
change: update contributors
joshuaellis Jan 3, 2021
cfd3df0
change: dom should be default renderer for react-dom
joshuaellis Jan 3, 2021
125c327
change: add dom to files
joshuaellis Jan 3, 2021
ae3f2f7
change: @types/react-dom should be >=16.9.0
joshuaellis Jan 3, 2021
0e73e21
change: use generic RendererOptions instead of specific to renderer
joshuaellis Jan 3, 2021
48af5ce
chore(deps-dev): bump eslint from 7.16.0 to 7.17.0
dependabot[bot] Jan 4, 2021
3773938
chore(refactor): moved require into getRenderer
mpeyper Jan 4, 2021
19d74ce
fix: improve error message when renderer can't be auto-detected
mpeyper Jan 4, 2021
bad29f9
feat: remove renderer specific types from core renderHook logic
mpeyper Jan 4, 2021
5730f15
change: refactor auto cleanup into a separate function
mpeyper Jan 4, 2021
50cdd55
feat: added render utility support for custom renderers
mpeyper Jan 4, 2021
ba99b1c
fix: type catch block errors to fixe lint error
mpeyper Jan 4, 2021
fcc8ba3
chore: ignore lint warning for purposely skipped tests
mpeyper Jan 4, 2021
9ada181
chore: removed unnecessary lint disable comment
mpeyper Jan 4, 2021
81ce711
Merge pull request #522 from testing-library/dependabot/npm_and_yarn/…
joshuaellis Jan 4, 2021
785afd2
feat: renderHook options are now generic based on renderer
mpeyper Jan 6, 2021
a1fa12f
chore: rename file without tsx exension as it no longer contains any jsx
mpeyper Jan 6, 2021
511cb33
fix: refactor type names and removed some unnecessary types
mpeyper Jan 6, 2021
b3ccb32
fix: remove server act login and just use basic act
mpeyper Jan 6, 2021
3dfbbb7
fix: ensure react types are only imported if a react renderer is used
mpeyper Jan 6, 2021
e1d2153
fix: separate types into folders are export types for imports
mpeyper Jan 6, 2021
137f50c
fix: remove unused types, cleanup types and format all files
mpeyper Jan 6, 2021
89e678c
fix: update error message when a renderer cannot be auto-detected
mpeyper Jan 6, 2021
36b81c0
chore: restructure code for better readability after formatting
mpeyper Jan 6, 2021
1c6b9e5
fix: remove unnecessary structures to remove need to cast in renderHook
mpeyper Jan 6, 2021
b936962
fix: remove duplicate type from internal types
mpeyper Jan 6, 2021
080d98a
fix: simplify type of RenderHook
mpeyper Jan 6, 2021
816d312
Merge pull request #1 from testing-library/feature/RHTL-68-suggestions
joshuaellis Jan 6, 2021
209619d
feat: generate submodules as part of build step
mpeyper Jan 6, 2021
e9fa116
fix: don't wait for already resolved promise in waitForNextUpdate
mpeyper Jan 7, 2021
fd00043
fix: ensure submodule directory is cleaned before generating files
mpeyper Jan 7, 2021
16ba7e0
chore: remove unused custom module
mpeyper Jan 7, 2021
6146121
fix: minor renaming of private members
mpeyper Jan 7, 2021
1970833
Merge branch 'beta'
mpeyper Jan 7, 2021
c9460c5
Merge branch 'master' into feature/RHTL-68
mpeyper Jan 7, 2021
e315dce
chore: updated all-contributors to be more accurate
mpeyper Jan 7, 2021
1e2cb37
Merge branch 'master' into feature/RHTL-68
mpeyper Jan 7, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
7 changes: 6 additions & 1 deletion .all-contributorsrc
Expand Up @@ -201,7 +201,12 @@
"avatar_url": "https://avatars0.githubusercontent.com/u/37798644?v=4",
"profile": "https://github.com/joshuaellis",
"contributions": [
"doc"
"doc",
"code",
"design",
"ideas",
"maintenance",
"test"
]
},
{
Expand Down
4 changes: 4 additions & 0 deletions .eslintignore
@@ -1,5 +1,9 @@
node_modules
coverage
lib
dom
native
server
pure
.docz
site
3 changes: 1 addition & 2 deletions .eslintrc
Expand Up @@ -6,12 +6,11 @@
"no-await-in-loop": "off",
"no-console": "off",
"import/no-unresolved": "off",
"react-hooks/rules-of-hooks": "off",
"@typescript-eslint/no-floating-promises": "off",
"@typescript-eslint/no-unnecessary-condition": "off",
"@typescript-eslint/no-invalid-void-type": "off"
},
"parserOptions": {
"project": ["./tsconfig.json", "./test/tsconfig.json"]
"project": ["./tsconfig.json", "./test/tsconfig.json", "./scripts/tsconfig.json"]
}
}
7 changes: 6 additions & 1 deletion .gitignore
@@ -1,5 +1,10 @@
node_modules
coverage
lib
dom
native
server
pure
.docz
site
site
.vscode
3 changes: 2 additions & 1 deletion README.md
Expand Up @@ -42,6 +42,7 @@
<!-- START doctoc generated TOC please keep comment here to allow auto update -->
<!-- DON'T EDIT THIS SECTION, INSTEAD RE-RUN doctoc TO UPDATE -->


- [The problem](#the-problem)
- [The solution](#the-solution)
- [When to use this library](#when-to-use-this-library)
Expand Down Expand Up @@ -189,7 +190,7 @@ Thanks goes to these wonderful people ([emoji key](https://allcontributors.org/d
<td align="center"><a href="https://github.com/hemlok"><img src="https://avatars2.githubusercontent.com/u/9043345?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Adam Seckel</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=hemlok" title="Code">💻</a></td>
<td align="center"><a href="https://keiya01.github.io/portfolio"><img src="https://avatars1.githubusercontent.com/u/34934510?v=4?s=100" width="100px;" alt=""/><br /><sub><b>keiya sasaki</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=keiya01" title="Tests">⚠️</a></td>
<td align="center"><a href="https://huchen.dev/"><img src="https://avatars3.githubusercontent.com/u/2078389?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Hu Chen</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=huchenme" title="Code">💻</a> <a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=huchenme" title="Documentation">📖</a> <a href="#example-huchenme" title="Examples">💡</a></td>
<td align="center"><a href="https://github.com/joshuaellis"><img src="https://avatars0.githubusercontent.com/u/37798644?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Josh</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=joshuaellis" title="Documentation">📖</a></td>
<td align="center"><a href="https://github.com/joshuaellis"><img src="https://avatars0.githubusercontent.com/u/37798644?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Josh</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=joshuaellis" title="Documentation">📖</a> <a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=joshuaellis" title="Code">💻</a> <a href="#design-joshuaellis" title="Design">🎨</a> <a href="#ideas-joshuaellis" title="Ideas, Planning, & Feedback">🤔</a> <a href="#maintenance-joshuaellis" title="Maintenance">🚧</a> <a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=joshuaellis" title="Tests">⚠️</a></td>
<td align="center"><a href="https://github.com/Goldziher"><img src="https://avatars1.githubusercontent.com/u/30733348?v=4?s=100" width="100px;" alt=""/><br /><sub><b>Na'aman Hirschfeld</b></sub></a><br /><a href="https://github.com/testing-library/react-hooks-testing-library/commits?author=Goldziher" title="Code">💻</a></td>
</tr>
<tr>
Expand Down
2 changes: 1 addition & 1 deletion jest.config.js
Expand Up @@ -3,5 +3,5 @@ const { jest: jestConfig } = require('kcd-scripts/config')

module.exports = Object.assign(jestConfig, {
roots: ['<rootDir>/src', '<rootDir>/test'],
testMatch: ['<rootDir>/test/*.(ts|tsx|js)']
testMatch: ['<rootDir>/test/**/*.(ts|tsx|js)']
})
23 changes: 21 additions & 2 deletions package.json
Expand Up @@ -14,7 +14,10 @@
"files": [
"lib",
"src",
"pure.js",
"dom",
"native",
"server",
joshuaellis marked this conversation as resolved.
Show resolved Hide resolved
"pure",
"dont-cleanup-after-each.js"
],
"author": "Michael Peyper <mpeyper7@gmail.com>",
Expand All @@ -27,7 +30,8 @@
"setup": "npm install && npm run validate -s",
"validate": "kcd-scripts validate",
"prepare": "npm run build",
"build": "kcd-scripts build --out-dir lib",
"build": "kcd-scripts build --out-dir lib && npm run generate:submodules",
"generate:submodules": "ts-node scripts/generate-submodules.ts",
"test": "kcd-scripts test",
"typecheck": "kcd-scripts typecheck",
"lint": "kcd-scripts lint",
Expand All @@ -40,6 +44,7 @@
"dependencies": {
"@babel/runtime": "^7.12.5",
"@types/react": ">=16.9.0",
"@types/react-dom": ">=16.9.0",
"@types/react-test-renderer": ">=16.9.0"
},
"devDependencies": {
Expand All @@ -54,11 +59,25 @@
"kcd-scripts": "7.5.3",
"prettier": "^2.2.1",
"react": "17.0.1",
"react-dom": "^17.0.1",
"react-test-renderer": "17.0.1",
"ts-node": "^9.1.1",
"typescript": "4.1.3"
},
"peerDependencies": {
"react": ">=16.9.0",
"react-dom": ">=16.9.0",
"react-test-renderer": ">=16.9.0"
},
"peerDependenciesMeta": {
"react": {
"optional": true
},
"react-dom": {
"optional": true
},
"react-test-renderer": {
"optional": true
}
}
}
2 changes: 0 additions & 2 deletions pure.js

This file was deleted.

63 changes: 63 additions & 0 deletions scripts/generate-submodules.ts
@@ -0,0 +1,63 @@
import fs from 'fs'
import path from 'path'

type Template = (submodule: string) => string

const templates = {
index: {
'.js': (submodule: string) => `module.exports = require('../lib/${submodule}')`,
'.d.ts': (submodule: string) => `export * from '../lib/${submodule}'`
},
pure: {
'.js': (submodule: string) => `module.exports = require('../lib/${submodule}/pure')`,
'.d.ts': (submodule: string) => `export * from '../lib/${submodule}/pure'`
}
}

const submodules = ['dom', 'native', 'server', 'pure']

function cleanDirectory(directory: string) {
const files = fs.readdirSync(directory)
files.forEach((file) => fs.unlinkSync(path.join(directory, file)))
}

function makeDirectory(submodule: string) {
const submoduleDir = path.join(process.cwd(), submodule)

if (fs.existsSync(submoduleDir)) {
cleanDirectory(submoduleDir)
} else {
fs.mkdirSync(submoduleDir)
}

return submoduleDir
}

function requiredFile(submodule: string) {
return ([name]: [string, unknown]) => {
return name !== submodule
}
}

function makeFile(directory: string, submodule: string) {
return ([name, extensions]: [string, Record<string, Template>]) => {
Object.entries(extensions).forEach(([extension, template]) => {
const fileName = `${name}${extension}`
console.log(` - ${fileName}`)
const filePath = path.join(directory, fileName)
fs.writeFileSync(filePath, template(submodule))
})
}
}

function makeFiles(directory: string, submodule: string) {
Object.entries(templates).filter(requiredFile(submodule)).forEach(makeFile(directory, submodule))
}

function createSubmodule(submodule: string) {
console.log(`Generating submodule: ${submodule}`)
const submoduleDir = makeDirectory(submodule)
makeFiles(submoduleDir, submodule)
}

submodules.forEach(createSubmodule)
8 changes: 8 additions & 0 deletions scripts/tsconfig.json
@@ -0,0 +1,8 @@
{
"extends": "../tsconfig",
"compilerOptions": {
"declaration": false
},
"exclude": [],
"include": ["./**/*.ts"]
}
34 changes: 10 additions & 24 deletions src/asyncUtils.ts → src/core/asyncUtils.ts
@@ -1,28 +1,15 @@
import { act } from 'react-test-renderer'
import { Act, WaitOptions, AsyncUtils } from '../types'

export interface WaitOptions {
interval?: number
timeout?: number
suppressErrors?: boolean
}

class TimeoutError extends Error {
constructor(util: Function, timeout: number) {
super(`Timed out in ${util.name} after ${timeout}ms.`)
}
}

function resolveAfter(ms: number) {
return new Promise((resolve) => {
setTimeout(resolve, ms)
})
}
import { resolveAfter } from '../helpers/promises'
import { TimeoutError } from '../helpers/error'

function asyncUtils(addResolver: (callback: () => void) => void) {
function asyncUtils(act: Act, addResolver: (callback: () => void) => void): AsyncUtils {
let nextUpdatePromise: Promise<void> | null = null

const waitForNextUpdate = async ({ timeout }: Pick<WaitOptions, 'timeout'> = {}) => {
if (!nextUpdatePromise) {
if (nextUpdatePromise) {
await nextUpdatePromise
} else {
joshuaellis marked this conversation as resolved.
Show resolved Hide resolved
nextUpdatePromise = new Promise((resolve, reject) => {
let timeoutId: ReturnType<typeof setTimeout>
if (timeout && timeout > 0) {
Expand All @@ -39,7 +26,6 @@ function asyncUtils(addResolver: (callback: () => void) => void) {
})
await act(() => nextUpdatePromise as Promise<void>)
}
await nextUpdatePromise
}

const waitFor = async (
Expand All @@ -52,7 +38,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) {
return callbackResult ?? callbackResult === undefined
} catch (error: unknown) {
if (!suppressErrors) {
throw error as Error
throw error
}
return undefined
}
Expand All @@ -76,7 +62,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) {
if (error instanceof TimeoutError && initialTimeout) {
throw new TimeoutError(waitFor, initialTimeout)
}
throw error as Error
throw error
}
if (timeout) timeout -= Date.now() - startTime
}
Expand All @@ -98,7 +84,7 @@ function asyncUtils(addResolver: (callback: () => void) => void) {
if (error instanceof TimeoutError && options.timeout) {
throw new TimeoutError(waitForValueToChange, options.timeout)
}
throw error as Error
throw error
}
}

Expand Down
11 changes: 10 additions & 1 deletion src/cleanup.ts → src/core/cleanup.ts
Expand Up @@ -16,4 +16,13 @@ function removeCleanup(callback: () => Promise<void> | void) {
cleanupCallbacks = cleanupCallbacks.filter((cb) => cb !== callback)
}

export { cleanup, addCleanup, removeCleanup }
function autoRegisterCleanup() {
// Automatically registers cleanup in supported testing frameworks
if (typeof afterEach === 'function' && !process.env.RHTL_SKIP_AUTO_CLEANUP) {
afterEach(async () => {
await cleanup()
})
}
}

export { cleanup, addCleanup, removeCleanup, autoRegisterCleanup }
78 changes: 78 additions & 0 deletions src/core/index.ts
@@ -0,0 +1,78 @@
import { CreateRenderer, Renderer, RenderResult, RenderHook } from '../types'
import { ResultContainer, RenderHookOptions } from '../types/internal'

import asyncUtils from './asyncUtils'
import { cleanup, addCleanup, removeCleanup } from './cleanup'

function resultContainer<TValue>(): ResultContainer<TValue> {
const results: Array<{ value?: TValue; error?: Error }> = []
const resolvers: Array<() => void> = []

const result: RenderResult<TValue> = {
get all() {
return results.map(({ value, error }) => error ?? value)
},
get current() {
const { value, error } = results[results.length - 1]
if (error) {
throw error
}
return value as TValue
},
get error() {
const { error } = results[results.length - 1]
return error
}
}

const updateResult = (value?: TValue, error?: Error) => {
results.push({ value, error })
resolvers.splice(0, resolvers.length).forEach((resolve) => resolve())
}

return {
result,
addResolver: (resolver: () => void) => {
resolvers.push(resolver)
},
setValue: (value: TValue) => updateResult(value),
setError: (error: Error) => updateResult(undefined, error)
}
}

const createRenderHook = <TProps, TResult, TOptions extends {}, TRenderer extends Renderer<TProps>>(
createRenderer: CreateRenderer<TProps, TResult, TOptions, TRenderer>
) => (
callback: (props: TProps) => TResult,
options: RenderHookOptions<TProps, TOptions> = {} as RenderHookOptions<TProps, TOptions>
): RenderHook<TProps, TResult, TRenderer> => {
const { result, setValue, setError, addResolver } = resultContainer<TResult>()
const renderProps = { callback, setValue, setError }
let hookProps = options.initialProps

const { render, rerender, unmount, act, ...renderUtils } = createRenderer(renderProps, options)

render(hookProps)

function rerenderHook(newProps = hookProps) {
hookProps = newProps
rerender(hookProps)
}

function unmountHook() {
removeCleanup(unmountHook)
unmount()
}

addCleanup(unmountHook)

return {
result,
rerender: rerenderHook,
unmount: unmountHook,
...asyncUtils(act, addResolver),
...renderUtils
}
}

export { createRenderHook, cleanup, addCleanup, removeCleanup }
5 changes: 5 additions & 0 deletions src/dom/index.ts
@@ -0,0 +1,5 @@
import { autoRegisterCleanup } from '../core/cleanup'

autoRegisterCleanup()

export * from './pure'