Commit 824cd869 authored by Even Gultvedt's avatar Even Gultvedt
Browse files

Removed Flow from test files. Added validation to user input, as well as errors when those occur.

parent e5e0c06b
Pipeline #58837 passed with stage
in 1 minute and 18 seconds
......@@ -71,3 +71,20 @@ body {
align-content: center;
}
input, textarea, select{
margin-bottom: 1em;
}
input[type=text]:hover, textarea:hover, select:hover{
box-shadow: 0 0 3px 3px rgba(81,203,238,1);
}
input:required:focus, textarea:required:focus{
border: 3px solid red;
}
input[type=checkbox]{
transform: scale(3);
margin: 1em;
}
//@flow
module.exports = class Article{
export class Article{
static constantURL:string = 'https://png.pngtree.com/png-clipart/20190515/original/pngtree-coffee-time-png-image_3626459.jpg';
id: number;
author: string;
......
......@@ -3,7 +3,7 @@
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {NavLink} from "react-router-dom";
import {Button, Card, Column, Row} from "./widgets";
import {Alert, Button, Card, Column, Row} from "./widgets";
import Image from "react-bootstrap/Image";
import * as React from "react";
import {Header} from "./myWidgets";
......@@ -74,6 +74,7 @@ export class SingleArticle extends Component<{ match: { params: { id: number } }
comment={comment}/>)
}
</Card>
<Alert/>
<AddComment article_id={articleStore.currentArticle.id}/>
</div>
);
......@@ -86,7 +87,8 @@ export class SingleArticle extends Component<{ match: { params: { id: number } }
class AddOrEditArticle extends Component {
author: string;
title: string;
imageURL: string | null;
imageURL: string;
imageError: string = "";
content: string;
category: number;
important: boolean;
......@@ -98,6 +100,8 @@ class AddOrEditArticle extends Component {
categoryRef: any;
importantRef: any;
errors: string[] = [];
validateURL(URL: string) {
return (URL.match(/\.(jpeg|jpg|gif|png)$/) != null);
}
......@@ -116,49 +120,59 @@ class AddOrEditArticle extends Component {
return (
<div>
<Header/>
<Alert/>
<Card title='Add article' id='title'>
<form>
<Row>
<Column width={2}>
<span className='input-group-text'>Brukernavn</span>
</Column>
<Column>
<Column width={8}>
<input type='text' id='authorValue' ref={this.authorRef}
value={this.author}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.author = event.target.value
}}
className="form-control" aria-label="Large"
aria-describedby="inputGroup-sizing-sm"/>
aria-describedby="inputGroup-sizing-sm" required/>
</Column>
<Column>*</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Overskrift</span>
</Column>
<Column>
<Column width={8}>
<input type='text' id='titleValue' ref={this.titleRef}
value={this.title}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.title = event.target.value
}}
className="form-control" aria-label="Large"
aria-describedby="inputGroup-sizing-sm"/>
aria-describedby="inputGroup-sizing-sm" required/>
</Column>
<Column>*</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Bilde (Bare lenke)</span>
</Column>
<Column>
<Column width={8}>
<input type="text" className='form-control' id="imageURL" ref={this.imageRef}
onChange={event => {
if (this.validateURL(event.target.value)) {
if (event.target.value.trim() === ""){
this.imageError = "";
this.imageURL = null;
}
else if (this.validateURL(event.target.value)) {
console.log("Validate");
this.imageURL = event.target.value;
this.imageError = "";
} else {
this.imageError = "Not an image";
this.imageURL = null;
}
}}/>
}} pattern=".(jpeg|jpg|gif|png)"/>
</Column>
</Row>
<Row>
......@@ -167,38 +181,45 @@ class AddOrEditArticle extends Component {
<span className='input-group-text'>Innhold</span>
</div>
</Column>
<Column>
<textarea id='contentValue' ref={this.contentRef} value={this.content} className='form-control'
aria-label="With textarea" rows={15}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.content = event.target.value
}}/></Column>
<Column width={8}>
<textarea id='contentValue' ref={this.contentRef} value={this.content} className='form-control'
aria-label="With textarea" rows={15}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => {
this.content = event.target.value
}} required/>
</Column>
<Column>*</Column>
</Row>
<Row>
<Column width={2}>
Kategori
<span className="input-group-text">Kategori</span>
</Column>
<Column>
<Column width={8}>
<select id='categorySelection' ref={this.categoryRef} defaultValue={0}
className="custom-select"
onChange={event => {
this.category = event.target.options[event.target.selectedIndex].value
}}>
{
articleStore.categories.map(c => <option value={c.id}>{c.text}</option>)
articleStore.categories.map(c => <option value={c.id} key={c.id}>{c.text}</option>)
}
</select>
</Column>
<Column>*</Column>
</Row>
<Row>
<Column width={2}>
Hovedsideverdig?
<span className="input-group-text">Hovedsideverdig?</span>
</Column>
<Column>
<input id='importantValue' ref={this.importantRef} type='checkbox' onClick={event => {
this.important = event.target.checked
}}/>
<Column width={8}>
<input id='importantValue' className="custom-checkbox" ref={this.importantRef} type='checkbox' onClick={event => {
this.important = event.target.checked
}}/>
</Column>
<Column>*</Column>
</Row>
<Row>
<Column width={10}>Alle felter merket med * er nødvendige</Column>
</Row>
<button className="btn btn-outline-secondary" type="button"
onClick={this.submitArticle}>Submit
......@@ -239,38 +260,61 @@ class AddOrEditArticle extends Component {
}
submitArticle() {
let errors: string[] = [];
if (this.authorRef.current.value.length === 0){
errors.push("Husk brukernavn");
}
if (this.titleRef.current.value.length === 0){
errors.push("Husk overskrift");
}
if (this.imageRef.current.value.length > 0 && !(this.validateURL(this.imageRef.current.value))){
errors.push("Bildet er feil");
}
if (this.contentRef.current.value.length === 0){
errors.push("Husk innhold");
}
if (errors.length > 0){
let error = errors.reduce((acc, currenValue) => (acc + currenValue + " | "), "");
Alert.danger(error);
return false;
}
else{
return true;
}
}
}
export class AddArticle extends AddOrEditArticle {
submitArticle() {
articleStore.getIds().then(ids => {
console.log(ids);
let id: number;
if (ids.length === 0) {
id = 0;
} else {
id = Math.max(...ids) + 1;
}
if (super.submitArticle()){
articleStore.getIds().then(ids => {
console.log(ids);
let id: number;
if (ids.length === 0) {
id = 0;
} else {
id = Math.max(...ids) + 1;
}
super.updateInternalValues();
super.updateInternalValues();
console.log(id);
console.log(id);
let newArticle = super.createArticle(id);
let newArticle = super.createArticle(id);
console.log(newArticle);
console.log(newArticle);
articleStore.articles.push(newArticle);
articleStore.articles.push(newArticle);
articleStore.postArticle(newArticle).then(response => {
if (response.statusText === 'OK') {
history.push("/articles/article/" + newArticle.id);
}
}).catch(error => console.log("Error:" + error));
});
articleStore.postArticle(newArticle).then(response => {
if (response.statusText === 'OK') {
history.push("/articles/article/" + newArticle.id);
}
}).catch(error => console.log("Error:" + error));
});
}
return true;
}
}
......@@ -290,14 +334,18 @@ export class EditArticle extends AddOrEditArticle<{ match: { params: { article_i
}
submitArticle() {
super.updateInternalValues();
if (super.submitArticle()){
super.updateInternalValues();
let editedArticle = super.createArticle(this.props.match.params.article_id);
let editedArticle = super.createArticle(this.props.match.params.article_id);
articleStore.editArticle(editedArticle).then(response => {
if (response.statusText === 'OK') {
history.push("/articles/article/" + editedArticle.id);
}
}).catch(error => console.log(error));
}
return true;
articleStore.editArticle(editedArticle).then(response => {
if (response.statusText === 'OK') {
history.push("/articles/article/" + editedArticle.id);
}
}).catch(error => console.log(error));
}
}
\ No newline at end of file
module.exports = class Comment{
//@flow
export class Comment{
id: number;
author: string;
content: string;
......
//@flow
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {Button, Card, Column, Row} from "./widgets";
import {Alert, Button, Card, Column, Row} from "./widgets";
import * as React from "react";
const Comment = require('../src/Comment');
import Comment from './Comment';
export class SingleComment extends Component<{ comment: Comment }> {
render() {
......@@ -59,14 +59,26 @@ export class AddComment extends Component<{ article_id: number }> {
}
addComment() {
let id: number = Math.max(...articleStore.currentArticle.comments.map(e => e.id)) + 1;
if (id < 0) {
id = 0;
let errors: string[] = [];
if (this.author === undefined || this.author.length === 0){
errors.push("Husk brukernavn");
}
if (this.content === undefined || this.content.length === 0){
errors.push("Husk innhold");
}
if (errors.length > 0){
Alert.danger(errors.reduce((acc, e) => (acc + e + " | "), ""));
}
else{
let id: number = Math.max(...articleStore.currentArticle.comments.map(e => e.id)) + 1;
if (id < 0) {
id = 0;
}
let comment: Comment = new Comment(id, this.author, this.content, articleStore.currentArticle.id);
console.log(comment);
this.author = '';
this.content = '';
articleStore.postComment(comment).then(() => articleStore.getArticle(articleStore.currentArticle.id));
}
let comment: Comment = new Comment(id, this.author, this.content, articleStore.currentArticle.id);
console.log(comment);
this.author = '';
this.content = '';
articleStore.postComment(comment).then(() => articleStore.getArticle(articleStore.currentArticle.id));
}
}
\ No newline at end of file
//@flow
import React from 'react';
import ReactDOM from 'react-dom';
import {Category, FindError, Home, ServerError} from './myWidgets';
......@@ -5,6 +6,7 @@ import {HashRouter, Route, Switch} from "react-router-dom";
import {articleStore} from "./stores";
import {AddArticle, EditArticle, SingleArticle} from "./ArticleWidgets";
import {history} from "./myWidgets";
import {Alert} from "./widgets";
const root: any = document.getElementById('root');
if (root)
......
//@flow
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {NavLink} from "react-router-dom";
......
//@flow
import axios from 'axios';
import {sharedComponentData} from "react-simplified";
import {history} from "./myWidgets";
const Article = require('../src/Article');
const Comment = require('../src/Comment');
import Article from './Article';
import Comment from './Comment';
const header = {
"Content-Type": "application/json"
......@@ -272,6 +270,4 @@ class ArticleStore {
}
}
export let articleStore = sharedComponentData(new ArticleStore());
module.export = articleStore;
\ No newline at end of file
export let articleStore = sharedComponentData(new ArticleStore());
\ No newline at end of file
//@flow
import * as React from 'react';
import { Component } from 'react-simplified';
import { NavLink } from 'react-router-dom';
......
//@flow
let mysql = require('mysql');
const runsqlfile = require('../../server/tests/runsqlfile');
import {Article} from '../src/Article';
import {Comment} from "../src/Comment";
import {articleStore} from "../src/stores";
let pool = mysql.createPool({
connectionLimit: 1,
host: "mysql",
user: "root",
password: "secret",
database: "supertestdb",
debug: false,
multipleStatements: true
});
let privatepool = mysql.createPool({
connectionLimit: 2,
......@@ -9,7 +20,8 @@ let privatepool = mysql.createPool({
user: "evengu",
password: "O7KhlwWQ",
database: "evengu",
debug: false
debug: false,
multipleStatements:true
}
);
......@@ -22,10 +34,10 @@ beforeAll(done => {
afterAll(() => privatepool.end());
test("Hello", done => {
expect(1).toBe(2);
expect(1).toBe(1);
done();
});
/*
test("Checking Comment() constructor", done => {
let comment = new Comment(0, "evengu", "Some content", 0);
expect(comment.id).toBe(0);
......@@ -128,4 +140,4 @@ test("Delete comment", done => {
})
})
})
});*/
});
// flow-typed signature: e58955fd9864ac5f1c162da932f492d9
// flow-typed version: 358ad43cd9/axios_v0.19.x/flow_>=v0.80.x
declare module 'axios' {
import type { Agent as HttpAgent } from 'http';
import type { Agent as HttpsAgent } from 'https';
declare type AxiosTransformer<T> = (
data: T,
headers?: { [key: string]: any }
) => any;
declare type ProxyConfig = {|
host: string,
port: number,
auth?: {
username: string,
password: string,
},
protocol?: string,
|};
declare class Cancel {
constructor(message?: string): Cancel;
message: string;
}
declare type Canceler = (message?: string) => void;
declare type CancelTokenSource = {|
token: CancelToken,
cancel: Canceler,
|};
declare class CancelToken {
constructor(executor: (cancel: Canceler) => void): void;
static source(): CancelTokenSource;
promise: Promise<Cancel>;
reason?: Cancel;
throwIfRequested(): void;
}
declare type Method =
| 'get'
| 'GET'
| 'delete'
| 'DELETE'
| 'head'
| 'HEAD'
| 'options'
| 'OPTIONS'
| 'post'
| 'POST'
| 'put'
| 'PUT'
| 'patch'
| 'PATCH';
declare type ResponseType =
| 'arraybuffer'
| 'blob'
| 'document'
| 'json'
| 'text'
| 'stream';
declare type AxiosAdapter = (
config: AxiosXHRConfig<any>
) => Promise<AxiosXHR<any>>;
declare type AxiosXHRConfigBase<T, R = T> = {
adapter?: AxiosAdapter,
auth?: {
username: string,
password: string,
},
baseURL?: string,
cancelToken?: CancelToken,
headers?: { [key: string]: any },
httpAgent?: HttpAgent,
httpsAgent?: HttpsAgent,
maxContentLength?: number,
maxRedirects?: number,
socketPath?: string | null,
params?: { [key: string]: any },
paramsSerializer?: (params: { [key: string]: any }) => string,
onUploadProgress?: (progressEvent: ProgressEvent) => void,
onDownloadProgress?: (progressEvent: ProgressEvent) => void,
proxy?: ProxyConfig | false,
responseType?: ResponseType,
timeout?: number,
transformRequest?: AxiosTransformer<T> | Array<AxiosTransformer<T>>,
transformResponse?: AxiosTransformer<R> | Array<AxiosTransformer<R>>,
validateStatus?: (status: number) => boolean,
withCredentials?: boolean,
xsrfCookieName?: string,
xsrfHeaderName?: string,
};
declare type AxiosXHRConfig<T, R = T> = {|
...$Exact<AxiosXHRConfigBase<T, R>>,
data?: T,
method?: Method,
url: string,
|};
declare type AxiosXHRConfigShape<T, R = T> = $Shape<AxiosXHRConfig<T, R>>;
declare type AxiosXHR<T, R = T> = {
config: AxiosXHRConfig<T, R>,
data: R,
headers: ?{ [key: string]: any },
status: number,
statusText: string,
request: http$ClientRequest<> | XMLHttpRequest | mixed,
};
declare type AxiosInterceptorIdent = number;
declare type AxiosRequestInterceptor<T, R = T> = {|
use(
onFulfilled: ?(
response: AxiosXHRConfig<T, R>
) => Promise<AxiosXHRConfig<mixed>> | AxiosXHRConfig<mixed>,
onRejected: ?(error: mixed) => mixed
): AxiosInterceptorIdent,
eject(ident: AxiosInterceptorIdent): void,
|};
declare type AxiosResponseInterceptor<T, R = T> = {|
use(
onFulfilled: ?(response: AxiosXHR<T, R>) => mixed,
onRejected: ?(error: mixed) => mixed
): AxiosInterceptorIdent,
eject(ident: AxiosInterceptorIdent): void,
|};
declare type AxiosPromise<T, R = T> = Promise<AxiosXHR<T, R>>;
declare class Axios {
<T, R>(
config: AxiosXHRConfig<T, R> | string,