Skip to content
Snippets Groups Projects
Commit 200bf5be authored by Martin Immanuel Burgos's avatar Martin Immanuel Burgos
Browse files

Merge branch '3-frontend-annonser-form' into 'master'

Resolve "Frontend: Annonser form"

Closes #3

See merge request !3
parents 1d6befb4 a177dac9
No related branches found
No related tags found
1 merge request!3Resolve "Frontend: Annonser form"
Showing
with 510 additions and 61 deletions
...@@ -3,7 +3,7 @@ ...@@ -3,7 +3,7 @@
"version": "0.0.0", "version": "0.0.0",
"scripts": { "scripts": {
"ng": "ng", "ng": "ng",
"start": "ng serve", "start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build", "build": "ng build",
"test": "ng test", "test": "ng test",
"lint": "ng lint", "lint": "ng lint",
......
{
"/api": {
"target": "http://localhost:3000",
"secure": false
}
}
\ No newline at end of file
...@@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router'; ...@@ -3,7 +3,7 @@ import { RouterModule, Routes } from '@angular/router';
import { PostFormComponent } from './posts/post-form/post-form.component'; import { PostFormComponent } from './posts/post-form/post-form.component';
const routes: Routes = [ const routes: Routes = [
{ path: 'postForm', component: PostFormComponent } { path: 'annonse/ny', component: PostFormComponent }
]; ];
@NgModule({ @NgModule({
......
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser'; import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module'; import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component'; import { AppComponent } from './app.component';
import { PostModule } from './posts/post.module'; import { PostModule } from './posts/post.module';
import { SharedModule } from './shared/shared.module';
@NgModule({ @NgModule({
declarations: [ declarations: [
...@@ -12,7 +14,9 @@ import { PostModule } from './posts/post.module'; ...@@ -12,7 +14,9 @@ import { PostModule } from './posts/post.module';
imports: [ imports: [
BrowserModule, BrowserModule,
AppRoutingModule, AppRoutingModule,
PostModule PostModule,
SharedModule,
HttpClientModule
], ],
providers: [], providers: [],
bootstrap: [AppComponent] bootstrap: [AppComponent]
......
export interface Deserializable { export interface Deserializable {
deserialize(input: string): this; deserialize(input: Object): this;
} }
\ No newline at end of file
...@@ -2,72 +2,119 @@ import { Deserializable } from "./deserializable.model"; ...@@ -2,72 +2,119 @@ import { Deserializable } from "./deserializable.model";
import { Serializable } from "./serializable.model"; import { Serializable } from "./serializable.model";
export class Post implements Deserializable, Serializable { export class Post implements Deserializable, Serializable {
private id: number;
private title: string; private title: string;
private description: string; private description: string;
private timestamp: Date; private timestamp: Date;
private user: string; private owner: string;
private imageUrl: string;
private price: number;
private categoryid: number;
constructor(input: any = null) { constructor(input: any = null) {
if (input) { if (input) {
this.id = input.id;
this.title = input.title; this.title = input.title;
this.description = input.description; this.description = input.description;
this.timestamp = new Date(input.timestamp); this.timestamp = input.timestamp;
this.user = input.user; this.owner = input.owner;
this.imageUrl = input.imageUrl;
this.price = input.price;
this.categoryid = input.categoryid;
} else { } else {
this.title = ""; this.id = 0;
this.description = ""; this.title = null;
this.description = null;
this.timestamp = new Date(); this.timestamp = new Date();
this.user = ""; this.owner = null;
this.imageUrl = null;
this.price = null;
this.categoryid = null;
} }
} }
deserialize(input: string): this { deserialize(input: Object): this {
const obj = JSON.parse(input); Object.assign(this, input);
Object.assign(this, obj);
this.timestamp = new Date(this.timestamp); this.timestamp = new Date(this.timestamp);
return this; return this;
} }
serialize(): string { serialize(): Object {
return JSON.stringify({ return {
id: this.id,
title: this.title, title: this.title,
description: this.description, description: this.description,
timestamp: this.timestamp.valueOf(), timestamp: this.timestamp.valueOf(),
user: this.user owner: this.owner,
}); imageUrl: this.imageUrl,
price: this.price,
categoryid: this.categoryid
};
} }
get getTitle () { get getId() {
return this.id;
}
set setId(id: number) {
this.id = id;
}
get getTitle() {
return this.title; return this.title;
} }
set setTitle (title: string) { set setTitle(title: string) {
this.title = title; this.title = title;
} }
get getDescription () { get getDescription() {
return this.description; return this.description;
} }
set setDescription (description: string) { set setDescription(description: string) {
this.description = description; this.description = description;
} }
get getTimestamp () { get getTimestamp() {
return this.timestamp; return this.timestamp;
} }
set setTimestamp (timestamp: Date) { set setTimestamp(timestamp: Date) {
this.timestamp = timestamp; this.timestamp = timestamp;
} }
get getUser () { get getOwner() {
return this.user; return this.owner;
}
set setOwner(owner: string) {
this.owner = owner;
}
get getImageUrl() {
return this.imageUrl;
}
set setImageUrl(imageUrl: string) {
this.imageUrl = imageUrl;
}
get getPrice() {
return this.price;
}
set setPrice(price: number) {
this.price = price;
}
get getCategory() {
return this.categoryid;
} }
set setUser (user: string) { set setCategory(categoryid: number) {
this.user = user; this.categoryid = categoryid;
} }
} }
\ No newline at end of file
export interface Serializable { export interface Serializable {
serialize(): string; serialize(): Object;
} }
\ No newline at end of file
<p>Post form!</p> <h3>Lag annonse</h3>
<p>Title: {{deserializedPost.getTitle}}</p> <app-text-input [(inputModel)]="title" label="Tittel" (blur)="checkForm()"></app-text-input>
<p>Description: {{deserializedPost.getDescription}}</p>
<app-text-input [(inputModel)]="description" label="Beskrivelse" (blur)="checkForm()"></app-text-input>
<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>
</app-select>
<p>{{statusMessage}}</p>
<app-button (click)="publishPost()" text="Publiser"></app-button>
\ No newline at end of file
import { ComponentFixture, TestBed } from '@angular/core/testing'; import { HttpClientTestingModule } from '@angular/common/http/testing';
import { ComponentFixture, fakeAsync, TestBed, tick } from '@angular/core/testing';
import { FormsModule } from '@angular/forms';
import { Router } from '@angular/router';
import { RouterTestingModule } from '@angular/router/testing';
import { PostFormComponent } from './post-form.component'; import { PostFormComponent } from './post-form.component';
describe('PostFormComponent', () => { describe('PostFormComponent', () => {
let component: PostFormComponent; let component: PostFormComponent;
let fixture: ComponentFixture<PostFormComponent>; let fixture: ComponentFixture<PostFormComponent>;
let router: Router;
beforeEach(async () => { beforeEach(async () => {
await TestBed.configureTestingModule({ await TestBed.configureTestingModule({
declarations: [ PostFormComponent ] declarations: [ PostFormComponent ],
imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule ]
}) })
.compileComponents(); .compileComponents();
}); });
...@@ -17,23 +23,52 @@ describe('PostFormComponent', () => { ...@@ -17,23 +23,52 @@ describe('PostFormComponent', () => {
fixture = TestBed.createComponent(PostFormComponent); fixture = TestBed.createComponent(PostFormComponent);
component = fixture.componentInstance; component = fixture.componentInstance;
fixture.detectChanges(); fixture.detectChanges();
router = TestBed.inject(Router);
}); });
it('should create', () => { it('should create', () => {
expect(component).toBeTruthy(); expect(component).toBeTruthy();
}); });
it('should serialize Post', () => { it('should validate form', () => {
expect(component.serializedPost == "").toBeFalse(); expect(component.checkForm()).toBeFalse();
expect(component.statusMessage).toBe("Tittelen kan ikke være tom");
component.title = "Title";
expect(component.checkForm()).toBeFalse();
expect(component.statusMessage).toBe("Beskrivelsen kan ikke være tom");
component.description = "Description";
component.price = -100;
expect(component.checkForm()).toBeFalse();
expect(component.statusMessage).toBe("Prisen kan ikke være negativ");
try{ component.price = null;
const obj = JSON.parse(component.serializedPost); expect(component.checkForm()).toBeFalse();
}catch{ expect(component.statusMessage).toBe("Annonsen må ha en pris");
fail("Could not serialize");
}
})
it('should deserialize Post', () => { component.price = 50;
expect(component.deserializedPost.getUser).toEqual("Admin"); expect(component.checkForm()).toBeFalse();
}) expect(component.statusMessage).toBe("Annonsen må ha en kategori");
component.categoryid = 2;
expect(component.checkForm()).toBeTrue();
expect(component.statusMessage).toBe("");
});
it('should stop publishing invalid post', fakeAsync(() => {
component.publishPost();
expect(component.statusMessage).toBe("Tittelen kan ikke være tom");
}));
it('should route after publishing post', () => {
component.title = "Title";
component.description = "Description";
component.price = 50;
component.categoryid = 2;
component.publishPost();
expect(router.url).toBe('/');
});
}); });
import { Component, OnInit } from '@angular/core'; import { Component, OnInit } from '@angular/core';
import { Router } from '@angular/router';
import { Post } from 'src/app/models/post.model'; import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service';
@Component({ @Component({
selector: 'app-post-form', selector: 'app-post-form',
...@@ -8,20 +10,75 @@ import { Post } from 'src/app/models/post.model'; ...@@ -8,20 +10,75 @@ import { Post } from 'src/app/models/post.model';
}) })
export class PostFormComponent implements OnInit { export class PostFormComponent implements OnInit {
serializedPost: string = ""; title: string = "";
deserializedPost: Post = new Post(); description: string = "";
price: number = 0;
constructor() { } categoryid: number = 0;
ngOnInit(): void { statusMessage: string = "";
let post = new Post({
title: "TestAnnonse", constructor(private postService: PostService, private router: Router) { }
description: "Beskrivelse",
timestamp: 1612952332000, ngOnInit() {
user: "Admin" }
});
/**
this.serializedPost = post.serialize(); * Validates form.
this.deserializedPost.deserialize(post.serialize()); */
checkForm(): boolean {
if (this.title == "") {
this.setStatusMessage("Tittelen kan ikke være tom");
return false;
}
if (this.description == "") {
this.setStatusMessage("Beskrivelsen kan ikke være tom");
return false;
}
if (this.price < 0) {
this.setStatusMessage("Prisen kan ikke være negativ");
return false;
}
if (this.price == null) {
this.setStatusMessage("Annonsen må ha en pris");
return false;
}
if (this.categoryid < 1) {
this.setStatusMessage("Annonsen må ha en kategori");
return false;
}
this.setStatusMessage("");
return true;
}
/**
* Publishes post if it is valid.
*/
publishPost() {
if (this.checkForm()) {
let newPost = new Post({
title: this.title,
description: this.description,
timestamp: new Date(),
owner: "admin",
imageUrl: "",
price: this.price,
categoryid: this.categoryid
});
this.postService.addPost(newPost).then(status => {
console.log("Post was added: " + status);
this.router.navigateByUrl("/");
}).catch(error => {
console.log("Error adding post: " + error);
});
}
}
/**
* Sets a status message.
*/
setStatusMessage(message: string){
this.statusMessage = message;
} }
} }
\ No newline at end of file
import { NgModule } from '@angular/core'; import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common'; import { CommonModule } from '@angular/common';
import { PostFormComponent } from './post-form/post-form.component'; import { PostFormComponent } from './post-form/post-form.component';
import { SharedModule } from '../shared/shared.module';
import { FormsModule } from '@angular/forms';
...@@ -9,7 +11,10 @@ import { PostFormComponent } from './post-form/post-form.component'; ...@@ -9,7 +11,10 @@ import { PostFormComponent } from './post-form/post-form.component';
PostFormComponent PostFormComponent
], ],
imports: [ imports: [
CommonModule CommonModule,
SharedModule,
FormsModule
] ]
}) })
export class PostModule { } export class PostModule { }
import { HttpClientTestingModule, HttpTestingController } from '@angular/common/http/testing';
import { TestBed } from '@angular/core/testing';
import { Post } from '../models/post.model';
import { PostService } from './post.service';
describe('PostService', () => {
let service: PostService;
let httpMock: HttpTestingController;
beforeEach(() => {
TestBed.configureTestingModule({
imports: [ HttpClientTestingModule ]
});
service = TestBed.inject(PostService);
httpMock = TestBed.inject(HttpTestingController);
});
it('should be created', () => {
expect(service).toBeTruthy();
});
describe('getPost', () => {
it('should get a post', () => {
service.getPost(1).then(post => {
expect(post.getId).toBe(1);
expect(post.getTitle).toBe("Test");
}).catch(error => {
fail();
});
const req = httpMock.expectOne("api/post/1");
expect(req.request.method).toBe("GET");
req.flush({
data: [{
id: 1,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
}]
});
});
it('should reject on invalid post', () => {
service.getPost(2).then(post => {
fail();
}).catch(error => {});
const req = httpMock.expectOne("api/post/2");
expect(req.request.method).toBe("GET");
req.flush({
data: [{
id: 0,
title: "Test",
description: "TestDescription",
timestamp: 23947298
}]
});
});
it('should reject on http error', () => {
service.getPost(2).then(post => {
fail();
}).catch(error => {});
const req = httpMock.expectOne("api/post/2");
expect(req.request.method).toBe("GET");
req.error(new ErrorEvent("400"));
});
});
describe('addPost', () => {
it('should add a post', () => {
let post = new Post({
id: 1,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
});
service.addPost(post)
.then(post => {})
.catch(error => {
fail();
});
const req = httpMock.expectOne("api/post/");
expect(req.request.method).toBe("POST");
expect(req.request.body).toEqual(post.serialize());
req.flush({
data: [{
status: "success"
}]
});
});
it('should reject on http error', () => {
let post = new Post({
id: 1,
title: "Test",
description: "TestDescription",
timestamp: 23947298,
owner: "user",
imageUrl: null,
price: 49,
categoryid: 2
});
service.addPost(post).then(post => {
fail();
}).catch(error => {});
const req = httpMock.expectOne("api/post/");
expect(req.request.method).toBe("POST");
expect(req.request.body).toEqual(post.serialize());
req.error(new ErrorEvent("400"));
});
});
});
import { HttpClient } from '@angular/common/http';
import { Injectable } from '@angular/core';
import { Post } from '../models/post.model';
@Injectable({
providedIn: 'root'
})
export class PostService {
postUrl = "api/post/";
constructor(private http: HttpClient) { }
/**
* Get post from database by id.
*/
getPost(id: number): Promise<Post> {
return new Promise<Post>(
(resolve, reject) => {
this.get_post(id).subscribe((data: any) => {
try {
const post = new Post();
post.deserialize(data.data[0]);
if (post.getId == 0) {
reject("Could not find Post with id: " + id);
return;
}
resolve(post);
} catch (err: any) {
reject(err);
}
},
(err: any) => {
console.log(err.message);
reject(err);
});
}
);
}
private get_post(id: number) {
return this.http.get(this.postUrl + id);
}
/**
* Adds post to database.
*/
addPost(post: Post): Promise<string> {
return new Promise<string>(
(resolve, reject) => {
this.add_post(post).subscribe((data: any) => {
try {
resolve(data.status);
} catch (err: any) {
reject(err);
}
},
(err: any) => {
console.log(err.message);
reject(err);
});
}
);
}
private add_post(post: Post) {
return this.http.post(this.postUrl, post.serialize());
}
}
<button>{{text}}</button>
\ No newline at end of file
button{
padding: 5px;
}
\ No newline at end of file
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { ButtonComponent } from './button.component';
describe('ButtonComponent', () => {
let component: ButtonComponent;
let fixture: ComponentFixture<ButtonComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ ButtonComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(ButtonComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
import { Component, EventEmitter, Input, Output } from '@angular/core';
@Component({
selector: 'app-button',
templateUrl: './button.component.html',
styleUrls: ['./button.component.scss']
})
export class ButtonComponent {
@Input()
text: string;
constructor() { }
}
<label>
{{label}}
<input
type="number"
[placeholder]="placeholder"
[(ngModel)]="inputModel"
(ngModelChange)="inputModelChange.emit(inputModel)"
(change)="change.emit($event)"
(focus)="focus.emit($event)"
(blur)="blur.emit($event)">
</label>
\ No newline at end of file
input{
padding: 5px;
}
\ No newline at end of file
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { NumberInputComponent } from './number-input.component';
describe('NumberInputComponent', () => {
let component: NumberInputComponent;
let fixture: ComponentFixture<NumberInputComponent>;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ NumberInputComponent ]
})
.compileComponents();
});
beforeEach(() => {
fixture = TestBed.createComponent(NumberInputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment