UNPKG

input-otp-native

Version:

One time passcode Input For React Native/Expo. Unstyled and fully customizable.

310 lines (299 loc) 12.8 kB
"use strict"; var _reactNative = require("react-native"); var React = _interopRequireWildcard(require("react")); var _reactNative2 = require("@testing-library/react-native"); var _input = require("./input.js"); var _jsxRuntime = require("react/jsx-runtime"); function _getRequireWildcardCache(e) { if ("function" != typeof WeakMap) return null; var r = new WeakMap(), t = new WeakMap(); return (_getRequireWildcardCache = function (e) { return e ? t : r; })(e); } function _interopRequireWildcard(e, r) { if (!r && e && e.__esModule) return e; if (null === e || "object" != typeof e && "function" != typeof e) return { default: e }; var t = _getRequireWildcardCache(r); if (t && t.has(e)) return t.get(e); var n = { __proto__: null }, a = Object.defineProperty && Object.getOwnPropertyDescriptor; for (var u in e) if ("default" !== u && {}.hasOwnProperty.call(e, u)) { var i = a ? Object.getOwnPropertyDescriptor(e, u) : null; i && (i.get || i.set) ? Object.defineProperty(n, u, i) : n[u] = e[u]; } return n.default = e, t && t.set(e, n), n; } afterEach(_reactNative2.cleanup); afterEach(() => { // Reset Platform.OS mock after each test _reactNative.Platform.OS = 'ios'; }); const onChangeMock = jest.fn(); const onCompleteMock = jest.fn(); const defaultRender = props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { testID: "otp-cells", "data-focused": props.isFocused, children: props.slots.map((slot, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { testID: `otp-cell-${index}`, style: { opacity: props.isFocused ? 1 : 0.5 }, children: slot.char && /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { children: slot.char }) }, index)) }); /** * Simulate typing on the input like a user would do by typing one character at a time. * * @param input - The input element to simulate typing on. * @param text - The text to simulate typing. */ const simulateTyping = (input, text) => { let accumulated = ''; for (const char of text) { accumulated += char; _reactNative2.fireEvent.changeText(input, accumulated); (0, _reactNative2.fireEvent)(input, 'keyPress', { nativeEvent: { key: char } }); } }; // Mock Platform.OS jest.mock('react-native/Libraries/Utilities/Platform', () => ({ OS: 'ios', select: jest.fn() })); describe('OTPInput', () => { beforeEach(() => { jest.clearAllMocks(); }); describe('Rendering', () => { test('renders correctly with default props', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); expect(input).toBeTruthy(); expect(input.props.inputMode).toBe('numeric'); expect(input.props.autoComplete).toBe('one-time-code'); }); test('sets correct autoComplete value for Android', async () => { _reactNative.Platform.OS = 'android'; (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); expect(input.props.autoComplete).toBe('sms-otp'); }); test('sets correct autoComplete value for iOS', async () => { _reactNative.Platform.OS = 'ios'; (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); expect(input.props.autoComplete).toBe('one-time-code'); }); test('renders correctly without render prop', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6 })); const input = await _reactNative2.screen.findByTestId('otp-input'); expect(input).toBeTruthy(); // The container should still be rendered const container = await _reactNative2.screen.findByTestId('otp-input-container'); expect(container).toBeTruthy(); // Container should have exactly two children: // 1. null from the render prop (React still counts this) // 2. The TextInput component const customContent = container.children.length; expect(customContent).toBe(2); }); test('renders with custom props', async () => { const placeholder = '******'; (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 4, placeholder: placeholder, inputMode: "text", render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); expect(input.props.placeholder).toBe(placeholder); expect(input.props.inputMode).toBe('text'); }); test('renders custom content using render prop', async () => { const customRender = props => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.View, { testID: "custom-content", children: props.slots.map((slot, index) => /*#__PURE__*/(0, _jsxRuntime.jsx)(_reactNative.Text, { children: slot.char || slot.placeholderChar }, index)) }); (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 4, render: customRender })); expect(await _reactNative2.screen.findByTestId('custom-content')).toBeTruthy(); }); }); describe('Interactions', () => { test('handles text input correctly', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, onComplete: onCompleteMock, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); simulateTyping(input, '123456'); expect(onChangeMock).toHaveBeenCalledWith('123456'); expect(onCompleteMock).toHaveBeenCalledWith('123456'); }); test('onComplete is called when maxLength is reached only', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, onComplete: onCompleteMock, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); simulateTyping(input, '12345'); expect(onChangeMock).toHaveBeenCalledWith('12345'); expect(onCompleteMock).not.toHaveBeenCalled(); simulateTyping(input, '123456'); expect(onChangeMock).toHaveBeenCalledWith('123456'); expect(onCompleteMock).toHaveBeenCalledWith('123456'); }); test('clears input on container press', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); simulateTyping(input, '123'); const container = await _reactNative2.screen.findByTestId('otp-input-container'); _reactNative2.fireEvent.press(container); expect(input.props.value).toBe(''); }); test('handles focus and blur events', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); const cells = await _reactNative2.screen.findByTestId('otp-cells'); (0, _reactNative2.fireEvent)(input, 'focus'); expect(cells.props['data-focused']).toBe(true); (0, _reactNative2.fireEvent)(input, 'blur'); expect(cells.props['data-focused']).toBe(false); }); }); describe('Validation', () => { test('respects pattern prop for input validation', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 6, pattern: "^[0-9]+$", render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); // Test invalid input simulateTyping(input, 'abc'); expect(onChangeMock).toHaveBeenCalledTimes(2); expect(onChangeMock).toHaveBeenCalledWith(''); expect(input.props.value).toBe(''); // Test valid input simulateTyping(input, '123'); expect(onChangeMock).toHaveBeenCalledWith('123'); expect(input.props.value).toBe('123'); }); test('respects maxLength prop', async () => { (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { onChange: onChangeMock, maxLength: 4, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); simulateTyping(input, '123456'); expect(input.props.value.length).toBeLessThanOrEqual(4); }); }); describe('Ref Methods', () => { test('setValue updates input value through ref', async () => { const ref = /*#__PURE__*/React.createRef(); (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { ref: ref, onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); await (0, _reactNative2.act)(async () => { ref.current?.setValue('1'); }); expect(input.props.value).toBe('1'); expect(onChangeMock).toHaveBeenCalledWith('1'); }); test('focus method focuses the input through ref', async () => { const ref = /*#__PURE__*/React.createRef(); (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { ref: ref, onChange: onChangeMock, maxLength: 6, render: defaultRender })); const cells = await _reactNative2.screen.findByTestId('otp-cells'); const input = await _reactNative2.screen.findByTestId('otp-input'); await (0, _reactNative2.act)(async () => { ref.current?.focus(); // we need to call onFocus by fireEvent because test environment do not support onFocus // see the issue https://github.com/callstack/react-native-testing-library/issues/1069 (0, _reactNative2.fireEvent)(input, 'focus'); }); expect(cells.props['data-focused']).toBe(true); }); test('blur method blurs the input through ref', async () => { const ref = /*#__PURE__*/React.createRef(); (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { ref: ref, onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); const cells = await _reactNative2.screen.findByTestId('otp-cells'); // First focus await (0, _reactNative2.act)(async () => { ref.current?.focus(); // we need to call onFocus by fireEvent because test environment do not support onFocus // see the issue https://github.com/callstack/react-native-testing-library/issues/1069 (0, _reactNative2.fireEvent)(input, 'focus'); }); expect(cells.props['data-focused']).toBe(true); // Then blur await (0, _reactNative2.act)(async () => { ref.current?.blur(); // we need to call onBlur by fireEvent because test environment do not support onBlur // see the issue https://github.com/callstack/react-native-testing-library/issues/1069 (0, _reactNative2.fireEvent)(input, 'blur'); }); expect(cells.props['data-focused']).toBe(false); }); test('clear method clears the input through ref', async () => { const ref = /*#__PURE__*/React.createRef(); (0, _reactNative2.render)(/*#__PURE__*/(0, _jsxRuntime.jsx)(_input.OTPInput, { ref: ref, onChange: onChangeMock, maxLength: 6, render: defaultRender })); const input = await _reactNative2.screen.findByTestId('otp-input'); // First set a value await (0, _reactNative2.act)(async () => { ref.current?.setValue('1'); }); expect(input.props.value).toBe('1'); // Then clear it await (0, _reactNative2.act)(async () => { ref.current?.clear(); }); expect(input.props.value).toBe(''); }); }); }); //# sourceMappingURL=input.test.js.map