Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2026 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.

* Adobe permits you to use and modify this file solely in accordance with
* the terms of the Adobe license agreement accompanying it.
*************************************************************************/

import { fireEvent } from '@testing-library/react-native';
import Password from '../../src/components/Password';
import { ReactTestInstance } from 'react-test-renderer';
import { renderComponent, DEFAULT_ERROR_MESSAGE } from '../utils';

const field = {
name: 'password',
id: 'password',
label: {
value: 'password field',
},
fieldType: 'password',
placeholder: 'enter password field',
visible: true,
required: true,
};
const helper = renderComponent(Password);

describe('Password Field', () => {

test('value entered by user in password field is set in model', async () => {
const f = {
...field,
};
const { renderResponse, element } = await helper(f);
const input: ReactTestInstance = await renderResponse.findByPlaceholderText(f.placeholder);
const inputVal = 'secret123';
fireEvent.changeText(input, inputVal);
const state = element.getState();
expect(state.value).toEqual(inputVal);
});

test('the input is rendered as a masked (secure) entry by default', async () => {
const { renderResponse } = await helper(field);
const input: ReactTestInstance = await renderResponse.findByPlaceholderText(field.placeholder);
expect(input.props.secureTextEntry).toBe(true);
});

test('the show/hide toggle reveals and re-masks the value', async () => {
const { renderResponse } = await helper(field);
let input: ReactTestInstance = await renderResponse.findByPlaceholderText(field.placeholder);
expect(input.props.secureTextEntry).toBe(true);

const toggle = renderResponse.getByTestId(`${field.id}-toggle`);
fireEvent.press(toggle);
input = await renderResponse.findByPlaceholderText(field.placeholder);
expect(input.props.secureTextEntry).toBe(false);

fireEvent.press(toggle);
input = await renderResponse.findByPlaceholderText(field.placeholder);
expect(input.props.secureTextEntry).toBe(true);
});

test('the toggle is not rendered when fd:showHidePassword is false', async () => {
const f = {
...field,
properties: {
'fd:showHidePassword': false,
},
};
const { renderResponse } = await helper(f);
expect(renderResponse.queryByTestId(`${f.id}-toggle`)).toBeNull();
});

test('it should handle visible property', async () => {
const f = {
...field,
visible: false,
};
const { renderResponse } = await helper(f);
expect(renderResponse.queryByText(f.label.value)).toBeNull();
});

test('help text content changes when field becomes invalid', async () => {
const f = {
...field,
description: 'some description',
};
const { renderResponse, element } = await helper(f);
let description = renderResponse.queryByText('some description');
expect(description).not.toBeNull();

element.value = null;
let error = renderResponse.queryByTestId(`${f.id}-error`);
description = renderResponse.queryByText('some description');
expect(error).not.toBeNull();
expect(description).toBeNull();
});

});
86 changes: 86 additions & 0 deletions packages/react-native-components/src/components/Password.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2026 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.

* Adobe permits you to use and modify this file solely in accordance with
* the terms of the Adobe license agreement accompanying it.
*************************************************************************/

import React, { useCallback, useState } from 'react';
import { Input, FormControl, Pressable, Text } from 'native-base';
import { PROPS, INPUT } from '../utils/types';
import withRuleEngine from '../shared/withRuleEngine';

const PasswordComponent = function (props: PROPS) {
const { isError, required, label, errorMessage, description } = props;
const [revealed, setRevealed] = useState(false);

// Mirrors PasswordInput.isShowHidePasswordEnabled() (default true) from the core component.
const showHidePasswordEnabled = (props as any).properties?.['fd:showHidePassword'] !== false;

const changeHandler = useCallback((event: any) => {
props.dispatchChange(event);
}, [props.dispatchChange]);

const blurHandler = useCallback((event: any) => {
props.dispatchBlur(event);
}, [props.dispatchBlur]);

const focusHandler = useCallback((event: any) => {
props.dispatchFocus(event);
}, [props.dispatchFocus]);

const toggleLabel = revealed ? 'Hide password' : 'Show password';

const inputProps: INPUT = {
placeholder: props.placeholder || '',
value: props.value == null ? '' : props.value,
onChangeText: changeHandler,
onBlur: blurHandler,
onFocus: focusHandler,
isReadOnly: props.readOnly === true,
isRequired: props.required === true,
isDisabled: props.enabled === false,
KeyboardTypeOptions: 'default',
maxLength: props.maxLength,
minLength: props.minLength,
pattern: props.pattern,
type: revealed ? 'text' : 'password'
};

return (
<FormControl isInvalid={isError} isRequired={required} {...props.layout}>
{label?.visible && <FormControl.Label>{label?.value}</FormControl.Label>}
<Input
{...inputProps as any}
InputRightElement={showHidePasswordEnabled ? (
<Pressable
onPress={() => setRevealed((prev) => !prev)}
accessibilityRole="button"
accessibilityLabel={toggleLabel}
accessibilityState={{ selected: revealed }}
testID={`${props.id}-toggle`}
>
<Text mx={3}>{toggleLabel}</Text>
</Pressable>
) : undefined}
/>
{errorMessage && <FormControl.ErrorMessage testID={`${props.id}-error`}>{errorMessage}</FormControl.ErrorMessage>}
{description && !errorMessage && <FormControl.HelperText testID={`${props.id}-description`}>{description}</FormControl.HelperText>}
</FormControl>
);
};

export default withRuleEngine(PasswordComponent);
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ const TextFieldComponent = function (props: PROPS) {
maxLength: props.maxLength,
minLength: props.minLength,
pattern: props.pattern,
type: props[':type'] === 'password-input' ? 'password' : 'text'
type: 'text'
};
const Comp = props.fieldType === 'multiline-input' ? TextArea: Input;

Expand Down
2 changes: 2 additions & 0 deletions packages/react-native-components/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import PlainText from "./components/PlainText";
import RadioGroup from "./components/RadioGroup";
import Repeater from "./components/Repeater";
import TextField from "./components/TextField";
import Password from "./components/Password";
import DatePicker from "./components/DatePicker";
import Themes from './utils/theme';

Expand All @@ -48,6 +49,7 @@ export {
RadioGroup,
Repeater,
TextField,
Password,
DatePicker,
Themes
}
3 changes: 2 additions & 1 deletion packages/react-native-components/src/utils/mappings.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@

import Form from '../components/Form';
import TextField from '../components/TextField';
import Password from '../components/Password';
import NumberField from '../components/NumberField';
import Panel from '../components/Panel';
import Button from '../components/Button';
Expand All @@ -44,7 +45,7 @@ export default {
'checkbox-group': CheckboxGroup,
'radio-group': RadioGroup,
'drop-down': DropDown,
'password-input': TextField,
password: Password,
'plain-text': PlainText,
'file-input': FileUpload,
'date-input': DatePicker
Expand Down
1 change: 1 addition & 0 deletions packages/react-vanilla-components/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ The document provides detailed information on React Vanilla Components(HTML5 bas
- Image
- Number Field
- Panel
- Password
- Plain Text
- Radio Group
- ReCAPTCHA
Expand Down
Loading
Loading