Skip to content

Commit

Permalink
fix(blade): Trigger native select events in dropdown/ file upload / D…
Browse files Browse the repository at this point in the history
…ate picker [FC-3151] (#2408)

* chore: trigger native events

* chore: trigger native events

* fix: fireNativeEvents in case of dropdown

* chore: remove extra code

* chore: change value of fireNative events to unknown

* chore: remove fireNativeEvent from autocompelte

* chore: remove add fireNative event on remove , dissmiss

* chore: remove logs

* chore: code clean up

* chore: minor change

* chore: minor changes

* chore: fix type

* chore: fire native event is not supported on react-native

* chore: change fire native event

* chore: review change useFireNativeEvent

* feat: added fireNativeEvent test

* chore: test for fireNativeEvent react native

* chore: update fireNativeEvent error

* chore: add tests in AutoComplete

* feat: add test for fire naitve event in DatePicker

* feat: added test case for FileUpload

* fix: failing build

* chore: update snap

* chore: add fixes to fireNativeEvent

* chore: change throwBladeError  to logger

* chore: fix errors

* chore: increase timeout
  • Loading branch information
tewarig authored Nov 27, 2024
1 parent f294a41 commit 2e94a3c
Show file tree
Hide file tree
Showing 18 changed files with 380 additions and 19 deletions.
7 changes: 6 additions & 1 deletion packages/blade/src/components/DatePicker/DatePicker.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ import type { StyledPropsBlade } from '~components/Box/styledProps';
import { getStyledProps } from '~components/Box/styledProps';
import { metaAttribute, MetaConstants } from '~utils/metaAttribute';
import { componentZIndices } from '~utils/componentZIndices';
import { fireNativeEvent } from '~utils/fireNativeEvent';

const DatePicker = <Type extends DateSelectionType = 'single'>({
selectionType,
Expand Down Expand Up @@ -71,6 +72,7 @@ const DatePicker = <Type extends DateSelectionType = 'single'>({
const isSingle = _selectionType === 'single';
const [_, forceRerender] = React.useReducer((x: number) => x + 1, 0);
const [selectedPreset, setSelectedPreset] = React.useState<DatesRangeValue | null>(null);
const referenceRef = React.useRef<HTMLButtonElement>(null);

const [_picker, setPicker] = useControllableState<PickerType>({
defaultValue: defaultPicker,
Expand All @@ -97,6 +99,7 @@ const DatePicker = <Type extends DateSelectionType = 'single'>({
defaultValue,
onChange: (date) => {
onChange?.(date as never);
fireNativeEvent(referenceRef, ['input']);
if (isSingle) return;
// sync selected preset with value
setSelectedPreset(date as DatesRangeValue);
Expand Down Expand Up @@ -124,6 +127,7 @@ const DatePicker = <Type extends DateSelectionType = 'single'>({
const handleApply = (): void => {
if (isSingle) {
onChange?.(controlledValue);
fireNativeEvent(referenceRef, ['change']);
setOldValue(controlledValue);
onApply?.(controlledValue);
close();
Expand All @@ -132,6 +136,7 @@ const DatePicker = <Type extends DateSelectionType = 'single'>({
// only apply if both dates are selected
if (hasBothDatesSelected) {
onChange?.(controlledValue);
fireNativeEvent(referenceRef, ['change']);
setOldValue(controlledValue);
onApply?.(controlledValue);
close();
Expand All @@ -140,14 +145,14 @@ const DatePicker = <Type extends DateSelectionType = 'single'>({

const handleCancel = (): void => {
setControlledValue(oldValue);
fireNativeEvent(referenceRef, ['change']);
setPickedDate(null);
close();
};

const isMobile = useIsMobile();
const defaultInitialFocusRef = React.useRef<HTMLButtonElement>(null);
const titleId = useId('datepicker-title');
const referenceRef = React.useRef<HTMLButtonElement>(null);
const {
context,
refs,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,61 @@
import React, { useEffect, useRef } from 'react';
import dayjs from 'dayjs';
import userEvent from '@testing-library/user-event';
import { waitFor } from '@testing-library/react';
import { DatePicker as DatePickerComponent } from '..';
import { Box } from '~components/Box';
import renderWithTheme from '~utils/testing/renderWithTheme.web';

describe('<DatePicker/> ', () => {
jest.setTimeout(10000);
it('should fire native events like input and change', async () => {
const handleInput = jest.fn();
const handleChange = jest.fn();

const DatePicker = (): React.ReactElement => {
const ref = useRef<HTMLElement>(null);

const addEventListeners = (): void => {
if (ref.current) {
ref.current.addEventListener('input', handleInput);
ref.current.addEventListener('change', handleChange);
}
};

const removeEventListeners = (): void => {
if (ref.current) {
ref.current.removeEventListener('input', handleInput);
ref.current.removeEventListener('change', handleChange);
}
};

useEffect(() => {
addEventListeners();
return removeEventListeners;
}, []);

return (
<Box ref={ref}>
<DatePickerComponent accessibilityLabel="Select Date" />
</Box>
);
};

const user = userEvent.setup();
const { getByRole, queryByText } = renderWithTheme(<DatePicker />);

const input = getByRole('combobox', { name: /Select Date/i });
await user.click(input);

await waitFor(() => expect(queryByText('Sun')).toBeVisible());

const dateToSelect = dayjs().add(1, 'day');
const date = getByRole('button', { name: dateToSelect.format('DD MMMM YYYY') });
await user.click(date);

const applyButton = getByRole('button', { name: /Apply/i });
await user.click(applyButton);
expect(handleChange).toBeCalled();
expect(handleInput).toBeCalled();
});
});
6 changes: 5 additions & 1 deletion packages/blade/src/components/Dropdown/useDropdown.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@ import type { DropdownProps } from './types';

import { dropdownComponentIds } from './dropdownComponentIds';
import type { FormInputHandleOnKeyDownEvent } from '~components/Form/FormTypes';
import { isReactNative } from '~utils';
import { isReactNative, isBrowser } from '~utils';
import type { ContainerElementType } from '~utils/types';
import { fireNativeEvent } from '~utils/fireNativeEvent';

// eslint-disable-next-line @typescript-eslint/no-empty-function
const noop = (): void => {};
Expand Down Expand Up @@ -355,6 +356,9 @@ const useDropdown = (): UseDropdownReturnValue => {

const optionValues = options.map((option) => option.value);
ensureScrollVisiblity(updatedIndex, rest.actionListItemRef.current, optionValues);
if (isBrowser()) {
fireNativeEvent(rest.actionListItemRef as React.RefObject<HTMLElement>, ['change', 'input']);
}
};

/**
Expand Down
5 changes: 5 additions & 0 deletions packages/blade/src/components/FileUpload/FileUpload.web.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ import { makeAccessible } from '~utils/makeAccessible';
import { formHintLeftLabelMarginLeft } from '~components/Input/BaseInput/baseInputTokens';
import { useMergeRefs } from '~utils/useMergeRefs';
import { useControllableState } from '~utils/useControllable';
import { fireNativeEvent } from '~utils/fireNativeEvent';

const _FileUpload: React.ForwardRefRenderFunction<BladeElementRef, FileUploadProps> = (
{
Expand Down Expand Up @@ -158,6 +159,7 @@ const _FileUpload: React.ForwardRefRenderFunction<BladeElementRef, FileUploadPro
if (!hasValidationErrors) {
handleFilesChange(droppedFiles);
onDrop?.({ name, fileList: allFiles });
fireNativeEvent(inputRef, ['change', 'input']);
}
};

Expand Down Expand Up @@ -307,6 +309,7 @@ const _FileUpload: React.ForwardRefRenderFunction<BladeElementRef, FileUploadPro
const newFiles = selectedFiles.filter(({ id }) => id !== selectedFiles[0].id);
setSelectedFiles(() => newFiles);
onRemove?.({ file: selectedFiles[0] });
fireNativeEvent(inputRef, ['change', 'input']);
}}
onReupload={() => {
const newFiles = selectedFiles.filter(({ id }) => id !== selectedFiles[0].id);
Expand Down Expand Up @@ -368,6 +371,7 @@ const _FileUpload: React.ForwardRefRenderFunction<BladeElementRef, FileUploadPro
const newFiles = selectedFiles.filter(({ id }) => id !== file.id);
setSelectedFiles(() => newFiles);
onRemove?.({ file });
fireNativeEvent(inputRef, ['change', 'input']);
}}
onReupload={() => {
const newFiles = selectedFiles.filter(({ id }) => id !== file.id);
Expand All @@ -386,6 +390,7 @@ const _FileUpload: React.ForwardRefRenderFunction<BladeElementRef, FileUploadPro
const newFiles = selectedFiles.filter(({ id }) => id !== file.id);
setSelectedFiles(() => newFiles);
onDismiss?.({ file });
fireNativeEvent(inputRef, ['change', 'input']);
}}
onPreview={onPreview}
/>
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import React from 'react';
import React, { useEffect, useRef } from 'react';
import userEvent from '@testing-library/user-event';
import { FileUpload } from '../FileUpload';
import { Box } from '~components/Box';
import renderWithTheme from '~utils/testing/renderWithTheme.web';
import assertAccessible from '~utils/testing/assertAccessible.web';

Expand Down Expand Up @@ -102,4 +104,56 @@ describe('<FileUpload />', () => {

expect(getByTestId('file-upload-test')).toBeTruthy();
});
it('Should fire native events like input and change', async () => {
const blob = new Blob(['']);
const filename = 'my-image.png';
const file = new File([blob], filename, {
type: 'image/png',
});
const user = userEvent.setup();
const handleInput = jest.fn();
const handleChange = jest.fn();

const DatePicker = (): React.ReactElement => {
const ref = useRef<HTMLElement>(null);
const addEventListeners = (): void => {
if (ref.current) {
ref.current.addEventListener('input', handleInput);
ref.current.addEventListener('change', handleChange);
}
};

const removeEventListeners = (): void => {
if (ref.current) {
ref.current.removeEventListener('input', handleInput);
ref.current.removeEventListener('change', handleChange);
}
};

useEffect(() => {
addEventListeners();
return removeEventListeners;
}, []);
return (
<Box ref={ref}>
<FileUpload
uploadType="single"
label="Upload GST certificate"
helpText="Upload .jpg, .jpeg, or .png file only"
accept="image/*"
name="single-file-upload-input"
/>
</Box>
);
};
const { getByText } = renderWithTheme(<DatePicker />);

const input = getByText('Drag files here or').closest('div')?.querySelector('input');

await user.upload(input as HTMLElement, file);
expect(getByText(filename)).toBeVisible();

expect(handleChange).toBeCalled();
expect(handleInput).toBeCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -134,7 +134,6 @@ const useAutoComplete = ({
}
props.onChange?.({ name: props.name, values });
};

return {
onSelectionChange,
onTriggerKeydown,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,10 @@ import React from 'react';
import { BaseInput } from '../BaseInput';
import type { BaseInputProps } from '../BaseInput';
import { InputChevronIcon } from './InputChevronIcon';
import type { BaseDropdownInputTriggerProps } from './types';
import type { BaseDropdownInputTriggerProps, useControlledDropdownInputProps } from './types';
import isEmpty from '~utils/lodashButBetter/isEmpty';
import { useDropdown } from '~components/Dropdown/useDropdown';
import { isReactNative } from '~utils';
import { isReactNative, isBrowser } from '~utils';
import { getActionListContainerRole } from '~components/ActionList/getA11yRoles';
import { MetaConstants } from '~utils/metaAttribute';
import { getTagsGroup } from '~components/Tag/getTagsGroup';
Expand All @@ -18,19 +18,9 @@ import {
validationStateToInputTrailingIconMap,
} from '~components/Table/tokens';
import { useTableEditableCell } from '~components/Table/TableEditableCellContext';
import { fireNativeEvent } from '~utils/fireNativeEvent';

const useControlledDropdownInput = (
props: Pick<
BaseDropdownInputTriggerProps,
| 'onChange'
| 'name'
| 'value'
| 'defaultValue'
| 'onInputValueChange'
| 'syncInputValueWithSelection'
| 'isSelectInput'
>,
): void => {
const useControlledDropdownInput = (props: useControlledDropdownInputProps): void => {
const isFirstRender = useFirstRender();
const {
changeCallbackTriggerer,
Expand Down Expand Up @@ -116,6 +106,9 @@ const useControlledDropdownInput = (
name: props.name,
values: getValuesArrayFromIndices(),
});
if (isBrowser()) {
fireNativeEvent(props.triggererRef, ['change', 'input']);
}
}
// eslint-disable-next-line react-hooks/exhaustive-deps
}, [changeCallbackTriggerer]);
Expand Down Expand Up @@ -176,6 +169,7 @@ const _BaseDropdownInputTrigger = (
defaultValue: props.defaultValue,
syncInputValueWithSelection: props.syncInputValueWithSelection,
isSelectInput: props.isSelectInput,
triggererRef,
});

const getValue = (): string | undefined => {
Expand Down
Loading

0 comments on commit 2e94a3c

Please sign in to comment.