-
Notifications
You must be signed in to change notification settings - Fork 832
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
b04aac8
commit fcc6c3d
Showing
4 changed files
with
318 additions
and
13 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,188 @@ | ||
import * as React from 'react'; | ||
import PropTypes from 'prop-types'; | ||
import { | ||
act, | ||
buildQueries, | ||
cleanup, | ||
createEvent, | ||
fireEvent as rtlFireEvent, | ||
queries, | ||
render as testingLibraryRender, | ||
prettyDOM, | ||
} from '@testing-library/react/pure'; | ||
|
||
// holes are *All* selectors which aren't necessary for id selectors | ||
const [queryDescriptionOf, , getDescriptionOf, , findDescriptionOf] = buildQueries( | ||
function queryAllDescriptionsOf(container: HTMLElement, element: HTMLElement) { | ||
return container.querySelectorAll(`#${element.getAttribute('aria-describedby')}`); | ||
} as any, | ||
function getMultipleError() { | ||
return `Found multiple descriptions. An element should be described by a unique element.`; | ||
}, | ||
function getMissingError() { | ||
return `Found no describing element.`; | ||
} | ||
); | ||
|
||
const customQueries = { queryDescriptionOf, getDescriptionOf, findDescriptionOf }; | ||
|
||
/** | ||
* | ||
* @param {React.ReactElement} element | ||
* @param {object} [options] | ||
* @param {boolean} [options.baseElement] - https://testing-library.com/docs/react-testing-library/api#baseelement-1 | ||
* @param {boolean} [options.disableUnnmount] - if true does not cleanup before mount | ||
* @param {boolean} [options.strict] - wrap in React.StrictMode? | ||
* @returns {import('@testing-library/react').RenderResult<typeof queries & typeof customQueries> & { setProps(props: object): void}} | ||
* TODO: type return RenderResult in setProps | ||
*/ | ||
function clientRender( | ||
element: any, | ||
options: { wrapper?: any; baseElement?: any; strict?: boolean } = {} | ||
) { | ||
const { baseElement, strict = true, wrapper: InnerWrapper = React.Fragment } = options; | ||
|
||
const Mode = strict ? React.StrictMode : React.Fragment; | ||
function Wrapper({ children }: { children?: React.ReactNode }) { | ||
return ( | ||
<Mode> | ||
<InnerWrapper>{children}</InnerWrapper> | ||
</Mode> | ||
); | ||
} | ||
Wrapper.propTypes = { children: PropTypes.node }; | ||
|
||
const result = testingLibraryRender(element, { | ||
baseElement, | ||
queries: { ...queries, ...customQueries }, | ||
wrapper: Wrapper, | ||
}) as any; | ||
|
||
/** | ||
* convenience helper. Better than repeating all props. | ||
*/ | ||
result.setProps = function setProps(props: any) { | ||
result.rerender(React.cloneElement(element, props)); | ||
return result; | ||
}; | ||
|
||
result.forceUpdate = function forceUpdate() { | ||
result.rerender( | ||
React.cloneElement(element, { | ||
'data-force-update': String(Math.random()), | ||
}) | ||
); | ||
return result; | ||
}; | ||
|
||
return result; | ||
} | ||
|
||
export function createClientRender(globalOptions: { strict?: boolean } = {}) { | ||
const { strict: globalStrict } = globalOptions; | ||
|
||
afterEach(async () => { | ||
// If this issues an act() warning you probably didn't | ||
// wait for an async event in your test (or didn't wrap it in act() at all). | ||
// please wait for every update in your test and make appropriate assertions | ||
await cleanup(); | ||
}); | ||
|
||
return function configuredClientRender(element: any, options: { strict?: boolean } = {}) { | ||
const { strict = globalStrict, ...localOptions } = options; | ||
|
||
return clientRender(element, { ...localOptions, strict }); | ||
}; | ||
} | ||
|
||
const fireEvent = Object.assign(rtlFireEvent, { | ||
// polyfill event.key(Code) for chrome 49 and edge 15 (supported in Material-UI v4) | ||
// for user-interactions react does the polyfilling but manually created | ||
// events don't have this luxury | ||
keyDown(element: any, options: { key?: string; keyCode?: string } = {}) { | ||
// `element` shouldn't be `document` but we catch this later anyway | ||
const document = element.ownerDocument || element; | ||
const target = document.activeElement || document.body || document.documentElement; | ||
if (target !== element) { | ||
// see https://www.w3.org/TR/uievents/#keydown | ||
const error = new Error( | ||
`\`keydown\` events can only be targeted at the active element which is ${prettyDOM( | ||
target, | ||
undefined, | ||
{ maxDepth: 1 } | ||
)}` | ||
); | ||
// We're only interested in the callsite of fireEvent.keyDown | ||
error.stack = error.stack | ||
? error.stack | ||
.split('\n') | ||
.filter(line => !/at Function.key/.test(line)) | ||
.join('\n') | ||
: ''; | ||
throw error; | ||
} | ||
|
||
const event = createEvent.keyDown(element, options) as any; | ||
Object.defineProperty(event, 'key', { | ||
get() { | ||
return options.key || ''; | ||
}, | ||
}); | ||
if (options.keyCode !== undefined && event.keyCode === 0) { | ||
Object.defineProperty(event, 'keyCode', { | ||
get() { | ||
return options.keyCode; | ||
}, | ||
}); | ||
} | ||
|
||
rtlFireEvent(element, event); | ||
}, | ||
keyUp(element: any, options: { key?: string; keyCode?: string } = {}) { | ||
// `element` shouldn't be `document` but we catch this later anyway | ||
const document = element.ownerDocument || element; | ||
const target = document.activeElement || document.body || document.documentElement; | ||
if (target !== element) { | ||
// see https://www.w3.org/TR/uievents/#keyup | ||
const error = new Error( | ||
`\`keyup\` events can only be targeted at the active element which is ${prettyDOM( | ||
target, | ||
undefined, | ||
{ maxDepth: 1 } | ||
)}` | ||
); | ||
// We're only interested in the callsite of fireEvent.keyUp | ||
error.stack = error.stack | ||
? error.stack | ||
.split('\n') | ||
.filter(line => !/at Function.key/.test(line)) | ||
.join('\n') | ||
: ''; | ||
throw error; | ||
} | ||
const event = createEvent.keyUp(element, options) as any; | ||
Object.defineProperty(event, 'key', { | ||
get() { | ||
return options.key || ''; | ||
}, | ||
}); | ||
if (options.keyCode !== undefined && event.keyCode === 0) { | ||
Object.defineProperty(event, 'keyCode', { | ||
get() { | ||
return options.keyCode; | ||
}, | ||
}); | ||
} | ||
|
||
rtlFireEvent(element, event); | ||
}, | ||
}); | ||
|
||
export * from '@testing-library/react/pure'; | ||
export { act, cleanup, fireEvent }; | ||
|
||
export function render() { | ||
throw new Error( | ||
"Don't use `render` directly. Instead use the return value from `createClientRender`" | ||
); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.