Ks89/angular-modal-gallery

View on GitHub
projects/ks89/angular-modal-gallery/src/lib/components/carousel/carousel-previews/carousel-previews.component.spec.ts

Summary

Maintainability
F
1 wk
Test Coverage
/*
 * Copyright (C) 2017-2021 Stefano Cappa
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import { ComponentFixture, fakeAsync, flush, TestBed, tick } from '@angular/core/testing';
import { DebugElement, SimpleChanges } from '@angular/core';
import { By, SafeResourceUrl } from '@angular/platform-browser';
import { CarouselPreviewsComponent } from './carousel-previews.component';
import { SizeDirective } from '../../../directives/size.directive';
import { KS_DEFAULT_ACCESSIBILITY_CONFIG } from '../../accessibility-default';
import { InternalLibImage } from '../../../model/image-internal.class';
import { AccessibilityConfig } from '../../../model/accessibility.interface';
import { Image, ImageEvent, ModalImage, PlainImage } from '../../../model/image.class';
import { CarouselConfig } from '../../../model/carousel-config.interface';
import { BreakpointsConfig, CarouselPreviewConfig } from '../../../model/carousel-preview-config.interface';
import { Action } from '../../../model/action.enum';
import { BreakpointObserver } from '@angular/cdk/layout';
import { MediumMockedBreakpointObserver } from '../../../utils/breakpoint-observer-mock.spec';
import { ConfigService } from '../../../services/config.service';
import { FallbackImageDirective } from '../../../directives/fallback-image.directive';

let comp: CarouselPreviewsComponent;
let fixture: ComponentFixture<CarouselPreviewsComponent>;

interface NavigationTestData {
  initial: {
    start: number,
    end: number,
    activeIndex: number
  };
  expected: {
    start: number,
    end: number,
    activeIndex: number
  };
}

const CUSTOM_ACCESSIBILITY: AccessibilityConfig = Object.assign({}, KS_DEFAULT_ACCESSIBILITY_CONFIG);
CUSTOM_ACCESSIBILITY.previewsContainerAriaLabel = 'custom previewsContainerAriaLabel';
CUSTOM_ACCESSIBILITY.previewsContainerTitle = 'custom previewsContainerTitle';
CUSTOM_ACCESSIBILITY.previewScrollNextAriaLabel = 'custom previewScrollNextAriaLabel';
CUSTOM_ACCESSIBILITY.previewScrollNextTitle = 'custom previewScrollNextTitle';
CUSTOM_ACCESSIBILITY.previewScrollPrevAriaLabel = 'custom previewScrollPrevAriaLabel';
CUSTOM_ACCESSIBILITY.previewScrollPrevTitle = 'custom previewScrollPrevTitle';

const DEFAULT_WIDTH = '25%';
const DEFAULT_HEIGHT = '150px';
const CUSTOM_PREVIEW_HEIGHTS: string[] = ['200px', '150px', '300px'];

const GALLERY_ID = 1;

const IMAGES: InternalLibImage[] = [
  new InternalLibImage(
    0,
    {
      img: '/assets/images/gallery/milan-pegasus-gallery-statue.jpg',
      description: 'Description 1'
    },
    {
      img: '/assets/images/gallery/thumbs/t-milan-pegasus-gallery-statue.jpg',
      title: 'First image title',
      alt: 'First image alt',
      ariaLabel: 'First image aria-label'
    }
  ),
  new InternalLibImage(1, {img: '/assets/images/gallery/pexels-photo-47223.jpeg'}, {img: '/assets/images/gallery/thumbs/t-pexels-photo-47223.jpg'}),
  new InternalLibImage(
    2,
    {
      img: '/assets/images/gallery/pexels-photo-52062.jpeg',
      description: 'Description 3',
      title: 'Third image title',
      alt: 'Third image alt',
      ariaLabel: 'Third image aria-label'
    },
    {
      img: '/assets/images/gallery/thumbs/t-pexels-photo-52062.jpg',
      description: 'Description 3'
    }
  ),
  new InternalLibImage(
    3,
    {
      img: '/assets/images/gallery/pexels-photo-66943.jpeg',
      description: 'Description 4',
      title: 'Fourth image title (modal obj)',
      alt: 'Fourth image alt (modal obj)',
      ariaLabel: 'Fourth image aria-label (modal obj)'
    },
    {
      img: '/assets/images/gallery/thumbs/t-pexels-photo-66943.jpg',
      title: 'Fourth image title (plain obj)',
      alt: 'Fourth image alt (plain obj)',
      ariaLabel: 'Fourth image aria-label (plain obj)'
    }
  ),
  new InternalLibImage(4, {img: '/assets/images/gallery/pexels-photo-93750.jpeg'}, {img: '/assets/images/gallery/thumbs/t-pexels-photo-93750.jpg'}),
  new InternalLibImage(
    5,
    {
      img: '/assets/images/gallery/pexels-photo-94420.jpeg',
      description: 'Description 6'
    },
    {img: '/assets/images/gallery/thumbs/t-pexels-photo-94420.jpg'}
  ),
  new InternalLibImage(6, {img: '/assets/images/gallery/pexels-photo-96947.jpeg'}, {img: '/assets/images/gallery/thumbs/t-pexels-photo-96947.jpg'})
];

const NAVIGATION_NEXT_PREVIEWS: NavigationTestData[] = [
  {
    initial: {start: 0, end: 4, activeIndex: 0},
    expected: {start: 1, end: 5, activeIndex: 2}
  },
  {
    initial: {start: 0, end: 4, activeIndex: 0},
    expected: {start: 0, end: 4, activeIndex: 0}
  },
  {
    initial: {start: 0, end: 4, activeIndex: 0},
    expected: {start: 0, end: 4, activeIndex: 1}
  },
  {
    initial: {start: 0, end: 4, activeIndex: 0},
    expected: {start: 1, end: 5, activeIndex: 3}
  }
];

const NAVIGATION_PREV_PREVIEWS: NavigationTestData[] = [
  {
    initial: {start: 3, end: 7, activeIndex: 6},
    expected: {start: 2, end: 6, activeIndex: 4}
  },
  {
    initial: {start: 3, end: 7, activeIndex: 6},
    expected: {start: 3, end: 7, activeIndex: 6}
  },
  {
    initial: {start: 3, end: 7, activeIndex: 6},
    expected: {start: 3, end: 7, activeIndex: 5}
  },
  {
    initial: {start: 3, end: 7, activeIndex: 6},
    expected: {start: 2, end: 6, activeIndex: 3}
  }
];

function checkArrows(arrows: DebugElement[], first: boolean, last: boolean,
                     accessibility: AccessibilityConfig = KS_DEFAULT_ACCESSIBILITY_CONFIG): void {
  const prevArrowClass = first ? 'empty-arrow-preview-image' : 'left-arrow-preview-image';
  const nextArrowClass = last ? 'empty-arrow-preview-image' : 'right-arrow-preview-image';
  expect(arrows.length).toBe(2);
  expect(arrows[0].attributes.class).toBe('nav-left');
  expect(arrows[0].attributes.role).toBe('button');
  expect(arrows[0].attributes['aria-label']).toBe(accessibility.carouselPreviewScrollPrevAriaLabel);
  expect(arrows[0].properties.tabIndex).toBe(first ? -1 : 0); // because with the first image, prev arrow is hidden
  expect(arrows[0].children[0].attributes['aria-hidden']).toBe('true');
  expect(arrows[0].children[0].properties.title).toBe(accessibility.carouselPreviewScrollPrevTitle);
  expect(containsClasses(arrows[0].children[0].properties.className, prevArrowClass + ' inside')).toBeTrue();
  expect(arrows[1].attributes.class).toBe('nav-right');
  expect(arrows[1].attributes.role).toBe('button');
  expect(arrows[1].attributes['aria-label']).toBe(accessibility.carouselPreviewScrollNextAriaLabel);
  expect(arrows[1].properties.tabIndex).toBe(last ? -1 : 0);
  expect(arrows[1].children[0].attributes['aria-hidden']).toBe('true');
  expect(arrows[1].children[0].properties.title).toBe(accessibility.carouselPreviewScrollNextTitle);
  expect(containsClasses(arrows[1].children[0].properties.className, nextArrowClass + ' inside')).toBeTrue();
}

function containsClasses(actualClasses: string, expectedClasses: string): boolean {
  const actual: string[] = actualClasses.split(' ');
  const expected: string[] = expectedClasses.split(' ');
  let count = 0;
  if (actual.length !== expected.length) {
    return false;
  }
  expected.forEach((item: string) => {
    if (actual.includes(item)) {
      count++;
    }
  });
  return count === expected.length;
}

function getAriaLabel(preview: Image): string {
  if (!preview.plain) {
    return preview.modal.ariaLabel || '';
  }
  return preview.plain.ariaLabel || preview.modal.ariaLabel || '';
}

function getTitle(preview: Image): string {
  if (!preview.plain) {
    return preview.modal.title || '';
  }
  return preview.plain.title || preview.modal.title || '';
}

function getAlt(preview: Image): string {
  if (!preview.plain) {
    return preview.modal.alt || '';
  }
  return preview.plain.alt || preview.modal.alt || '';
}

function checkPreview(previewElement: DebugElement, previewImage: InternalLibImage, isActive: boolean, width: string = DEFAULT_WIDTH, height: string = DEFAULT_HEIGHT): void {
  const currentPlainImg: PlainImage | undefined = previewImage.plain;
  const currentModalImg: ModalImage = previewImage.modal;
  expect(previewElement.name).toBe('img');
  expect(previewElement.attributes.role).toBe('img');
  expect(previewElement.attributes['aria-label']).toBe(getAriaLabel(previewImage));
  expect(previewElement.styles.width).toBe(width);
  // FIXME why is not working?
  // expect(previewElement.styles.height).toBe(height);
  expect(containsClasses(previewElement.properties.className, 'inside preview-image' + (isActive ? ' active' : ''))).toBeTrue();
  expect(previewElement.properties.src).toBe(currentPlainImg && currentPlainImg.img ? currentPlainImg.img : currentModalImg.img);
  expect(previewElement.properties.title).toBe(getTitle(previewImage));
  expect(previewElement.properties.alt).toBe(getAlt(previewImage));
  expect(previewElement.properties.tabIndex).toBe(0);
}

function initTestBed(): void {
  TestBed.configureTestingModule({
    declarations: [CarouselPreviewsComponent, SizeDirective, FallbackImageDirective]
  }).overrideComponent(CarouselPreviewsComponent, {
    set: {
      providers: [
        {
          // by default inject a mocked BreakpointObserver service with Medium size by default
          provide: BreakpointObserver,
          useClass: MediumMockedBreakpointObserver
        },
        {
          provide: ConfigService,
          useClass: ConfigService
        }
      ]
    }
  });
}

const CAROUSEL_CONFIG_DEFAULT: CarouselConfig = {
  maxWidth: '100%',
  maxHeight: '400px',
  showArrows: true,
  objectFit: 'cover',
  keyboardEnable: true,
  modalGalleryEnable: false
} as CarouselConfig;

const DEFAULT_BREAKPOINTS: BreakpointsConfig = {xSmall: 100, small: 100, medium: 150, large: 200, xLarge: 200};
const DEFAULT_PREVIEW_CONFIG: CarouselPreviewConfig = {
  visible: true,
  number: 4,
  arrows: true,
  clickable: true,
  width: 100 / 4 + '%',
  maxHeight: '200px',
  breakpoints: DEFAULT_BREAKPOINTS
};

describe('CarouselPreviewsComponent', () => {
  beforeEach(() => {
    initTestBed();
    fixture = TestBed.createComponent(CarouselPreviewsComponent);
    comp = fixture.componentInstance;
  });

  it('should instantiate it', () => expect(comp).not.toBeNull());

  describe('---YES---', () => {

    it(`should display previews (first one is active) based of input images`, () => {
      const configService = fixture.debugElement.injector.get(ConfigService);
      configService.setConfig(GALLERY_ID, {
        carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
        accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
        carouselConfig: CAROUSEL_CONFIG_DEFAULT
      });
      const initialActiveImage = 0;
      const numOfPreviews: number = DEFAULT_PREVIEW_CONFIG.number as number;
      comp.id = GALLERY_ID;
      comp.currentImage = IMAGES[initialActiveImage];
      comp.images = IMAGES;
      fixture.detectChanges();

      expect(comp.start).toBe(initialActiveImage);
      expect(comp.end).toBe(numOfPreviews);
      expect(comp.previews).toEqual(IMAGES.slice(initialActiveImage, numOfPreviews));

      const element: DebugElement = fixture.debugElement;

      const arrows: DebugElement[] = element.queryAll(By.css('a'));
      checkArrows(arrows, true, false);

      const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
      expect(previewsContainer.name).toBe('nav');
      expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
      expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);

      const previews: DebugElement[] = element.queryAll(By.css('img'));
      expect(previews.length).toBe(numOfPreviews);

      const previewImages: InternalLibImage[] = IMAGES.slice(initialActiveImage, numOfPreviews);
      previews.forEach((preview: DebugElement, i: number) => {
        checkPreview(preview, previewImages[i], i === initialActiveImage, DEFAULT_WIDTH, DEFAULT_HEIGHT);
      });
    });

    NAVIGATION_NEXT_PREVIEWS.forEach((val: NavigationTestData, index: number) => {
      it(`should display previews and navigate next clicking on images. Test i=${index}`, () => {
        const configService = fixture.debugElement.injector.get(ConfigService);
        configService.setConfig(GALLERY_ID, {
          carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
          accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
          carouselConfig: CAROUSEL_CONFIG_DEFAULT
        });
        const initialActiveImage = val.initial.activeIndex; // initial active preview
        const newActiveImage = val.expected.activeIndex; // preview to click => so the next active preview after the click action
        const numOfPreviews: number = DEFAULT_PREVIEW_CONFIG.number as number;
        comp.id = GALLERY_ID;
        comp.currentImage = IMAGES[initialActiveImage];
        comp.images = IMAGES;
        fixture.detectChanges();

        expect(comp.start).toBe(val.initial.start);
        expect(comp.end).toBe(val.initial.end);
        expect(comp.previews).toEqual(IMAGES.slice(val.initial.start, val.initial.end));

        spyOn(comp, 'onImageEvent').and.callThrough();

        const element: DebugElement = fixture.debugElement;

        let previews: DebugElement[] = element.queryAll(By.css('img'));
        expect(previews.length).toBe(numOfPreviews);

        let arrows: DebugElement[] = element.queryAll(By.css('a'));
        checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);

        let previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
        expect(previewsContainer.name).toBe('nav');
        expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
        expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);

        let previewImages: InternalLibImage[] = IMAGES.slice(val.initial.start, val.initial.end);
        previews.forEach((preview: DebugElement, i: number) => {
          checkPreview(preview, previewImages[i], i === val.initial.activeIndex - val.initial.start, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        });

        comp.clickPreview.subscribe((e: ImageEvent) => {
          // check click event payload
          expect(e.action).toBe(Action.CLICK);
          expect(e.result).toBe(val.expected.activeIndex);

          // it's required to change the currentImage,
          // because this operation is done by its parent GalleryComponent
          comp.currentImage = IMAGES[newActiveImage];

          comp.ngOnChanges({
            currentImage: {
              previousValue: IMAGES[initialActiveImage],
              currentValue: IMAGES[newActiveImage],
              firstChange: true,
              isFirstChange: () => true
            },
            images: {
              previousValue: IMAGES,
              currentValue: IMAGES,
              firstChange: false,
              isFirstChange: () => false
            }
          } as SimpleChanges);

          fixture.detectChanges();
          expect(comp.onImageEvent).toHaveBeenCalled();

          previewImages = IMAGES.slice(val.expected.start, val.expected.end);

          expect(comp.start).toBe(val.expected.start);
          expect(comp.end).toBe(val.expected.end);
          expect(comp.previews).toEqual(previewImages);

          arrows = element.queryAll(By.css('a'));
          checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);

          previewsContainer = element.query(By.css('nav.previews-container'));
          expect(previewsContainer.name).toBe('nav');
          expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
          expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);

          previews = element.queryAll(By.css('img'));
          expect(previews.length).toBe(numOfPreviews);

          previews.forEach((preview: DebugElement, i: number) => {
            checkPreview(preview, previewImages[i], i === val.expected.activeIndex - val.expected.start, DEFAULT_WIDTH, DEFAULT_HEIGHT);
          });
        });

        previews[newActiveImage].nativeElement.click();
      });
    });

    NAVIGATION_PREV_PREVIEWS.forEach((val: NavigationTestData, index: number) => {
      it(`should display previews and navigate prev clicking on images. Test i=${index}`, fakeAsync(() => {
        const configService = fixture.debugElement.injector.get(ConfigService);
        configService.setConfig(GALLERY_ID, {
          carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
          accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
          carouselConfig: CAROUSEL_CONFIG_DEFAULT
        });
        const initialActiveImage = val.initial.activeIndex; // initial active preview
        const newActiveImage = val.expected.activeIndex; // preview to click => so the next active preview after the click action
        const numOfPreviews: number = DEFAULT_PREVIEW_CONFIG.number as number;
        comp.id = GALLERY_ID;
        comp.currentImage = IMAGES[initialActiveImage];
        comp.images = IMAGES;
        fixture.detectChanges();
        const element: DebugElement = fixture.debugElement;
        let arrows: DebugElement[] = element.queryAll(By.css('a'));
        // first I have to go to the end of previews to be able to navigate back to the beginning as specified by the NAVIGATION_PREV_PREVIEWS item
        arrows[1].nativeElement.click();
        tick(10);
        flush();
        fixture.detectChanges();
        arrows[1].nativeElement.click();
        tick(10);
        flush();
        fixture.detectChanges();
        arrows[1].nativeElement.click();
        tick(10);
        flush();
        fixture.detectChanges();
        // check if I'm really at the end
        expect(comp.start).toBe(val.initial.start);
        expect(comp.end).toBe(val.initial.end);
        expect(comp.previews).toEqual(IMAGES.slice(val.initial.start, val.initial.end));
        let previews: DebugElement[] = element.queryAll(By.css('img'));
        expect(previews.length).toBe(numOfPreviews);
        arrows = element.queryAll(By.css('a'));
        checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);
        let previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
        expect(previewsContainer.name).toBe('nav');
        expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
        expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);
        let previewImages: InternalLibImage[] = IMAGES.slice(val.initial.start, val.initial.end);
        previews.forEach((preview: DebugElement, i: number) => {
          checkPreview(preview, previewImages[i], i === val.initial.activeIndex - val.initial.start, DEFAULT_WIDTH, DEFAULT_HEIGHT);
        });

        spyOn(comp, 'onImageEvent').and.callThrough();

        comp.clickPreview.subscribe((e: ImageEvent) => {
          // check click event payload
          expect(e.action).toBe(Action.CLICK);
          expect(e.result).toBe(val.expected.activeIndex);

          // it's required to change the currentImage,
          // because this operation is done by its parent GalleryComponent
          comp.currentImage = IMAGES[newActiveImage];

          comp.ngOnChanges({
            currentImage: {
              previousValue: IMAGES[initialActiveImage],
              currentValue: IMAGES[newActiveImage],
              firstChange: true,
              isFirstChange: () => true
            },
            images: {
              previousValue: IMAGES,
              currentValue: IMAGES,
              firstChange: false,
              isFirstChange: () => false
            }
          } as SimpleChanges);

          fixture.detectChanges();
          expect(comp.onImageEvent).toHaveBeenCalled();

          previewImages = IMAGES.slice(val.expected.start, val.expected.end);

          expect(comp.start).toBe(val.expected.start);
          expect(comp.end).toBe(val.expected.end);
          expect(comp.previews).toEqual(previewImages);

          arrows = element.queryAll(By.css('a'));
          checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);

          previewsContainer = element.query(By.css('nav.previews-container'));
          expect(previewsContainer.name).toBe('nav');
          expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
          expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);

          previews = element.queryAll(By.css('img'));
          expect(previews.length).toBe(numOfPreviews);

          previews.forEach((preview: DebugElement, i: number) => {
            checkPreview(preview, previewImages[i], i === val.expected.activeIndex - val.expected.start, DEFAULT_WIDTH, DEFAULT_HEIGHT);
          });
        });

        previews[newActiveImage - val.initial.start].nativeElement.click();
      }));
    });

    it(`should display previews and navigate clicking on arrow 'next'`, fakeAsync(() => {
      const configService = fixture.debugElement.injector.get(ConfigService);
      configService.setConfig(GALLERY_ID, {
        carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
        accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
        carouselConfig: CAROUSEL_CONFIG_DEFAULT
      });
      const initialActiveImage = 0; // initial active preview
      const numOfPreviews: number = DEFAULT_PREVIEW_CONFIG.number as number;
      comp.id = GALLERY_ID;
      comp.currentImage = IMAGES[initialActiveImage];
      comp.images = IMAGES;
      fixture.detectChanges();

      expect(comp.start).toBe(initialActiveImage);
      expect(comp.end).toBe(numOfPreviews);
      expect(comp.previews).toEqual(IMAGES.slice(initialActiveImage, numOfPreviews));

      spyOn(comp, 'onNavigationEvent').and.callThrough();
      const element: DebugElement = fixture.debugElement;

      // check initial state
      let previews: DebugElement[] = element.queryAll(By.css('img'));
      expect(previews.length).toBe(numOfPreviews);
      let arrows: DebugElement[] = element.queryAll(By.css('a'));
      checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);
      const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
      expect(previewsContainer.name).toBe('nav');
      expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
      expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);
      let previewImages: InternalLibImage[] = IMAGES.slice(initialActiveImage, numOfPreviews);
      previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], i === 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));

      // click on right arrow to navigate next
      [{start: 1, end: 5}, {start: 2, end: 6}, {start: 3, end: 7}].forEach((val: { start: number, end: number }) => {
        arrows[1].nativeElement.click();
        tick(10);
        flush();
        fixture.detectChanges();
        comp.currentImage = IMAGES[initialActiveImage]; // current image must be the initial image, because I'm navigating previews without changing the current image
        previewImages = IMAGES.slice(val.start, val.end);
        expect(comp.start).toBe(val.start);
        expect(comp.end).toBe(val.end);
        expect(comp.previews).toEqual(IMAGES.slice(val.start, val.end));
        arrows = element.queryAll(By.css('a'));
        checkArrows(arrows, comp.start === 0, comp.end === IMAGES.length);
        previews = element.queryAll(By.css('img'));
        expect(previews.length).toBe(numOfPreviews);
        previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], false, DEFAULT_WIDTH, DEFAULT_HEIGHT));
      });
    }));

    it(`should display previews with custom accessibility`, () => {
      const IMAGES_CUSTOM_ACCESSIBILITY: InternalLibImage[] = [...IMAGES].map((image: InternalLibImage) => {
        const newImage: InternalLibImage = Object.assign({}, image);
        newImage.modal.title = 'custom accessibility title';
        newImage.modal.alt = 'custom accessibility alt';
        newImage.modal.ariaLabel = 'custom accessibility ariaLabel';
        if (newImage.plain) {
          newImage.plain.title = 'custom accessibility title';
          newImage.plain.alt = 'custom accessibility alt';
          newImage.plain.ariaLabel = 'custom accessibility ariaLabel';
        }
        return newImage;
      });
      const configService = fixture.debugElement.injector.get(ConfigService);
      configService.setConfig(GALLERY_ID, {
        carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
        accessibilityConfig: CUSTOM_ACCESSIBILITY,
        carouselConfig: CAROUSEL_CONFIG_DEFAULT
      });
      const numOfPreviews = 4;
      const initialActiveImage = 0;
      comp.id = GALLERY_ID;
      comp.currentImage = IMAGES_CUSTOM_ACCESSIBILITY[initialActiveImage];
      comp.images = IMAGES_CUSTOM_ACCESSIBILITY;
      fixture.detectChanges();

      const previewImages: InternalLibImage[] = IMAGES_CUSTOM_ACCESSIBILITY.slice(initialActiveImage, numOfPreviews);

      expect(comp.start).toBe(initialActiveImage);
      expect(comp.end).toBe(DEFAULT_PREVIEW_CONFIG.number as number);
      expect(comp.previews).toEqual(previewImages);

      const element: DebugElement = fixture.debugElement;

      const arrows: DebugElement[] = element.queryAll(By.css('a'));
      expect(arrows.length).toBe(2);
      checkArrows(arrows, true, false, CUSTOM_ACCESSIBILITY);

      const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
      expect(previewsContainer.name).toBe('nav');
      expect(previewsContainer.attributes['aria-label']).toBe(CUSTOM_ACCESSIBILITY.carouselPreviewsContainerAriaLabel);
      expect(previewsContainer.properties.title).toBe(CUSTOM_ACCESSIBILITY.carouselPreviewsContainerTitle);

      const previews: DebugElement[] = element.queryAll(By.css('img'));
      expect(previews.length).toBe(numOfPreviews);
      // DEFAULT_HEIGHT because forces by breakpoints when browser is small (like in test)
      previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], i === 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));
    });

    it(`should display a custom number of previews without hidden navigation arrows`, () => {
      const numOfPreviews = 5;
      const CUSTOM_WIDTH = 100 / numOfPreviews + '%';
      const customPreviewConfigFive: CarouselPreviewConfig = {
        visible: true,
        number: numOfPreviews,
        arrows: false,
        clickable: true,
        width: CUSTOM_WIDTH,
        maxHeight: '200px', // however, if browser is small (like in test), this will be smaller
        breakpoints: DEFAULT_BREAKPOINTS
      };
      const initialActiveImage = 0;
      const configService = fixture.debugElement.injector.get(ConfigService);
      configService.setConfig(GALLERY_ID, {
        carouselPreviewsConfig: customPreviewConfigFive,
        accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
        carouselConfig: CAROUSEL_CONFIG_DEFAULT
      });
      comp.id = GALLERY_ID;
      comp.currentImage = IMAGES[initialActiveImage];
      comp.images = IMAGES;
      fixture.detectChanges();

      const previewImages: InternalLibImage[] = IMAGES.slice(initialActiveImage, numOfPreviews);

      expect(comp.start).toBe(initialActiveImage);
      expect(comp.end).toBe(customPreviewConfigFive.number as number);
      expect(comp.previews).toEqual(previewImages);

      const element: DebugElement = fixture.debugElement;

      const arrows: DebugElement[] = element.queryAll(By.css('a'));
      expect(arrows.length).toBe(2);
      // use true, true because both arrows are hidden
      checkArrows(arrows, true, true);

      const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
      expect(previewsContainer.name).toBe('nav');
      expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.carouselPreviewsContainerAriaLabel);
      expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.carouselPreviewsContainerTitle);

      const previews: DebugElement[] = element.queryAll(By.css('img'));
      expect(previews.length).toBe(numOfPreviews);
      // DEFAULT_HEIGHT because forces by breakpoints when browser is small (like in test)
      previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], i === 0, CUSTOM_WIDTH, DEFAULT_HEIGHT));
    });

    CUSTOM_PREVIEW_HEIGHTS.forEach((height: string, index: number) => {
      it(`should display previews with custom sizes. Index i=${index}`, () => {
        const initialActiveImage = 0;
        // create a custom preview config based on the default one, but with different maxHeight
        const CUSTOM_PREVIEW_CONFIG: CarouselPreviewConfig = Object.assign({}, DEFAULT_PREVIEW_CONFIG, {maxHeight: height});
        const configService = fixture.debugElement.injector.get(ConfigService);
        configService.setConfig(GALLERY_ID, {
          carouselPreviewsConfig: CUSTOM_PREVIEW_CONFIG,
          accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
          carouselConfig: CAROUSEL_CONFIG_DEFAULT
        });
        comp.id = GALLERY_ID;
        comp.currentImage = IMAGES[initialActiveImage];
        comp.images = IMAGES;
        fixture.detectChanges();

        const previewImages: InternalLibImage[] = IMAGES.slice(initialActiveImage, CUSTOM_PREVIEW_CONFIG.number);

        expect(comp.start).toBe(initialActiveImage);
        expect(comp.end).toBe(CUSTOM_PREVIEW_CONFIG.number as number);
        expect(comp.previews).toEqual(previewImages);

        const element: DebugElement = fixture.debugElement;

        const arrows: DebugElement[] = element.queryAll(By.css('a'));
        expect(arrows.length).toBe(2);
        checkArrows(arrows, true, false);

        const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
        expect(previewsContainer.name).toBe('nav');
        expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
        expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);

        const previews: DebugElement[] = element.queryAll(By.css('img'));
        expect(previews.length).toBe(CUSTOM_PREVIEW_CONFIG.number as number);
        // DEFAULT_HEIGHT because forces by breakpoints when browser is small (like in test)
        previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], i === 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));
      });
    });

    // TODO not working. But it isn't an issue of this test, instead it's something related to the library, because
    // TODO I should implement keyboard navigation on the previews-container and not on single previews
    // it(`should display previews (first one is active) and go to the second one with keyboard's right arrow`, fakeAsync(() => {
    //   const initialActiveImage = 0;
    //   comp.previewConfig = DEFAULT_PREVIEW_CONFIG;
    //   comp.accessibilityConfig = KS_DEFAULT_ACCESSIBILITY_CONFIG;
    //   comp.currentImage = IMAGES[initialActiveImage];
    //   comp.carouselConfig = CAROUSEL_CONFIG_DEFAULT;
    //   comp.images = IMAGES;
    //   fixture.detectChanges();
    //
    //   spyOn(comp, 'onImageEvent').and.callThrough();
    //
    //   const previewImages: InternalLibImage[] = IMAGES.slice(initialActiveImage, DEFAULT_PREVIEW_CONFIG.number);
    //
    //   expect(comp.start).toBe(initialActiveImage);
    //   expect(comp.end).toBe(DEFAULT_PREVIEW_CONFIG.number);
    //   expect(comp.previews).toEqual(previewImages);
    //
    //   const element: DebugElement = fixture.debugElement;
    //
    //   const arrows: DebugElement[] = element.queryAll(By.css('a'));
    //   expect(arrows.length).toBe(2);
    //   checkArrows(arrows, true, false);
    //
    //   const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
    //   expect(previewsContainer.name).toBe('nav');
    //   expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
    //   expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);
    //
    //   const previews: DebugElement[] = element.queryAll(By.css('img'));
    //   expect(previews.length).toBe(DEFAULT_PREVIEW_CONFIG.number);
    //   // DEFAULT_HEIGHT because forces by breakpoints when browser is small (like in test)
    //   previews.forEach((preview: DebugElement, i: number) => checkPreview(preview, previewImages[i], i === 0, DEFAULT_WIDTH, DEFAULT_HEIGHT));
    //
    //   previews[initialActiveImage + 1].nativeElement.focus();
    //   previews[initialActiveImage + 1].triggerEventHandler('keyup', <KeyboardEvent>{keyCode: RIGHT_ARROW_KEYCODE});
    //   tick(100);
    //   flush();
    //   fixture.detectChanges();
    //   expect(comp.start).toBe(initialActiveImage + 1);
    //   expect(comp.end).toBe(DEFAULT_PREVIEW_CONFIG.number + 1);
    //   expect(comp.previews).toEqual(previewImages);
    // }));

    [-2, -1, 0].forEach((numberOfPreviews: number, index: number) => {
      it(`should display previews with number <= 0, so it will be forced to the default value. Test i=${index}`, () => {
        const configService = fixture.debugElement.injector.get(ConfigService);
        configService.setConfig(GALLERY_ID, {
          carouselPreviewsConfig: DEFAULT_PREVIEW_CONFIG,
          accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
          carouselConfig: CAROUSEL_CONFIG_DEFAULT
        });
        const initialActiveImage = 0;
        const numOfPreviews = 4;
        comp.id = GALLERY_ID;
        comp.currentImage = IMAGES[initialActiveImage];
        comp.images = IMAGES;
        fixture.detectChanges();

        expect(comp.start).toBe(initialActiveImage);
        expect(comp.end).toBe(numOfPreviews);
        expect(comp.previews).toEqual(IMAGES.slice(initialActiveImage, numOfPreviews));

        const element: DebugElement = fixture.debugElement;
        const arrows: DebugElement[] = element.queryAll(By.css('a'));
        checkArrows(arrows, true, false);
        const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
        expect(previewsContainer.name).toBe('nav');
        expect(previewsContainer.attributes['aria-label']).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerAriaLabel);
        expect(previewsContainer.properties.title).toBe(KS_DEFAULT_ACCESSIBILITY_CONFIG.previewsContainerTitle);
        const previews: DebugElement[] = element.queryAll(By.css('img'));
        expect(previews.length).toBe(numOfPreviews);
      });
    });
  });

  describe('---NO---', () => {
    it(`shouldn't display previews because visibility is false`, () => {
      const configService = fixture.debugElement.injector.get(ConfigService);
      configService.setConfig(GALLERY_ID, {
        carouselPreviewsConfig: {visible: false} as CarouselPreviewConfig, // hide previews
        accessibilityConfig: KS_DEFAULT_ACCESSIBILITY_CONFIG,
        carouselConfig: CAROUSEL_CONFIG_DEFAULT
      });
      const initialActiveImage = 0;
      const numOfPreviews: number = DEFAULT_PREVIEW_CONFIG.number as number;
      comp.id = GALLERY_ID;
      comp.currentImage = IMAGES[initialActiveImage];
      comp.images = IMAGES;
      fixture.detectChanges();

      expect(comp.start).toBe(initialActiveImage);
      expect(comp.end).toBe(numOfPreviews);
      expect(comp.previews).toEqual(IMAGES.slice(initialActiveImage, numOfPreviews));

      const element: DebugElement = fixture.debugElement;
      const previewsContainer: DebugElement = element.query(By.css('nav.previews-container'));
      expect(previewsContainer).toBeNull();
      const previews: DebugElement[] = element.queryAll(By.css('img'));
      expect(previews.length).toBe(0);
    });
  });
});