Skip to content
Merged
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
6 changes: 6 additions & 0 deletions .js.env.example
Original file line number Diff line number Diff line change
Expand Up @@ -188,6 +188,12 @@ export MM_PERPS_GTM_MODAL_ENABLED="true"
export MM_PERPS_ORDER_BOOK_ENABLED="true"
export MM_PERPS_FEEDBACK_ENABLED="true"
export MM_PERPS_MYX_PROVIDER_ENABLED="true"
export MM_PERPS_MYX_APP_ID_TESTNET=""
export MM_PERPS_MYX_API_SECRET_TESTNET=""
export MM_PERPS_MYX_BROKER_ADDRESS_TESTNET=""
export MM_PERPS_MYX_APP_ID_MAINNET=""
export MM_PERPS_MYX_API_SECRET_MAINNET=""
export MM_PERPS_MYX_BROKER_ADDRESS_MAINNET=""
# HIP-3 Feature Flags (remote override with local fallback)
export MM_PERPS_HIP3_ENABLED="true"
export MM_PERPS_HIP3_ALLOWLIST_MARKETS="" # Allowlist: Empty = enable all markets. Examples: "xyz:XYZ100,xyz:TSLA" or "xyz:*,abc:TSLA"
Expand Down
48 changes: 48 additions & 0 deletions app/__mocks__/@myx-trade/sdk.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,54 @@ class MyxClient {
}
}

// SDK enums (mirrored from the real SDK to support adapter tests)
const Direction = { LONG: 0, SHORT: 1 };
const DirectionEnum = { Long: 0, Short: 1 };
const OrderTypeEnum = { Market: 0, Limit: 1, Stop: 2, Conditional: 3 };
const OperationEnum = { Increase: 0, Decrease: 1 };
const OrderStatusEnum = { Cancelled: 1, Expired: 2, Successful: 9 };
const ExecTypeEnum = {
Market: 1,
Limit: 2,
TP: 3,
SL: 4,
ADL: 5,
Liquidation: 6,
};
const TradeFlowTypeEnum = {
Increase: 0,
Decrease: 1,
AddMargin: 2,
RemoveMargin: 3,
CancelOrder: 4,
ADL: 5,
Liquidation: 6,
MarketClose: 7,
EarlyClose: 8,
AddTPSL: 9,
SecurityDeposit: 10,
TransferToWallet: 11,
MarginAccountDeposit: 12,
ReferralReward: 13,
};
const TriggerType = { None: 0, TP: 1, SL: 2 };
const OrderType = { Market: 0, Limit: 1 };
const OperationType = { Increase: 0, Decrease: 1 };
const OrderStatus = { Pending: 0, Cancelled: 1, Expired: 2, Filled: 9 };
const TimeInForce = { IOC: 0 };

module.exports = {
MyxClient,
Direction,
DirectionEnum,
OrderTypeEnum,
OperationEnum,
OrderStatusEnum,
ExecTypeEnum,
TradeFlowTypeEnum,
TriggerType,
OrderType,
OperationType,
OrderStatus,
TimeInForce,
};
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ const BottomSheetHeader: React.FC<BottomSheetHeaderProps> = ({
iconName={IconName.ArrowLeft}
iconColor={IconColor.Default}
onPress={onBack}
size={ButtonIconSizes.Lg}
size={ButtonIconSizes.Md}
{...backButtonProps}
/>
);
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
// Third party dependencies.
import { StyleSheet, TextStyle } from 'react-native';
import { Platform, StyleSheet, TextStyle } from 'react-native';

// External dependencies.
import { Theme } from '../../../../../../util/theme/models';
Expand All @@ -19,8 +19,19 @@ import { InputStyleSheetVars } from './Input.types';
*/
const styleSheet = (params: { theme: Theme; vars: InputStyleSheetVars }) => {
const { theme, vars } = params;
const { style, textVariant, isDisabled, isStateStylesDisabled, isFocused } =
vars;
const {
style,
textVariant,
isDisabled,
isStateStylesDisabled,
isFocused,
value = '',
placeholder,
} = vars;

const hasPlaceholder = placeholder != null && placeholder !== '';
const isPlaceholderVisible =
hasPlaceholder && (value === '' || value == null);

const stateObj = isStateStylesDisabled
? {
Expand All @@ -45,6 +56,7 @@ const styleSheet = (params: { theme: Theme; vars: InputStyleSheetVars }) => {
fontWeight: theme.typography[textVariant].fontWeight,
fontSize: theme.typography[textVariant].fontSize,
letterSpacing: theme.typography[textVariant].letterSpacing,
...(Platform.OS === 'ios' && isPlaceholderVisible && { lineHeight: 0 }),
},
style,
) as TextStyle,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,43 +1,107 @@
// Third party dependencies.
import React from 'react';
import { shallow } from 'enzyme';
import { fireEvent, render } from '@testing-library/react-native';

// External dependencies.
import { TextVariant } from '../../../../Texts/Text';
import { mockTheme } from '../../../../../../util/theme';
import { ThemeContext, mockTheme } from '../../../../../../util/theme';

// Internal dependencies.
import Input from './Input';
import { INPUT_TEST_ID } from './Input.constants';

const renderWithTheme = (ui: React.ReactElement) =>
render(<ThemeContext.Provider value={mockTheme}>{ui}</ThemeContext.Provider>);

const getStyleProp = (
style: Record<string, unknown> | Record<string, unknown>[] | undefined,
key: string,
): unknown => {
if (!style) return undefined;
const arr = Array.isArray(style) ? style : [style];
for (let i = arr.length - 1; i >= 0; i--) {
const val = (arr[i] as Record<string, unknown>)?.[key];
if (val !== undefined) return val;
}
return undefined;
};

describe('Input', () => {
it('should render correctly', () => {
const wrapper = shallow(<Input />);
expect(wrapper).toMatchSnapshot();
});
it('should render Input with the correct TextVariant', () => {
const wrapper = shallow(<Input textVariant={TextVariant.HeadingSM} />);
const inputComponent = wrapper.findWhere(
(node) => node.prop('testID') === INPUT_TEST_ID,
beforeEach(() => {
jest.clearAllMocks();
});

it('renders input with testID', () => {
const { getByTestId } = renderWithTheme(<Input value="" />);

expect(getByTestId(INPUT_TEST_ID)).toBeOnTheScreen();
});

it('applies TextVariant typography when textVariant provided', () => {
const { getByTestId } = renderWithTheme(
<Input value="" textVariant={TextVariant.HeadingSM} />,
);
expect(inputComponent.props().style.fontSize).toBe(
mockTheme.typography.sHeadingSM.fontSize,

const input = getByTestId(INPUT_TEST_ID);
const fontSize = getStyleProp(input.props.style, 'fontSize');

expect(fontSize).toBe(mockTheme.typography.sHeadingSM.fontSize);
});

it('sets editable false and opacity 0.5 when isDisabled', () => {
const { getByTestId } = renderWithTheme(<Input value="" isDisabled />);

const input = getByTestId(INPUT_TEST_ID);

expect(input.props.editable).toBe(false);
expect(getStyleProp(input.props.style, 'opacity')).toBe(0.5);
});

it('keeps opacity 1 when isStateStylesDisabled', () => {
const { getByTestId } = renderWithTheme(
<Input value="" isStateStylesDisabled />,
);

const input = getByTestId(INPUT_TEST_ID);

expect(getStyleProp(input.props.style, 'opacity')).toBe(1);
});
it('should render the correct disabled state when disabled = true', () => {
const wrapper = shallow(<Input isDisabled />);
const inputComponent = wrapper.findWhere(
(node) => node.prop('testID') === INPUT_TEST_ID,

it('applies lineHeight 0 when placeholder is provided and value is empty', () => {
const { getByTestId } = renderWithTheme(
<Input value="" placeholder="Enter text" />,
);
expect(inputComponent.props().editable).toBe(false);
expect(inputComponent.props().style.opacity).toBe(0.5);

const input = getByTestId(INPUT_TEST_ID);

expect(getStyleProp(input.props.style, 'lineHeight')).toBe(0);
});

it('should not render state styles when isStateStylesDisabled = true', () => {
const wrapper = shallow(<Input isStateStylesDisabled />);
const inputComponent = wrapper.findWhere(
(node) => node.prop('testID') === INPUT_TEST_ID,
it('omits lineHeight when value is empty but no placeholder', () => {
const { getByTestId } = renderWithTheme(<Input value="" />);

const input = getByTestId(INPUT_TEST_ID);

expect(getStyleProp(input.props.style, 'lineHeight')).toBeUndefined();
});

it('omits lineHeight when value is non-empty', () => {
const { getByTestId } = renderWithTheme(<Input value="hello" />);

const input = getByTestId(INPUT_TEST_ID);

expect(getStyleProp(input.props.style, 'lineHeight')).toBeUndefined();
});

it('calls onChangeText when text changes', () => {
const onChangeText = jest.fn();
const { getByTestId } = renderWithTheme(
<Input value="" onChangeText={onChangeText} />,
);
expect(inputComponent.props().style.opacity).toBe(1);

const input = getByTestId(INPUT_TEST_ID);
fireEvent.changeText(input, 'a');

expect(onChangeText).toHaveBeenCalledWith('a');
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,11 @@

// Third party dependencies.
import React, { useCallback, useState } from 'react';
import { TextInput } from 'react-native';
import {
TextInput,
NativeSyntheticEvent,
TextInputFocusEventData,
} from 'react-native';

// External dependencies.
import { useStyles } from '../../../../../hooks';
Expand Down Expand Up @@ -30,6 +34,9 @@ const Input = React.forwardRef<TextInput, InputProps>(
onBlur,
onFocus,
autoFocus = true,
value,
placeholder,
onChangeText,
...props
},
ref,
Expand All @@ -42,37 +49,38 @@ const Input = React.forwardRef<TextInput, InputProps>(
isStateStylesDisabled,
isDisabled,
isFocused,
value: value ?? '',
placeholder,
});

const onBlurHandler = useCallback(
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: any) => {
(e: NativeSyntheticEvent<TextInputFocusEventData>) => {
if (!isDisabled) {
setIsFocused(false);
onBlur?.(e);
}
},
[isDisabled, setIsFocused, onBlur],
[isDisabled, onBlur],
);

const onFocusHandler = useCallback(
// TODO: Replace "any" with type
// eslint-disable-next-line @typescript-eslint/no-explicit-any
(e: any) => {
(e: NativeSyntheticEvent<TextInputFocusEventData>) => {
if (!isDisabled) {
setIsFocused(true);
onFocus?.(e);
}
},
[isDisabled, setIsFocused, onFocus],
[isDisabled, onFocus],
);

return (
<TextInput
testID={INPUT_TEST_ID}
placeholderTextColor={theme.colors.text.alternative}
{...props}
placeholder={placeholder}
value={value}
onChangeText={onChangeText}
style={styles.base}
editable={!isDisabled && !isReadonly}
autoFocus={autoFocus}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,10 +32,11 @@ export interface InputProps extends Omit<TextInputProps, 'editable'> {

/**
* Style sheet input parameters.
* Placeholder visibility (for lineHeight) is derived in the style sheet from value + placeholder.
*/
export type InputStyleSheetVars = Pick<
InputProps,
'style' | 'isStateStylesDisabled' | 'isDisabled'
'style' | 'isStateStylesDisabled' | 'isDisabled' | 'value' | 'placeholder'
> & {
isFocused: boolean;
textVariant: TextVariant;
Expand Down

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export interface TextFieldSearchProps extends TextFieldProps {
/**
* Optional prop to pass any additional props to the clear button.
*/
clearButtonProps?: ButtonIconProps;
clearButtonProps?: Partial<ButtonIconProps>;
/**
* Function to trigger when pressing the clear button.
* The clear button is automatically shown when the input has a value.
Expand Down
Loading
Loading