This commit is contained in:
2025-01-01 15:16:24 +01:00
parent a9bf3781e0
commit 14cdc06de5
18 changed files with 1636 additions and 46 deletions

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,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'

View File

@@ -172,7 +172,7 @@ const config: Config = {
// testResultsProcessor: undefined,
// This option allows use of a custom test runner
// testRunner: "jest-circus/runner",
//testRunner: "jest-circus/runner",
// A map from regular expressions to paths to transformers
// transform: undefined,

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",
"test": "jest --verbose",
"test:coverage": "jest --coverage",
"test:watch": "jest --watch"
"test:watch": "jest --watch",
"cypress": "cypress open"
},
"private": true,
"dependencies": {
@@ -30,6 +31,7 @@
"@angular/cli": "^18.2.8",
"@angular/compiler-cli": "^18.2.0",
"@types/jest": "^29.5.14",
"cypress": "^13.17.0",
"jest": "^29.7.0",
"jest-preset-angular": "^14.4.2",
"typescript": "~5.5.2"

View File

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

View File

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

View File

@@ -1,8 +1,45 @@
import { pipe } from 'rxjs';
import { FilterPokemonPipePipe } from './filter-pokemon--pipe.pipe';
describe('FilterPokemonPipePipe', () => {
let pipe: FilterPokemonPipePipe;
beforeEach(() => {
pipe = new FilterPokemonPipePipe();
});
it('create an instance', () => {
const pipe = new FilterPokemonPipePipe();
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';
describe('MyComponentComponent', () => {
let component: 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 () => {
pokeAPI = jasmine.createSpyObj('PokeAPIServiceService', ['getPokemons', 'getPokemonInfo']);
pokeShare = jasmine.createSpyObj('PokeShareInfoService', ['setValue']);
await TestBed.configureTestingModule({
declarations: [MyComponentComponent]
})
.compileComponents();
,
providers: [
{ provide: PokeAPIServiceService, useValue: pokeAPI },
{ provide: PokeShareInfoService, useValue: pokeShare}
]
}).compileComponents();
fixture = TestBed.createComponent(MyComponentComponent);
component = fixture.componentInstance;
@@ -20,4 +71,25 @@ describe('MyComponentComponent', () => {
it('should create', () => {
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 { HttpTestingController } from '@angular/common/http/testing';
import { PokeAPIServiceService } from './poke-apiservice.service';
import { PokeServiceRes, PokeDetail } from './pokemon';
describe('PokeAPIServiceService', () => {
let service: PokeAPIServiceService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({});
TestBed.configureTestingModule({
providers: [PokeAPIServiceService],
});
service = TestBed.inject(PokeAPIServiceService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should be created', () => {
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', () => {
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 { PokedetailComponent } from './pokedetail.component';
import { PokeShareInfoService } from '../poke-share-info.service';
describe('PokedetailComponent', () => {
let component: PokedetailComponent;
let fixture: ComponentFixture<PokedetailComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PokedetailComponent]
declarations: [PokedetailComponent],
providers: [PokeShareInfoService]
})
.compileComponents();
@@ -17,7 +19,7 @@ describe('PokedetailComponent', () => {
fixture.detectChanges();
});
it('should create', () => {
it('should be created', () => {
expect(component).toBeTruthy();
});
});

View File

@@ -2,6 +2,9 @@ import { Pokemon } from './pokemon';
describe('Pokemon', () => {
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;
}
export class Pokemon {
constructor(public id:string, public name:string){
}
}