Compare commits

4 Commits

Author SHA1 Message Date
Vu Tuan Minh
7aa90bb219 Update README.md 2025-01-01 23:15:21 +00:00
7d142df99c readme 2025-01-02 00:10:56 +01:00
20ba36c052 cypress 2025-01-01 23:45:40 +01:00
14cdc06de5 test v1 2025-01-01 15:16:24 +01:00
21 changed files with 1704 additions and 64 deletions

View File

@@ -1,27 +1,31 @@
# Pokedemo ## Description
This is a project for 4rd year academic of Université de Rennes 1.
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 18.2.8. ---
## Development server ## Disclaimer
> For the pokedemo it's in the main branch. The test is in tp_test branch
> We don't write all the test for cypress and Jest because it's a little bit too long. But we covered some major test.
> For Jest, we have some issue with pokeAPI so we try to mock but didnt work
> For the cypress it worked fine but we dont have many tests.
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The application will automatically reload if you change any of the source files. ---
## Code scaffolding ## Prerequisits
Before cloning/forking this project, make sure the following tools are installed:
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`. - [npm] `curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.1/install.sh | bash`
- [nodejs] `nvm install 22`
## Build ## Installation
* Install npm and nodejs
* Uninstall karma jasmin `npm uninstall karma karma-chrome-launcher karma-coverage karma-jasmine karma-jasmine-html-reporter @types/jasmine jasmine-core`
* Install jest `npm i --save-dev jest @types/jest jest-preset-angular`
* Install cypress `npm install cypress --save-dev`
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. ## Contributors
[//]: contributor-faces
## Running unit tests <a href="https://github.com/vuminh224"><img src="https://avatars.githubusercontent.com/u/114408235?v=4" title="Tuan Minh VU" width="80" height="80"></a>
Run `ng test` to execute the unit tests via [Karma](https://karma-runner.github.io). <a href="https://gitlab2.istic.univ-rennes1.fr/trochas"><img src="https://secure.gravatar.com/avatar/980b9890d56d0a70c7253e3a198111938d79d7b396fd31240a99a9d5c4cd6b96?s=384&d=identicon" title="Rochas Thibaut" width="80" height="80"></a>
## Running end-to-end tests
Run `ng e2e` to execute the end-to-end tests via a platform of your choice. To use this command, you need to first add a package that implements end-to-end testing capabilities.
## Further help
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI Overview and Command Reference](https://angular.dev/tools/cli) page.

10
cypress.config.ts Normal file
View File

@@ -0,0 +1,10 @@
import { defineConfig } from "cypress";
export default defineConfig({
e2e: {
setupNodeEvents(on, config) {
// implement node event listeners here
},
"baseUrl": "http://localhost:4200"
},
});

View File

@@ -0,0 +1,57 @@
const login = require("../../fixtures/login");
context("Login and buy stuff", () => {
before(() => {
cy.visit('/');
});
describe("Attempt to sign in", () => {
it('should have link to sign in', () => {
cy.get('[data-cy=signInBtn]').click();
});
it("Type data and submit form", () => {
cy.get('[data-cy=email]')
.type(login.email)
.should("have.value", login.email);
cy.get('[data-cy=password]')
.type(login.password)
.should("have.value", login.password);
cy.get('[data-cy=submit]').click();
});
});
describe("Buy stuff process", () => {
it('url should be /shop', () => {
cy.url().should("include", "/shop");
});
it('check the cart to be zero', () => {
cy.get('[data-cy=cart]').contains(0);
})
it('click on stuff 2', () => {
cy.get('[data-cy=stuff-1]').contains('Add');
cy.get('[data-cy=stuff-1]').click();
});
it('increment the cart', () => {
cy.get('[data-cy=cart]').contains(1);
})
it('click on cart', () => {
cy.get('[data-cy=cart]').click();
});
it('redirect to cart view', () => {
cy.url().should("include", "/cart");
});
it('purchase stuff and see confirm message', () => {
cy.get('[data-cy=submit]').contains('PURCHASE');
cy.get('[data-cy=submit]').click();
cy.get('[data-cy=success]').contains('Thank you for your purchase');
});
})
});

View File

@@ -0,0 +1,13 @@
describe('AppComponent', () => {
beforeEach(() => {
cy.visit('http://localhost:4200')
})
it('should display title', () => {
cy.get('h1').should('contain', 'pokedemo!')
})
it('should load the page', () => {
cy.get('app-root').should('exist')
})
})

View File

@@ -0,0 +1,25 @@
describe('FilterPokemonPipe', () => {
beforeEach(() => {
cy.visit('http://localhost:4200')
})
it('should test search', () => {
/* class using . */
cy.get('.filter_pokedex').clear()
cy.get('.filter_pokedex').type('charm')
cy.get('.select_pokedex').select(1)
cy.get('button').contains('GO').click()
cy.get('.pokemon').should('be.visible')
})
it('should use combo box', () => {
cy.get('.select_pokedex').select(0)
cy.get('button').contains('GO').click()
cy.get('.pokemon').should('be.visible')
})
it('should navigate to previous pokemon', () => {
cy.get('.select_pokedex').select(1)
cy.get('button').contains('GO').click()
})
})

View File

@@ -0,0 +1,21 @@
describe('Pokemon API Tests', () => {
beforeEach(() => {
cy.visit('http://localhost:4200')
})
it('should get the database', () => {
/*https://docs.cypress.io/api/commands/request*/
cy.request('GET', 'https://pokeapi.co/api/v2/pokemon')
.then((response) => {
// 200 = OK
expect(response.status).to.eq(200)
const pokeData = response.body
expect(pokeData).to.have.property('count')
expect(pokeData.count).to.be.a('number')
expect(pokeData.next).to.be.a('string')
expect(pokeData.results[0]).to.have.property('name')
expect(pokeData.results[0].name).to.eq('bulbasaur')
})
})
})

View File

@@ -0,0 +1,5 @@
{
"name": "Using fixtures to represent data",
"email": "hello@cypress.io",
"body": "Fixtures are a great way to mock data for responses to routes"
}

View File

@@ -0,0 +1,37 @@
/// <reference types="cypress" />
// ***********************************************
// This example commands.ts shows you how to
// create various custom commands and overwrite
// existing commands.
//
// For more comprehensive examples of custom
// commands please read more here:
// https://on.cypress.io/custom-commands
// ***********************************************
//
//
// -- This is a parent command --
// Cypress.Commands.add('login', (email, password) => { ... })
//
//
// -- This is a child command --
// Cypress.Commands.add('drag', { prevSubject: 'element'}, (subject, options) => { ... })
//
//
// -- This is a dual command --
// Cypress.Commands.add('dismiss', { prevSubject: 'optional'}, (subject, options) => { ... })
//
//
// -- This will overwrite an existing command --
// Cypress.Commands.overwrite('visit', (originalFn, url, options) => { ... })
//
// declare global {
// namespace Cypress {
// interface Chainable {
// login(email: string, password: string): Chainable<void>
// drag(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// dismiss(subject: string, options?: Partial<TypeOptions>): Chainable<Element>
// visit(originalFn: CommandOriginalFn, url: string, options: Partial<VisitOptions>): Chainable<Element>
// }
// }
// }

17
cypress/support/e2e.ts Normal file
View File

@@ -0,0 +1,17 @@
// ***********************************************************
// This example support/e2e.ts is processed and
// loaded automatically before your test files.
//
// This is a great place to put global configuration and
// behavior that modifies Cypress.
//
// You can change the location of this file or turn off
// automatically serving support files with the
// 'supportFile' configuration option.
//
// You can read more here:
// https://on.cypress.io/configuration
// ***********************************************************
// Import commands.js using ES2015 syntax:
import './commands'

1316
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@@ -8,7 +8,8 @@
"watch": "ng build --watch --configuration development", "watch": "ng build --watch --configuration development",
"test": "jest --verbose", "test": "jest --verbose",
"test:coverage": "jest --coverage", "test:coverage": "jest --coverage",
"test:watch": "jest --watch" "test:watch": "jest --watch",
"cypress": "cypress open"
}, },
"private": true, "private": true,
"dependencies": { "dependencies": {
@@ -30,6 +31,7 @@
"@angular/cli": "^18.2.8", "@angular/cli": "^18.2.8",
"@angular/compiler-cli": "^18.2.0", "@angular/compiler-cli": "^18.2.0",
"@types/jest": "^29.5.14", "@types/jest": "^29.5.14",
"cypress": "^13.17.0",
"jest": "^29.7.0", "jest": "^29.7.0",
"jest-preset-angular": "^14.4.2", "jest-preset-angular": "^14.4.2",
"typescript": "~5.5.2" "typescript": "~5.5.2"

View File

@@ -1,35 +1,44 @@
import { TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { RouterModule } from '@angular/router';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { provideHttpClient } from '@angular/common/http';
import { provideHttpClientTesting } from '@angular/common/http/testing';
import { provideRouter } from '@angular/router';
describe('AppComponent', () => { describe('AppComponent', () => {
let component: AppComponent;
let fixture: ComponentFixture<AppComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
imports: [ // si AppComponent est un composant standalone
RouterModule.forRoot([]) //imports: [AppComponent],
], // si AppComponent n'est PAS un composant standalone
declarations: [ declarations: [AppComponent],
AppComponent
providers: [
provideHttpClient(),
provideHttpClientTesting(),
provideRouter([]),
], ],
}).compileComponents(); }).compileComponents();
}); });
beforeEach(() => {
fixture = TestBed.createComponent(AppComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create the app', () => { it('should create the app', () => {
const fixture = TestBed.createComponent(AppComponent); expect(component).toBeTruthy();
const app = fixture.componentInstance;
expect(app).toBeTruthy();
}); });
it(`should have as title 'pokedemo'`, () => { it(`should have as title 'pokedemo'`, () => {
const fixture = TestBed.createComponent(AppComponent); expect(component.title).toEqual('pokedemo');
const app = fixture.componentInstance;
expect(app.title).toEqual('pokedemo');
}); });
it('should render title', () => { it('should render the title', () => {
const fixture = TestBed.createComponent(AppComponent);
fixture.detectChanges();
const compiled = fixture.nativeElement as HTMLElement; const compiled = fixture.nativeElement as HTMLElement;
expect(compiled.querySelector('h1')?.textContent).toContain('Hello, pokedemo'); expect(compiled.querySelector('h1')?.textContent).toContain('pokedemo!');
}); });
}); });

View File

@@ -4,7 +4,7 @@ import { Component } from '@angular/core';
@Component({ @Component({
selector: 'app-root', selector: 'app-root',
templateUrl: './app.component.html', templateUrl: './app.component.html',
styleUrl: './app.component.css' styleUrl: './app.component.css',
}) })
export class AppComponent { export class AppComponent {
title = 'pokedemo'; title = 'pokedemo';

View File

@@ -1,8 +1,45 @@
import { pipe } from 'rxjs';
import { FilterPokemonPipePipe } from './filter-pokemon--pipe.pipe'; import { FilterPokemonPipePipe } from './filter-pokemon--pipe.pipe';
describe('FilterPokemonPipePipe', () => { describe('FilterPokemonPipePipe', () => {
let pipe: FilterPokemonPipePipe;
beforeEach(() => {
pipe = new FilterPokemonPipePipe();
});
it('create an instance', () => { it('create an instance', () => {
const pipe = new FilterPokemonPipePipe(); const pipe = new FilterPokemonPipePipe();
expect(pipe).toBeTruthy(); expect(pipe).toBeTruthy();
}); });
it('search is undefined so return original', () => {
const pokes = [{ name: 'bulbasaur' }, { name: 'ivysaur' }];
const result = pipe.transform(pokes, 'name');
expect(result).toEqual(pokes);
});
it('filtre by property', () => {
const pokes = [{ name: 'bulbasaur' }, { name: 'ivysaur' }, { name: 'venusaur' }];
const result = pipe.transform(pokes, 'name', 'ivy');
expect(result).toEqual([{ name: 'ivysaur' }]);
});
it('pokes is undefined', () => {
const result = pipe.transform(undefined, 'name', 'ivy');
expect(result).toEqual([]);
});
it('property is undefined', () => {
const pokes = [{ name: 'bulbasaur' }, { name: 'ivysaur' }];
const result = pipe.transform(pokes, undefined, 'ivy');
expect(result).toEqual([]);
});
it('emty array because no matched', () => {
const pokes = [{ name: 'bulbasaur' }, { name: 'ivysaur' }];
const result = pipe.transform(pokes, 'name', 'charmander');
expect(result).toEqual([]);
});
}); });

View File

@@ -1,16 +1,67 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, fakeAsync, flush, TestBed } from '@angular/core/testing';
import { PokeDetail, Pokemon } from '../pokemon';
import { PokeAPIServiceService } from '../poke-apiservice.service';
import { PokeShareInfoService } from '../poke-share-info.service';
import { MyComponentComponent } from './my-component.component'; import { MyComponentComponent } from './my-component.component';
describe('MyComponentComponent', () => { describe('MyComponentComponent', () => {
let component: MyComponentComponent; let component: MyComponentComponent;
let fixture: ComponentFixture<MyComponentComponent>; let fixture: ComponentFixture<MyComponentComponent>;
let pokeAPI: PokeAPIServiceService;
let pokeShare: PokeShareInfoService;
const mockPokemonList = {
results: [
{ name: 'bulbasaur' },
{ name: 'ivysaur' },
{ name: 'venusaur' }
]
};
const mockPokeDetail: PokeDetail = {
id: 1,
name: 'bulbasaur',
height: 7,
weight: 69,
types: [],
sprites: {
front_default: 'https://raw.githubuserco…er/sprites/pokemon/1.png"',
back_default: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/1.png',
back_female: null,
back_shiny: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/back/shiny/1.png',
back_shiny_female: null,
front_female: null,
front_shiny: 'https://raw.githubusercontent.com/PokeAPI/sprites/master/sprites/pokemon/shiny/1.png',
front_shiny_female: null
},
abilities: [],
base_experience: 64,
cries: undefined,
forms: [],
game_indices: [],
held_items: [],
is_default: true,
location_area_encounters: 'https://pokeapi.co/api/v2/pokemon/1/encounters',
moves: [],
order: 1,
past_abilities: [],
past_types: [],
species: undefined,
stats: []
};
beforeEach(async () => { beforeEach(async () => {
pokeAPI = jasmine.createSpyObj('PokeAPIServiceService', ['getPokemons', 'getPokemonInfo']);
pokeShare = jasmine.createSpyObj('PokeShareInfoService', ['setValue']);
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [MyComponentComponent] declarations: [MyComponentComponent]
}) ,
.compileComponents(); providers: [
{ provide: PokeAPIServiceService, useValue: pokeAPI },
{ provide: PokeShareInfoService, useValue: pokeShare}
]
}).compileComponents();
fixture = TestBed.createComponent(MyComponentComponent); fixture = TestBed.createComponent(MyComponentComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
@@ -20,4 +71,25 @@ describe('MyComponentComponent', () => {
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
//ngOnINIT
it('should load pokemon list on init', fakeAsync(() => {
fixture.detectChanges();
flush();
expect(pokeAPI.getPokemons).toHaveBeenCalled();
expect(component.pokes.length).toBe(3);
expect(component.pokes[0]).toEqual(jasmine.objectContaining({
id: '1',
name: 'bulbasaur'
}));
}));
it('go()', fakeAsync(() => {
component.selectedPokeId = '1';
component.go();
flush();
expect(pokeAPI.getPokemonInfo).toHaveBeenCalledWith('1');
expect(component.pokeDetail).toEqual(mockPokeDetail);
}));
}); });

View File

@@ -1,16 +1,40 @@
import { TestBed } from '@angular/core/testing'; import { TestBed } from '@angular/core/testing';
import { HttpTestingController } from '@angular/common/http/testing';
import { PokeAPIServiceService } from './poke-apiservice.service'; import { PokeAPIServiceService } from './poke-apiservice.service';
import { PokeServiceRes, PokeDetail } from './pokemon';
describe('PokeAPIServiceService', () => { describe('PokeAPIServiceService', () => {
let service: PokeAPIServiceService; let service: PokeAPIServiceService;
let httpMock: HttpTestingController;
beforeEach(() => { beforeEach(() => {
TestBed.configureTestingModule({}); TestBed.configureTestingModule({
providers: [PokeAPIServiceService],
});
service = TestBed.inject(PokeAPIServiceService); service = TestBed.inject(PokeAPIServiceService);
httpMock = TestBed.inject(HttpTestingController);
}); });
it('should be created', () => { it('should be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
/*
it('should create', () => {
const mock: PokeServiceRes = {
results: [
{ name: 'bulbasaur', url: 'https://pokeapi.co/api/v2/pokemon/1/' },
{ name: 'ivysaur', url: 'https://pokeapi.co/api/v2/pokemon/2/' },
],
count: 0,
next: '',
previous: null
};
service.getPokemons().subscribe((response) => {
expect(response).toEqual(mock);
expect(response.results.length).toBe(2);
expect(response.results[0].name).toBe('bulbasaur');
});
});
*/
}); });

View File

@@ -13,4 +13,14 @@ describe('PokeShareInfoService', () => {
it('should be created', () => { it('should be created', () => {
expect(service).toBeTruthy(); expect(service).toBeTruthy();
}); });
it('should set value when setValue is called', (done) => {
const testValue = 'Test Value';
service.getObservable().subscribe((value) => {
expect(value).toBe(testValue);
done();
});
service.setValue(testValue);
});
}); });

View File

@@ -1,14 +1,16 @@
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { ComponentFixture, TestBed } from '@angular/core/testing';
import { PokedetailComponent } from './pokedetail.component'; import { PokedetailComponent } from './pokedetail.component';
import { PokeShareInfoService } from '../poke-share-info.service';
describe('PokedetailComponent', () => { describe('PokedetailComponent', () => {
let component: PokedetailComponent; let component: PokedetailComponent;
let fixture: ComponentFixture<PokedetailComponent>; let fixture: ComponentFixture<PokedetailComponent>;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [PokedetailComponent] declarations: [PokedetailComponent],
providers: [PokeShareInfoService]
}) })
.compileComponents(); .compileComponents();
@@ -17,7 +19,7 @@ describe('PokedetailComponent', () => {
fixture.detectChanges(); fixture.detectChanges();
}); });
it('should create', () => { it('should be created', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
}); });

View File

@@ -2,6 +2,9 @@ import { Pokemon } from './pokemon';
describe('Pokemon', () => { describe('Pokemon', () => {
it('should create an instance', () => { it('should create an instance', () => {
//expect(new Pokemon("1","test")).toBeTruthy(); const pokemon = new Pokemon('1', 'Bulbasaur');
expect(pokemon).toBeTruthy();
expect(pokemon.id).toBe('1');
expect(pokemon.name).toBe('Bulbasaur');
}); });
}); });

View File

@@ -191,13 +191,7 @@ export interface Type {
type: Species; type: Species;
} }
export class Pokemon { export class Pokemon {
constructor(public id:string, public name:string){ constructor(public id:string, public name:string){
} }
} }