diff --git a/client/src/app/models/category.model.ts b/client/src/app/models/category.model.ts new file mode 100644 index 0000000000000000000000000000000000000000..e24c400101e3c498d51cf9def9b721308c05e871 --- /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 3e4c006cb7243a96675b70a20b0623a594870703..fcf5e849bd889d4fa77683b0569df936e20a88bd 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 e89803521b22e8a339b97868c51233890838d3f4..192abd1de24c1537052a059f5156cc4468ec6e56 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 7e82535b45a1a2c9cfa883b910a956bd3dc4ff89..96f7a12f252d95f763b41c1fcd6c33ceddb78784 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 5864463a491b3e0ac1ee152b7db60a0017e1b836..667b24bb79800bae7ba083269985ea1f8d7c1600 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 24c073bf45c74c0c777218e4220d8f29d1f7179b..0439fbe95c5eafe960d20c4e678c5672ab71c278 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 e476800c7e5c82b55cb3c2c5f4ad54c4bfe784c3..2199c9683d411ba3a15a79623897bc8d89aea5d9 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 2ff4b9f58e39fd3382d19ef53f1a2e00d68d8806..0342fc6b221817a7ae7d46960c532cac207bee74 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 2fb4207eb6dd5656d0ae4555b2ae7dbec7b18249..aa38c80dcd80f2ebe7cbf8c50de66ce85af2e895 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 f895776fe5022ace055990ea6f39868f964b89ec..c54aff01908e42b3e3b7ff1b29d4e844861a8cd3 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 6d9d91a1d812ac99c00af092a9ed2928a80d678f..ca491fea8591f37105f304d533c261041fb00c23 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]) );