/*
 * Copyright (C) 2018 Robert Bosch GmbH Copyright (C) 2018 Robert Bosch Manufacturing Solutions GmbH, Germany. All rights reserved.
 */

import { inject, TestBed } from '@angular/core/testing';
import { PicklistItem } from './picklist.model';
import { PicklistService } from './picklist.service';
import { combineLatest, firstValueFrom } from 'rxjs';

describe('PicklistService', () => {
  const source = [
    {
      name: 'c',
      val: 1
    },
    {
      name: 'a',
      val: 2
    },
    {
      name: 'b',
      val: 3
    },
  ];
  const target = [
    {
      name: 'b',
      val: 1
    },
    {
      name: 'c',
      val: 2
    },
    {
      name: 'a',
      val: 3
    },
  ];

  beforeEach(() => {
    TestBed.configureTestingModule({
      providers: [PicklistService],
    });
  });

  it('should be created', inject([PicklistService], (service: PicklistService) => {
    expect(service).toBeTruthy();
  }));

  it('should get if items are selected', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      const expectedSourceValue = true;
      const expectedTargetValue = true;
      service.selectAllSourceItems();
      service.selectAllTargetItems();

      service.selectionState$.subscribe((selection) => {
        expect(selection.sourceItemsSelected).toBe(expectedSourceValue);
        expect(selection.targetItemsSelected).toBe(expectedTargetValue);
        done();
      });
    })()
  });

  it('should select and move all source values', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none', 'name', 'move');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toEqual(0);
        expect(targetItems.length).toEqual(6);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should select and move all source values because target items limit is not reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(7);
      service.initialize(source, target, 'none', 'none', 'name', 'move');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toEqual(0);
          expect(targetItems.length).toEqual(6);
          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should select and not move all source values because target items limit would be exceeded', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'none', 'none', 'name', 'move');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toEqual(3);
          expect(targetItems.length).toEqual(3);
          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('should select and copy all source values', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none', 'name', 'copy');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toEqual(3);
        expect(targetItems.length).toEqual(6);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should select and copy all source values because target items limit is not reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(7);
      service.initialize(source, target, 'none', 'none', 'name', 'copy');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toEqual(3);
          expect(targetItems.length).toEqual(6);
          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should select and not copy all source values because target items limit is already reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none', 'name', 'copy');
      selectAndAddAllSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toEqual(3);
          expect(targetItems.length).toEqual(3);
          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should select and move all target values', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none', 'name', 'move');
      selectAndRemoveAllTargetItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toEqual(6);
        expect(targetItems.length).toEqual(0);
        done();
      });
    })()
  });

  it('should select and copy all target values', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none', 'name', 'copy');
      selectAndRemoveAllTargetItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toEqual(3);
        expect(targetItems.length).toEqual(0);
        done();
      });
    })()
  });

  it('should initialize with no sorting', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems[2].value.name).toBe('b');
        expect(sourceItems.length).toBe(3);

        expect(targetItems.length).toBe(3);
        expect(targetItems[2].value.name).toBe('a');
        done();
      });
    })()
  });

  it('should initialize with sorting', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'asc', 'desc');

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(sourceItems[0].value.name).toBe('a');

        expect(targetItems.length).toBe(3);
        expect(targetItems[0].value.name).toBe('c');
        done();
      });
    })()
  });

  it('should select source item and move', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'asc', 'desc', 'name', 'move');
      const item = source[0];
      selectSourceItemsAndAdd(service, [item]);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(2);
        expect(targetItems.length).toBe(4);
        expect(targetItems[1].value.val).toEqual(item.val);
        expect(targetItems[1].label).toEqual(item.name);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should select source item and move because target items limit is not reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'asc', 'desc', 'name', 'move');
      const item = source[0];
      selectSourceItemsAndAdd(service, [item]);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(2);

          expect(targetItems.length).toBe(4);
          expect(targetItems[1].value.val).toEqual(item.val);
          expect(targetItems[1].label).toEqual(item.name);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should select source item and not move because target items limit is already reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'asc', 'desc', 'name', 'move');
      const item = source[0];
      selectSourceItemsAndAdd(service, [item]);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);
          expect(targetItems.length).toBe(3);

          expect(targetItems[3]).toEqual(undefined);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should select source items and copy', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'asc', 'desc', 'name', 'copy');
      selectSourceItemsAndAdd(service, [source[0], source[1]]);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(5);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should select source items and copy because target items limit is not reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(6);
      service.initialize(source, target, 'asc', 'desc', 'name', 'copy');
      selectSourceItemsAndAdd(service, [source[0], source[1]]);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);
          expect(targetItems.length).toBe(5);
          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should select source items and not copy because target items limit is already reached', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'asc', 'desc', 'name', 'copy');
      selectSourceItemsAndAdd(service, [source[0], source[1]]);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);
          expect(targetItems.length).toBe(3);
          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should select target item and move', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const item = target[0];
      selectTargetItemAndRemove(service, [item]);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(4);
        expect(sourceItems[3].value.val).toEqual(item.val);
        expect(sourceItems[3].value.name).toEqual(item.name);

        expect(targetItems.length).toBe(2);
        done();
      });
    })()
  });

  it('should select target item and copy', (done) => {
    inject([PicklistService], (service: PicklistService) => {
      service.initialize(source, target, 'asc', 'desc');
      service.mode = 'copy';
      selectTargetItemAndRemove(service, [target[0]]);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(2);
        done();
      });
    })()
  });

  it('should copy items', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const item = await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(4);
        expect(targetItems[3]).toEqual(item);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should copy items because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const item = await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(4);
          expect(targetItems[3]).toEqual(item);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not copy items because target items limit is already reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(3);
          expect(targetItems[3]).toEqual(undefined);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should move items', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const item = await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(2);
        expect(targetItems.length).toBe(4);
        expect(targetItems[3]).toEqual(item);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should move items because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const item = await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(2);

          expect(targetItems.length).toBe(4);
          expect(targetItems[3]).toEqual(item);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not move items because target items limit is already reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      await addFirstItemFromSourceItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(3);
          expect(targetItems[3]).toEqual(undefined);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should remove items (with move option)', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const item = await removeFirstItemFromTargetItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(4);
        expect(sourceItems[3]).toEqual(item);
        expect(targetItems.length).toBe(2);
        done();
      });
    })()
  });

  it('should remove items (with copy option)', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      await removeFirstItemFromTargetItems(service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(2);
        done();
      });
    })()
  });

  it('should insert item at index with move', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      const item = await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(2);
        expect(targetItems.length).toBe(4);
        expect(targetItems[index]).toEqual(item);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should insert item at index with move because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      const item = await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(2);

          expect(targetItems.length).toBe(4);
          expect(targetItems[index]).toEqual(item);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not insert item at index with move because target items limit is already reached', done => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);
          expect(targetItems.length).toBe(3);
          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should insert multiple items at index with move', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      const insertedItems = await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(1);
        expect(targetItems.length).toBe(5);
        expect(targetItems[index]).toEqual(insertedItems[0]);
        expect(targetItems[index + 1]).toEqual(insertedItems[1]);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should insert multiple items at index with move because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(6);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      const insertedItems = await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(1);

          expect(targetItems.length).toBe(5);
          expect(targetItems[index]).toEqual(insertedItems[0]);
          expect(targetItems[index + 1]).toEqual(insertedItems[1]);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not insert multiple items at index with move because target items limit is already reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'move';
      const index = 1;
      await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(3);
          expect(targetItems[index].value).toEqual(target[index]);
          expect(targetItems[index + 1].value).toEqual(target[index + 1]);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should insert item at index with copy', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      const item = await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(4);
        expect(targetItems[index]).toEqual(item);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should insert item at index with copy because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(5);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      const item = await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(4);
          expect(targetItems[index]).toEqual(item);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not insert item at index with copy because target items limit is already reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      await insertFirstItemAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(3);
          expect(targetItems[index].value).toEqual(target[index]);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should insert multiple items at index with copy', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      const insertedItems = await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems]).subscribe(([sourceItems, targetItems]) => {
        expect(sourceItems.length).toBe(3);
        expect(targetItems.length).toBe(5);
        expect(targetItems[index]).toEqual(insertedItems[0]);
        expect(targetItems[index + 1]).toEqual(insertedItems[1]);
        done();
      });
    })()
  });

  it('targetItemsMaxLimit set: should insert multiple items at index with copy because target items limit is not reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(6);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      const insertedItems = await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(5);
          expect(targetItems[index]).toEqual(insertedItems[0]);
          expect(targetItems[index + 1]).toEqual(insertedItems[1]);

          expect(isTargetItemsMaxLimitReached).toBe(false);
          done();
        });
    })()
  });

  it('targetItemsMaxLimit set: should not insert multiple items at index with copy because target items limit is already reached', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.setTargetItemsMaxLimit(3);
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 1;
      await insertMultipleItemsAtIndexFromSourceItems(index, service);

      combineLatest([service.processedSourceItems, service.processedTargetItems, service.isTargetItemsMaxLimitReachedState])
        .subscribe(([sourceItems, targetItems, isTargetItemsMaxLimitReached]) => {
          expect(sourceItems.length).toBe(3);

          expect(targetItems.length).toBe(3);
          expect(targetItems[index].value).toEqual(target[index]);
          expect(targetItems[index + 1].value).toEqual(target[index + 1]);

          expect(isTargetItemsMaxLimitReached).toBe(true);
          done();
        });
    })()
  });

  it('should reposition target item', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 2;
      const item = await repositionTargetItemAtIndex(index, service);

      service.processedTargetItems.subscribe((targetItems) => {
        expect(targetItems.length).toBe(3);
        expect(targetItems[1]).toEqual(item);
        done();
      });
    })()
  });

  it('should reposition multiple target items', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      service.mode = 'copy';
      const index = 3;
      const repositionedItems = await repositionMultipleTargetItemsAtIndes(index, service);

      service.processedTargetItems.subscribe((targetItems) => {
        expect(targetItems.length).toBe(3);
        expect(targetItems[1]).toEqual(repositionedItems[0]);
        expect(targetItems[2]).toEqual(repositionedItems[1]);
        done();
      });
    })()
  });

  it('should apply source filter', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      const filteredData = 1;
      service.setSourceFilter('{"name":"c","val":1}');

      await new Promise((r) => setTimeout(r, 305));

      service.processedSourceItems.subscribe((sourceItems) => {
        expect(sourceItems.length).toBe(filteredData);
        done();
      });

    })()
  });

  it('should apply target filter', (done) => {
    inject([PicklistService], async (service: PicklistService) => {
      service.initialize(source, target, 'none', 'none');
      const filteredData = 1;
      service.setTargetFilter('{"name":"a","val":3}');

      await new Promise((r) => setTimeout(r, 305));

      service.processedTargetItems.subscribe((targetItems) => {
        expect(targetItems.length).toEqual(filteredData);
        done();
      });
    })()
  });
});

function selectAndAddAllSourceItems(service: PicklistService) {
  service.selectAllSourceItems();
  service.addAll();
}

function selectAndRemoveAllTargetItems(service: PicklistService) {
  service.selectAllTargetItems();
  service.removeAll();
}

function selectSourceItemsAndAdd(service: PicklistService, sourceItems) {
  service.selectSourceItems(sourceItems);
  service.addSelection();
}

function selectTargetItemAndRemove(service: PicklistService, targetItems) {
  service.selectTargetItems(targetItems);
  service.removeSelection();
}

async function addFirstItemFromSourceItems(service: PicklistService) {
  const sourceItems = await firstValueFrom(service.processedSourceItems);
  const item = sourceItems[0];
  service.addItems(item);
  return item;
}

async function removeFirstItemFromTargetItems(service: PicklistService) {
  const targetItems = await firstValueFrom(service.processedTargetItems);
  const item = targetItems[0];
  service.removeItems(3, item);
  return item;
}

async function insertFirstItemAtIndexFromSourceItems(index: number, service: PicklistService) {
  const sourceItems = await firstValueFrom(service.processedSourceItems);
  const item = sourceItems[0];
  service.insertItems(index, item);
  return item;
}

async function insertMultipleItemsAtIndexFromSourceItems(index: number, service: PicklistService) {
  const sourceItems = await firstValueFrom(service.processedSourceItems);
  const items: PicklistItem[] = [];
  items.push(sourceItems[0], sourceItems[2]);
  service.insertItems(index, ...items);
  return items;
}

async function repositionTargetItemAtIndex(index: number, service: PicklistService) {
  const targetItems = await firstValueFrom(service.processedTargetItems);
  const item = targetItems[0];
  service.repositionTargetItems(index, item);
  return item;
}

async function repositionMultipleTargetItemsAtIndes(index: number, service: PicklistService) {
  const targetItems = await firstValueFrom(service.processedTargetItems);
  const items: PicklistItem[] = [];
  items.push(targetItems[1], targetItems[2]);
  service.repositionTargetItems(index, ...items);
  return items;
}
