Просмотр исходного кода

test(ui): Mock out react-date-range in tests (#37433)

Malachi Willey 2 лет назад
Родитель
Сommit
a1c7a343af

+ 107 - 0
static/app/__mocks__/react-date-range.tsx

@@ -0,0 +1,107 @@
+import type {CalendarProps, DateRangeProps, Range, RangeKeyDict} from 'react-date-range';
+import format from 'date-fns/format';
+
+/**
+ * Auto-mock of the react-date-range library for jest
+ *
+ * We mock out these components in tests because they are heavy (causes timeouts),
+ * difficult to interact with, and we don't need to validate their behavior.
+ *
+ * If your test is dependent on this library's functionality, you may unmock with
+ * jest.unmock('react-date-range')
+ */
+
+type DatePickerInputProps = {
+  'data-test-id': string;
+  date?: Date;
+  onChange?: (date: Date) => void;
+};
+
+type DateRangeInputsProps = {
+  onChange: (range: Range) => void;
+  range: Range;
+};
+
+const DatePickerInput = ({date, onChange, ...props}: DatePickerInputProps) => {
+  return (
+    <input
+      type="date"
+      value={date ? format(date, 'yyyy-MM-dd') : ''}
+      onChange={e => {
+        const newDate = new Date(e.target.value + 'T00:00:00');
+
+        onChange?.(newDate);
+      }}
+      {...props}
+    />
+  );
+};
+
+/**
+ * Replaces the react-date-range Calendar component with a date input
+ *
+ * Example usage:
+ *
+ * const datePicker = screen.getByTestId('date-picker')
+ * fireEvent.change(datePicker, {target: {value: '2022-01-01'}})
+ */
+export const Calendar = ({date, onChange}: CalendarProps) => {
+  return <DatePickerInput data-test-id="date-picker" date={date} onChange={onChange} />;
+};
+
+const DateRangeInputs = ({range, onChange}: DateRangeInputsProps) => {
+  return (
+    <div data-test-id={`date-range-${range.key}`}>
+      <DatePickerInput
+        data-test-id={`date-range-${range.key}-from`}
+        date={range.startDate}
+        onChange={date => {
+          onChange({startDate: date, endDate: range.endDate ?? date, key: range.key});
+        }}
+      />
+      <DatePickerInput
+        data-test-id={`date-range-${range.key}-to`}
+        date={range.endDate}
+        onChange={date => {
+          onChange({endDate: date, startDate: range.startDate ?? date, key: range.key});
+        }}
+      />
+    </div>
+  );
+};
+
+/**
+ * Replaces the react-date-range DateRange component with multiple date inputs
+ * Will render a pair of date inputs for each range
+ *
+ * Example usage:
+ *
+ * const datePickerFrom = screen.getByTestId(date-range-primary-from')
+ * const datePickerTo = screen.getByTestId('date-range-primary-to')
+ * fireEvent.change(datePickerFrom, {target: {value: '2022-01-01'}})
+ * fireEvent.change(datePickerTo, {target: {value: '2022-01-02'}})
+ */
+export const DateRange = ({ranges, onChange}: DateRangeProps) => {
+  return (
+    <div data-test-id="date-range-picker">
+      {ranges?.map(range => (
+        <DateRangeInputs
+          range={range}
+          onChange={({startDate, endDate, key}) => {
+            const rangesByKey = ranges?.reduce(
+              (acc, nextRange) => ({
+                ...acc,
+                [nextRange?.key ?? '']:
+                  nextRange.key === key ? {...nextRange, startDate, endDate} : nextRange,
+              }),
+              {} as RangeKeyDict
+            );
+
+            onChange?.(rangesByKey);
+          }}
+          key={range.key}
+        />
+      )) ?? null}
+    </div>
+  );
+};

+ 2 - 2
static/app/components/calendar/datePicker.tsx

@@ -4,7 +4,7 @@ import CalendarStylesWrapper from './calendarStylesWrapper';
 
 export type DatePickerProps = CalendarProps;
 
-const DateRangePicker = (props: DatePickerProps) => {
+const DatePicker = (props: DatePickerProps) => {
   return (
     <CalendarStylesWrapper>
       <Calendar {...props} />
@@ -12,4 +12,4 @@ const DateRangePicker = (props: DatePickerProps) => {
   );
 };
 
-export default DateRangePicker;
+export default DatePicker;

+ 0 - 345
tests/js/spec/components/organizations/timeRangeSelector/dateRange.spec.jsx

@@ -1,345 +0,0 @@
-import MockDate from 'mockdate';
-
-import {mountWithTheme} from 'sentry-test/enzyme';
-
-import DateRange from 'sentry/components/organizations/timeRangeSelector/dateRange';
-import ConfigStore from 'sentry/stores/configStore';
-
-// 2017-10-14T02:38:00.000Z
-// 2017-10-17T02:38:00.000Z
-const start = new Date(1507948680000);
-const end = new Date(1508207880000); // National Pasta Day
-
-const getSelectedRange = wrapper => [
-  wrapper.find('.rdrStartEdge').closest('DayCell').find('.rdrDayNumber span').text(),
-  ...wrapper
-    .find('.rdrInRange')
-    .map(el => el.closest('DayCell').find('.rdrDayNumber span').text()),
-
-  wrapper.find('.rdrEndEdge').closest('DayCell').find('.rdrDayNumber span').text(),
-];
-
-function getTimeText(element) {
-  const valueRegex = /value="([0-9]{2}:[0-9]{2})"/;
-  return element.html().match(valueRegex)[1];
-}
-
-describe('DateRange', function () {
-  let wrapper;
-  const onChange = jest.fn();
-  const routerContext = TestStubs.routerContext();
-
-  beforeAll(function () {
-    MockDate.set(new Date('2017-10-16T23:41:20.000Z'));
-    ConfigStore.loadInitialData({
-      user: {options: {timezone: 'America/New_York'}},
-    });
-  });
-
-  afterAll(function () {
-    // reset mock date
-    MockDate.set(new Date(1508208080000));
-  });
-
-  describe('Local time', function () {
-    beforeEach(function () {
-      onChange.mockReset();
-    });
-    beforeEach(async function () {
-      wrapper = mountWithTheme(
-        <DateRange
-          start={start}
-          end={end}
-          showTimePicker
-          onChange={onChange}
-          onChangeUtc={jest.fn()}
-          organization={TestStubs.Organization()}
-        />,
-        routerContext
-      );
-
-      await tick();
-      await tick();
-      wrapper.update();
-    });
-
-    it('has the right max date', function () {
-      expect(wrapper.find('DateRangePicker').at(0).prop('maxDate')).toEqual(
-        new Date('2017-10-16T23:41:20.000Z')
-      );
-    });
-
-    it('has the right days selected', function () {
-      // start/end inputs
-      const startEndInputs = wrapper.find(
-        '.rdrDateRangeWrapper .rdrDateDisplayItem input'
-      );
-
-      expect(startEndInputs.at(0).prop('value')).toBe('Oct 13, 2017');
-      expect(startEndInputs.at(1).prop('value')).toBe('Oct 16, 2017');
-
-      expect(getSelectedRange(wrapper)).toEqual(['13', '14', '15', '16']);
-    });
-
-    it('can select a date (midnight)', function () {
-      wrapper.find('DayCell').at(0).simulate('mouseUp');
-
-      //
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-01T04:00:00.000Z'),
-        end: new Date('2017-10-02T03:59:59.000Z'),
-      });
-    });
-
-    it('changes start time for existing date', function () {
-      wrapper
-        .find('input[data-test-id="startTime"]')
-        .simulate('change', {target: {value: '11:00'}});
-
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-13T15:00:00.000Z'),
-        end: new Date('2017-10-17T02:38:00.000Z'),
-        hasDateRangeErrors: false,
-      });
-    });
-
-    it('changes end time for existing date', function () {
-      wrapper
-        .find('input[data-test-id="endTime"]')
-        .simulate('change', {target: {value: '12:00'}});
-
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-14T02:38:00.000Z'),
-        end: new Date('2017-10-16T16:00:00.000Z'),
-        hasDateRangeErrors: false,
-      });
-    });
-
-    it('does not change for bad start/end time', function () {
-      wrapper
-        .find('input[data-test-id="startTime"]')
-        .simulate('change', {target: {value: null}});
-
-      expect(onChange).not.toHaveBeenLastCalledWith();
-
-      wrapper
-        .find('input[data-test-id="endTime"]')
-        .simulate('change', {target: {value: null}});
-
-      expect(onChange).not.toHaveBeenLastCalledWith();
-    });
-
-    it('updates start time input only if not focused', async function () {
-      const time = start.getTime() + 60000;
-
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:38'
-      );
-
-      wrapper.find('input[data-test-id="startTime"]').simulate('focus');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({start: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened while the component still has focus, no update
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:38'
-      );
-
-      wrapper.find('input[data-test-id="startTime"]').simulate('blur');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({start: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened after the component lost focus, it updates
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:39'
-      );
-    });
-
-    it('updates end time input only if not focused', async function () {
-      const time = end.getTime() + 60000;
-
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:38');
-
-      wrapper.find('input[data-test-id="endTime"]').simulate('focus');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({end: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened while the component still has focus, no update
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:38');
-
-      wrapper.find('input[data-test-id="endTime"]').simulate('blur');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({end: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened after the component lost focus, it updates
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:39');
-    });
-  });
-
-  describe('UTC', function () {
-    beforeEach(function () {
-      onChange.mockReset();
-      wrapper = mountWithTheme(
-        <DateRange
-          start={start}
-          end={end}
-          showTimePicker
-          utc
-          onChange={onChange}
-          onChangeUtc={jest.fn()}
-          organization={TestStubs.Organization()}
-        />,
-        routerContext
-      );
-    });
-
-    it('has the right max date', function () {
-      expect(wrapper.find('DateRangePicker').at(0).prop('maxDate')).toEqual(
-        new Date('2017-10-16T23:41:20.000Z')
-      );
-    });
-
-    it('has the right days selected', function () {
-      // start/end inputs
-      const startEndInputs = wrapper.find(
-        '.rdrDateRangeWrapper .rdrDateDisplayItem input'
-      );
-
-      expect(startEndInputs.at(0).prop('value')).toBe('Oct 13, 2017');
-      expect(startEndInputs.at(1).prop('value')).toBe('Oct 16, 2017');
-
-      expect(getSelectedRange(wrapper)).toEqual(['13', '14', '15', '16']);
-    });
-
-    it('can select a date (midnight)', function () {
-      wrapper.find('DayCell').at(0).simulate('mouseUp');
-
-      //
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-01T04:00:00.000Z'),
-        end: new Date('2017-10-02T03:59:59.000Z'),
-      });
-    });
-
-    it('changes utc start time for existing date', function () {
-      wrapper
-        .find('input[data-test-id="startTime"]')
-        .simulate('change', {target: {value: '11:00'}});
-
-      // Initial start date  is 2017-10-13T22:38:00-0400
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-13T15:00:00.000Z'),
-        end: new Date('2017-10-17T02:38:00.000Z'),
-        hasDateRangeErrors: false,
-      });
-    });
-
-    it('changes utc end time for existing date', function () {
-      wrapper
-        .find('input[data-test-id="endTime"]')
-        .simulate('change', {target: {value: '12:00'}});
-
-      // Initial end time is 2017-10-16T22:38:00-0400
-      // Setting this to 12:00 means 2017-10-16T12:00-0400
-      expect(onChange).toHaveBeenLastCalledWith({
-        start: new Date('2017-10-14T02:38:00.000Z'),
-        end: new Date('2017-10-16T16:00:00.000Z'),
-        hasDateRangeErrors: false,
-      });
-    });
-
-    it('does not change for bad start/end time', function () {
-      wrapper
-        .find('input[data-test-id="startTime"]')
-        .simulate('change', {target: {value: null}});
-
-      expect(onChange).not.toHaveBeenLastCalledWith();
-
-      wrapper
-        .find('input[data-test-id="endTime"]')
-        .simulate('change', {target: {value: null}});
-
-      expect(onChange).not.toHaveBeenLastCalledWith();
-    });
-
-    it('updates utc start time input only if not focused', async function () {
-      // NOTE: the DateRange component initializes the time inputs with the local time
-      const time = start.getTime() + 60000;
-
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:38'
-      );
-
-      wrapper.find('input[data-test-id="startTime"]').simulate('focus');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({start: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened while the component still has focus, no update
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:38'
-      );
-
-      wrapper.find('input[data-test-id="startTime"]').simulate('blur');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({start: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened after the component lost focus, it updates
-      expect(getTimeText(wrapper.find('input[data-test-id="startTime"]'))).toEqual(
-        '22:39'
-      );
-    });
-
-    it('updates utc end time input only if not focused', async function () {
-      // NOTE: the DateRange component initializes the time inputs with the local time
-      const time = end.getTime() + 60000;
-
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:38');
-
-      wrapper.find('input[data-test-id="endTime"]').simulate('focus');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({end: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened while the component still has focus, no update
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:38');
-
-      wrapper.find('input[data-test-id="endTime"]').simulate('blur');
-      await tick();
-      wrapper.update();
-
-      wrapper.setProps({end: new Date(time)});
-      await tick();
-      wrapper.update();
-
-      // because the prop change happened after the component lost focus, it updates
-      expect(getTimeText(wrapper.find('input[data-test-id="endTime"]'))).toEqual('22:39');
-    });
-  });
-});

+ 64 - 3
tests/js/spec/components/organizations/timeRangeSelector/index.spec.jsx

@@ -1,4 +1,4 @@
-import {render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
+import {fireEvent, render, screen, userEvent} from 'sentry-test/reactTestingLibrary';
 
 import TimeRangeSelector from 'sentry/components/organizations/timeRangeSelector';
 import ConfigStore from 'sentry/stores/configStore';
@@ -88,7 +88,7 @@ describe('TimeRangeSelector', function () {
     expect(screen.queryByText(/last hour/i)).not.toBeInTheDocument();
   });
 
-  it('selects absolute item', async function () {
+  it('can select an absolute date range', async function () {
     renderComponent();
 
     userEvent.click(screen.getByRole('button'));
@@ -103,9 +103,30 @@ describe('TimeRangeSelector', function () {
     expect(onChange).toHaveBeenLastCalledWith(newProps);
 
     expect(await screen.findByTestId('date-range')).toBeInTheDocument();
+
+    const fromDateInput = screen.getByTestId('date-range-primary-from');
+    const toDateInput = screen.getByTestId('date-range-primary-to');
+
+    expect(fromDateInput).toHaveValue('2017-10-02');
+    expect(toDateInput).toHaveValue('2017-10-16');
+
+    expect(screen.getByTestId('startTime')).toHaveValue('22:41');
+    expect(screen.getByTestId('endTime')).toHaveValue('22:41');
+
+    fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
+    fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
+
+    // Selecting new date range resets time inputs to start/end of day
+    expect(onChange).toHaveBeenLastCalledWith({
+      relative: null,
+      start: new Date('2017-10-03T00:00:00'), // local time
+      end: new Date('2017-10-04T23:59:59'), // local time
+    });
+    expect(screen.getByTestId('startTime')).toHaveValue('00:00');
+    expect(screen.getByTestId('endTime')).toHaveValue('23:59');
   });
 
-  it('selects absolute item with utc enabled', async function () {
+  it('can select an absolute range with utc enabled', async function () {
     renderComponent({utc: true});
 
     userEvent.click(screen.getByRole('button'));
@@ -121,6 +142,46 @@ describe('TimeRangeSelector', function () {
     expect(onChange).toHaveBeenLastCalledWith(newProps);
 
     expect(await screen.findByTestId('date-range')).toBeInTheDocument();
+
+    const fromDateInput = screen.getByTestId('date-range-primary-from');
+    const toDateInput = screen.getByTestId('date-range-primary-to');
+
+    expect(fromDateInput).toHaveValue('2017-10-02');
+    expect(toDateInput).toHaveValue('2017-10-16');
+
+    expect(screen.getByTestId('startTime')).toHaveValue('22:41');
+    expect(screen.getByTestId('endTime')).toHaveValue('22:41');
+
+    fireEvent.change(fromDateInput, {target: {value: '2017-10-03'}});
+    fireEvent.change(toDateInput, {target: {value: '2017-10-04'}});
+
+    // Selecting new date range resets time inputs to start/end of day
+    expect(onChange).toHaveBeenLastCalledWith({
+      relative: null,
+      start: new Date('2017-10-03T00:00:00Z'), // utc time
+      end: new Date('2017-10-04T23:59:59Z'), // utc time
+      utc: true,
+    });
+    expect(screen.getByTestId('startTime')).toHaveValue('00:00');
+    expect(screen.getByTestId('endTime')).toHaveValue('23:59');
+  });
+
+  it('keeps time inputs focused while interacting with them', async function () {
+    renderComponent();
+
+    userEvent.click(screen.getByRole('button'));
+    userEvent.click(await screen.findByTestId('absolute'));
+    await screen.findByTestId('date-range');
+
+    userEvent.click(screen.getByTestId('startTime'));
+    fireEvent.change(screen.getByTestId('startTime'), {target: {value: '05:00'}});
+
+    expect(screen.getByTestId('startTime')).toHaveFocus();
+
+    userEvent.click(screen.getByTestId('endTime'));
+    fireEvent.change(screen.getByTestId('endTime'), {target: {value: '05:00'}});
+
+    expect(screen.getByTestId('endTime')).toHaveFocus();
   });
 
   it('switches from relative to absolute while maintaining equivalent date range', async function () {