From a8957330c40ef47b97322a2a20f25477c22b4aac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Tormod=20Nyg=C3=A5rd?= <tormodny@stud.ntnu.no> Date: Wed, 17 Feb 2021 11:23:12 +0100 Subject: [PATCH] Issue: Get categories from DB (#4) --- client/src/app/models/category.model.ts | 45 ++++++++++++++++ client/src/app/models/post.model.ts | 9 +--- .../posts/post-form/post-form.component.html | 6 +-- .../post-form/post-form.component.spec.ts | 39 ++++++++++++-- .../posts/post-form/post-form.component.ts | 10 ++++ client/src/app/posts/post.service.spec.ts | 52 +++++++++++++++++++ client/src/app/posts/post.service.ts | 41 ++++++++++++++- .../number-input.component.spec.ts | 5 +- .../shared/select/select.component.spec.ts | 4 +- .../text-input/text-input.component.spec.ts | 4 +- .../controllers/categoryController/index.ts | 4 +- 11 files changed, 197 insertions(+), 22 deletions(-) create mode 100644 client/src/app/models/category.model.ts diff --git a/client/src/app/models/category.model.ts b/client/src/app/models/category.model.ts new file mode 100644 index 0000000..e24c400 --- /dev/null +++ b/client/src/app/models/category.model.ts @@ -0,0 +1,45 @@ +import { Deserializable } from "./deserializable.model"; +import { Serializable } from "./serializable.model"; + +export class Category implements Deserializable, Serializable { + private categoryid: number; + private name: string; + + constructor(input: any = null) { + if (input) { + this.deserialize(input); + } else { + this.categoryid = 0; + this.name = null; + } + } + + deserialize(input: Object): this { + Object.assign(this, input); + return this; + } + + serialize(): Object { + return { + categoryid: this.categoryid, + name: this.name + }; + } + + get getCategoryId() { + return this.categoryid; + } + + set setCategoryId(categoryid: number) { + this.categoryid = categoryid; + } + + get getName() { + return this.name; + } + + set setName(name: string) { + this.name = name; + } + +} \ No newline at end of file diff --git a/client/src/app/models/post.model.ts b/client/src/app/models/post.model.ts index 3e4c006..fcf5e84 100644 --- a/client/src/app/models/post.model.ts +++ b/client/src/app/models/post.model.ts @@ -13,14 +13,7 @@ export class Post implements Deserializable, Serializable { constructor(input: any = null) { if (input) { - this.id = input.id; - this.title = input.title; - this.description = input.description; - this.timestamp = input.timestamp; - this.owner = input.owner; - this.imageUrl = input.imageUrl; - this.price = input.price; - this.categoryid = input.categoryid; + this.deserialize(input); } else { this.id = 0; this.title = null; diff --git a/client/src/app/posts/post-form/post-form.component.html b/client/src/app/posts/post-form/post-form.component.html index e898035..192abd1 100644 --- a/client/src/app/posts/post-form/post-form.component.html +++ b/client/src/app/posts/post-form/post-form.component.html @@ -7,10 +7,8 @@ <app-number-input [(inputModel)]="price" label="Pris" (blur)="checkForm()"></app-number-input> <app-select [(inputModel)]="categoryid" (change)="checkForm()" label="Kategori"> - <option value="0" selected>Velg kategori</option> - <option value="1">Antikviteter og Kunst</option> - <option value="2">Dyr og Utstyr</option> - <option value="3">Elektronikk og Hvitevarer</option> + <option value="0" selected hidden>Velg kategori</option> + <option *ngFor="let category of categories" [value]="category.getCategoryId">{{category.getName}}</option> </app-select> <p>{{statusMessage}}</p> diff --git a/client/src/app/posts/post-form/post-form.component.spec.ts b/client/src/app/posts/post-form/post-form.component.spec.ts index 7e82535..96f7a12 100644 --- a/client/src/app/posts/post-form/post-form.component.spec.ts +++ b/client/src/app/posts/post-form/post-form.component.spec.ts @@ -3,6 +3,9 @@ import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testin import { FormsModule } from '@angular/forms'; import { Router } from '@angular/router'; import { RouterTestingModule } from '@angular/router/testing'; +import { Category } from 'src/app/models/category.model'; +import { SharedModule } from 'src/app/shared/shared.module'; +import { PostService } from '../post.service'; import { PostFormComponent } from './post-form.component'; @@ -10,11 +13,29 @@ describe('PostFormComponent', () => { let component: PostFormComponent; let fixture: ComponentFixture<PostFormComponent>; let router: Router; + let mockPostService; beforeEach(async () => { + // PostService mock setup + mockPostService = jasmine.createSpyObj(['getAllCategories', 'addPost']); + mockPostService.getAllCategories.and.returnValue( + new Promise<Array<Category>>( + (resolve) => { + resolve([new Category({categoryid: 1, name: "Elektronikk"}), new Category({categoryid: 2, name: "Bil"})]) + }) + ); + mockPostService.addPost.and.returnValue( + new Promise<string>( + (resolve) => { + resolve("success") + }) + ); + + await TestBed.configureTestingModule({ declarations: [ PostFormComponent ], - imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule ] + imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule, SharedModule ], + providers: [ { provide: PostService, useValue: mockPostService } ] }) .compileComponents(); }); @@ -27,11 +48,20 @@ describe('PostFormComponent', () => { router = TestBed.inject(Router); }); - it('should create', () => { + it('should create and get all categories', async () => { expect(component).toBeTruthy(); + + // Waits for ngOnInit and checks that we get categories + fixture.whenStable().then(() => { + expect(mockPostService.getAllCategories).toHaveBeenCalled(); + expect(component.categories.length).toBe(2); + expect(component.categories[0].getCategoryId).toBe(1); + expect(component.categories[1].getName).toBe("Bil"); + }); }); it('should validate form', () => { + // Tests all if-sentences in checkForm expect(component.checkForm()).toBeFalse(); expect(component.statusMessage).toBe("Tittelen kan ikke være tom"); @@ -58,17 +88,20 @@ describe('PostFormComponent', () => { }); it('should stop publishing invalid post', fakeAsync(() => { + // Tests that publishing should be stopped on invalid post component.publishPost(); expect(component.statusMessage).toBe("Tittelen kan ikke være tom"); })); it('should route after publishing post', () => { + // Tests that url is changed after post is published component.title = "Title"; component.description = "Description"; component.price = 50; component.categoryid = 2; component.publishPost(); - + + expect(mockPostService.addPost).toHaveBeenCalled(); expect(router.url).toBe('/'); }); }); diff --git a/client/src/app/posts/post-form/post-form.component.ts b/client/src/app/posts/post-form/post-form.component.ts index 5864463..667b24b 100644 --- a/client/src/app/posts/post-form/post-form.component.ts +++ b/client/src/app/posts/post-form/post-form.component.ts @@ -1,5 +1,6 @@ import { Component, OnInit } from '@angular/core'; import { Router } from '@angular/router'; +import { Category } from 'src/app/models/category.model'; import { Post } from 'src/app/models/post.model'; import { PostService } from '../post.service'; @@ -17,9 +18,17 @@ export class PostFormComponent implements OnInit { statusMessage: string = ""; + categories: Array<Category>; + constructor(private postService: PostService, private router: Router) { } ngOnInit() { + // Gets all categories and displays them in dropdown + this.postService.getAllCategories().then(categories => { + this.categories = categories; + }).catch (error => { + console.log("Error adding catrgories:" + error); + }); } /** @@ -66,6 +75,7 @@ export class PostFormComponent implements OnInit { categoryid: this.categoryid }); + // Adds post to database and changes page afterwards this.postService.addPost(newPost).then(status => { console.log("Post was added: " + status); this.router.navigateByUrl("/"); diff --git a/client/src/app/posts/post.service.spec.ts b/client/src/app/posts/post.service.spec.ts index 24c073b..0439fbe 100644 --- a/client/src/app/posts/post.service.spec.ts +++ b/client/src/app/posts/post.service.spec.ts @@ -1,5 +1,6 @@ import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing'; import { TestBed } from '@angular/core/testing'; +import { Category } from '../models/category.model'; import { Post } from '../models/post.model'; import { PostService } from './post.service'; @@ -22,6 +23,7 @@ describe('PostService', () => { describe('getPost', () => { it('should get a post', () => { + // Gets post and checks values service.getPost(1).then(post => { expect(post.getId).toBe(1); expect(post.getTitle).toBe("Test"); @@ -29,6 +31,7 @@ describe('PostService', () => { fail(); }); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/1"); expect(req.request.method).toBe("GET"); req.flush({ @@ -46,10 +49,12 @@ describe('PostService', () => { }); it('should reject on invalid post', () => { + // Gets invalid post, should go to catch service.getPost(2).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/2"); expect(req.request.method).toBe("GET"); req.flush({ @@ -63,10 +68,12 @@ describe('PostService', () => { }); it('should reject on http error', () => { + // Gets HTTP error instead of post, should catch service.getPost(2).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/2"); expect(req.request.method).toBe("GET"); req.error(new ErrorEvent("400")); @@ -86,12 +93,14 @@ describe('PostService', () => { categoryid: 2 }); + // Adds post service.addPost(post) .then(post => {}) .catch(error => { fail(); }); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/"); expect(req.request.method).toBe("POST"); expect(req.request.body).toEqual(post.serialize()); @@ -114,15 +123,58 @@ describe('PostService', () => { categoryid: 2 }); + // Adds post, gets HTTP error, should catch service.addPost(post).then(post => { fail(); }).catch(error => {}); + // Mocks and checks HTTP request const req = httpMock.expectOne("api/post/"); expect(req.request.method).toBe("POST"); expect(req.request.body).toEqual(post.serialize()); req.error(new ErrorEvent("400")); }); }); + + describe('getAllCategories', () => { + it('should get categories', () => { + + // Gets all categories and checks values + service.getAllCategories() + .then(categories => { + expect(categories.length).toBe(3); + expect(categories[0] instanceof Category).toBeTrue(); + expect(categories[1].getCategoryId).toBe(2); + expect(categories[2].getName).toBe("Elektronikk"); + }) + .catch(error => { + fail(); + }); + + // Mocks and checks HTTP request + const req = httpMock.expectOne("api/category/"); + expect(req.request.method).toBe("GET"); + req.flush({ + data: [ + {categoryid: 1, name: "Dyr"}, + {categoryid: 2, name: "Bil"}, + {categoryid: 3, name: "Elektronikk"} + ] + }); + }); + + it('should reject on http error', () => { + + // Gets HTTP error, should catch + service.getAllCategories().then(categories => { + fail(); + }).catch(error => {}); + + // Mocks and checks HTTP request + const req = httpMock.expectOne("api/category/"); + expect(req.request.method).toBe("GET"); + req.error(new ErrorEvent("400")); + }); + }); }); diff --git a/client/src/app/posts/post.service.ts b/client/src/app/posts/post.service.ts index e476800..2199c96 100644 --- a/client/src/app/posts/post.service.ts +++ b/client/src/app/posts/post.service.ts @@ -1,5 +1,6 @@ import { HttpClient } from '@angular/common/http'; import { Injectable } from '@angular/core'; +import { Category } from '../models/category.model'; import { Post } from '../models/post.model'; @Injectable({ @@ -8,6 +9,7 @@ import { Post } from '../models/post.model'; export class PostService { postUrl = "api/post/"; + categoryUrl = "api/category/"; constructor(private http: HttpClient) { } @@ -19,8 +21,7 @@ export class PostService { (resolve, reject) => { this.get_post(id).subscribe((data: any) => { try { - const post = new Post(); - post.deserialize(data.data[0]); + const post = new Post(data.data[0]); if (post.getId == 0) { reject("Could not find Post with id: " + id); return; @@ -66,4 +67,40 @@ export class PostService { private add_post(post: Post) { return this.http.post(this.postUrl, post.serialize()); } + + + /** + * Get all categories from database. + */ + getAllCategories(): Promise<Array<Category>>{ + return new Promise<Array<Category>>( + (resolve, reject) => { + this.get_all_categories().subscribe((data: any) => { + try { + let outputCategories = []; + for (let dataCategory of data.data) { + const category = new Category(dataCategory); + outputCategories.push(category); + + if (category.getCategoryId == 0) { + reject("Could not deserialize category"); + return; + } + } + resolve(outputCategories); + } catch (err: any){ + reject(err); + } + }, + (err: any) => { + console.log(err.message); + reject(err); + }); + } + ); + } + + private get_all_categories() { + return this.http.get(this.categoryUrl); + } } diff --git a/client/src/app/shared/number-input/number-input.component.spec.ts b/client/src/app/shared/number-input/number-input.component.spec.ts index 2ff4b9f..0342fc6 100644 --- a/client/src/app/shared/number-input/number-input.component.spec.ts +++ b/client/src/app/shared/number-input/number-input.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { NumberInputComponent } from './number-input.component'; @@ -8,7 +9,9 @@ describe('NumberInputComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ NumberInputComponent ] + declarations: [ NumberInputComponent ], + imports: [ FormsModule ] + }) .compileComponents(); }); diff --git a/client/src/app/shared/select/select.component.spec.ts b/client/src/app/shared/select/select.component.spec.ts index 2fb4207..aa38c80 100644 --- a/client/src/app/shared/select/select.component.spec.ts +++ b/client/src/app/shared/select/select.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { SelectComponent } from './select.component'; @@ -8,7 +9,8 @@ describe('SelectComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ SelectComponent ] + declarations: [ SelectComponent ], + imports: [ FormsModule ] }) .compileComponents(); }); diff --git a/client/src/app/shared/text-input/text-input.component.spec.ts b/client/src/app/shared/text-input/text-input.component.spec.ts index f895776..c54aff0 100644 --- a/client/src/app/shared/text-input/text-input.component.spec.ts +++ b/client/src/app/shared/text-input/text-input.component.spec.ts @@ -1,4 +1,5 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { FormsModule } from '@angular/forms'; import { TextInputComponent } from './text-input.component'; @@ -8,7 +9,8 @@ describe('TextInputComponent', () => { beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [ TextInputComponent ] + declarations: [ TextInputComponent ], + imports: [ FormsModule ] }) .compileComponents(); }); diff --git a/server/src/controllers/categoryController/index.ts b/server/src/controllers/categoryController/index.ts index 6d9d91a..ca491fe 100644 --- a/server/src/controllers/categoryController/index.ts +++ b/server/src/controllers/categoryController/index.ts @@ -11,7 +11,7 @@ const category = new Category(); // - hente et bestemt kategori (get) // SELECT * FROM category WHERE categoryid = #num; // - remove specific cateogry (post) -// INSERT INTO `jonnynl_tdt4140`.`category` (`categoryid`, `navn`) VALUES ('4', 'ad'); +// INSERT INTO `jonnynl_tdt4140`.`category` (`categoryid`, `name`) VALUES ('4', 'ad'); // - add category (post) // DELETE FROM `jonnynl_tdt4140`.`category` WHERE (`categoryid` = '3'); @@ -20,7 +20,7 @@ const category = new Category(); router.route('/').post(async (request: Request, response: Response) => { const {category} = request.body; try { - const input = (` INSERT INTO category(navn) VALUES (?);`) + const input = (` INSERT INTO category(name) VALUES (?);`) return response.status(200).json( await query(input,[category]) ); -- GitLab