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

import { fakeAsync, flush, tick } from '@angular/core/testing';
import { ValidationErrors } from '@angular/forms';
import { MatChipInputEvent } from '@angular/material/chips';
import { of } from 'rxjs';
import { BciChipListAutocompleteComponent, FilterableChipOptionProvider } from './chip-list-autocomplete.component';
import { ChipOption } from './chip-option.model';

describe('Auto-Completable Chip List Input Component', () => {
  const testOptions: ChipOption[] = [
    { id: '1', name: 'OptionX' },
    { id: '2', name: 'OptionY' },
    { id: '3', name: 'Other Thing' },
  ];

  const testOptionsExternallyLoaded: ChipOption[] = [
    { id: '1', name: 'OptionX externally loaded' },
    { id: '2', name: 'OptionY externally loaded' },
    { id: '3', name: 'Other Thing externally loaded' },
  ];

  const testOptionProvider: FilterableChipOptionProvider = (userInput: string) => {
    return of(testOptionsExternallyLoaded.filter((option) => !userInput || option.name.indexOf(userInput) > -1));
  };

  let component;
  let focusMonitorMock, htmlElementRefMock, intl;

  beforeEach(() => {
    focusMonitorMock = {
      monitor: jest.fn(() => of()),
      stopMonitoring: jest.fn(),
    };
    htmlElementRefMock = { nativeElement: null, querySelector: jest.fn() };
    intl = { oneMoreItem: 'more item', moreItems: 'more items' };
    component = new BciChipListAutocompleteComponent(intl, focusMonitorMock, htmlElementRefMock);
    component.userInput = {
      nativeElement: {
        value: null,
        blur: jest.fn(),
        touched: false,
      },
    };

    component.ngOnInit();
  });

  function assertSelectedChipOptions(expectedChipOptions: ChipOption[]) {
    expect(component.value).toEqual(expectedChipOptions);
  }

  function assertSelectedChipOptionsNotContain(chipOptions: ChipOption[]) {
    for (const chipOption of chipOptions) {
      expect(component.value).not.toContain(chipOption);
    }
  }

  function enterKeyStokesToFormInput(...keyStokes) {
    for (const keyStoke of keyStokes) {
      component.userInputControl['touched'] = true;
      component.userInputControl.setValue(keyStoke, { emitEvent: true });
    }
  }

  it('should have disabled false by default', () => {
    expect(component.disabled).toEqual(false);
  });

  it('should disabled the userInput if disabled is set', () => {
    component.disabled = true;
    expect(component.userInputControl.disabled).toEqual(true);
  });

  it('should have allow free text false by default', () => {
    expect(component.allowFreeText).toEqual(false);
  });

  it('should add new chips in order if show reversed is not set', () => {
    const firstTestChip = 'firstTestChip';
    const secondTestChip = 'secondTestChip';
    component.showReversedChips = false;
    component.allowFreeText = true;
    component.addFreeTextChip(buildInputEventFromInput(firstTestChip));
    component.addFreeTextChip(buildInputEventFromInput(secondTestChip));

    assertSelectedChipOptions([
      { id: firstTestChip, name: firstTestChip },
      { id: secondTestChip, name: secondTestChip },
    ]);
  });

  it('should add new chips in reverse order if show reversed is set', () => {
    const firstTestChip = 'firstTestChip';
    const secondTestChip = 'secondTestChip';
    component.showReversedChips = true;
    component.allowFreeText = true;
    component.addFreeTextChip(buildInputEventFromInput(firstTestChip));
    component.addFreeTextChip(buildInputEventFromInput(secondTestChip));

    assertSelectedChipOptions([
      { id: secondTestChip, name: secondTestChip },
      { id: firstTestChip, name: firstTestChip },
    ]);
  });

  it('should remove only the hidden chips', () => {
    const firstTestChip = 'firstTestChip';
    const secondTestChip = 'secondTestChip';
    const thirdTestChip = 'thirdTestChip';
    component.maxChipsVisible = 1;
    component.allowFreeText = true;
    component.addFreeTextChip(buildInputEventFromInput(firstTestChip));
    component.addFreeTextChip(buildInputEventFromInput(secondTestChip));
    component.addFreeTextChip(buildInputEventFromInput(thirdTestChip));

    assertSelectedChipOptions([
      { id: firstTestChip, name: firstTestChip },
      { id: secondTestChip, name: secondTestChip },
      { id: thirdTestChip, name: thirdTestChip },
    ]);

    component.onHiddenChipsRemoved();
    assertSelectedChipOptions([{ id: firstTestChip, name: firstTestChip }]);
  });

  describe('when static options list given', () => {
    beforeEach(() => {
      component.options = testOptions;
    });

    it('should fail when also setting provider', () => {
      expect(
        () =>
          (component.optionsProvider = (userInput: string) => {
            return of([
              { id: '1', name: `${userInput} externally loaded` },
              { id: '2', name: `${userInput} externally loaded` },
            ]);
          })
      ).toThrow(
        new Error(
          'Initialising failed. When [optionsProvider] input is set, you cant pass static options via [options].'
        )
      );
    });
  });

  describe('when only options provider configured', () => {
    beforeEach(() => {
      component.options = undefined;
      component.optionsProvider = testOptionProvider;
      component.ngOnInit();
    });

    describe('auto completion options', () => {
      it('should return all options from provider when input is cleared', fakeAsync(() => {
        let result: ChipOption[];
        component.userFilteredOptions.subscribe((options: ChipOption[]) => {
          result = options;
        });

        enterKeyStokesToFormInput('');

        tick(100);

        expect(result).toEqual(testOptionsExternallyLoaded);
      }));

      it('should return all matching options for the given input from provider.', fakeAsync(() => {
        let result: ChipOption[];
        component.userFilteredOptions.subscribe((options: ChipOption[]) => {
          result = options;
        });

        enterKeyStokesToFormInput('Option');

        tick(100);

        const expectedResult = [testOptionsExternallyLoaded[0], testOptionsExternallyLoaded[1]];

        expect(result).toEqual(expectedResult);
      }));

      it('should return all matching options for the given input from provider except for the selected input.', fakeAsync(() => {
        component.value = [testOptionsExternallyLoaded[1]];
        let result: ChipOption[];
        component.userFilteredOptions.subscribe((options: ChipOption[]) => {
          result = options;
        });

        enterKeyStokesToFormInput('Option');

        tick(100);

        const expectedResult = [testOptionsExternallyLoaded[0]];

        expect(result).toEqual(expectedResult);
      }));
    });

    describe('retrieving the remaining options', () => {
      beforeEach(() => {
        component.userFilteredOptions.subscribe();
      });

      it('should return the available options excluding the selected options', () => {
        component.value = [testOptionsExternallyLoaded[0]];
        const nonSelectedOptions = component.filterAvailableOptions(testOptionsExternallyLoaded);
        expect(nonSelectedOptions).toEqual([testOptionsExternallyLoaded[1], testOptionsExternallyLoaded[2]]);
      });

      it('should return an empty array if options are undefined', () => {
        const nonSelectedOptions = component.filterAvailableOptions(undefined);
        expect(nonSelectedOptions).toEqual([]);
      });

      it('should return the unfiltered options if the selected options are undefined', () => {
        component.value = undefined;
        const nonSelectedOptions = component.filterAvailableOptions(testOptionsExternallyLoaded);
        expect(nonSelectedOptions).toEqual(testOptionsExternallyLoaded);
      });
    });

    describe('on option selected', () => {
      beforeEach(fakeAsync(() => {
        component.userFilteredOptions.subscribe();

        enterKeyStokesToFormInput('O');

        tick(100);

        component.onAutocompleteOptionSelected({
          option: {
            id: testOptionsExternallyLoaded[0].id,
            value: testOptionsExternallyLoaded[0].name,
          },
        });
        tick(100);
      }));

      describe('with no limit', () => {
        beforeEach(fakeAsync(() => {
          component.chipLimit = undefined;
          component.onAutocompleteOptionSelected({
            option: {
              id: testOptionsExternallyLoaded[1].id,
              value: testOptionsExternallyLoaded[1].name,
            },
          });
          tick(100);
        }));

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([testOptionsExternallyLoaded[0], testOptionsExternallyLoaded[1]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptionsExternallyLoaded[0]);
          expect(component.availableOptions).not.toContain(testOptionsExternallyLoaded[1]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });

      describe('with limit of one chip', () => {
        beforeEach(fakeAsync(() => {
          component.chipLimit = 1;
          component.onAutocompleteOptionSelected({
            option: {
              id: testOptionsExternallyLoaded[1].id,
              value: testOptionsExternallyLoaded[1].name,
            },
          });
          tick(100);
        }));

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([testOptionsExternallyLoaded[0]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptionsExternallyLoaded[0]);
          expect(component.availableOptions).toContain(testOptionsExternallyLoaded[1]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });
    });

    describe('with other already selected value', () => {
      describe('that is not in the current provider result', () => {
        const preSelectedOptions = [{ id: '42', name: 'other-value' }];

        beforeEach(fakeAsync(() => {
          component.value = preSelectedOptions;
          component.userFilteredOptions.subscribe();

          enterKeyStokesToFormInput('O');

          tick(100);

          component.onAutocompleteOptionSelected({
            option: {
              id: testOptionsExternallyLoaded[0].id,
              value: testOptionsExternallyLoaded[0].name,
            },
          });

          tick(100);
        }));

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([preSelectedOptions[0], testOptionsExternallyLoaded[0]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptionsExternallyLoaded[0]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });

      describe('that is in the current provider result', () => {
        const preSelectedOptions = [{ id: '2', name: 'OptionY externally loaded' }];

        beforeEach(fakeAsync(() => {
          component.value = preSelectedOptions;
          component.userFilteredOptions.subscribe();

          enterKeyStokesToFormInput('O');

          tick(100);

          component.onAutocompleteOptionSelected({
            option: {
              id: testOptionsExternallyLoaded[0].id,
              value: testOptionsExternallyLoaded[0].name,
            },
          });

          tick(100);
        }));

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([preSelectedOptions[0], testOptionsExternallyLoaded[0]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptionsExternallyLoaded[0]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });
    });

    describe('on option removed', () => {
      beforeEach(() => {
        component.userFilteredOptions.subscribe();

        component.value = [testOptionsExternallyLoaded[0]];
        component.onSelectedOptionRemoved(testOptionsExternallyLoaded[0].id);
      });

      it('should remove the option from the selected options', () => {
        assertSelectedChipOptionsNotContain([testOptionsExternallyLoaded[0]]);
      });

      it('should re-add the option to the available options', () => {
        expect(component.availableOptions).toEqual(expect.arrayContaining(testOptionsExternallyLoaded));
      });

      it('should reset the user input', () => {
        expect(component.userInput.nativeElement.value).toEqual('');
        expect(component.userInputControl.value).toEqual('');
      });
    });

    describe('and free text is allowed', () => {
      it('should add a new chip for entered free text', () => {
        const testInput = 'testInput';
        component.allowFreeText = true;

        component.addFreeTextChip(buildInputEventFromInput(testInput));

        assertSelectedChipOptions([{ id: testInput, name: testInput }]);
      });

      describe('options and free text', () => {
        beforeEach(() => {
          component.allowFreeText = true;

          component.userFilteredOptions.subscribe();

          component.value = [testOptionsExternallyLoaded[0]];
        });

        it('should allow entering free text after option has been removed (BCIPPM-4591).', () => {
          component.onSelectedOptionRemoved(testOptionsExternallyLoaded[0].id);

          const testInput = 'testInput';

          component.addFreeTextChip(buildInputEventFromInput(testInput));

          assertSelectedChipOptions([{ id: testInput, name: testInput }]);
        });
      });
    });

    describe('user input validation', () => {
      beforeEach(() => {
        component.userFilteredOptions.subscribe();
      });

      describe('with free text NOT allowed', () => {
        beforeEach(() => {
          component = new BciChipListAutocompleteComponent(intl, focusMonitorMock, htmlElementRefMock);
          component.userInput = {
            nativeElement: {
              value: null,
              blur: jest.fn(),
            },
          };
          component.allowFreeText = false;
          component.options = testOptions;
          component.ngOnInit();
        });

        it('should show error if input is NOT one of the available options', fakeAsync(() => {
          enterKeyStokesToFormInput('UNKNOWN');

          tick(100);

          expect(component.userInputControl.hasError('requireMatch')).toEqual(true);
        }));

        it('should NOT show error if input is one of the available options', fakeAsync(() => {
          enterKeyStokesToFormInput('OptionX');

          tick(100);

          expect(component.userInputControl.hasError('requireMatch')).toEqual(false);
        }));
      });

      describe('with free text allowed', () => {
        beforeEach(() => {
          component.allowFreeText = true;
          component.chipList = { errorState: false };
        });

        it('should NOT show error if input is NOT one of the available options', () => {
          const validationErrors = component.validateUserInputIsValidOption({
            value: 'nonExistentItem',
          });

          expect(validationErrors).toEqual(null);
          expect(component.userInputControl.valid).toEqual(true);
        });

        it('should NOT show error if input is one of the available options', () => {
          const validationErrors = component.validateUserInputIsValidOption({
            value: 'OptionX',
          });

          expect(validationErrors).toEqual(null);
          expect(component.userInputControl.valid).toEqual(true);
        });

        describe('and external validator', () => {
          // eslint-disable-next-line no-useless-escape
          const EMAIL_REGEXP = /^[a-z0-9!#$%&'*+\/=?^_`{|}~.-]+@[a-z0-9]([a-z0-9-]*[a-z0-9])?(\.[a-z0-9]([a-z0-9-]*[a-z0-9])?)*$/i;

          beforeEach(() => {
            component.externalFreeTextValidator = (userInput: string) => {
              let errors: ValidationErrors = {};
              const userInputIsValidOption = EMAIL_REGEXP.test(userInput);

              if (userInput && !userInputIsValidOption) {
                errors.requireEmail = true;
              } else {
                errors = null;
              }
              return errors;
            };

            component.chipList = { errorState: false };
          });

          it('should allow to enter email address', fakeAsync(() => {
            const validEmail = 'no@m.de';

            enterKeyStokesToFormInput(validEmail);

            tick(100);

            component.addFreeTextChip(buildInputEventFromInput(validEmail));
            assertSelectedChipOptions([{ id: validEmail, name: validEmail }]);
            expect(component.userInputControl.hasError('requireEmail')).toEqual(false);
          }));

          it('should NOT allow non email address', fakeAsync(() => {
            const invalidEmail = 'no!m.de';
            enterKeyStokesToFormInput(invalidEmail);
            component.addFreeTextChip(buildInputEventFromInput(invalidEmail));

            tick(100);

            assertSelectedChipOptions([]);
            expect(component.userInputControl.hasError('requireEmail')).toEqual(true);
          }));

          it('should not allow to add free text chip when validation fails', fakeAsync(() => {
            const invalidEmail = 'no!m.de';

            component.allowFreeText = true;

            enterKeyStokesToFormInput(invalidEmail);
            component.addFreeTextChip(buildInputEventFromInput(invalidEmail));

            tick(100);

            assertSelectedChipOptions([]);
            expect(component.userInputControl.valid).toEqual(false);
          }));

          describe('with selected options and required field', () => {
            beforeEach(() => {
              component.value = [testOptionsExternallyLoaded[0]];
              component.required = true;
            });

            it('should allow entering free text after option has been removed (BCIPPM-4591).', fakeAsync(() => {
              component.onSelectedOptionRemoved(testOptionsExternallyLoaded[0].id);
              flush();

              const validEmail = 'no@m.de';

              enterKeyStokesToFormInput(validEmail);

              tick(100);

              component.addFreeTextChip(buildInputEventFromInput(validEmail));
              assertSelectedChipOptions([{ id: validEmail, name: validEmail }]);
              expect(component.userInputControl.valid).toEqual(true);
            }));
          });
        });
      });
    });
  });

  describe('when static options list is configured', () => {
    beforeEach(() => {
      component.options = testOptions;
      component.optionsProvider = undefined;
      component.ngOnInit();

      component.userFilteredOptions.subscribe();
    });

    describe('on option selected', () => {
      beforeEach(() => {
        component.onAutocompleteOptionSelected({
          option: {
            id: testOptions[0].id,
            value: testOptions[0].name,
          },
        });
      });

      describe('with no limit', () => {
        beforeEach(() => {
          component.chipLimit = undefined;
          component.onAutocompleteOptionSelected({
            option: {
              id: testOptions[1].id,
              value: testOptions[1].name,
            },
          });
        });

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([testOptions[0], testOptions[1]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptions[0]);
          expect(component.availableOptions).not.toContain(testOptions[1]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });

      describe('with limit of one chip', () => {
        beforeEach(() => {
          component.chipLimit = 1;
          component.onAutocompleteOptionSelected({
            option: {
              id: testOptions[1].id,
              value: testOptions[1].name,
            },
          });
        });

        it('should add the selection to the selected options', () => {
          assertSelectedChipOptions([testOptions[0]]);
        });

        it('should remove the selected option from available options', () => {
          expect(component.availableOptions).not.toContain(testOptions[0]);
          expect(component.availableOptions).toContain(testOptions[1]);
        });

        it('should reset the user input', () => {
          expect(component.userInput.nativeElement.value).toEqual('');
          expect(component.userInputControl.value).toEqual('');
        });
      });
    });

    describe('on option removed', () => {
      beforeEach(fakeAsync(() => {
        component.value = [testOptions[0]];
        component.onSelectedOptionRemoved(testOptions[0].id);
        tick(100);
      }));

      it('should remove the option from the selected options', () => {
        assertSelectedChipOptionsNotContain([testOptions[0]]);
      });

      it('should re-add the option to the available options', () => {
        expect(component.availableOptions).toEqual(expect.arrayContaining(testOptions));
      });

      it('should reset the user input', () => {
        expect(component.userInput.nativeElement.value).toEqual('');
        expect(component.userInputControl.value).toEqual('');
      });
    });

    describe('adding a free text chip', () => {
      const testInput0 = 'testChip0';
      const testInput1 = 'testChip1';

      describe('with no limit', () => {
        beforeEach(() => {
          component.chipLimit = undefined;
        });

        it('should add a new chips if free text is allowed', () => {
          component.allowFreeText = true;
          component.addFreeTextChip(buildInputEventFromInput(testInput0));
          component.addFreeTextChip(buildInputEventFromInput(testInput1));

          assertSelectedChipOptions([
            { id: testInput0, name: testInput0 },
            { id: testInput1, name: testInput1 },
          ]);
        });

        it('should remove item from available options when free-text equals list option', () => {
          component.allowFreeText = true;
          component.addFreeTextChip(buildInputEventFromInput(testInput0));
          component.addFreeTextChip(buildInputEventFromInput(testOptions[0].name));

          assertSelectedChipOptions([{ id: testInput0, name: testInput0 }, testOptions[0]]);

          expect(component.availableOptions).toEqual([testOptions[1], testOptions[2]]);
        });

        it('should not add a new chip if free text is not allowed', () => {
          component.allowFreeText = false;
          component.addFreeTextChip(buildInputEventFromInput(testInput1));

          assertSelectedChipOptionsNotContain([{ id: testInput1, name: testInput1 }]);
        });
      });

      describe('with limit of one chip', () => {
        beforeEach(() => {
          component.chipLimit = 1;
        });

        it('should add a new chip if free text is allowed', () => {
          component.allowFreeText = true;
          component.addFreeTextChip(buildInputEventFromInput(testInput0));
          component.addFreeTextChip(buildInputEventFromInput(testInput1));

          assertSelectedChipOptions([{ id: testInput0, name: testInput0 }]);
        });
      });
    });

    describe('user input validation', () => {
      describe('with free text NOT allowed', () => {
        beforeEach(() => {
          component.allowFreeText = false;
        });

        it('should show error if input is NOT one of the available options', () => {
          enterKeyStokesToFormInput('nonExistentItem');

          expect(component.userInputControl.hasError('requireMatch')).toEqual(true);
          expect(component.userInputControl.valid).toEqual(false);
        });

        it('should NOT show error if input is one of the available options', () => {
          const validationErrors = component.validateUserInputIsValidOption('OptionX');

          expect(validationErrors).toEqual(null);
          expect(component.userInputControl.valid).toEqual(true);
        });
      });

      describe('with free text allowed', () => {
        beforeEach(() => {
          component.allowFreeText = true;
          component.chipList = { errorState: false };
        });

        it('should NOT show error if input is NOT one of the available options', () => {
          const validationErrors = component.validateUserInputIsValidOption({
            value: 'nonExistentItem',
          });

          expect(validationErrors).toEqual(null);
          expect(component.userInputControl.valid).toEqual(true);
        });

        it('should NOT show error if input is one of the available options', () => {
          const validationErrors = component.validateUserInputIsValidOption({
            value: 'OptionX',
          });

          expect(validationErrors).toEqual(null);
          expect(component.userInputControl.valid).toEqual(true);
        });
      });
    });

    describe('retrieving the remaining options', () => {
      it('should return the available options excluding the selected options', () => {
        component.value = [testOptions[0]];
        const nonSelectedOptions = component.filterAvailableOptions(testOptions);
        expect(nonSelectedOptions).toEqual([testOptions[1], testOptions[2]]);
      });

      it('should return an empty array if options are undefined', () => {
        const nonSelectedOptions = component.filterAvailableOptions(undefined);
        expect(nonSelectedOptions).toEqual([]);
      });

      it('should return the unfiltered options if the selected options are undefined', () => {
        component.value = undefined;
        const nonSelectedOptions = component.filterAvailableOptions(testOptions);
        expect(nonSelectedOptions).toEqual(testOptions);
      });
    });

    it('should filter the available options by user input case insensitively', () => {
      const filteredOptions = component.filterAvailablePredefinedOptions('opTiOnx');
      expect(filteredOptions).toEqual([testOptions[0]]);
    });
  });

  describe('when neither provider nor options configured', () => {
    beforeEach(() => {
      component.options = undefined;
      component.optionsProvider = undefined;
      component.ngOnInit();

      component.userFilteredOptions.subscribe();
    });

    describe('on option removed', () => {
      beforeEach(fakeAsync(() => {
        component.value = [testOptions[0]];
        component.onSelectedOptionRemoved(testOptions[0].id);
        tick(100);
      }));

      it('should remove the option from the selected options', () => {
        assertSelectedChipOptionsNotContain([testOptions[0]]);
      });

      it('should re-add the option to the available options', () => {
        expect(component.availableOptions).toEqual([]);
      });

      it('should reset the user input', () => {
        expect(component.userInput.nativeElement.value).toEqual('');
        expect(component.userInputControl.value).toEqual('');
      });
    });

    describe('adding a free text chip', () => {
      const testInput0 = 'testChip0';
      const testInput1 = 'testChip1';

      it('should add a new chip if free text is allowed', () => {
        component.allowFreeText = true;
        component.addFreeTextChip(buildInputEventFromInput(testInput0));

        assertSelectedChipOptions([{ id: testInput0, name: testInput0 }]);
      });

      it('should not add a new chip if free text is not allowed', () => {
        component.allowFreeText = false;
        component.addFreeTextChip(buildInputEventFromInput(testInput1));

        assertSelectedChipOptionsNotContain([{ id: testInput1, name: testInput1 }]);
      });
    });

    describe('retrieving the remaining options', () => {
      it('should return the available options excluding the selected options', () => {
        component.value = [testOptions[0]];
        const nonSelectedOptions = component.filterAvailableOptions(testOptions);
        expect(nonSelectedOptions).toEqual([testOptions[1], testOptions[2]]);
      });

      it('should return an empty array if options are undefined', () => {
        const nonSelectedOptions = component.filterAvailableOptions(undefined);
        expect(nonSelectedOptions).toEqual([]);
      });

      it('should return the unfiltered options if the selected options are undefined', () => {
        component.value = undefined;
        const nonSelectedOptions = component.filterAvailableOptions(testOptions);
        expect(nonSelectedOptions).toEqual(testOptions);
      });
    });
  });

  function buildInputEventFromInput(input: string) {
    return {
      input: {
        value: input,
      },
      value: input,
    } as MatChipInputEvent;
  }
});
