Skip to content

Commit

Permalink
add test case
Browse files Browse the repository at this point in the history
  • Loading branch information
oliviertassinari committed May 22, 2020
1 parent b04aac8 commit fcc6c3d
Show file tree
Hide file tree
Showing 4 changed files with 318 additions and 13 deletions.
56 changes: 44 additions & 12 deletions lib/src/__tests__/DateTimePicker.test.tsx
Original file line number Diff line number Diff line change
@@ -1,14 +1,48 @@
import * as React from 'react';
import { ReactWrapper } from 'enzyme';
import DateFnsUtils from '@date-io/date-fns';
import DateFnsLocaleDe from 'date-fns/locale/de';
import LocalizationProvider from '../LocalizationProvider';
import { TextField } from '@material-ui/core';
import { mount as enzymeDefaultMount } from 'enzyme';
import { MaterialUiPickersDate } from '../typings/date';
import { ThemeProvider, createMuiTheme } from '@material-ui/core';
import { mount as enzymeDefaultMount, ReactWrapper } from 'enzyme';
import { createClientRender, fireEvent } from './createClientRender';
import { mount, utilsToUse, mountPickerWithState } from './test-utils';
import { DateTimePicker, DateTimePickerProps } from '../DateTimePicker/DateTimePicker';
import {
DateTimePicker,
DesktopDateTimePicker,
DateTimePickerProps,
} from '../DateTimePicker/DateTimePicker';

const format = process.env.UTILS === 'moment' ? 'MM/DD/YYYY HH:mm' : 'MM/dd/yyyy hh:mm';

describe('DateTimePicker', () => {
const render = createClientRender();

describe('prop: mask', () => {
it('should take the mask prop into account', () => {
const { getByRole } = render(
<LocalizationProvider dateAdapter={DateFnsUtils} locale={DateFnsLocaleDe}>
<DesktopDateTimePicker
renderInput={props => <TextField autoFocus {...props} />}
mask="__.__.____ __:__"
onChange={() => {}}
value={null}
/>
</LocalizationProvider>
);
const textbox = getByRole('textbox');
fireEvent.change(textbox, {
target: {
selectionStart: 2,
selectionEnd: 2,
value: '12',
},
});
expect(textbox.value).toBe('12.');
});
});
});

describe('e2e - DateTimePicker', () => {
let component: ReactWrapper<DateTimePickerProps>;

Expand Down Expand Up @@ -88,14 +122,12 @@ describe('e2e -- Override utils using `dateAdapter`', () => {

beforeEach(() => {
component = enzymeDefaultMount(
<ThemeProvider theme={createMuiTheme()}>
<DateTimePicker
renderInput={props => <TextField {...props} />}
value={utilsToUse.date('2018-01-01T00:00:00.000Z')}
onChange={jest.fn()}
dateAdapter={utilsToUse}
/>
</ThemeProvider>
<DateTimePicker
renderInput={props => <TextField {...props} />}
value={utilsToUse.date('2018-01-01T00:00:00.000Z')}
onChange={jest.fn()}
dateAdapter={utilsToUse}
/>
);
});

Expand Down
188 changes: 188 additions & 0 deletions lib/src/__tests__/createClientRender.tsx
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`"
);
}
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,8 @@
"@babel/runtime": "^7.8.4",
"@cypress/webpack-preprocessor": "^4.1.0",
"@percy/cypress": "^2.3.0",
"@testing-library/dom": "^7.5.7",
"@testing-library/react": "^10.0.4",
"@typescript-eslint/eslint-plugin": "^1.6.0",
"@typescript-eslint/parser": "^1.6.0",
"cypress": "4.5.0",
Expand Down
Loading

0 comments on commit fcc6c3d

Please sign in to comment.