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
No related merge requests found
Showing
with 510 additions and 61 deletions
......@@ -3,7 +3,7 @@
"version": "0.0.0",
"scripts": {
"ng": "ng",
"start": "ng serve",
"start": "ng serve --proxy-config proxy.conf.json",
"build": "ng build",
"test": "ng test",
"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';
import { PostFormComponent } from './posts/post-form/post-form.component';
const routes: Routes = [
{ path: 'postForm', component: PostFormComponent }
{ path: 'annonse/ny', component: PostFormComponent }
];
@NgModule({
......
import { NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
import { HttpClientModule } from '@angular/common/http';
import { AppRoutingModule } from './app-routing.module';
import { AppComponent } from './app.component';
import { PostModule } from './posts/post.module';
import { SharedModule } from './shared/shared.module';
@NgModule({
declarations: [
......@@ -12,7 +14,9 @@ import { PostModule } from './posts/post.module';
imports: [
BrowserModule,
AppRoutingModule,
PostModule
PostModule,
SharedModule,
HttpClientModule
],
providers: [],
bootstrap: [AppComponent]
......
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";
import { Serializable } from "./serializable.model";
export class Post implements Deserializable, Serializable {
private id: number;
private title: string;
private description: string;
private timestamp: Date;
private user: string;
private owner: string;
private imageUrl: string;
private price: number;
private categoryid: number;
constructor(input: any = null) {
if (input) {
this.id = input.id;
this.title = input.title;
this.description = input.description;
this.timestamp = new Date(input.timestamp);
this.user = input.user;
this.timestamp = input.timestamp;
this.owner = input.owner;
this.imageUrl = input.imageUrl;
this.price = input.price;
this.categoryid = input.categoryid;
} else {
this.title = "";
this.description = "";
this.id = 0;
this.title = null;
this.description = null;
this.timestamp = new Date();
this.user = "";
this.owner = null;
this.imageUrl = null;
this.price = null;
this.categoryid = null;
}
}
deserialize(input: string): this {
const obj = JSON.parse(input);
Object.assign(this, obj);
deserialize(input: Object): this {
Object.assign(this, input);
this.timestamp = new Date(this.timestamp);
return this;
}
serialize(): string {
return JSON.stringify({
serialize(): Object {
return {
id: this.id,
title: this.title,
description: this.description,
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;
}
set setTitle (title: string) {
set setTitle(title: string) {
this.title = title;
}
get getDescription () {
get getDescription() {
return this.description;
}
set setDescription (description: string) {
set setDescription(description: string) {
this.description = description;
}
get getTimestamp () {
get getTimestamp() {
return this.timestamp;
}
set setTimestamp (timestamp: Date) {
set setTimestamp(timestamp: Date) {
this.timestamp = timestamp;
}
get getUser () {
return this.user;
get getOwner() {
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) {
this.user = user;
set setCategory(categoryid: number) {
this.categoryid = categoryid;
}
}
\ No newline at end of file
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>
<p>Description: {{deserializedPost.getDescription}}</p>
<app-text-input [(inputModel)]="title" label="Tittel" (blur)="checkForm()"></app-text-input>
<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';
describe('PostFormComponent', () => {
let component: PostFormComponent;
let fixture: ComponentFixture<PostFormComponent>;
let router: Router;
beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [ PostFormComponent ]
declarations: [ PostFormComponent ],
imports: [ HttpClientTestingModule, RouterTestingModule, FormsModule ]
})
.compileComponents();
});
......@@ -17,23 +23,52 @@ describe('PostFormComponent', () => {
fixture = TestBed.createComponent(PostFormComponent);
component = fixture.componentInstance;
fixture.detectChanges();
router = TestBed.inject(Router);
});
it('should create', () => {
expect(component).toBeTruthy();
});
it('should serialize Post', () => {
expect(component.serializedPost == "").toBeFalse();
it('should validate form', () => {
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{
const obj = JSON.parse(component.serializedPost);
}catch{
fail("Could not serialize");
}
})
component.price = null;
expect(component.checkForm()).toBeFalse();
expect(component.statusMessage).toBe("Annonsen må ha en pris");
it('should deserialize Post', () => {
expect(component.deserializedPost.getUser).toEqual("Admin");
})
component.price = 50;
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 { Router } from '@angular/router';
import { Post } from 'src/app/models/post.model';
import { PostService } from '../post.service';
@Component({
selector: 'app-post-form',
......@@ -8,20 +10,75 @@ import { Post } from 'src/app/models/post.model';
})
export class PostFormComponent implements OnInit {
serializedPost: string = "";
deserializedPost: Post = new Post();
constructor() { }
ngOnInit(): void {
let post = new Post({
title: "TestAnnonse",
description: "Beskrivelse",
timestamp: 1612952332000,
user: "Admin"
});
this.serializedPost = post.serialize();
this.deserializedPost.deserialize(post.serialize());
title: string = "";
description: string = "";
price: number = 0;
categoryid: number = 0;
statusMessage: string = "";
constructor(private postService: PostService, private router: Router) { }
ngOnInit() {
}
/**
* Validates form.
*/
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 { CommonModule } from '@angular/common';
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';
PostFormComponent
],
imports: [
CommonModule
CommonModule,
SharedModule,
FormsModule
]
})
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