import {
  filterBySearchTextInOptionLabel,
  getAllObjectivesWithFakeAllMetricsObjective,
  getCampaignCategory,
  getCampaignCategoryColor,
  getCampaignCategoryIconName,
  getDaysDiffInclusive,
  getFieldsWithDiffValues,
  getJoinedNames,
  getLinksWithLabels,
  getMultipleItemsString,
  getNextDayFormattedDate,
  getObjectivesWithoutFakeAllMetricsObjective,
  getProjectSchedule,
  getSearchedObjectives,
  getStatusIconName,
  getStatusId,
  getStatusName,
  getTreeNodes,
  isAvailableDatesToCreatePhase,
  isCampaignExternal,
} from '../transducers';
import { insertItem, moveItem, notNullable, removeItemAt } from 'utils/data';
import { navigateModTo, percentageFromTotal } from 'utils/number';
import {
  DEFAULT_DATE_FORMAT,
  DEFAULT_DATE_FORMAT_A,
  FAKE_ALL_METRICS_OBJECTIVE_ID,
  FAKE_ALL_METRICS_OBJECTIVE_NAME,
  ID_BY_STATUS,
  Status,
  STATUS_BY_ID,
  STATUS_ICONS_BY_ID,
} from '../constants';
import React from 'react';
import { CampaignSources, PerformanceObjective, PerformanceObjectivesAndMetrics } from 'backend-api/models';
import { getArtistMock } from 'backend-api/models/artist/__mocks__';
import { getCampaignCategoryAndTypesMock, getCampaignDetailsMock } from 'backend-api/models/campaign/__mocks__';
import { getTerritoryMock } from 'backend-api/models/common/__mocks__';
import { getLabelMock } from 'backend-api/models/label/__mocks__';
import { getPlaylistMock } from 'backend-api/models/playlist/__mocks__';
import { getProjectDetailsMock, getProjectMetadataMock, getProjectMock } from 'backend-api/models/project/__mocks__';
import { formatCurrency, formatNumeral, formatShortenCurrency, formatShortenNumeral } from 'utils/format';
import { calculateDateRangePercentProgress } from 'common/components/timeline/transducers';
import { isElementInTree } from 'utils/dom';
import { pluralizeString } from 'utils/string';
import { getPerformanceMetricMock, getPerformanceObjectiveMock } from 'backend-api/models/performance/__mocks__';
import { getPhaseMock } from 'backend-api/models/phase/__mocks__';
import {
  getStubMetric4,
  getStubObjective1,
  getStubObjective2,
  getStubObjective4,
  getStubObjectives,
} from '../__mocks__';
import { TreeItemType } from 'common/components/tree-list';
import { ROLES } from 'common-v2/constants';
import { parseDateByFormat } from 'common-v2/utils';

describe('common transducers', () => {
  describe('notNullable transducer', () => {
    it('should return true if value is not nullable', () => {
      const value = getProjectDetailsMock();
      const output = notNullable(value);
      expect(output).toEqual(true);
    });

    it('should return false if value is null', () => {
      const output = notNullable(null);
      expect(output).toEqual(false);
    });

    it('should return false if value is undefined', () => {
      const output = notNullable(undefined);
      expect(output).toEqual(false);
    });
  });

  describe('navigateModTo transducer', () => {
    it('should return proper value', () => {
      expect(navigateModTo(0)(-1)).toEqual(NaN);
      expect(navigateModTo(0)(0)).toEqual(NaN);
      expect(navigateModTo(0)(1)).toEqual(NaN);
      expect(navigateModTo(1)(-1)).toEqual(0);
      expect(navigateModTo(1)(0)).toEqual(0);
      expect(navigateModTo(1)(1)).toEqual(0);
      expect(navigateModTo(1)(2)).toEqual(0);
      expect(navigateModTo(2)(-1)).toEqual(1);
      expect(navigateModTo(2)(0)).toEqual(0);
      expect(navigateModTo(2)(1)).toEqual(1);
      expect(navigateModTo(2)(2)).toEqual(0);
      expect(navigateModTo(3)(-3)).toEqual(0);
      expect(navigateModTo(3)(-2)).toEqual(1);
      expect(navigateModTo(3)(-1)).toEqual(2);
      expect(navigateModTo(3)(0)).toEqual(0);
      expect(navigateModTo(3)(1)).toEqual(1);
      expect(navigateModTo(3)(2)).toEqual(2);
      expect(navigateModTo(3)(3)).toEqual(0);
      expect(navigateModTo(3)(4)).toEqual(1);
      expect(navigateModTo(3)(5)).toEqual(2);
      expect(navigateModTo(3)(6)).toEqual(0);
    });
  });

  describe('filterBySearchTextInOptionLabel transducer', () => {
    it('should return true if search text is undefined', () => {
      const option = {
        data: { id: 1, name: 'Name' },
        label: 'Name',
        value: 1,
      };
      const output = filterBySearchTextInOptionLabel(option, undefined);
      expect(output).toEqual(true);
    });

    it('should return true if search text found', () => {
      const option = {
        data: { id: 1, name: 'Name' },
        label: 'Name',
        value: 1,
      };
      const output = filterBySearchTextInOptionLabel(option, 'na');
      expect(output).toEqual(true);
    });

    it('should return false if search text not found', () => {
      const option = {
        data: { id: 1, name: 'Name' },
        label: 'Name',
        value: 1,
      };
      const output = filterBySearchTextInOptionLabel(option, 'Another');
      expect(output).toEqual(false);
    });
  });

  describe('getStatusIconName transducer', () => {
    it('should return proper icon name', () => {
      const scheduled = getStatusIconName(1);
      const inProgress = getStatusIconName(2);
      const completed = getStatusIconName(3);

      expect(scheduled).toEqual(STATUS_ICONS_BY_ID[1]);
      expect(inProgress).toEqual(STATUS_ICONS_BY_ID[2]);
      expect(completed).toEqual(STATUS_ICONS_BY_ID[3]);
    });
  });

  describe('getStatusName transducer', () => {
    it('should return proper status name', () => {
      const scheduled = getStatusName(1);
      const inProgress = getStatusName(2);
      const completed = getStatusName(3);

      expect(scheduled).toEqual(STATUS_BY_ID[1]);
      expect(inProgress).toEqual(STATUS_BY_ID[2]);
      expect(completed).toEqual(STATUS_BY_ID[3]);
    });
  });

  describe('getStatusId transducer', () => {
    it('should return proper status name', () => {
      const scheduled = getStatusId(Status.Scheduled);
      const inProgress = getStatusId(Status.InProgress);
      const completed = getStatusId(Status.Completed);

      expect(scheduled).toEqual(ID_BY_STATUS[Status.Scheduled]);
      expect(inProgress).toEqual(ID_BY_STATUS[Status.InProgress]);
      expect(completed).toEqual(ID_BY_STATUS[Status.Completed]);
    });
  });

  describe('isElementInTree transducer', () => {
    it('should return true', () => {
      const domTreeWithElement = document.createElement('div');
      const elementToSearchFor = document.createElement('div');

      domTreeWithElement.append(elementToSearchFor);

      const output = isElementInTree(domTreeWithElement, elementToSearchFor);
      expect(output).toEqual(true);
    });

    it('should return false', () => {
      const domTreeWithElement = document.createElement('div');
      const elementToSearchFor = document.createElement('div');

      const output = isElementInTree(domTreeWithElement, elementToSearchFor);
      expect(output).toEqual(false);
    });
  });

  describe('formatShortenCurrency transducer', () => {
    it('should return proper values', () => {
      expect(formatShortenCurrency(100)).toEqual('$100');
      expect(formatShortenCurrency(500)).toEqual('$500');
      expect(formatShortenCurrency(1050)).toEqual('$1.1K');
      expect(formatShortenCurrency(1100)).toEqual('$1.1K');
      expect(formatShortenCurrency(1499)).toEqual('$1.5K');
      expect(formatShortenCurrency(1999)).toEqual('$2K');
      expect(formatShortenCurrency(100599)).toEqual('$100.6K');
      expect(formatShortenCurrency(1005990)).toEqual('$1M');
      expect(formatShortenCurrency(1059900)).toEqual('$1.1M');
      expect(formatShortenCurrency(11111111)).toEqual('$11.1M');
      expect(formatShortenCurrency(99999999)).toEqual('$100M');
    });
  });

  describe('formatCurrency transducer', () => {
    it('should return proper values', () => {
      expect(formatCurrency(100)).toEqual('$100');
      expect(formatCurrency(500.255)).toEqual('$500.26');
      expect(formatCurrency(1050.563)).toEqual('$1,050.56');
      expect(formatCurrency(1100)).toEqual('$1,100');
      expect(formatCurrency(1499.999)).toEqual('$1,500');
      expect(formatCurrency(1999)).toEqual('$1,999');
      expect(formatCurrency(100599.0)).toEqual('$100,599');
      expect(formatCurrency(1005990.555)).toEqual('$1,005,990.56');
      expect(formatCurrency(1059900)).toEqual('$1,059,900');
      expect(formatCurrency(11111111)).toEqual('$11,111,111');
      expect(formatCurrency(99999999)).toEqual('$99,999,999');
      expect(formatCurrency(999999999.999)).toEqual('$1,000,000,000');
    });
  });

  describe('formatShortenNumeral transducer', () => {
    it('should return proper values', () => {
      expect(formatShortenNumeral(100)).toEqual('100');
      expect(formatShortenNumeral(500)).toEqual('500');
      expect(formatShortenNumeral(1050)).toEqual('1.1K');
      expect(formatShortenNumeral(1100)).toEqual('1.1K');
      expect(formatShortenNumeral(1499)).toEqual('1.5K');
      expect(formatShortenNumeral(1999)).toEqual('2K');
      expect(formatShortenNumeral(100599)).toEqual('100.6K');
      expect(formatShortenNumeral(1005990)).toEqual('1M');
      expect(formatShortenNumeral(1059900)).toEqual('1.1M');
      expect(formatShortenNumeral(11111111)).toEqual('11.1M');
      expect(formatShortenNumeral(99999999)).toEqual('100M');
    });
  });

  describe('formatNumeral transducer', () => {
    it('should return proper values', () => {
      expect(formatNumeral(100)).toEqual('100');
      expect(formatNumeral(500.255)).toEqual('500');
      expect(formatNumeral(1050.563)).toEqual('1,051');
      expect(formatNumeral(1100)).toEqual('1,100');
      expect(formatNumeral(1499.999)).toEqual('1,500');
      expect(formatNumeral(1999)).toEqual('1,999');
      expect(formatNumeral(100599.0)).toEqual('100,599');
      expect(formatNumeral(1005990.555)).toEqual('1,005,991');
      expect(formatNumeral(1059900)).toEqual('1,059,900');
      expect(formatNumeral(11111111)).toEqual('11,111,111');
      expect(formatNumeral(99999999)).toEqual('99,999,999');
      expect(formatNumeral(999999999.999)).toEqual('1,000,000,000');
    });
  });

  describe('percentageFromTotal transducer', () => {
    it('should return proper percentage', () => {
      expect(percentageFromTotal(0, 0)).toEqual(0);
      expect(percentageFromTotal(0, 100)).toEqual(0);
      expect(percentageFromTotal(1, 100)).toEqual(1);
      expect(percentageFromTotal(50, 100)).toEqual(50);
      expect(percentageFromTotal(100, 100)).toEqual(100);
      expect(percentageFromTotal(0, 174)).toEqual(0);
      expect(percentageFromTotal(1, 174)).toEqual(1);
      expect(percentageFromTotal(50, 174)).toEqual(29);
      expect(percentageFromTotal(100, 174)).toEqual(57);
      expect(percentageFromTotal(174, 174)).toEqual(100);
    });
  });

  describe('getCampaignCategory transducer', () => {
    it('should return proper campaign category', () => {
      const category1 = getCampaignCategoryAndTypesMock({
        id: 1,
        name: 'Digital',
        campaignTypes: [
          {
            id: 1,
            name: 'Type 1',
          },
          {
            id: 2,
            name: 'Type 2',
          },
        ],
      });
      const category2 = getCampaignCategoryAndTypesMock({
        id: 2,
        name: 'Traditional',
        campaignTypes: [
          {
            id: 3,
            name: 'Type 3',
          },
          {
            id: 4,
            name: 'Type 4',
          },
        ],
      });
      const category3 = getCampaignCategoryAndTypesMock({
        id: 3,
        name: 'Appearances',
        campaignTypes: [
          {
            id: 5,
            name: 'Type 5',
          },
          {
            id: 6,
            name: 'Type 6',
          },
        ],
      });

      const campaignCategories = [category1, category2, category3];

      expect(getCampaignCategory(1, campaignCategories)).toEqual(category1);
      expect(getCampaignCategory(2, campaignCategories)).toEqual(category1);
      expect(getCampaignCategory(3, campaignCategories)).toEqual(category2);
      expect(getCampaignCategory(4, campaignCategories)).toEqual(category2);
      expect(getCampaignCategory(5, campaignCategories)).toEqual(category3);
      expect(getCampaignCategory(6, campaignCategories)).toEqual(category3);
      expect(getCampaignCategory(7, campaignCategories)).toEqual(undefined);
    });
  });

  describe('getCampaignCategoryColor transducer', () => {
    it('should return proper campaign category color', () => {
      const category1 = getCampaignCategoryAndTypesMock({
        id: 1,
        name: 'Digital',
      });
      const category2 = getCampaignCategoryAndTypesMock({
        id: 2,
        name: 'Traditional',
      });
      const category3 = getCampaignCategoryAndTypesMock({
        id: 3,
        name: 'Appearances',
      });
      const category4 = getCampaignCategoryAndTypesMock({
        id: 4,
        name: 'Not existed',
      });

      expect(getCampaignCategoryColor(category1.id)).toEqual('cerulean');
      expect(getCampaignCategoryColor(category2.id)).toEqual('amethyst');
      expect(getCampaignCategoryColor(category3.id)).toEqual('mountainMeadow');
      expect(getCampaignCategoryColor(category4.id)).toEqual('uncategorizedGray');
    });
  });

  describe('getCampaignCategoryIconName transducer', () => {
    it('should return proper campaign category icon name', () => {
      const category1 = getCampaignCategoryAndTypesMock({
        id: 1,
        name: 'Digital',
      });
      const category2 = getCampaignCategoryAndTypesMock({
        id: 2,
        name: 'Traditional',
      });
      const category3 = getCampaignCategoryAndTypesMock({
        id: 3,
        name: 'Appearances',
      });
      const category4 = getCampaignCategoryAndTypesMock({
        id: 4,
        name: 'Not existed',
      });

      expect(getCampaignCategoryIconName(category1)).toEqual('social-medium');
      expect(getCampaignCategoryIconName(category2)).toEqual('tv-ad-medium');
      expect(getCampaignCategoryIconName(category3)).toEqual('appearances');
      expect(getCampaignCategoryIconName(category4)).toEqual('unknown');
    });
  });

  describe('calculateDateRangePercentProgress transducer', () => {
    it('should return 100 percents', () => {
      const startDate = parseDateByFormat('2020-01-01', 'yyyy-MM-dd');
      const endDate = parseDateByFormat('2020-01-02', 'yyyy-MM-dd');

      const output = calculateDateRangePercentProgress(startDate, endDate);
      const expected = 100;
      expect(output).toEqual(expected);
    });

    it('should return 0 percents', () => {
      const startDate = parseDateByFormat('2030-01-01', 'yyyy-MM-dd');
      const endDate = parseDateByFormat('2030-01-02', 'yyyy-MM-dd');

      const output = calculateDateRangePercentProgress(startDate, endDate);
      const expected = 0;
      expect(output).toEqual(expected);
    });
  });

  describe('getFieldsWithDiffValues transducer', () => {
    it('should return different field names', () => {
      const firstObj = getProjectDetailsMock({
        name: 'Project 1',
        notes: 'Project 1',
      });
      const secondObj = getProjectDetailsMock({
        name: 'Project 2',
        notes: 'Project 2',
      });
      const output = getFieldsWithDiffValues(firstObj, secondObj);
      const expected = ['name', 'notes'];
      expect(output).toEqual(expected);
    });

    it('should return empty array', () => {
      const firstObj = getProjectDetailsMock({});
      const secondObj = getProjectDetailsMock({});
      const output = getFieldsWithDiffValues(firstObj, secondObj);
      const expected = [];
      expect(output).toEqual(expected);
    });

    it('should return all field names', () => {
      const firstObj = getProjectDetailsMock({
        id: 0,
        name: 'Project',
        userRoles: [{ ...ROLES.EDITOR, categoryIds: [] }],
        status: 1,
        allocation: 50000,
        startDate: parseDateByFormat('1970-01-01', 'yyyy-MM-dd'),
        endDate: parseDateByFormat('1970-02-01', 'yyyy-MM-dd'),
        createUser: { id: 'auth0', email: 'user@user.com' },
        editUser: { id: 'auth0', email: 'user1@user.com' },
        label: getLabelMock({
          id: 0,
          name: '0',
        }),
        editDate: parseDateByFormat('1970-01-01', 'yyyy-MM-dd'),
        territories: [],
        targets: {
          items: [getArtistMock()],
          type: 'Artist',
        },
        notes: 'notes',
        grasData: {
          id: 'grass',
          title: 'grasData_title',
        },
        metadata: getProjectMetadataMock({
          assignedUsersCount: 2,
          campaigns: {
            internalCampaignsCount: 1,
            externalCampaignsCount: 1,
            pendingCampaignsCount: 1,
          },
        }),
      });
      const secondObj = getProjectDetailsMock({
        id: 1,
        name: 'Project 1',
        userRoles: [{ ...ROLES.VIEWER, categoryIds: [] }],
        status: 2,
        startDate: parseDateByFormat('2020-01-01', 'yyyy-MM-dd'),
        endDate: parseDateByFormat('2020-02-01', 'yyyy-MM-dd'),
        createUser: { id: 'auth0', email: 'user1@user.com' },
        editUser: { id: 'auth0', email: 'user2@user.com' },
        label: getLabelMock({
          id: 1,
          name: '1',
        }),
        budget: 100,
        allocation: 500,
        editDate: parseDateByFormat('2020-01-01', 'yyyy-MM-dd'),
        territories: [getTerritoryMock()],
        targets: {
          items: [getPlaylistMock()],
          type: 'Playlist',
        },
        notes: 'another notes',
        grasData: {
          id: 'another grass',
          title: 'grasData_title',
        },
        metadata: getProjectMetadataMock({
          assignedUsersCount: 3,
          campaigns: {
            internalCampaignsCount: 2,
            externalCampaignsCount: 4,
            pendingCampaignsCount: 4,
          },
        }),
      });
      const output = getFieldsWithDiffValues(firstObj, secondObj);
      const expected = [
        'id',
        'name',
        'userRoles',
        'status',
        'startDate',
        'endDate',
        'budget',
        'allocation',
        'createUser',
        'editUser',
        'label',
        'editDate',
        'territories',
        'targets',
        'notes',
        'grasData',
        'metadata',
      ];
      expect(output).toEqual(expected);
    });
  });

  describe('getJoinedNames', () => {
    it('should return empty string if there are no items', () => {
      expect(getJoinedNames([])).toEqual('');
    });

    it('should return name only if there is one item', () => {
      const territory = getTerritoryMock({ name: 'United States' });
      expect(getJoinedNames([territory])).toEqual('United States');
    });

    it('should return names separated by comma if there are more than one item', () => {
      const territories = [
        getTerritoryMock({ name: 'United States' }),
        getTerritoryMock({ name: 'United Arab Emirates' }),
        getTerritoryMock({ name: 'Italy' }),
      ];
      const expected = 'United States, United Arab Emirates, Italy';

      expect(getJoinedNames(territories)).toEqual(expected);
    });
  });

  describe('pluralizeString', () => {
    it('should return empty string if value is empty string', () => {
      expect(pluralizeString('', 0)).toEqual('');
      expect(pluralizeString('', 1)).toEqual('');
      expect(pluralizeString('', 2)).toEqual('');
    });

    it('should return singular value if value is not empty and count is 1', () => {
      expect(pluralizeString('test', 1)).toEqual('test');
    });

    it('should return plural value if value is not empty and count is not 1', () => {
      expect(pluralizeString('test', 0)).toEqual('tests');
      expect(pluralizeString('test', 2)).toEqual('tests');
      expect(pluralizeString('test', 5)).toEqual('tests');
      expect(pluralizeString('test', 10)).toEqual('tests');
      expect(pluralizeString('campaign', 10)).toEqual('campaigns');
    });
  });

  describe('getProjectSchedule', () => {
    it('should return undefined  if project undefined', () => {
      expect(getProjectSchedule(undefined, DEFAULT_DATE_FORMAT)).toEqual(undefined);
    });

    it('should return schedule string if project not undefined and there is dates', () => {
      const project = getProjectMock({
        startDate: parseDateByFormat('1994-10-10', 'yyyy-MM-dd'),
        endDate: parseDateByFormat('2020-10-10', 'yyyy-MM-dd'),
        isClaimed: true,
      });
      expect(getProjectSchedule(project, DEFAULT_DATE_FORMAT)).toEqual('10/10/94-10/10/20');
    });

    it('should return start date when project is unassigned', () => {
      const projectWithoutLatestDate = getProjectDetailsMock({
        startDate: parseDateByFormat('1994-10-10', 'yyyy-MM-dd'),
        isClaimed: false,
      });
      expect(getProjectSchedule(projectWithoutLatestDate, DEFAULT_DATE_FORMAT)).toEqual('10/10/94');
    });
  });

  describe('getMultipleItemsString ', () => {
    describe('getMultipleItemsString with formatting', () => {
      it('getMultipleItemsString should return string with and if more than 1 item', () => {
        const expectedString = (
          <p>
            <b>Item1, Item2</b> and <b>Item3</b>
          </p>
        );
        const items = [{ name: 'Item1' }, { name: 'Item2' }, { name: 'Item3' }];
        expect(getMultipleItemsString(items, true)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return string without and if 1 item', () => {
        const expectedString = (
          <p>
            <b>Item1</b>
          </p>
        );
        const items = [{ name: 'Item1' }];
        expect(getMultipleItemsString(items, true)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return string without comas if 2 item', () => {
        const expectedString = (
          <p>
            <b>Item1</b> and <b>Item2</b>
          </p>
        );
        const items = [{ name: 'Item1' }, { name: 'Item2' }];
        expect(getMultipleItemsString(items, true)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return empty string no item', () => {
        const expectedString = '';
        const items = [];
        expect(getMultipleItemsString(items, true)).toStrictEqual(expectedString);
      });
    });

    describe('getMultipleItemsString no formatting', () => {
      it('getMultipleItemsString should return string with and if more than 1 item', () => {
        const expectedString = 'Item1, Item2 and Item3';
        const items = [{ name: 'Item1' }, { name: 'Item2' }, { name: 'Item3' }];
        expect(getMultipleItemsString(items)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return string without and if 1 item', () => {
        const expectedString = 'Item1';
        const items = [{ name: 'Item1' }];
        expect(getMultipleItemsString(items)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return string without comas if 2 item', () => {
        const expectedString = 'Item1 and Item2';
        const items = [{ name: 'Item1' }, { name: 'Item2' }];
        expect(getMultipleItemsString(items)).toStrictEqual(expectedString);
      });

      it('getMultipleItemsString should return empty string no item', () => {
        const expectedString = '';
        const items = [];
        expect(getMultipleItemsString(items)).toStrictEqual(expectedString);
      });
    });
  });

  describe('moveItem in array', () => {
    it('Move right', () => {
      const array = [1, 2, 3];
      const newArray = moveItem(array, 0, 1);
      expect(newArray[0]).toStrictEqual(2);
      expect(newArray[1]).toStrictEqual(1);
      expect(newArray[2]).toStrictEqual(3);
    });
    it('Move left', () => {
      const array = [1, 2, 3];
      const newArray = moveItem(array, 1, 0);
      expect(newArray[0]).toStrictEqual(2);
      expect(newArray[1]).toStrictEqual(1);
      expect(newArray[2]).toStrictEqual(3);
    });
    it('Move out of bounds to the left', () => {
      const array = [1, 2, 3];
      const newArray = moveItem(array, 1, -2);
      expect(newArray[0]).toStrictEqual(2);
      expect(newArray[1]).toStrictEqual(1);
      expect(newArray[2]).toStrictEqual(3);
    });
    it('Move out of bounds to the right', () => {
      const array = [1, 2, 3];
      const newArray = moveItem(array, 1, 4);
      expect(newArray[0]).toStrictEqual(1);
      expect(newArray[1]).toStrictEqual(3);
      expect(newArray[2]).toStrictEqual(2);
    });
  });

  describe('insertItem in array', () => {
    it('insert item to the start', () => {
      const array = [1, 2, 3];
      const newArray = insertItem(array, 0, 4);
      expect(newArray[0]).toStrictEqual(4);
    });
    it('insert item to the end', () => {
      const array = [1, 2, 3];
      const newArray = insertItem(array, 2, 4);
      expect(newArray[2]).toStrictEqual(4);
    });
    it('insert item out of bonds to the start', () => {
      const array = [1, 2, 3];
      const newArray = insertItem(array, -1, 4);
      expect(newArray[0]).toStrictEqual(4);
    });
    it('insert item out of bonds to the end', () => {
      const array = [1, 2, 3];
      const newArray = insertItem(array, 3, 4);
      expect(newArray[3]).toStrictEqual(4);
    });
  });

  describe('removeItem in array', () => {
    it('remove item from the start', () => {
      const array = [1, 2, 3];
      const newArray = removeItemAt(array, 0);
      expect(newArray[0]).toStrictEqual(2);
      expect(newArray[1]).toStrictEqual(3);
    });
    it('remove item from the end', () => {
      const array = [1, 2, 3];
      const newArray = removeItemAt(array, 2);
      expect(newArray[0]).toStrictEqual(1);
      expect(newArray[1]).toStrictEqual(2);
    });
    it('remove item from the middle', () => {
      const array = [1, 2, 3];
      const newArray = removeItemAt(array, 1);
      expect(newArray[0]).toStrictEqual(1);
      expect(newArray[1]).toStrictEqual(3);
    });
    it('remove item out of bounds from the start', () => {
      const array = [1, 2, 3];
      const newArray = removeItemAt(array, -1);
      expect(newArray[0]).toStrictEqual(1);
      expect(newArray[1]).toStrictEqual(2);
      expect(newArray[2]).toStrictEqual(3);
    });
    it('remove item out of bounds from the end', () => {
      const array = [1, 2, 3];
      const newArray = removeItemAt(array, 4);
      expect(newArray[0]).toStrictEqual(1);
      expect(newArray[1]).toStrictEqual(2);
      expect(newArray[2]).toStrictEqual(3);
    });
  });

  describe('getLinksWithLabels transducer', () => {
    it('should return list of LinkSelectOption with numbered same domains', () => {
      const expected = [
        {
          id: 0,
          link: 'https://www.decibel.stream/',
          label: 'Decibel Link',
        },
        {
          id: 1,
          link: 'https://www.decibel.stream/projects',
          label: 'Decibel Link 1',
        },
      ];
      expect(getLinksWithLabels(['https://www.decibel.stream/', 'https://www.decibel.stream/projects'])).toEqual(
        expected
      );
    });
  });

  describe('getDaysDiffInclusive transducer', () => {
    it('should return right days diff if dates different', () => {
      const firstDate = parseDateByFormat('2020-10-25', 'yyyy-MM-dd');
      const secondDate = parseDateByFormat('2020-10-23', 'yyyy-MM-dd');

      const expectedDaysDiff = 3;

      expect(getDaysDiffInclusive(firstDate, secondDate)).toStrictEqual(expectedDaysDiff);
    });

    it('should return one day diff if dates equal', () => {
      const firstDate = parseDateByFormat('2020-10-25', 'yyyy-MM-dd');
      const secondDate = parseDateByFormat('2020-10-25', 'yyyy-MM-dd');

      const expectedDaysDiff = 1;

      expect(getDaysDiffInclusive(firstDate, secondDate)).toStrictEqual(expectedDaysDiff);
    });

    it('should return right days diff if second date greater then first', () => {
      const firstDate = parseDateByFormat('2020-10-25', 'yyyy-MM-dd');
      const secondDate = parseDateByFormat('2020-10-27', 'yyyy-MM-dd');

      const expectedDaysDiff = 3;

      expect(getDaysDiffInclusive(firstDate, secondDate)).toStrictEqual(expectedDaysDiff);
    });
  });

  describe('getNextDayFormattedDate transducer', () => {
    it('should return next day date string formatted with default format', () => {
      const dateString = '2020-11-30';

      const expectedDateString = '2020-12-01';

      expect(getNextDayFormattedDate(dateString)).toStrictEqual(expectedDateString);
    });

    it('should return next day date string formatted with custom format', () => {
      const dateString = '2020-11-30';

      const expectedDateString = '12/01/2020 12:00 AM';

      expect(getNextDayFormattedDate(dateString, DEFAULT_DATE_FORMAT_A)).toStrictEqual(expectedDateString);
    });
  });

  describe('isCampaignExternal', () => {
    it('should return false for empty campaign details', () => {
      const isIncomplete = isCampaignExternal(undefined);
      expect(isIncomplete).toEqual(false);
    });

    it('should return false if campaign details external id is empty', () => {
      const campaignDetails = getCampaignDetailsMock({ source: CampaignSources.MANUAL });
      const isIncomplete = isCampaignExternal(campaignDetails);
      expect(isIncomplete).toEqual(false);
    });

    it('should return true if campaign details has external id', () => {
      const campaignFacebookDetails = getCampaignDetailsMock({ source: CampaignSources.FACEBOOK });
      const campaignGoogleDetails = getCampaignDetailsMock({ source: CampaignSources.GOOGLE });

      expect(isCampaignExternal(campaignFacebookDetails)).toEqual(true);
      expect(isCampaignExternal(campaignGoogleDetails)).toEqual(true);
    });
  });

  describe('getAllObjectivesWithFakeAllMetricsObjective', () => {
    it('getAllObjectivesWithFakeAllMetricsObjective returns empty array if undefined passed', () => {
      expect(getAllObjectivesWithFakeAllMetricsObjective()).toStrictEqual([]);
    });

    it('getAllObjectivesWithFakeAllMetricsObjective returns array of objective with fake all metrics objective if undefined passed', () => {
      const objectives = [
        getPerformanceObjectiveMock({ id: 1, name: 'Objective 1' }),
        getPerformanceObjectiveMock({ id: 2, name: 'Objective 2' }),
        getPerformanceObjectiveMock({ id: 3, name: 'Objective 3' }),
      ];

      const allFields = [
        getPerformanceMetricMock({ id: 1, name: 'Metric 1' }),
        getPerformanceMetricMock({ id: 2, name: 'Metric 2' }),
        getPerformanceMetricMock({ id: 3, name: 'Metric 3' }),
      ];

      const objectivesAndMetrics: PerformanceObjectivesAndMetrics = {
        objectives,
        allFields,
      };

      const fakeAllMetricsObjective: PerformanceObjective = {
        id: FAKE_ALL_METRICS_OBJECTIVE_ID,
        name: FAKE_ALL_METRICS_OBJECTIVE_NAME,
        fields: allFields,
      };

      const expectedObjectives = [fakeAllMetricsObjective, ...objectives];

      expect(getAllObjectivesWithFakeAllMetricsObjective(objectivesAndMetrics)).toStrictEqual(expectedObjectives);
    });
  });

  describe('getObjectivesWithoutFakeAllMetricsObjective', () => {
    it('getObjectivesWithoutFakeAllMetricsObjective returns empty array if undefined passed', () => {
      expect(getObjectivesWithoutFakeAllMetricsObjective()).toStrictEqual([]);
    });

    it('getObjectivesWithoutFakeAllMetricsObjective returns empty array if empty array passed', () => {
      expect(getObjectivesWithoutFakeAllMetricsObjective([])).toStrictEqual([]);
    });

    it('getObjectivesWithoutFakeAllMetricsObjective returns empty array if passed array contains only fake all metrics objective', () => {
      const allObjectives: PerformanceObjective[] = [
        {
          id: FAKE_ALL_METRICS_OBJECTIVE_ID,
          name: FAKE_ALL_METRICS_OBJECTIVE_NAME,
          fields: [getPerformanceMetricMock()],
        },
      ];

      expect(getObjectivesWithoutFakeAllMetricsObjective(allObjectives)).toStrictEqual([]);
    });

    it('getObjectivesWithoutFakeAllMetricsObjective returns unchanged array if passed array does not contains fake all metrics objective', () => {
      const objectives = [
        getPerformanceObjectiveMock({ id: 1, name: 'Objective 1' }),
        getPerformanceObjectiveMock({ id: 2, name: 'Objective 2' }),
        getPerformanceObjectiveMock({ id: 3, name: 'Objective 3' }),
      ];

      expect(getObjectivesWithoutFakeAllMetricsObjective(objectives)).toStrictEqual(objectives);
    });

    it('getObjectivesWithoutFakeAllMetricsObjective returns objectives array without fake all metrics objective', () => {
      const objective1 = getPerformanceObjectiveMock({ id: 1, name: 'Objective 1' });
      const objective2 = getPerformanceObjectiveMock({ id: 2, name: 'Objective 2' });
      const objective3 = getPerformanceObjectiveMock({ id: 3, name: 'Objective 3' });

      const fakeAllMetricsObjective: PerformanceObjective = {
        id: FAKE_ALL_METRICS_OBJECTIVE_ID,
        name: FAKE_ALL_METRICS_OBJECTIVE_NAME,
        fields: [getPerformanceMetricMock()],
      };

      const allObjectives = [fakeAllMetricsObjective, objective1, objective2, objective3];

      const expectedObjectives = [objective1, objective2, objective3];

      expect(getObjectivesWithoutFakeAllMetricsObjective(allObjectives)).toStrictEqual(expectedObjectives);
    });
  });

  describe('isAvailableDatesToCreatePhase', () => {
    it('isAvailableDatesToCreatePhase returns false if no phases passed', () => {
      const projectMock = getProjectDetailsMock();
      expect(isAvailableDatesToCreatePhase(undefined, projectMock)).toStrictEqual(false);
    });

    it('isAvailableDatesToCreatePhase returns false if no project passed', () => {
      const phaseMock = getPhaseMock();
      expect(isAvailableDatesToCreatePhase([phaseMock], undefined)).toStrictEqual(false);
    });

    it('isAvailableDatesToCreatePhase returns false if no project and no phases passed', () => {
      expect(isAvailableDatesToCreatePhase(undefined, undefined)).toStrictEqual(false);
    });

    it('isAvailableDatesToCreatePhase returns false if empty phases array passed', () => {
      const projectMock = getProjectDetailsMock();
      expect(isAvailableDatesToCreatePhase([], projectMock)).toStrictEqual(false);
    });

    it('isAvailableDatesToCreatePhase returns false if project end date and last phase end date is same day and last phase is 1 day long', () => {
      const projectMock = getProjectDetailsMock({ endDate: parseDateByFormat('2020-10-10', 'yyyy-MM-dd') });

      const phase1 = getPhaseMock({ end_date: parseDateByFormat('2018-10-10', 'yyyy-MM-dd') });
      const phase2 = getPhaseMock({ end_date: parseDateByFormat('2019-10-10', 'yyyy-MM-dd') });
      const phase3 = getPhaseMock({
        start_date: parseDateByFormat('2020-10-10', 'yyyy-MM-dd'),
        end_date: parseDateByFormat('2020-10-10', 'yyyy-MM-dd'),
      });

      const phases = [phase1, phase2, phase3];

      expect(isAvailableDatesToCreatePhase(phases, projectMock)).toStrictEqual(false);
    });

    it('isAvailableDatesToCreatePhase returns true if project end date and last phase end date is same day and last phase is more than 1 day long', () => {
      const projectMock = getProjectDetailsMock({ endDate: parseDateByFormat('2020-10-10', 'yyyy-MM-dd') });

      const phase1 = getPhaseMock({ end_date: parseDateByFormat('2018-10-10', 'yyyy-MM-dd') });
      const phase2 = getPhaseMock({ end_date: parseDateByFormat('2019-10-10', 'yyyy-MM-dd') });
      const phase3 = getPhaseMock({
        start_date: parseDateByFormat('2020-10-9', 'yyyy-MM-dd'),
        end_date: parseDateByFormat('2020-10-10', 'yyyy-MM-dd'),
      });

      const phases = [phase1, phase2, phase3];

      expect(isAvailableDatesToCreatePhase(phases, projectMock)).toStrictEqual(true);
    });

    it('isAvailableDatesToCreatePhase returns true if project end date is after (at least + 1 day) than last phase end date and phase longer than 1 day', () => {
      const projectMock = getProjectDetailsMock({ endDate: parseDateByFormat('2020-10-11', 'yyyy-MM-dd') });

      const phase1 = getPhaseMock({ end_date: parseDateByFormat('2018-10-10', 'yyyy-MM-dd') });
      const phase2 = getPhaseMock({ end_date: parseDateByFormat('2019-10-10', 'yyyy-MM-dd') });
      const phase3 = getPhaseMock({ end_date: parseDateByFormat('2020-10-10', 'yyyy-MM-dd') });

      const phases = [phase1, phase2, phase3];

      expect(isAvailableDatesToCreatePhase(phases, projectMock)).toStrictEqual(true);
    });
  });

  describe('getSearchedObjectives', () => {
    it('should return empty array if there are no search query and objectives', () => {
      expect(getSearchedObjectives('')).toStrictEqual([]);
    });

    it('should return empty array if there is some search query and no objectives', () => {
      expect(getSearchedObjectives('objective')).toStrictEqual([]);
    });

    it('should return empty array if there are no occurrences', () => {
      const objectives = getStubObjectives();

      expect(getSearchedObjectives('bla-bla-bla', objectives)).toStrictEqual([]);
    });

    it('should return empty array if search query contains objectives name, but not metrics', () => {
      const objectives = getStubObjectives();

      expect(getSearchedObjectives('objective', objectives)).toStrictEqual([]);
    });

    it('should return searched objectives', () => {
      const objectives = getStubObjectives();

      const metric4 = getStubMetric4();
      const expectedSearchedObjectives = [
        {
          id: 2,
          name: 'objective 2',
          fields: [metric4],
        },
        {
          id: 4,
          name: 'objective 4',
          fields: [metric4],
        },
      ];

      expect(getSearchedObjectives('metric 4', objectives)).toStrictEqual(expectedSearchedObjectives);
    });

    it('should return all objectives', () => {
      const objectives = getStubObjectives();

      const expectedObjectives = [getStubObjective1(), getStubObjective2(), getStubObjective4()];

      expect(getSearchedObjectives('metric', objectives)).toStrictEqual(expectedObjectives);
    });
  });

  describe('getTreeNodes', () => {
    it('should return empty array if there are no objectives, and selected objective id', () => {
      expect(getTreeNodes()).toStrictEqual([]);
    });

    it('should return empty array if there are no objectives, and is selected objective id', () => {
      expect(getTreeNodes(undefined, 1)).toStrictEqual([]);
    });

    it('should return empty array if there are objectives, but no selected objective id', () => {
      expect(getTreeNodes([getStubObjective1()])).toStrictEqual([]);
    });

    it('should return correct tree nodes if there are objectives, but no selected objective id', () => {
      const objectives = getStubObjectives();

      expect(getTreeNodes(objectives)).toStrictEqual([]);
    });

    it('should return correct tree nodes if there are objectives and selected objective id that not found', () => {
      const objectives = getStubObjectives();

      const expectedTreeNodes = [
        {
          id: 1,
          name: 'Advertising',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 1,
              name: 'Metric 1',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 6,
              name: 'Metric 6',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 4,
          name: 'Streams',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 4,
              name: 'Metric 4',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 3,
          name: 'Video',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 7,
              name: 'Metric 7',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 3,
              name: 'Metric 3',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 2,
          name: 'Finance',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 2,
              name: 'Metric 2',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 5,
          name: 'Other',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 5,
              name: 'Metric 5',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
      ];

      expect(getTreeNodes(objectives, 10)).toStrictEqual(expectedTreeNodes);
    });

    it('should return correct tree nodes if there are objectives and selected objective id', () => {
      const objectives = getStubObjectives();

      const expectedTreeNodes = [
        {
          id: 0,
          name: 'objective 4',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 4,
              name: 'Metric 4',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 5,
              name: 'Metric 5',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 6,
              name: 'Metric 6',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 7,
              name: 'Metric 7',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 1,
          name: 'Advertising',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 1,
              name: 'Metric 1',
              children: [],
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
            },
            {
              id: 6,
              name: 'Metric 6',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 4,
          name: 'Streams',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 4,
              name: 'Metric 4',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 3,
          name: 'Video',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 7,
              name: 'Metric 7',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
            {
              id: 3,
              name: 'Metric 3',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 2,
          name: 'Finance',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 2,
              name: 'Metric 2',
              description: 'Hello',
              type: TreeItemType.CHECKBOX,
              children: [],
            },
          ],
        },
        {
          id: 5,
          name: 'Other',
          type: TreeItemType.CHECKBOX,
          children: [
            {
              id: 5,
              name: 'Metric 5',
              description: 'Hello',
              children: [],
              type: TreeItemType.CHECKBOX,
            },
          ],
        },
      ];

      expect(getTreeNodes(objectives, 4)).toStrictEqual(expectedTreeNodes);
    });
  });
});
