core/templates/pages/contributor-dashboard-page/opportunities-list/opportunities-list.component.spec.ts

Summary

Maintainability
F
2 wks
Test Coverage
// Copyright 2021 The Oppia Authors. All Rights Reserved.
//
// 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.

/**
 * @fileoverview Unit tests for the Opportunities List Component.
 */

import {HttpClientTestingModule} from '@angular/common/http/testing';
import {EventEmitter} from '@angular/core';
import {NO_ERRORS_SCHEMA} from '@angular/core';
import {
  ComponentFixture,
  fakeAsync,
  TestBed,
  tick,
} from '@angular/core/testing';
import {TranslationLanguageService} from 'pages/exploration-editor-page/translation-tab/services/translation-language.service';
import {TranslationTopicService} from 'pages/exploration-editor-page/translation-tab/services/translation-topic.service';
import {ExplorationOpportunity} from '../opportunities-list-item/opportunities-list-item.component';
import {ContributionOpportunitiesService} from '../services/contribution-opportunities.service';
import {OpportunitiesListComponent} from './opportunities-list.component';
import {MatIconModule} from '@angular/material/icon';

describe('Opportunities List Component', () => {
  let component: OpportunitiesListComponent;
  let fixture: ComponentFixture<OpportunitiesListComponent>;

  let translationLanguageService: TranslationLanguageService;
  let translationTopicService: TranslationTopicService;
  let contributionOpportunitiesService: ContributionOpportunitiesService;

  beforeEach(() => {
    TestBed.configureTestingModule({
      imports: [HttpClientTestingModule, MatIconModule],
      declarations: [OpportunitiesListComponent],
      providers: [
        ContributionOpportunitiesService,
        TranslationLanguageService,
        TranslationTopicService,
      ],
      schemas: [NO_ERRORS_SCHEMA],
    }).compileComponents();
  });

  beforeEach(() => {
    fixture = TestBed.createComponent(OpportunitiesListComponent);
    component = fixture.componentInstance;
    translationLanguageService = TestBed.inject(TranslationLanguageService);
    translationTopicService = TestBed.inject(TranslationTopicService);
    contributionOpportunitiesService = TestBed.inject(
      ContributionOpportunitiesService
    );
  });

  afterEach(() => {
    component.ngOnDestroy();
  });

  describe('when clicking on page number ', () => {
    const mockActiveLanguageEventEmitter = new EventEmitter();
    const mockActiveTopicEventEmitter = new EventEmitter();
    const mockReloadOpportunitiesEventEmitter = new EventEmitter();
    const mockRemoveOpportunitiesEventEmitter = new EventEmitter();
    const explorationOpportunitiesLoad1: ExplorationOpportunity[] = [
      {
        id: 'id1',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id2',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id3',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id4',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id5',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id6',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id7',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id8',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id9',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id10',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id11',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id12',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id13',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id14',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id15',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id16',
        labelText: 'text',
        labelColor: 'blue',
        progressPercentage: 30,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 30,
        topicName: 'Topic 1',
      },
    ];

    const explorationOpportunitiesLoad2: ExplorationOpportunity[] = [
      {
        id: 'id17',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id18',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id19',
        labelText: 'text',
        labelColor: 'blue',
        progressPercentage: 30,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 30,
        topicName: 'Topic 1',
      },
      {
        id: 'id20',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id21',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id22',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id23',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id24',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id25',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id26',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
    ];

    beforeEach(() => {
      component.loadOpportunities = () =>
        Promise.resolve({
          opportunitiesDicts: explorationOpportunitiesLoad1,
          more: true,
        });
      component.loadMoreOpportunities = () =>
        Promise.resolve({
          opportunitiesDicts: explorationOpportunitiesLoad2,
          more: false,
        });

      spyOnProperty(
        translationLanguageService,
        'onActiveLanguageChanged'
      ).and.returnValue(mockActiveLanguageEventEmitter);
      spyOnProperty(
        translationTopicService,
        'onActiveTopicChanged'
      ).and.returnValue(mockActiveTopicEventEmitter);
      spyOnProperty(
        contributionOpportunitiesService,
        'reloadOpportunitiesEventEmitter'
      ).and.returnValue(mockReloadOpportunitiesEventEmitter);
      spyOnProperty(
        contributionOpportunitiesService,
        'removeOpportunitiesEventEmitter'
      ).and.returnValue(mockRemoveOpportunitiesEventEmitter);
    });

    it(
      'should go to the new page when opportunities ' +
        'are greater then page length',
      fakeAsync(() => {
        expect(component.activePageNumber).toBe(1);

        component.init();
        component.onChangeLanguage('en');
        tick();
        mockReloadOpportunitiesEventEmitter.emit();
        tick();
        component.gotoPage(1);
        tick();
        expect(component.activePageNumber).toBe(1);
        expect(component.visibleOpportunities).toEqual([
          {
            id: 'id1',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id2',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id3',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id4',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id5',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id6',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id7',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id8',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id9',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
          {
            id: 'id10',
            labelText: 'text',
            labelColor: 'red',
            progressPercentage: 50,
            inReviewCount: 20,
            totalCount: 100,
            translationsCount: 50,
            topicName: 'Topic 1',
          },
        ]);
        component.gotoPage(2);
        tick();

        expect(component.activePageNumber).toBe(2);
      })
    );

    it(
      'should not go to the new page when opportunities ' +
        'are less then page length',
      fakeAsync(() => {
        // Setting more option to be false.
        component.loadMoreOpportunities = () =>
          Promise.resolve({
            opportunitiesDicts: explorationOpportunitiesLoad1,
            more: false,
          });
        expect(component.activePageNumber).toBe(1);

        component.init();
        tick();
        mockReloadOpportunitiesEventEmitter.emit();
        tick();
        component.gotoPage(1);
        tick();

        expect(component.activePageNumber).toBe(1);
      })
    );

    it('should show the first page when loadOpportunities is not set', fakeAsync(() => {
      component.loadOpportunities = undefined;
      expect(component.activePageNumber).toBe(1);

      component.init();
      tick();
      component.ngOnInit();
      tick();

      expect(component.activePageNumber).toBe(1);
    }));

    it('should load opportunities when initialized', fakeAsync(() => {
      expect(component.opportunities).toEqual([]);

      // Since the constructor will be automatically called in unit tests, it
      // is hard to test or spy on the constructor. So, we have created a
      // function to manually trigger and tests different edge cases.
      component.init();
      component.onChangeLanguage('en');
      tick();

      // Added two opportunities with id's as 'id1' and 'id2'.
      mockActiveLanguageEventEmitter.emit();
      tick();
      mockActiveTopicEventEmitter.emit();
      tick();
      mockReloadOpportunitiesEventEmitter.emit();
      tick();
      expect(component.opportunities).toEqual(explorationOpportunitiesLoad1);
      expect(component.opportunities.length).toEqual(16);

      // Removed opportunity with id as 'id2'.
      mockRemoveOpportunitiesEventEmitter.emit(['id2']);
      tick();

      expect(component.opportunities.length).toEqual(15);

      // RemoveOpportunitiesEvent with no opportunities, e.g. if a user closes a
      // review modal without completing a review.
      mockRemoveOpportunitiesEventEmitter.emit([]);
      tick();

      expect(component.opportunities.length).toEqual(15);
    }));

    it('should navigate to updated last page when current last page is removed', fakeAsync(() => {
      component.init();
      component.onChangeLanguage('en');
      tick();
      component.ngOnInit();
      tick();
      expect(component.opportunities).toEqual(explorationOpportunitiesLoad1);
      expect(component.opportunities.length).toEqual(16);
      expect(component.activePageNumber).toBe(1);
      // Navigate to the last page.
      component.gotoPage(2);
      tick();
      component.gotoPage(3);
      tick();
      expect(component.activePageNumber).toBe(3);
      // Reset the load method to return no more opportunities.
      component.loadMoreOpportunities = () =>
        Promise.resolve({
          opportunitiesDicts: [],
          more: false,
        });

      // Remove all opportunities on the last page.
      mockRemoveOpportunitiesEventEmitter.emit([
        'id20',
        'id21',
        'id22',
        'id23',
        'id24',
        'id25',
        'id26',
      ]);
      tick();

      expect(component.opportunities.length).toEqual(19);
      expect(component.activePageNumber).toBe(2);
    }));
  });

  describe('when clicking on pin-unpin icon', () => {
    const mockActiveLanguageEventEmitter = new EventEmitter();
    const mockActiveTopicEventEmitter = new EventEmitter();
    const mockReloadOpportunitiesEventEmitter = new EventEmitter();
    const mockRemoveOpportunitiesEventEmitter = new EventEmitter();
    const explorationOpportunitiesLoad1: ExplorationOpportunity[] = [
      {
        id: 'id1',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id2',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id3',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id4',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id5',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id6',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id7',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id8',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id9',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id10',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id11',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id12',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id13',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id14',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id15',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id16',
        labelText: 'text',
        labelColor: 'blue',
        progressPercentage: 30,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 30,
        topicName: 'Topic 1',
      },
    ];

    const explorationOpportunitiesLoad2: ExplorationOpportunity[] = [
      {
        id: 'id17',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id18',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id19',
        labelText: 'text',
        labelColor: 'blue',
        progressPercentage: 30,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 30,
        topicName: 'Topic 1',
      },
      {
        id: 'id20',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id21',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id22',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id23',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id24',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id25',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
      {
        id: 'id26',
        labelText: 'text',
        labelColor: 'red',
        progressPercentage: 50,
        inReviewCount: 20,
        totalCount: 100,
        translationsCount: 50,
        topicName: 'Topic 1',
      },
    ];

    let mockPinOpportunitiesEventEmitter = new EventEmitter();
    let mockUnpinOpportunitiesEventEmitter = new EventEmitter();

    beforeEach(() => {
      mockPinOpportunitiesEventEmitter = new EventEmitter();
      mockUnpinOpportunitiesEventEmitter = new EventEmitter();

      spyOnProperty(
        contributionOpportunitiesService,
        'pinnedOpportunitiesChanged'
      ).and.returnValue(mockPinOpportunitiesEventEmitter);
      spyOnProperty(
        contributionOpportunitiesService,
        'unpinnedOpportunitiesChanged'
      ).and.returnValue(mockUnpinOpportunitiesEventEmitter);

      component.loadOpportunities = () =>
        Promise.resolve({
          opportunitiesDicts: explorationOpportunitiesLoad1,
          more: true,
        });
      component.loadMoreOpportunities = () =>
        Promise.resolve({
          opportunitiesDicts: explorationOpportunitiesLoad2,
          more: false,
        });

      spyOnProperty(
        translationLanguageService,
        'onActiveLanguageChanged'
      ).and.returnValue(mockActiveLanguageEventEmitter);
      spyOnProperty(
        translationTopicService,
        'onActiveTopicChanged'
      ).and.returnValue(mockActiveTopicEventEmitter);
      spyOnProperty(
        contributionOpportunitiesService,
        'reloadOpportunitiesEventEmitter'
      ).and.returnValue(mockReloadOpportunitiesEventEmitter);
      spyOnProperty(
        contributionOpportunitiesService,
        'removeOpportunitiesEventEmitter'
      ).and.returnValue(mockRemoveOpportunitiesEventEmitter);
    });

    it('should pin an opportunity', fakeAsync(() => {
      component.init();
      component.onChangeLanguage('en');
      tick();

      mockActiveLanguageEventEmitter.emit();
      tick();
      mockActiveTopicEventEmitter.emit();
      tick();
      mockReloadOpportunitiesEventEmitter.emit();
      tick();
      const updatedData = {explorationId: 'id1', topicName: 'Topic 1'};
      component.pinOpportunity(updatedData);

      expect(component.opportunities[0].isPinned).toBe(true);
      // Ensure the pinned opportunity is at the top of the list.
      expect(component.opportunities[0].id).toBe('id1');
      expect(component.opportunities[0].topicName).toBe('Topic 1');

      component.pinOpportunity({explorationId: 'id2', topicName: 'Topic 1'});
    }));

    it('should unpin an opportunity', fakeAsync(() => {
      component.init();
      component.onChangeLanguage('en');
      tick();

      mockActiveLanguageEventEmitter.emit();
      tick();
      mockActiveTopicEventEmitter.emit();
      tick();
      mockReloadOpportunitiesEventEmitter.emit();
      tick();
      const updatedData = {explorationId: 'id1', topicName: 'Topic 1'};
      component.pinOpportunity(updatedData);

      expect(component.opportunities[0].isPinned).toBe(true);
      component.unpinOpportunity(updatedData);
      // Ensure the unpinned opportunity is at the end of the list.
      expect(
        component.opportunities[component.opportunities.length - 1].id
      ).toBe('id1');
      expect(
        component.opportunities[component.opportunities.length - 1].topicName
      ).toBe('Topic 1');
    }));

    it(
      'should subscribe to pinnedOpportunitiesChanged and call' +
        'pinOpportunity',
      () => {
        const updatedData = {
          explorationId: 'id1',
          topicName: 'topic',
        };
        spyOn(component, 'pinOpportunity').and.callThrough();

        component.subscribeToPinnedOpportunities();
        contributionOpportunitiesService.pinnedOpportunitiesChanged.emit(
          updatedData
        );

        expect(component.pinOpportunity).toHaveBeenCalledWith(updatedData);
      }
    );

    it(
      'should subscribe to unpinnedOpportunitiesChanged and call' +
        'unpinOpportunity',
      () => {
        const updatedData = {
          explorationId: 'id1',
          topicName: 'topic',
        };
        spyOn(component, 'unpinOpportunity').and.callThrough();

        component.subscribeToPinnedOpportunities();
        contributionOpportunitiesService.unpinnedOpportunitiesChanged.emit(
          updatedData
        );

        expect(component.unpinOpportunity).toHaveBeenCalledWith(updatedData);
      }
    );
  });
});