Загрузка данных


import { act, fireEvent, render, screen } from '@testing-library/react';
import { BehaviorSubject } from 'rxjs';

import { ChartHeader } from '@src/components/Header/ChartHeader';
import { Timeframes } from '@src/types/timeframes';

import type { ChartHeaderProps } from '@src/components/Header/types';
import type { UndoRedoState } from '@src/core/UndoRedo';
import type { ChartSeriesType } from '@src/types';
import type { ReactNode } from 'react';

jest.mock('exchange-elements/v2', () => ({
  Divider: () => <hr />,
}));

jest.mock('@src/components/Button', () => ({
  Button: ({
    onClick,
    label,
    disabled,
    pressed,
  }: {
    onClick?: () => void;
    label?: ReactNode;
    disabled?: boolean;
    pressed?: boolean;
  }) => (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      data-pressed={pressed}
    >
      {label}
    </button>
  ),
}));

jest.mock('@src/components/IndicatorsSelect', () => ({
  IndicatorsSelect: () => <div>Indicators</div>,
}));

jest.mock('@src/components/Tooltip', () => ({
  Tooltip: ({ children, label }: { children: ReactNode; label: string }) => (
    <div data-tooltip={label}>{children}</div>
  ),
}));

jest.mock('@src/components/Dropdown', () => ({
  Dropdown: ({ children, selectedValue }: { children: ReactNode; selectedValue?: ReactNode }) => (
    <div>
      <div>{selectedValue}</div>
      {children}
    </div>
  ),
}));

jest.mock('@src/components/Icon', () => ({
  BurgerMenuIcon: () => <span>Drawing toolbar</span>,
  FullscreenIcon: () => <span>Fullscreen</span>,
  PlusCircleIcon: () => <span>Compare</span>,
  RedoIcon: () => <span>Redo</span>,
  SearchIcon: () => <span>Search</span>,
  UndoIcon: () => <span>Undo</span>,
}));

jest.mock('@src/components/SeriesTypeIcon', () => ({
  SeriesTypeIcon: ({ type }: { type: string }) => <span>{type}</span>,
}));

jest.mock('@src/components/Menu', () => ({
  TimeframesMenu: ({ onClick }: { onClick: (value: string) => void }) => (
    <button
      type="button"
      onClick={() => onClick('1d')}
    >
      Set timeframe
    </button>
  ),
  SeriesMenu: ({ onClick }: { onClick: (value: string) => void }) => (
    <button
      type="button"
      onClick={() => onClick('Line')}
    >
      Set series
    </button>
  ),
}));

jest.mock('@src/translations', () => ({
  t: (key: string) => key,
}));

function createTestContext(overrides: Partial<ChartHeaderProps> = {}) {
  const selectedTimeframeObs = new BehaviorSubject(Timeframes['1d']);
  const selectedSeriesObs = new BehaviorSubject<ChartSeriesType>('Candlestick');
  const undoRedoState = new BehaviorSubject<UndoRedoState>({
    canUndo: false,
    canRedo: false,
  });

  const undo = jest.fn();
  const redo = jest.fn();
  const unsubscribe = jest.fn();

  const fullscreen = {
    isFullscreen: false,
    onChange: jest.fn<() => void, [() => void]>(() => unsubscribe),
    toggle: jest.fn(),
  };

  const props: ChartHeaderProps = {
    timeframes: [Timeframes['1d'], Timeframes['1h']],
    selectedTimeframeObs,
    setTimeframe: jest.fn(),
    seriesTypes: ['Candlestick', 'Line'],
    setSelectedSeries: jest.fn(),
    selectedSeriesObs,
    showSettingsModal: jest.fn(),
    addIndicatorToChart: jest.fn(),
    showCompareButton: true,
    showSymbolSearchButton: true,
    undoRedo: {
      getState: () => undoRedoState,
      undo,
      redo,
    } as unknown as ChartHeaderProps['undoRedo'],
    showMenuButton: true,
    showFullscreenButton: true,
    fullscreen: fullscreen as unknown as ChartHeaderProps['fullscreen'],
    openCompareModal: jest.fn(),
    openSymbolSearchModal: jest.fn(),
    isMXT: false,
    viewControl: <button type="button">View control</button>,
    snapshotControl: <button type="button">Snapshot control</button>,
    isToolbarOpen: true,
    onToggleToolbar: jest.fn(),
    ...overrides,
  };

  return {
    props,
    selectedTimeframeObs,
    selectedSeriesObs,
    undoRedoState,
    undo,
    redo,
    fullscreen,
    unsubscribe,
  };
}

describe('ChartHeader', () => {
  it('должен отображать переданные общие контролы и вызывать действия графика', () => {
    const { props } = createTestContext();

    render(<ChartHeader {...props} />);

    expect(screen.queryByText('View control')).not.toBeNull();
    expect(screen.queryByText('Snapshot control')).not.toBeNull();
    expect(screen.queryByText('Indicators')).not.toBeNull();

    fireEvent.click(screen.getByText('Drawing toolbar'));
    fireEvent.click(screen.getByText('Search'));
    fireEvent.click(screen.getByText('Compare'));
    fireEvent.click(screen.getByText('Settings'));

    expect(props.onToggleToolbar).toHaveBeenCalledTimes(1);
    expect(props.openSymbolSearchModal).toHaveBeenCalledTimes(1);
    expect(props.openCompareModal).toHaveBeenCalledTimes(1);
    expect(props.showSettingsModal).toHaveBeenCalledTimes(1);
  });

  it('должен передавать выбранные таймфрейм и тип графика в обработчики', () => {
    const { props } = createTestContext();

    render(<ChartHeader {...props} />);

    fireEvent.click(screen.getByText('Set timeframe'));
    fireEvent.click(screen.getByText('Set series'));

    expect(props.setTimeframe).toHaveBeenCalledWith(Timeframes['1d']);
    expect(props.setSelectedSeries).toHaveBeenCalledWith('Line');
  });

  it('должен обновлять выбранные значения из потоков', () => {
    const { props, selectedTimeframeObs, selectedSeriesObs } = createTestContext();

    render(<ChartHeader {...props} />);

    act(() => {
      selectedTimeframeObs.next(Timeframes['1h']);
      selectedSeriesObs.next('Line');
    });

    expect(screen.queryByText(Timeframes['1h'])).not.toBeNull();
    expect(screen.queryByText('Line')).not.toBeNull();
    expect(screen.queryByText('Candlestick')).toBeNull();
  });

  it('должен обновлять состояние кнопки рисования из props', () => {
    const { props } = createTestContext();
    const { rerender } = render(<ChartHeader {...props} />);
    const button = screen.getByRole('button', { name: 'Drawing toolbar' });

    expect(button.getAttribute('data-pressed')).toBe('true');
    expect(document.querySelector('[data-tooltip="Hide drawing toolbar"]')).not.toBeNull();

    rerender(
      <ChartHeader
        {...props}
        isToolbarOpen={false}
      />,
    );

    expect(button.getAttribute('data-pressed')).toBe('false');
    expect(document.querySelector('[data-tooltip="Show drawing toolbar"]')).not.toBeNull();
  });

  it('должен обновлять доступность undo и redo', () => {
    const { props, undoRedoState, undo, redo } = createTestContext();

    render(<ChartHeader {...props} />);

    const undoButton = screen.getByRole('button', { name: 'Undo' }) as HTMLButtonElement;
    const redoButton = screen.getByRole('button', { name: 'Redo' }) as HTMLButtonElement;

    expect(undoButton.disabled).toBe(true);
    expect(redoButton.disabled).toBe(true);

    fireEvent.click(undoButton);
    fireEvent.click(redoButton);

    expect(undo).not.toHaveBeenCalled();
    expect(redo).not.toHaveBeenCalled();

    act(() => undoRedoState.next({
      canUndo: true,
      canRedo: false,
    }));

    fireEvent.click(undoButton);

    expect(undoButton.disabled).toBe(false);
    expect(redoButton.disabled).toBe(true);
    expect(undo).toHaveBeenCalledTimes(1);

    act(() => undoRedoState.next({
      canUndo: false,
      canRedo: true,
    }));

    fireEvent.click(redoButton);

    expect(undoButton.disabled).toBe(true);
    expect(redoButton.disabled).toBe(false);
    expect(redo).toHaveBeenCalledTimes(1);
  });

  it('должен скрывать отключённые необязательные действия', () => {
    const { props } = createTestContext({
      showMenuButton: false,
      showCompareButton: false,
      showSymbolSearchButton: false,
      showSettingsModal: undefined,
      showFullscreenButton: false,
      undoRedo: undefined,
      snapshotControl: null,
    });

    render(<ChartHeader {...props} />);

    [
      'Drawing toolbar',
      'Search',
      'Compare',
      'Settings',
      'Fullscreen',
      'Undo',
      'Redo',
      'Snapshot control',
    ].forEach((label) => {
      expect(screen.queryByText(label)).toBeNull();
    });

    expect(screen.queryByText('Set timeframe')).not.toBeNull();
    expect(screen.queryByText('Set series')).not.toBeNull();
    expect(screen.queryByText('View control')).not.toBeNull();
  });

  it('должен отображать снимок при отключённом fullscreen', () => {
    const { props } = createTestContext({
      showFullscreenButton: false,
    });

    render(<ChartHeader {...props} />);

    expect(screen.queryByText('Snapshot control')).not.toBeNull();
    expect(screen.queryByText('Fullscreen')).toBeNull();
  });

  it('должен учитывать начальное состояние fullscreen', () => {
    const { props, fullscreen } = createTestContext();

    fullscreen.isFullscreen = true;

    render(<ChartHeader {...props} />);

    expect(
      screen.getByRole('button', { name: 'Fullscreen' }).getAttribute('data-pressed'),
    ).toBe('true');
    expect(document.querySelector('[data-tooltip="Exit fullscreen"]')).not.toBeNull();
  });

  it('должен переключать fullscreen и обновлять его состояние', () => {
    const { props, fullscreen } = createTestContext();

    render(<ChartHeader {...props} />);

    fireEvent.click(screen.getByText('Fullscreen'));

    expect(fullscreen.toggle).toHaveBeenCalledTimes(1);

    act(() => {
      fullscreen.isFullscreen = true;
      fullscreen.onChange.mock.calls[0][0]();
    });

    expect(
      screen.getByRole('button', { name: 'Fullscreen' }).getAttribute('data-pressed'),
    ).toBe('true');
    expect(document.querySelector('[data-tooltip="Exit fullscreen"]')).not.toBeNull();
  });

  it('должен менять подписку при замене fullscreen и очищать её при размонтировании', () => {
    const { props, fullscreen, unsubscribe } = createTestContext();
    const {
      fullscreen: nextFullscreen,
      unsubscribe: nextUnsubscribe,
    } = createTestContext();

    const { rerender, unmount } = render(<ChartHeader {...props} />);

    expect(fullscreen.onChange).toHaveBeenCalledTimes(1);

    rerender(
      <ChartHeader
        {...props}
        fullscreen={nextFullscreen as unknown as ChartHeaderProps['fullscreen']}
      />,
    );

    expect(unsubscribe).toHaveBeenCalledTimes(1);
    expect(nextFullscreen.onChange).toHaveBeenCalledTimes(1);
    expect(nextUnsubscribe).not.toHaveBeenCalled();

    unmount();

    expect(unsubscribe).toHaveBeenCalledTimes(1);
    expect(nextUnsubscribe).toHaveBeenCalledTimes(1);
  });
});

















import { act, fireEvent, render, screen } from '@testing-library/react';
import { BehaviorSubject } from 'rxjs';

import { Header } from '@src/components/Header';
import { ChartViewType } from '@src/types';
import { Timeframes } from '@src/types/timeframes';

import type { HeaderProps } from '@src/components/Header/types';
import type { UndoRedoState } from '@src/core/UndoRedo';
import type { ChartSeriesType } from '@src/types';
import type { ReactNode } from 'react';

jest.mock('exchange-elements/v2', () => ({
  Divider: () => <hr />,
}));

jest.mock('@src/components/Button', () => ({
  Button: ({
    onClick,
    label,
    disabled,
    pressed,
  }: {
    onClick?: () => void;
    label?: ReactNode;
    disabled?: boolean;
    pressed?: boolean;
  }) => (
    <button
      type="button"
      onClick={onClick}
      disabled={disabled}
      data-pressed={pressed}
    >
      {label}
    </button>
  ),
}));

jest.mock('@src/components/IndicatorsSelect', () => ({
  IndicatorsSelect: () => <div>Indicators</div>,
}));

jest.mock('@src/components/Tooltip', () => ({
  Tooltip: ({ children, label }: { children: ReactNode; label: string }) => (
    <div data-tooltip={label}>{children}</div>
  ),
}));

jest.mock('@src/components/Dropdown', () => ({
  Dropdown: ({ children, selectedValue }: { children: ReactNode; selectedValue?: ReactNode }) => (
    <div>
      <div>{selectedValue}</div>
      {children}
    </div>
  ),
}));

jest.mock('@src/components/Icon', () => ({
  BurgerMenuIcon: () => <span>Drawing toolbar</span>,
  CameraIcon: () => <span>Camera</span>,
  FullscreenIcon: () => <span>Fullscreen</span>,
  PlusCircleIcon: () => <span>Compare</span>,
  RedoIcon: () => <span>Redo</span>,
  SearchIcon: () => <span>Search</span>,
  UndoIcon: () => <span>Undo</span>,
}));

jest.mock('@src/components/SeriesTypeIcon', () => ({
  SeriesTypeIcon: ({ type }: { type: string }) => <span>{type}</span>,
}));

jest.mock('@src/components/ChartViewTypeIcon', () => ({
  ChartViewTypeIcon: ({ type }: { type: string }) => <span>{type}</span>,
}));

jest.mock('@src/components/Menu', () => ({
  TimeframesMenu: ({ onClick }: { onClick: (value: string) => void }) => (
    <button
      type="button"
      onClick={() => onClick('1d')}
    >
      Set timeframe
    </button>
  ),
  SeriesMenu: ({ onClick }: { onClick: (value: string) => void }) => (
    <button
      type="button"
      onClick={() => onClick('Line')}
    >
      Set series
    </button>
  ),
  SnapshotMenu: ({ onCopyImage }: { onCopyImage: () => Promise<void> }) => (
    <button
      type="button"
      onClick={() => onCopyImage()}
    >
      Copy image
    </button>
  ),
}));

jest.mock('@src/components/Menu/ChartViewMenu', () => ({
  ChartViewMenu: ({ onClick }: { onClick: (view: string) => void }) => (
    <>
      <button
        type="button"
        onClick={() => onClick('chart')}
      >
        Chart view
      </button>
      <button
        type="button"
        onClick={() => onClick('seasonality')}
      >
        Seasonality view
      </button>
    </>
  ),
}));

jest.mock('@src/translations', () => ({
  t: (key: string) => key,
}));

function createTestContext(overrides: Partial<HeaderProps> = {}) {
  const chartViewObs = new BehaviorSubject(ChartViewType.Chart);
  const canCopyImageObs = new BehaviorSubject(true);
  const selectedTimeframeObs = new BehaviorSubject(Timeframes['1d']);
  const selectedSeriesObs = new BehaviorSubject<ChartSeriesType>('Candlestick');
  const undoRedoState = new BehaviorSubject<UndoRedoState>({
    canUndo: false,
    canRedo: false,
  });

  const undo = jest.fn();
  const redo = jest.fn();
  const unsubscribe = jest.fn();

  const fullscreen = {
    isFullscreen: false,
    onChange: jest.fn<() => void, [() => void]>(() => unsubscribe),
    toggle: jest.fn(),
  };

  const props: HeaderProps = {
    chartViewObs,
    canCopyImageObs,
    timeframes: [Timeframes['1d']],
    selectedTimeframeObs,
    setTimeframe: jest.fn(),
    seriesTypes: ['Candlestick', 'Line'],
    setSelectedSeries: jest.fn(),
    selectedSeriesObs,
    showSettingsModal: jest.fn(),
    addIndicatorToChart: jest.fn(),
    toggleToolbarVisible: jest.fn(() => false),
    showCompareButton: true,
    showSymbolSearchButton: true,
    undoRedo: {
      getState: () => undoRedoState,
      undo,
      redo,
    } as unknown as HeaderProps['undoRedo'],
    showMenuButton: true,
    showFullscreenButton: true,
    fullscreen: fullscreen as unknown as HeaderProps['fullscreen'],
    openCompareModal: jest.fn(),
    openSymbolSearchModal: jest.fn(),
    copyImage: jest.fn().mockResolvedValue(undefined),
    setChartView: jest.fn(),
    isMXT: false,
    ...overrides,
  };

  return {
    props,
    chartViewObs,
    canCopyImageObs,
    selectedTimeframeObs,
    selectedSeriesObs,
    undoRedoState,
    undo,
    redo,
    fullscreen,
    unsubscribe,
  };
}

describe('Header', () => {
  it.each([ChartViewType.Chart, ChartViewType.Seasonality])(
    'должен копировать снимок в режиме %s',
    (view) => {
      const { props, chartViewObs } = createTestContext();

      chartViewObs.next(view);

      render(<Header {...props} />);
      fireEvent.click(screen.getByText('Copy image'));

      expect(document.querySelector('[data-tooltip="Snapshot"]')).not.toBeNull();
      expect(props.copyImage).toHaveBeenCalledTimes(1);
    },
  );

  it('должен оставлять в сезонности только переключение вида и снимок', () => {
    const { props, chartViewObs } = createTestContext();

    render(<Header {...props} />);

    expect(screen.queryByText('Set timeframe')).not.toBeNull();

    act(() => chartViewObs.next(ChartViewType.Seasonality));

    [
      'Drawing toolbar',
      'Search',
      'Compare',
      'Settings',
      'Indicators',
      'Set timeframe',
      'Set series',
      'Undo',
      'Redo',
      'Fullscreen',
    ].forEach((label) => {
      expect(screen.queryByText(label)).toBeNull();
    });

    expect(screen.queryByText('Chart view')).not.toBeNull();
    expect(screen.queryByText('Copy image')).not.toBeNull();

    act(() => chartViewObs.next(ChartViewType.Chart));

    expect(screen.queryByText('Set timeframe')).not.toBeNull();
  });

  it('должен обновлять доступность снимка из потока', () => {
    const { props, chartViewObs, canCopyImageObs } = createTestContext();

    chartViewObs.next(ChartViewType.Seasonality);
    canCopyImageObs.next(false);

    render(<Header {...props} />);

    expect(screen.queryByText('Copy image')).toBeNull();
    expect(screen.queryByText('Chart view')).not.toBeNull();

    act(() => canCopyImageObs.next(true));

    expect(screen.queryByText('Copy image')).not.toBeNull();

    act(() => canCopyImageObs.next(false));

    expect(screen.queryByText('Copy image')).toBeNull();
  });

  it('не должен показывать снимок без callback', () => {
    const { props } = createTestContext({
      copyImage: undefined,
    });

    render(<Header {...props} />);

    expect(screen.queryByText('Copy image')).toBeNull();
  });

  it('должен передавать выбранный режим в setChartView', () => {
    const { props } = createTestContext();

    render(<Header {...props} />);

    fireEvent.click(screen.getByText('Seasonality view'));
    fireEvent.click(screen.getByText('Chart view'));

    expect(props.setChartView).toHaveBeenNthCalledWith(1, ChartViewType.Seasonality);
    expect(props.setChartView).toHaveBeenNthCalledWith(2, ChartViewType.Chart);
  });

  it('не должен показывать переключение без callback', () => {
    const { props } = createTestContext({
      setChartView: undefined,
    });

    render(<Header {...props} />);

    expect(screen.queryByText('Chart view')).toBeNull();
    expect(screen.queryByText('Seasonality view')).toBeNull();
  });

  it('должен сохранять состояние панели рисования после переключения режимов', () => {
    const { props, chartViewObs } = createTestContext();

    render(<Header {...props} />);

    expect(
      screen.getByRole('button', { name: 'Drawing toolbar' }).getAttribute('data-pressed'),
    ).toBe('true');

    fireEvent.click(screen.getByText('Drawing toolbar'));

    expect(props.toggleToolbarVisible).toHaveBeenCalledTimes(1);
    expect(
      screen.getByRole('button', { name: 'Drawing toolbar' }).getAttribute('data-pressed'),
    ).toBe('false');

    act(() => chartViewObs.next(ChartViewType.Seasonality));
    act(() => chartViewObs.next(ChartViewType.Chart));

    expect(
      screen.getByRole('button', { name: 'Drawing toolbar' }).getAttribute('data-pressed'),
    ).toBe('false');
    expect(props.toggleToolbarVisible).toHaveBeenCalledTimes(1);
  });

  it('должен вызывать действия основного графика', () => {
    const { props } = createTestContext();

    render(<Header {...props} />);

    fireEvent.click(screen.getByText('Search'));
    fireEvent.click(screen.getByText('Compare'));
    fireEvent.click(screen.getByText('Settings'));
    fireEvent.click(screen.getByText('Set timeframe'));
    fireEvent.click(screen.getByText('Set series'));

    expect(props.openSymbolSearchModal).toHaveBeenCalledTimes(1);
    expect(props.openCompareModal).toHaveBeenCalledTimes(1);
    expect(props.showSettingsModal).toHaveBeenCalledTimes(1);
    expect(props.setTimeframe).toHaveBeenCalledWith(Timeframes['1d']);
    expect(props.setSelectedSeries).toHaveBeenCalledWith('Line');
  });

  it('должен обновлять выбранные таймфрейм и тип графика', () => {
    const { props, selectedTimeframeObs, selectedSeriesObs } = createTestContext();

    render(<Header {...props} />);

    act(() => {
      selectedTimeframeObs.next(Timeframes['1h']);
      selectedSeriesObs.next('Line');
    });

    expect(screen.queryByText(Timeframes['1h'])).not.toBeNull();
    expect(screen.queryByText('Line')).not.toBeNull();
    expect(screen.queryByText('Candlestick')).toBeNull();
  });

  it('должен обновлять доступность undo и redo из потока', () => {
    const { props, undoRedoState, undo, redo } = createTestContext();

    render(<Header {...props} />);

    const undoButton = screen.getByRole('button', { name: 'Undo' }) as HTMLButtonElement;
    const redoButton = screen.getByRole('button', { name: 'Redo' }) as HTMLButtonElement;

    expect(undoButton.disabled).toBe(true);
    expect(redoButton.disabled).toBe(true);

    act(() => undoRedoState.next({
      canUndo: true,
      canRedo: false,
    }));

    fireEvent.click(undoButton);
    fireEvent.click(redoButton);

    expect(undo).toHaveBeenCalledTimes(1);
    expect(redo).not.toHaveBeenCalled();

    act(() => undoRedoState.next({
      canUndo: false,
      canRedo: true,
    }));

    fireEvent.click(redoButton);

    expect(undoButton.disabled).toBe(true);
    expect(redo).toHaveBeenCalledTimes(1);
  });

  it('должен скрывать отключённые необязательные действия', () => {
    const { props } = createTestContext({
      showMenuButton: false,
      showCompareButton: false,
      showSymbolSearchButton: false,
      showSettingsModal: undefined,
      showFullscreenButton: false,
      undoRedo: undefined,
      copyImage: undefined,
    });

    render(<Header {...props} />);

    [
      'Drawing toolbar',
      'Search',
      'Compare',
      'Settings',
      'Fullscreen',
      'Undo',
      'Redo',
      'Copy image',
    ].forEach((label) => {
      expect(screen.queryByText(label)).toBeNull();
    });

    expect(screen.queryByText('Set timeframe')).not.toBeNull();
  });

  it('должен обновлять fullscreen и отписываться при смене режима', () => {
    const { props, chartViewObs, fullscreen, unsubscribe } = createTestContext();

    render(<Header {...props} />);

    fireEvent.click(screen.getByText('Fullscreen'));

    expect(fullscreen.toggle).toHaveBeenCalledTimes(1);

    act(() => {
      fullscreen.isFullscreen = true;
      fullscreen.onChange.mock.calls[0][0]();
    });

    expect(
      screen.getByRole('button', { name: 'Fullscreen' }).getAttribute('data-pressed'),
    ).toBe('true');
    expect(document.querySelector('[data-tooltip="Exit fullscreen"]')).not.toBeNull();

    act(() => chartViewObs.next(ChartViewType.Seasonality));

    expect(unsubscribe).toHaveBeenCalledTimes(1);

    act(() => chartViewObs.next(ChartViewType.Chart));

    expect(
      screen.getByRole('button', { name: 'Fullscreen' }).getAttribute('data-pressed'),
    ).toBe('true');
    expect(fullscreen.onChange).toHaveBeenCalledTimes(2);
  });

  it('должен отписываться от fullscreen при размонтировании', () => {
    const { props, unsubscribe } = createTestContext();
    const { unmount } = render(<Header {...props} />);

    unmount();

    expect(unsubscribe).toHaveBeenCalledTimes(1);
  });
});