Skip to content
Snippets Groups Projects
Commit 822f8ca3 authored by Even Gultvedt's avatar Even Gultvedt
Browse files

commit

parents
No related branches found
No related tags found
No related merge requests found
Pipeline #58473 failed
Showing
with 1272 additions and 0 deletions
# Created by .ignore support plugin (hsz.mobi)
### Node template
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
lerna-debug.log*
# Diagnostic reports (https://nodejs.org/api/report.html)
report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json
# Runtime data
pids
*.pid
*.seed
*.pid.lock
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
*.lcov
# nyc test coverage
.nyc_output
# Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Bower dependency directory (https://bower.io/)
bower_components
# node-waf configuration
.lock-wscript
# Compiled binary addons (https://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules/
jspm_packages/
# TypeScript v1 declaration files
typings/
# TypeScript cache
*.tsbuildinfo
# Optional npm cache directory
.npm
# Optional eslint cache
.eslintcache
# Optional REPL history
.node_repl_history
# Output of 'npm pack'
*.tgz
# Yarn Integrity file
.yarn-integrity
# dotenv environment variables file
.env
.env.test
# parcel-bundler cache (https://parceljs.org/)
.cache
# next.js build output
.next
# nuxt.js build output
.nuxt
# vuepress build output
.vuepress/dist
# Serverless directories
.serverless/
# FuseBox cache
.fusebox/
# DynamoDB Local files
.dynamodb/
.gitignore
.idea/
\ No newline at end of file
image: node:8
# Set up MySQL test-database
services:
- mysql:5.5
variables:
MYSQL_DATABASE: supertestdb
MYSQL_ROOT_PASSWORD: secret
#install node libraries
before_script:
- cd server
- npm install
- cd ../client_
- npm install
# Run JEST tests
run_tests:
stage: test
script:
- cd ../server
- npm test
- cd ../client_
- npm test
artifacts:
paths:
- coverage/
This diff is collapsed.
{
"name": "client",
"version": "0.1.0",
"private": true,
"dependencies": {
"react": "^16.11.0",
"react-bootstrap": "^1.0.0-beta.14",
"react-dom": "^16.11.0",
"react-scripts": "3.2.0",
"react-simplified": "^1.6.1",
"react-ticker": "^1.2.1",
"react-native-text-ticker": "^1.0.0",
"mysql": "^2.17.1"
},
"scripts": {
"start": "react-scripts start",
"build": "react-scripts build",
"test": "jest --detectOpenHandles --forceExit",
"eject": "react-scripts eject"
},
"eslintConfig": {
"extends": "react-app"
},
"browserslist": {
"production": [
">0.2%",
"not dead",
"not op_mini all"
],
"development": [
"last 1 chrome version",
"last 1 firefox version",
"last 1 safari version"
]
},
"devDependencies": {
"axios": "^0.19.0",
"history": "^4.10.1",
"react-native-text-ticker": "^1.0.0",
"react-router-dom": "^5.1.2"
}
}
This diff is collapsed.
This diff is collapsed.
/*Ticker CSS: https://codepen.io/lewismcarey/pen/GJZVoG*/
@-webkit-keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
@keyframes ticker {
0% {
-webkit-transform: translate3d(0, 0, 0);
transform: translate3d(0, 0, 0);
visibility: visible;
}
100% {
-webkit-transform: translate3d(-100%, 0, 0);
transform: translate3d(-100%, 0, 0);
}
}
.ticker-wrap {
width: 100%;
overflow: hidden;
height: 4rem;
background-color: rgba(0, 0, 0, 0.9);
padding-left: 100%;
box-sizing: content-box;
}
.ticker {
display: inline-block;
height: 4rem;
line-height: 4rem;
white-space: nowrap;
padding-right: 100%;
box-sizing: content-box;
-webkit-animation-iteration-count: infinite;
animation-iteration-count: infinite;
-webkit-animation-timing-function: linear;
animation-timing-function: linear;
-webkit-animation-name: ticker;
animation-name: ticker;
-webkit-animation-duration: 20s;
animation-duration: 20s;
}
.ticker-item{
display: inline-block;
padding: 0 2rem;
font-size: 2rem;
color: white;
}
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<link rel="stylesheet" href="bootstrap.min.css"/>
<link rel="stylesheet" href="bootstrap-grid.css" />
<link rel="stylesheet" href="index.css"/>
<title>React App</title>
</head>
<body>
<noscript>You need to enable JavaScript to run this app.</noscript>
<div id="root"></div>
</body>
</html>
[ignore]
[include]
[libs]
[lints]
[options]
[strict]
module.exports = class Article{
static constantURL:string = 'https://png.pngtree.com/png-clipart/20190515/original/pngtree-coffee-time-png-image_3626459.jpg';
id: number;
author: string;
title: string;
imageURL: string;
time: string;
content: string;
category: number;
important: boolean;
constantURL: string = 'https://png.pngtree.com/png-clipart/20190515/original/pngtree-coffee-time-png-image_3626459.jpg';
comments: Comment[];
constructor(id: number, author: string, title: string, imageURL: string, time: string, content: string, category: number, important: boolean){
this.id = id;
this.author = author;
this.title = title;
if (imageURL == null){
this.imageURL = this.constantURL;
}
else{
this.imageURL = imageURL;
}
this.time = time;
this.content = content;
this.category = category;
this.important = important;
this.comments = [];
}
addComments(comments: Comment[]){
if (comments){
comments.map(element => this.comments.push(element));
}
}
};
\ No newline at end of file
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {NavLink} from "react-router-dom";
import {Button, Card, Column, Row} from "./widgets";
import Image from "react-bootstrap/Image";
import * as React from "react";
import {Header} from "./myWidgets";
import {AddComment, SingleComment} from "./CommentWidgets";
import {history} from "./myWidgets";
const Article = require('../src/Article');
export class ArticleGrid extends Component<{ category?: number }> {
mounted() {
articleStore.getAllArticles().catch(error => console.log(error));
}
render() {
if (articleStore.articles != null) {
return (
<div className='container' key={1}>
{
articleStore.articles.filter(a => ((!this.props.category && a.important) || (a.category === this.props.category))).map(article => (
<div className='container m-4 article-box' key={article.id}>
<NavLink className='card-link' exact to={'/articles/article/' + article.id}>
<Card title={article.title}>
<Image alt={article.title} src={article.imageURL} style={{width: '200px'}}
fluid/>
</Card>
</NavLink>
</div>
)
)
}
</div>
);
} else {
return null;
}
}
}
export class SingleArticle extends Component<{ match: { params: { id: number } } }> {
constructor(props) {
super(props);
if (this.props.match != null && this.props.match.params.id) {
console.log("Fetching " + this.props.match.params.id);
articleStore.getArticle(this.props.match.params.id).catch(error => console.log(error));
} else if (this.props.id != null && this.props.id) {
console.log("Fetching " + this.props.id);
articleStore.getArticle(this.props.id).catch(error => console.log(error));
}
}
render() {
if (articleStore.currentArticle != null) {
return (
<div>
<Header/>
<Card title={articleStore.currentArticle.title}>
<Image alt={articleStore.currentArticle.title} src={articleStore.currentArticle.imageURL}
style={{width: '200px'}} fluid/>
</Card>
<Card title="">{articleStore.currentArticle.content}</Card>
<Row>
<Column><Button.Info
onClick={() => (history.push("/articles/edit/" + articleStore.currentArticle.id))}>Edit</Button.Info></Column>
<Column><Button.Danger
onClick={() => articleStore.deleteCurrentArticle()}>Delete</Button.Danger></Column>
</Row>
{
articleStore.currentArticle.comments.map(comment => <SingleComment key={comment.id}
comment={comment}/>)
}
<AddComment article_id={articleStore.currentArticle.id}/>
</div>
);
} else {
return null;
}
}
}
class AddOrEditArticle extends Component {
author: string;
title: string;
imageURL: string;
content: string;
category: number;
important: boolean;
time: string;
validateURL(URL: string) {
return (URL.match(/\.(jpeg|jpg|gif|png)$/) != null);
}
constructor(props) {
super(props);
this.authorRef = React.createRef();
this.titleRef = React.createRef();
this.imageRef = React.createRef();
this.contentRef = React.createRef();
this.categoryRef = React.createRef();
this.importantRef = React.createRef();
}
render() {
return (
<div>
<Header/>
<Card title='Add article' id='title'>
<form>
<Row>
<Column width={2}>
<span className='input-group-text'>Author</span>
</Column>
<Column>
<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"/>
</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Title</span>
</Column>
<Column>
<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"/>
</Column>
</Row>
<Row>
<Column width={2}>
<span className='input-group-text'>Image</span>
</Column>
<Column>
<input type="text" className='form-control' id="imageURL" ref={this.imageRef}
onChange={event => {
if (this.validateURL(event.target.value)) {
this.imageURL = event.target.value;
} else {
this.imageURL = null;
}
}}/>
</Column>
<Column>
<div className="input-group-append">
<button className="btn btn-outline-secondary" type="button"
onClick={(event: SyntheticInputEvent<HTMLButtonElement>) => this.imageURL = document.getElementById("imageURL").value}>Upload
</button>
</div>
</Column>
<Column>
<Image alt={this.title} src={this.imageURL === null ? null : this.imageURL}/>
</Column>
</Row>
<Row>
<Column width={2}>
<div className='input-group-prepend'>
<span className='input-group-text'>Content</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>
</Row>
<Row>
<Column width={2}>
Category
</Column>
<Column>
<select id='categorySelection' ref={this.categoryRef} defaultValue={1}
className="custom-select"
onChange={event => {
this.category = event.target.options[event.target.selectedIndex].value
}}>
<option value={1}>News</option>
<option value={2}>Sports</option>
<option value={3}>Culture</option>
</select>
</Column>
</Row>
<Row>
<Column width={2}>
Important
</Column>
<Column>
<input id='importantValue' ref={this.importantRef} type='checkbox' onClick={event => {
this.important = event.target.checked
}}/>
</Column>
</Row>
<button className="btn btn-outline-secondary" type="button"
onClick={this.submitArticle}>Submit
</button>
</form>
</Card>
</div>
);
}
updateInternalValues() {
this.author = this.authorRef.current.value;
this.title = this.titleRef.current.value;
this.imageURL = (this.imageRef.current.value.trim().length > 0 && this.validateURL(this.imageRef.current.value) ? this.imageRef.current.value : null);
this.content = this.contentRef.current.value;
this.important = this.importantRef.current.checked;
let categorySelection = this.categoryRef.current;
this.category = categorySelection.options[categorySelection.selectedIndex].value;
}
updateInputValues(author: string, title: string, imageURL: string, content: string, important: boolean, category: number) {
this.authorRef.current.value = author;
this.titleRef.current.value = title;
this.imageRef.current.value = (imageURL === Article.constantURL ? null : imageURL);
this.contentRef.current.value = content;
this.importantRef.current.checked = important;
this.categoryRef.current.value = category;
this.updateInternalValues();
}
createArticle(id: number) {
let today = new Date();
this.time = today.getFullYear() + '-' + (today.getMonth() + 1) + '-' + today.getDate() + " " +
(today.getHours() > 9 ? today.getHours() : '0' + today.getHours()) + ":" +
(today.getMinutes() > 9 ? today.getMinutes() : '0' + today.getMinutes());
console.log(this.imageURL);
return new Article(id, this.author, this.title, this.imageURL, this.time, this.content, this.category, this.important);
}
submitArticle() {
}
}
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;
}
super.updateInternalValues();
console.log(id);
let newArticle = super.createArticle(id);
console.log(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));
});
}
}
export class EditArticle extends AddOrEditArticle<{ match: { params: { article_id: number } } }> {
componentDidMount() {
articleStore.getArticle(this.props.match.params.article_id).then(() => {
super.updateInputValues(articleStore.currentArticle.author,
articleStore.currentArticle.title,
articleStore.currentArticle.imageURL,
articleStore.currentArticle.content,
articleStore.currentArticle.important,
articleStore.currentArticle.category);
}).catch(error => {
console.log(error)
});
}
submitArticle() {
super.updateInternalValues();
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));
}
}
\ No newline at end of file
module.exports = class Comment{
id: number;
author: string;
content: string;
articles_id: number;
constructor(id: number, author: string, content: string, article_id: number) {
this.id = id;
this.author = author;
this.content = content;
this.articles_id = article_id;
}
};
\ No newline at end of file
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {Button, Card, Column, Row} from "./widgets";
import * as React from "react";
const Comment = require('../src/Comment');
export class SingleComment extends Component<{ comment: Comment }> {
render() {
return (
<Card title={this.props.comment.author}>
<Row>
<Column>
<div className='card-text'>{this.props.comment.content}</div>
</Column>
<Column>
<Button.Danger onClick={() => this.deleteComment()}>Delete Comment</Button.Danger>
</Column>
</Row>
</Card>
);
}
deleteComment() {
articleStore.deleteComment(this.props.comment.id).then(articleStore.getArticle(articleStore.currentArticle.id));
}
}
export class AddComment extends Component<{ article_id: number }> {
author: string = '';
content: string = '';
render() {
return (
<Card title='Add Comment'>
<form>
<Row>
<Column width={2}><span className='input-group-text'>Username</span></Column>
<Column>
<input type='text'
value={this.author}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => (this.author = event.target.value)}
className='form-control' aria-label='Large' aria-describedby="inputGroup-sizing-sm"/>
</Column>
</Row>
<Row>
<Column width={2}><span className='input-group-text'>Comment</span></Column>
<Column>
<input type='text'
value={this.content}
onChange={(event: SyntheticInputEvent<HTMLInputElement>) => (this.content = event.target.value)}
className='form-control' aria-label='Large' aria-describedby="inputGroup-sizing-sm"/>
</Column>
</Row>
<Button.Info onClick={this.addComment}>Add comment</Button.Info>
</form>
</Card>
);
}
addComment() {
let id = Math.max(...articleStore.currentArticle.comments.map(e => e.id)) + 1;
if (id < 0) {
id = 0;
}
let 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
import React from 'react';
import ReactDOM from 'react-dom';
import {Category, Home} from './myWidgets';
import {HashRouter, Route} from "react-router-dom";
import {articleStore} from "./stores";
import {AddArticle, EditArticle, SingleArticle} from "./ArticleWidgets";
const root = document.getElementById('root');
if (root)
articleStore.getCategories().then(res => {
ReactDOM.render(
<HashRouter>
<div>
<Route exact path="/" component={Home} />
<Route exact path="/articles" component={Home}/>
<Route exact path="/articles/article" component={Home}/>
{
res.map(c => <Route key={c.id} exact path={"/articles/" + c.text} render={(props) => <Category {...props} category={c.id}/>}/>)
}
<Route exact path="/articles/article/:id" component={SingleArticle}/>
<Route exact path="/articles/create" component={AddArticle}/>
<Route exact path="/articles/edit/:article_id" component={EditArticle}/>
</div>
</HashRouter>,
root
);
});
import {Component} from "react-simplified";
import {articleStore} from "./stores";
import {NavLink} from "react-router-dom";
import * as React from "react";
import {createHashHistory} from "history";
import {ArticleGrid} from "./ArticleWidgets";
const Article = require('../src/Article');
export const history = createHashHistory();
export class Header extends Component {
render() {
return (
<nav className='navbar navbar-expand-sm bg-light navbar-light'>
<NavLink className='navbar-brand' activeClassName='active' exact to='/'><img src={Article.constantURL}
alt='koalalogo'
style={{width: '50px'}}/></NavLink>
<div className='navbar-nav w-100'>
<NavLink className='nav-link text-info' activeClassName='active' exact to='/'>HJEM</NavLink>
<CategoryLinks/>
</div>
<div className='navbar-nav w-25'>
<NavLink className='nav-link text-info' activeClassName='active' exact to='/articles/create'>Create
Article</NavLink>
</div>
</nav>
);
}
}
export class Home extends Component{
render(){
return(
<div>
<Header/>
<Ticker/>
<ArticleGrid/>
</div>
);
}
}
export class Category extends Component<{category: number}>{
render(){
return(
<div>
<Header/>
<ArticleGrid category={this.props.category}/>
</div>
);
}
}
class CategoryLinks extends Component{
render(){
if (articleStore.categories){
return (
<div>
{
articleStore.categories.map(c => <NavLink key={c.id} className='nav-link text-info' activeClassName='active' exact
to={'/articles/' + c.text}>{c.text}</NavLink>)
}
</div>
);
}
return null;
}
mounted(){
articleStore.getCategories().catch(error => console.log(error));
}
}
export class Ticker extends Component {
checkTime(input: string) {
let writtenDate = input.split(" ")[0];
let currentDate = new Date();
let writtenYear = writtenDate.split("-")[0];
let writtenMonth = writtenDate.split("-")[1];
return (writtenYear * 1 === currentDate.getFullYear() && writtenMonth * 1 === currentDate.getMonth() + 1);
}
render() {
return (
<div className='ticker-wrap card container'>
<div className='ticker'>
{
articleStore.articles.filter(e => this.checkTime(e.time)).map(e => (
<NavLink className='text-info ticker-item' exact to={'/articles/article/' + e.id}
key={e.id}>
{e.title} &nbsp; {e.time}
</NavLink>
))
}
</div>
</div>
);
}
mounted() {
articleStore.getAllArticles().catch(error => console.log(error));
}
}
\ No newline at end of file
//@flow
import axios from 'axios';
import {sharedComponentData} from "react-simplified";
import {history} from "./myWidgets";
const Article = require('../src/Article');
const Comment = require('../src/Comment');
const header = {
"Content-Type": "application/json"
};
class ArticleStore {
articles: Article[] = [];
currentArticle: Article = null;
categories = {};
getIds(){
return axios.get<number[]>("http://localhost:4001/ids").then(response => response.data).then(data => data.map(element => element.id));
}
getCategories(){
return axios.get('http://localhost:4001/categories').then(response => this.categories = response.data);
}
createArticle(response): Article{
return new Article(response.id,
response.author, response.title, response.imageURL, response.time, response.content, response.category,
response.important === 1, []);
}
getArticle(id: number): Article{
return axios.get('http://localhost:4001/article/' + id).then(response => {
if (response.data[0]){
this.currentArticle = this.createArticle(response.data[0]);
let article = this.articles.find(a => this.currentArticle.id === a.id);
if (article){
Object.assign(article, {...this.currentArticle});
}
}
else{
console.log(id + ' not found');
this.currentArticle = null;
}
return this.currentArticle;
}).then(() => {
if (this.currentArticle !== null){
this.getComments(id).then(res => this.currentArticle.addComments(res));
}
});
}
getAllArticles(): Article[]{
return axios.get('http://localhost:4001/article').then(response => {
this.articles = response.data.map(responsePart => this.createArticle(responsePart));
return this.articles;
});
}
deleteCurrentArticle(){
console.log('Deleting ' + this.currentArticle.id);
axios.delete('http://localhost:4001/article',
{
headers: header,
data: JSON.stringify({id: this.currentArticle.id})
}).then(() => {
this.articles = this.articles.filter(article => (article.id !== this.currentArticle.id));
this.currentArticle = null;
history.push('/')
});
}
getComments(id: number): Comment[]{
return axios.get('http://localhost:4001/comments/' + id,
{
headers: header,
data: JSON.stringify({article_id: id})
}).then(response => {
if (response.data.length > 0){
return response.data.map(commentData => new Comment(commentData.id, commentData.author, commentData.content));
}
return undefined;
});
}
postArticle(article: Article){
let data = {
id: article.id,
author: article.author,
date: article.time,
category: article.category,
important: article.important,
headline: article.title,
imageURL: article.imageURL,
content: article.content
};
return axios.post('http://localhost:4001/article', JSON.stringify(data), {headers: header}).then(res => res).catch(error => console.log(error));
}
editArticle(article: Article){
this.currentArticle.author = article.author;
this.currentArticle.time = article.time;
this.currentArticle.category = article.category;
this.currentArticle.important = article.important;
this.currentArticle.title = article.title;
this.currentArticle.imageURL = article.imageURL;
this.currentArticle.content = article.content;
let articleFromList = this.articles.find(a => this.currentArticle.id === a.id);
if (articleFromList){
Object.assign(articleFromList, {...this.currentArticle});
}
let data = {
author: article.author,
title: article.title,
imageURL: article.imageURL,
time: article.time,
content: article.content,
category: article.category,
important: article.important,
};
return axios.put('http://localhost:4001/article/edit/' + article.id,
JSON.stringify(data), {headers: header}).then(res => res).catch(error => console.log(error));
}
postComment(comment: Comment){
let data = {
id: comment.id,
author: comment.author,
content: comment.content,
articles_id: comment.articles_id
};
return axios.post('http://localhost:4001/comment', JSON.stringify(data), {headers: header}).then(res => {
return res;
}).catch(error => console.log(error));
}
deleteComment(id: number){
let data = {
id: id,
articles_id: this.currentArticle.id
};
return axios.delete('http://localhost:4001/comment', {headers: header, data: JSON.stringify(data)}).then(res => (res)).catch(error => console.log(error));
}
}
export let articleStore = sharedComponentData(new ArticleStore());
\ No newline at end of file
import * as React from 'react';
import { Component } from 'react-simplified';
import { NavLink } from 'react-router-dom';
/**
* Renders alert messages using Bootstrap classes.
*/
export class Alert extends Component {
alerts: { id: number, text: React.Node, type: string }[] = [];
static nextId = 0;
render() {
return (
<>
{this.alerts.map((alert, i) => (
<div key={alert.id} className={'alert alert-' + alert.type} role="alert">
{alert.text}
<button
type="button"
className="close"
onClick={() => {
this.alerts.splice(i, 1);
}}
>
&times;
</button>
</div>
))}
</>
);
}
static success(text: React.Node) {
// To avoid 'Cannot update during an existing state transition' errors, run after current event through setTimeout
setTimeout(() => {
for (let instance of Alert.instances()) instance.alerts.push({ id: Alert.nextId++, text: text, type: 'success' });
});
}
static info(text: React.Node) {
// To avoid 'Cannot update during an existing state transition' errors, run after current event through setTimeout
setTimeout(() => {
for (let instance of Alert.instances()) instance.alerts.push({ id: Alert.nextId++, text: text, type: 'info' });
});
}
static warning(text: React.Node) {
// To avoid 'Cannot update during an existing state transition' errors, run after current event through setTimeout
setTimeout(() => {
for (let instance of Alert.instances()) instance.alerts.push({ id: Alert.nextId++, text: text, type: 'warning' });
});
}
static danger(text: React.Node) {
// To avoid 'Cannot update during an existing state transition' errors, run after current event through setTimeout
setTimeout(() => {
for (let instance of Alert.instances()) instance.alerts.push({ id: Alert.nextId++, text: text, type: 'danger' });
});
}
}
class NavBarLink extends Component<{ exact?: boolean, to: string, children?: React.Node }> {
render() {
return (
<NavLink className="nav-link" activeClassName="active" exact={this.props.exact} to={this.props.to}>
{this.props.children}
</NavLink>
);
}
}
/**
* Renders a navigation bar using Bootstrap classes
*/
export class NavBar extends Component<{ brand?: React.Node, children?: React.Node }> {
static Link = NavBarLink;
render() {
return (
<nav className="navbar navbar-expand-sm bg-light navbar-light">
{
<NavLink className="navbar-brand" activeClassName="active" exact to="/">
{this.props.brand}
</NavLink>
}
<ul className="navbar-nav">{this.props.children}</ul>
</nav>
);
}
}
/**
* Renders an information card using Bootstrap classes
*/
export class Card extends Component<{ title: React.Node, children?: React.Node }> {
render() {
return (
<div className="card">
<div className="card-body">
<h5 className="card-title">{this.props.title}</h5>
<div className="card-text">{this.props.children}</div>
</div>
</div>
);
}
}
/**
* Renders a row using Bootstrap classes
*/
export class Row extends Component<{ children?: React.Node }> {
render() {
return <div className="row">{this.props.children}</div>;
}
}
/**
* Renders a column with specified width using Bootstrap classes
*/
export class Column extends Component<{ width?: number, right?: boolean, children?: React.Node }> {
render() {
return (
<div
className={'col' + (this.props.width ? '-' + this.props.width : '') + (this.props.right ? ' text-right' : '')}
>
{this.props.children}
</div>
);
}
}
class ButtonDanger extends Component<{
onClick: () => mixed, // Any function
children?: React.Node
}> {
render() {
return (
<button type="button" className="btn btn-danger" onClick={this.props.onClick}>
{this.props.children}
</button>
);
}
}
class ButtonInfo extends Component<{onClick: () => mixed, children?: Reack.Node}>{
render(){
return(
<button type='button' className='btn btn-info' onClick={this.props.onClick}>
{this.props.children}
</button>
);
}
}
/**
* Renders a button using Bootstrap classes
*/
export class Button {
static Danger = ButtonDanger;
static Info = ButtonInfo;
}
let mysql = require('mysql');
import {articleStore} from '../src/stores';
const Comment = require('../src/Comment');
const Article = require('../src/Article');
let privatePool = require('../../server/src/database').pool;
const runsqlfile = require('../../server/tests/runsqlfile.js');
let pool = mysql.createPool({
connectionLimit: 1,
host: "mysql",
user: "root",
password: "secret",
database: "supertestdb",
debug: false,
multipleStatements: true
});
beforeAll(done => {
console.log("Hello");
runsqlfile("../server/tests/createTables.sql", privatePool, () => {
runsqlfile("../server/tests/createTestData.sql", privatePool, done);
});
articleStore.currentArticle = null;
articleStore.articles = [];
articleStore.categories = {};
});
afterAll(pool.end());
test("Checking Comment() constructor", done => {
let comment = new Comment(0, "evengu", "Some content", 0);
expect(comment.id).toBe(0);
expect(comment.author).toBe("evengu");
expect(comment.articles_id).toBe(0);
done();
});
test("Checking Article constructor", done => {
let article = new Article(0, "evengu", "title", null, "time", "content", 1, true);
expect(article.imageURL).toBe(Article.constantURL);
done();
});
test("Checking getIds()", done => {
articleStore.getIds().then(res => {
expect(res[0]).toBe(0);
expect(res.length).toBe(6);
done();
})
});
test("Checking getCategories()", done => {
articleStore.getCategories().then(() => {
expect(articleStore.categories[0].id).toBe(0);
expect(articleStore.categories[2].text).toBe('Kultur');
done();
})
});
test("Getting article from the store", done => {
articleStore.getArticle(0).then(article => {
expect(article.id).toBe(0);
expect(article.important).toBe(true);
done();
})
});
test("Get all articles from the store", done => {
articleStore.getAllArticles().then(res => {
expect(res.length).toBe(6);
expect(res[0].id).toBe(0);
done();
})
});
test("Delete an article", done => {
articleStore.getArticle(0).then(() => {
articleStore.deleteCurrentArticle().then(() => {
articleStore.getIds().then(res => {
expect(res.length).toBe(5);
done();
})
})
})
});
test("Get all comments on article", done => {
articleStore.getComments(0).then(res => {
expect(res.length).toBe(2);
done();
})
});
test("Post article", done => {
let article = new Article(6, "evengu", "title", null, "time", "content", 1, true);
articleStore.postArticle(article).then(() => {
articleStore.getIds().then(res => {
expect(res.length).toBe(7);
done();
})
})
});
test("Edit article", done => {
let article = new Article(0, "Edited author", "Edited title", null, "Edited time", "Edited content", 1, true);
articleStore.editArticle(article).then(() => {
articleStore.getArticle(0).then(() => {
expect(articleStore.currentArticle.author).toBe("Edited author");
done();
})
})
});
test("Post comment", done => {
let comment = new Comment(1, "evengu", "Some content", 3);
articleStore.postComment(comment).then(() => {
articleStore.getComments(3).then(res => {
expect(res.length).toBe(2);
done();
})
})
});
test("Delete comment", done => {
articleStore.getArticle(0).then(() => {
articleStore.deleteComment(0).then(() => {
articleStore.getComments(0).then(res => {
expect(res.length).toBe(1);
})
})
})
});
This diff is collapsed.
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