3
Testing components

This chapter covers

  • Testing components
  • Knowing the differences between isolated and shallow tests
  • Testing classes and functions

Angular applications are built from components, so the most important place to start when testing an Angular application is with component tests. For example, imagine a component that displays a calendar. It might enable a user to select a date, change the selected date, cycle through months and years, and so on. You need to write a test case for each of these pieces of functionality.

In this chapter, we’ll cover key testing classes and functions, such as TestBed, ComponentFixture, and fakeAsync, which help you test your components. You’ll need a good grasp of these classes and functions to write component tests.

Don’t worry if you've never heard of these concepts before. You’ll practice using them as we go. By the end of this chapter, you’ll understand what components are and know how to write tests for them. Let’s kick off the chapter by looking at some basic component tests.

3.1 Basic component tests

The best way to get comfortable writing component tests is to write a few tests for a basic component. In this section, you’ll write tests for the ContactsComponent component. ContactsComponent has almost no functionality and will be easy to test.

To get started, follow the instructions for setting up the example project in appendix A, if you haven't already. Then, navigate into the testing-angular-applications directory, create a file named contacts.component.spec.ts in the website/src/app/contacts/ directory, and open it in your text editor or IDE. (While you’re there, if you want to take a peek at the source code for ContactsComponent that you’ll be writing tests against, open the contacts.component.ts file.)

The first step in creating your test is to import your dependencies. This kind of test requires two dependencies. The first is ContactsComponent in the contacts.component module. At the top of the file, add the following line:

import { ContactsComponent } from './contacts.component';

The second dependency you need to import is the interface that defines a contact. Immediately after the first import statement, add the following line of code:

import { Contact } from './shared/models';

Now, you’ll create the test suite that will house all your tests for ContactsComponent. After the import statement, add a describe block to create your test suite:

describe('ContactsComponent Tests', () => {
});

Next, you need to create a variable named contactsComponent that references an instance of ContactsComponent. You’ll set the contactsComponent variable in the beforeEach block of your tests. Doing so will guarantee that you’re generating a new instance of ContactsComponent when each test runs, which will prevent your test cases from interfering with each other. On the first line inside the describe callback function, add the following code:

c03_01.eps

Add a beforeEach function that sets the contactsComponent variable to a new instance of ContactsComponent before each test is executed:

beforeEach(() => {
  contactsComponent = new ContactsComponent();
});

Your first test validates that you can create an instance of ContactsComponent properly. Add the following code below the beforeEach statement:

it('should set instance correctly', () -> {
  expect(contactsComponent).not.toBeNull();
});

After adding this code snippet, your contacts.component.spec.ts file should look like the code in the following listing.

Listing 3.1 contacts.component.spec.ts

import { ContactsComponent } from './contacts.component';
import { Contact } from './shared/models';

describe('ContactsComponent Tests', () => {
  let contactsComponent: ContactsComponent = null;    ①  

  beforeEach(() => {
    contactsComponent = new ContactsComponent();    ②  
  });

  it('should set instance correctly', () => {
    expect(contactsComponent).notBeNull();    ③  
  });
});

This is a simple test, and if the contactsComponent variable contains anything other than null, the test will pass.

Run ng test to run your first test. You’ll see one passing test in the Chrome window (figure 3.1). If you get an error, examine the error messages to see where the test is failing.

c03_02.png

Figure 3.1 First passing unit test validating creation of ContactsComponent instance

You need to write a few more basic tests to finish the tests for ContactsComponent. For the next test, let’s see what happens if the component contains no contacts. Without any contacts, the contacts array length should be zero, because the contacts array is empty by default. Add the following test to your test file:

it('should be no contacts if there is no data', () => {
  expect(contactsComponent.contacts.length).toBe(0);
});

For the last test, you’ll make sure that you can add contacts to the list. To do this, create a new contact using the Contact interface and add it to an array called contactsList. Finally, set the contacts property of ContactsComponent to the contactsList array that you created. To do this, add the following code after the previous test:

it('should be contacts if there is data', () => {
  const newContact: Contact = { 
    id: 1,
    name: 'Jason Pipemaker'
  };
  const contactsList: Array<Contact> = [newContact];
  contactsComponent.contacts = contactsList;

  expect(contactsComponent.contacts.length).toBe(1);
});

Your completed contacts.component.spec.ts test should look like the following listing.

Listing 3.2 Completed contacts.component.spec.ts file

import { ContactsComponent } from './contacts.component';
import { Contact } from './shared/models';

describe('ContactsComponent Tests', () => {
  let contactsComponent: ContactsComponent = null;
    
  beforeEach(() => {
    contactsComponent = new ContactsComponent();
  });

  it('should set instance correctly', () => {
    expect(contactsComponent).not.toBeNull();
  });

  it('should be no contacts if there is no data', () => {
    expect(contactsComponent.contacts.length).toBe(0);    ①  
  });

  it('should be contacts if there is data', () => {
    const newContact: Contact = {
      id: 1,
      name: 'Jason Pipemaker'
    };
    const contactsList: Array<Contact> = [newContact];
    contactsComponent.contacts = contactsList;
      
    expect(contactsComponent.contacts.length).toBe(1);    ②  
  });
});

If your test process is still running, you should see three passing unit tests in the Chrome test-runner window (figure 3.2).

c03_03.png

Figure 3.2 Three passing unit tests for ContactsComponent

If you see any errors, check your code against the GitHub repository at http://mng.bz/1BFL.

So far, your tests haven’t needed any Angular-specific dependencies because ContactsComponent is a normal TypeScript class. When testing these types of components, you don’t need any help from the Angular testing modules. These types of tests are known as isolated tests because they don’t need any Angular dependencies and you can treat them like ordinary TypeScript files.

You might need to write this kind of test when a component has limited functionality. For example, let’s say you’ve created a component for a new page, like a sign-up page, but you haven’t implemented the logic yet. You could write a couple of isolated tests to make sure the component is created correctly.

You’ve warmed up your component testing skills a bit, so in the next section you can write tests for a component with more functionality.

3.2 Real-world component testing

In the real world, you’ll need to test more complex components. For example, say you want to test a sidebar that contains a menu. You’d like to be able to test the sidebar without worrying about the menu. In such situations, you can use what are known as shallow tests. Shallow tests let you test components one level deep, ignoring any child elements that the element may contain; you can test the parent component in isolation.

In this section, you’ll write shallow tests for the ContactEditComponent component. ContactEditComponent is similar to components used in real applications, so it’s a good example to write tests against. Navigate to website/src/app/contacts/contact-edit in the project directory and create a file named contact-edit.component.spec.ts. You’ll start by importing the necessary dependencies.

3.2.1 Importing the dependencies

Because ContactEditComponent is a fully functioning component, it requires a lot of dependencies. Your tests will reflect that in the number of import statements that you need. Let’s consider the imports in the following order:

  • Testing dependencies that come from Angular
  • Dependencies that are included with Angular
  • Dependencies that you created for this project

These dependencies are required for your test module to work. Let’s see how to meet that requirement by diving into the Angular dependencies.

Angular import statements

The following list provides a walkthrough of the import statements you’ll need for your tests:

  • import { DebugElement } from '@angular/core';—You can use DebugElement to inspect an element during testing. You can think of it as the native HTMLElement with additional methods and properties that can be useful for debugging elements.
  • import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
  • ComponentFixture—You can find this class in the @angular/core module. You can use it to create a fixture that you then can use for debugging.
  • TestBed—You use this class to set up and configure your tests. Because you use TestBed anytime you want to write a unit test for components, directives, and services, it’s one of the most important utilities that Angular provides for testing. In this book, you’ll be using the configureTestingModule, overrideModule, and createComponent methods, which you’ll put to use later in the chapter. Because the API for TestBed is extensive, we only scratch the surface of the API in this book. If you want to see what else belongs to the TestBed API, we recommend visiting https://angular.io/api/core/testing/TestBed.
  • fakeAsync—Using fakeAsync ensure that all asynchronous tasks are completed before executing the assertions. Not using fakeAsync may cause the test to fail because the assertions may be executed without all of the asynchronous tasks not being completed. When using fakeAsync, you can use tick to simulate the passage of time. It accepts one parameter, which is the number of milliseconds to move time forward. If you don’t provide a parameter, tick defaults to zero milliseconds.
  • import { By } from '@angular/platform-browser';By is a class included in the @angular/platform-browser module that you can use to select DOM elements. For example, let’s say you want to select an element with the CSS class name of highlight-row;. The element may look like the following HTML element:
<i class="highlight-row">

You would use the css method to retrieve that element using a CSS selector. The resulting code would look like this:

By.css('.highlight-row')

Note that you use a period to select the elements by CSS class name. In total, By provides three methods, which you can find in table 3.1.

Table 3.1 By methods
MethodDescriptionParameter
allUsing all will return all of the elements.None
cssUsing a CSS attribute, you can select certain elements.CSS attribute
directiveYou can use the name of a directive to select elements.Directive name
  • import { NoopAnimationsModule } from '@angular/platform-browser/animations';—You use the NoopAnimationsModule class to mock animations, which allows tests to run quickly without waiting for the animations to finish.
  • import { BrowserDynamicTestingModule } from '@angular/platform-browser-dynamic/testing';BrowserDynamicTestingModule is a module that helps bootstrap the browser to be used for testing.
  • import { RouterTestingModule } from '@angular/router/testing';—As the name implies, you can use RouterTestingModule to set up routing for testing. We include it with the tests for this component because some of the actions will involve changing routes.

At the top of your contact-edit.component.spec.ts file, add the import statements from Angular shown in the following listing.

Listing 3.3 Completed contact-edit.component.spec.ts file

import { DebugElement } from '@angular/core';    ①  
import { ComponentFixture, fakeAsync, TestBed, tick } from
  '@angular/core/testing';    ②  
import { By } from '@angular/platform-browser';    ③  
import { NoopAnimationsModule } from
  '@angular/platform-browser/animations';    ④  
import { BrowserDynamicTestingModule } from  ⑤  
  '@angular/platform-browser-dynamic/testing';    ⑥  
import { RouterTestingModule } from '@angular/router/testing';    ⑤  

We covered quite a bit in these import statements. You’ll be using all of these statements and will find all of them useful, but pay special attention to the most important classes and functions: TestBed, ComponentFixture, and fakeAsync.

angular nontesting module statement

You only need to import one Angular nontesting module—FormsModule. You need this module because the ContactEditComponent uses it for some Angular form controls. Right after the import statements that you added, add the following import statement:

import { FormsModule } from '@angular/forms';

remaining dependency statements

Now that we’ve covered the major classes and methods included in the Angular framework that you’ll use, you can add the rest of the dependencies you’ll need to finish the tests. Add the following lines of code after the existing imports:

import { Contact, ContactService, FavoriteIconDirective, InvalidEmailModalComponent, InvalidPhoneNumberModalComponent } from
  '../shared';
import { AppMaterialModule } from '../app.material.module';
import { ContactEditComponent } from './contact-edit.component';

import '../../../material-app-theme.scss';

Verify that your imports section looks like the code in the following listing before continuing.

Listing 3.4 contact-edit.component.spec.ts imports section

import { DebugElement } from '@angular/core';    ①  
import { ComponentFixture, fakeAsync, TestBed, tick } from
  '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { NoopAnimationsModule } from
  '@angular/platform-browser/animations';
import { BrowserDynamicTestingModule } from
  '@angular/platform-browser-dynamic/testing';
import { RouterTestingModule } from '@angular/router/testing';

import { FormsModule } from '@angular/forms';    ②  

import { Contact, ContactService, FavoriteIconDirective,
  InvalidEmailModalComponent, InvalidPhoneNumberModalComponent } from
  '../shared';    ③  
import { AppMaterialModule } from '../app.material.module';
import { ContactEditComponent } from './contact-edit.component';

Next, you’ll set up the tests.

3.2.2 Setting up the tests

The first step in setting up your tests is to create the describe block that will house all your tests and declare the instance variables they need. Beneath the import statements, add the following code:

describe('ContactEditComponent tests', () => {
  let fixture: ComponentFixture<ContactEditComponent>;
  let component: ContactEditComponent;
  let rootElement: DebugElement;
});

The describe method creates the test suite that contains all your tests. As for the instance variables:

  • fixture—Stores an instance of the ComponentFixture, which contains methods that help you debug and test a component
  • component—Stores an instance of the ContactEditComponent
  • rootElement—Stores the DebugElement for your component, which is how you’ll access its children

Faking ContactService

You’ll use a test fake for ContactService because the real ContactService makes HTTP calls, which would make your tests harder to run and less deterministic. Also, faking ContactService allows you to focus on testing the ContactEditComponent without worrying about how ContactService works. Angular’s dependency injection system makes it easy to instantiate a ContactEditComponent with a fake version of ContactService. The fake ContactService has the same type as the real one, so the TypeScript compiler will throw an error if you forget to stub out part of the interface.

Right after the last variable declaration, but still inside the describe block, add the code in the following listing to create the fake service named contactServiceStub.

Listing 3.5 Mock ContactService

const contactServiceStub = {
  contact: {    ①  
    id: 1,
    name: 'janet'
  },

  save: async function (contact: Contact) {    ②  
    component.contact = contact;
  },

  getContact: async function () {    ③  
    component.contact = this.contact;
    return this.contact;
  },

  updateContact: async function (contact: Contact) {    ④  
    component.contact = contact;
  }
};

The first beforeEach

Now that you have a fake ContactService, add two beforeEach blocks, which will execute before each test. The first beforeEach sets up your TestBed configuration. The second will set your instance variables. You could have just one beforeEach, but your test will be easier to read if you keep them separate.

A lot needs to happen now, as you can see in listing 3.6, so let’s break down the code a bit. The TestBed class has a method called configureTestingModule. You can probably guess its purpose, which is to configure the testing module. It’s much like the NgModule class that’s included in the app.module.ts file, which you can find at src/app. The only difference is that you only use configureTestingModule in tests. It takes an object that’s in the format of a TestModuleMetadata type alias. If you aren’t familiar with a type alias, for our purposes you can think of it like an interface. In the code listing, note the providers section:

  providers: [{provide: ContactService, useValue: contactServiceStub}]

This is where you provide your fake contact service contactServiceStub in place of the real ContactService with useValue.

You use overrideModule in this case because you need the two modal dialogs to be loaded lazily. Lazy loading means that the dialogs won’t be loaded until the user performs an action to cause them to load. Currently, the only way to do this is to use overrideModule and set the entryComponents value to an array that contains the two modal components that the ContactEditComponent uses—InvalidEmailModalComponent and InvalidPhoneNumberModalComponent.

Finally, the last line of this first beforeEach statement uses TestBed.get(ContactService) to get a reference to your fake contactService from Angular’s dependency injector. This will be the same instance that ContactEditComponent uses.

After the code for contactServiceStub, add the code in the following listing as your first beforeEach statement.

Listing 3.6 First beforeEach

beforeEach(() => {
  TestBed.configureTestingModule({
    declarations: [ContactEditComponent, FavoriteIconDirective,
      InvalidEmailModalComponent, InvalidPhoneNumberModalComponent],
    imports: [
      AppMaterialModule,
      FormsModule,
      NoopAnimationsModule,
      RouterTestingModule
    ],
    providers: [{provide: ContactService, 
      useValue: contactServiceStub}]    ①  
  });    ②  

  TestBed.overrideModule(BrowserDynamicTestingModule, {
    set: {
      entryComponents: [InvalidEmailModalComponent,
        InvalidPhoneNumberModalComponent]
    }
  });    ③  
});

You can see in the listing that TestModuleMetadata accepts four optional properties, which are described in table 3.2.

Table 3.2 TestModuleMetadata optional fields
FieldData TypeDescription
declarations any[ ]This is where you list any components that the component you’re testing may need.
importsany[ ]You set imports to an array of modules that the component you’re testing requires.
providersany[ ]Lets you override the providers Angular uses for dependency injection. In this case, you inject a fake ContactService.
schemasArray<SchemaMetadata | any[ ]>You can use schemas like CUSTOM_ELEMENTS_SCHEMA and NO_ERRORS_SCHEMA to allow for certain properties of elements. For example, the NO_ERRORS_SCHEMA will allow for any element that’s going to be tested to have any property.

The second beforeEach

Now you’ll add the second beforeEach statement. The fixture variable stores the component-like object from the TestBed.createComponent method that you can use for debugging and testing, which we mentioned earlier. The component variable holds a component that you get from your fixture using the componentInstance property.

But what is this fixture.detectChanges method that you haven’t seen before? The detectChanges method triggers a change-detection cycle for the component; you need to call it after initializing a component or changing a data-bound property value. After calling detectChanges, the updates to your component will be rendered in the DOM. In production, Angular uses something called zones (which you’ll learn more about in Chapter 9) to know when to run change detection, but in unit tests, you don’t have that mechanism. Instead, you need to call detectChanges frequently in your tests after making changes to a component.

Directly after the first beforeEach statement, add in the following code:

beforeEach(() => {
  fixture = TestBed.createComponent(ContactEditComponent);
  component = fixture.componentInstance;
  fixture.detectChanges();
  rootElement = fixture.debugElement;
});

So far, so good. You’ve added the code to set up your tests. In the next section, you’ll add the tests themselves.

3.2.3 Adding the tests

You’re ready to write your tests. You want to test the saveContact, loadContact, and updateContact methods for ContactEditComponent because those methods hold most of the functionality of the component. The ContactEditComponent class has several more private helper methods, but you don’t need those because testing the component’s public API will exercise them. In general, you shouldn’t test private methods; if a method is important enough to be tested, you should consider making it public.

Testing the saveContact method

First, you should write a test for the saveContact method. Calling saveContact changes the component’s state, which will be reflected in changes to the DOM. You’ll use the fakeAsync method to keep the test from finishing until the component has finished updating.

Next, create a contact object and set the component.isLoading property to false. You need to do this manually; otherwise, all that will render is the loading-progress bar. Then you’ll call the saveContact method to save the contact that’s stored in the contact variable. Normally, saveContact would use the real ContactService, but because you configured the testing module to provide contactServiceStub earlier, the component will call the stub.

After you’ve called the saveContact method, you’ll notice that you call detectChanges. As mentioned earlier, after you make changes to components, you need to call detectChanges so that those changes will be rendered, which allows you to test that changes to the component are reflected in the DOM.

After calling detectChanges, query rootElement using By.css for the contact-name class to get the input element that contains the contact name. Then call tick to simulate the passage of time so the component will finish updating. Notice that the tick method doesn’t have a parameter for milliseconds, so it uses the default value of zero milliseconds. Finally, assert that the value of nameInput is equal to lorace.

Add the code in the following listing directly after the last beforeEach statement. Make sure you stay within the overall test suite (the top-level describe block).

Listing 3.7 saveContact method test

describe('saveContact() test', () => {
  it('should display contact name after contact set', fakeAsync(() => {
    const contact = {    ①  
      id: 1,
      name: 'lorace'
    };

    component.isLoading = false;    ②  
    component.saveContact(contact);    ③  
    fixture.detectChanges();    ④  
    const nameInput = rootElement.query(By.css('.contact-name'));    ⑤  
    tick();    ⑥  
    expect(nameInput.nativeElement.value).toBe('lorace');    ⑦  
  }));
});

Testing the loadContact method

Next, you’ll write a test for the loadContact method. This test is similar to the test in listing 3.7. The only difference is that you’ll use the loadContact method instead of the saveContact method of the ContactEditComponent class. The loadContact method will load a contact for your testing purposes inside the contactServiceStub. The contact’s name is janet, which is the value you’ll use in the assertion.

Add the code from the following listing directly after the saveContact method test that you just created.

Listing 3.8 loadContact method test

describe('loadContact() test', () => {
  it('should load contact', fakeAsync(() => {
    component.isLoading = false;
    component.loadContact();    ①  
    fixture.detectChanges();
    const nameInput = rootElement.query(By.css('.contact-name'));
    tick();
    expect(nameInput.nativeElement.value).toBe('janet');    ②  
  }));
});

Now we’ll move on to testing the updateContact method.

Testing the updateContact method

By now, you’ve probably picked up on a pattern: this test is similar to the other two tests. This time, you first set a contact that has a name of rhonda and test that the component renders correctly. The major difference between this test and the other two tests is that it uses a second assertion. You want to check to see that the name updates when you call updateContact. To do this, call updateContact and pass it newContact.

You might notice that you call tick in the following listing with 100 as a parameter. You need this time because the updateContact method takes a bit longer to execute than the other methods that you’ve been testing. Add the code from the following listing after the previous test.

Listing 3.9 First updateContact method test

describe('updateContact() tests', () => {
  it('should update the contact', fakeAsync(() => {
    const newContact = {
      id: 1,
      name: 'delia',
      email: '[email protected]',
      number: '1234567890'
    };
    
    component.contact = {
      id: 2,
      name: 'rhonda',
      email: '[email protected]',
      number: '1234567890'
    };
 
    component.isLoading = false;
    fixture.detectChanges();
    const nameInput = rootElement.query(By.css('.contact-name'));
    tick();
    expect(nameInput.nativeElement.value).toBe('rhonda');

    component.updateContact(newContact);    ①  
    fixture.detectChanges();    ②  
    tick(100);    ③  
    expect(nameInput.nativeElement.value).toBe('delia');    ④  
  }));
});

Run ng t in your console (if you haven’t already). You should see six passing tests. If you don’t see six passing tests, go back to the code samples and make sure your code matches the code in this book.

You now have a test that will update a contact, but you need to test what happens when you try to update the contact with invalid contact data. First, see what happens when you try to update the contact with an invalid email address. The differences between listing 3.9 and 3.10 are highlighted in bold. The newContact variable now has an invalid email, and the last assertion doesn’t expect the contact to change because the email is invalid. That’s why both assertions expect the contact’s name to remain chauncey. Add the code in the following listing directly after your first updateContact method test.

Listing 3.10 Second updateContact method test

it('should not update the contact if email is invalid', fakeAsync(() => {
  const newContact = {
    id: 1,
    name: 'london',
    email: 'london@example',    ①  
    number: '1234567890'
  };
     
  component.contact = {
    id: 2,
    name: 'chauncey',
    email: '[email protected]',
    number: '1234567890'
  };
 
  component.isLoading = false;
  fixture.detectChanges();
  const nameInput = rootElement.query(By.css('.contact-name'));
  tick();
  expect(nameInput.nativeElement.value).toBe('chauncey');

  component.updateContact(newContact);
  fixture.detectChanges();
  tick(100);
  expect(nameInput.nativeElement.value).toBe('chauncey');    ②  
}));

Now let’s see what happens when you try to update a contact with an invalid phone number. Again, notice the bolded code in listing 3.11. The only difference between this test and the previous test is that the number now contains too many digits. Similar to the test before this one, the contact name is the same in both assertions.

Add the code in the following listing to the end of the second updateContact method test that you just wrote.

Listing 3.11 Third updateContact method test

it('should not update the contact if phone number is invalid',
  fakeAsync(() => {
  const newContact = {
    id: 1,
    name: 'london',
    email: '[email protected]',
    number: '12345678901'    ①  
  };
     
  component.contact = {
    id: 2,
    name: 'chauncey',
    email: '[email protected]',
    number: '1234567890'
  };
 
  component.isLoading = false;
  fixture.detectChanges();
  const nameInput = rootElement.query(By.css('.contact-name'));
  tick();
  expect(nameInput.nativeElement.value).toBe('chauncey');

  component.updateContact(newContact);
  fixture.detectChanges();
  tick(100);
  expect(nameInput.nativeElement.value).toBe('chauncey');    ②  
}));

Run ng t in your terminal again. You should see eight passing tests. If you see any errors, try checking your code against the version in the GitHub repository at http://mng.bz/Ud5b.

You’ve coded complete test coverage for a real-world component! You’re likely to come across components out there that are more advanced, but what you’ve learned here gives you a foundation for writing tests that can handle that complexity. Components are one of the most—if not the most—important concepts in Angular, so you need a firm understanding of the component testing basics to be successful in writing tests.

Summary

  • Isolated tests don’t rely on the built-in Angular classes and methods. You can test them as if you were using normal TypeScript classes. Sometimes for your tests you’ll have to render components one level deep without rendering child components. To accomplish that, you’ll use shallow tests.
  • Using the fakeAsync function, you can ensure that all asynchronous calls are completed within a test before the assertions are executed. Doing so prevents test from failing unexpectedly before all of the asynchronous calls are completed.
  • Use the ComponentFixture class to debug an element.
  • TestBed is a class that you use to set up and configure your tests. Use it anytime you want to write a unit test that tests components, directives, and services.
  • You can use DebugElement to dive deeper into an element. You can think of it as the HTMLElement, with methods and properties added that can be useful for debugging elements.
  • The nativeElement object is an Angular wrapper around the built-in DOM native element.
..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.116.43.127