Add API models, mock backend, more tests

This commit is contained in:
Jan Tuomi
2018-06-20 14:51:12 +03:00
parent 4ba5b368a6
commit 9e2403ba8a
15 changed files with 686 additions and 1923 deletions
+1
View File
@@ -0,0 +1 @@
API_URL=http://localhost:1234
+1
View File
@@ -6,3 +6,4 @@ tests/jest/__coverage__/
tests/jest/**/*.jsx
tests/testcafe/screenshots
.vscode/
.env
+7
View File
@@ -3,6 +3,7 @@ const {resolve} = require('path');
const {CheckerPlugin} = require('awesome-typescript-loader');
const StyleLintPlugin = require('stylelint-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const Dotenv = require('dotenv-webpack');
module.exports = {
resolve: {
@@ -46,6 +47,12 @@ module.exports = {
new CheckerPlugin(),
new StyleLintPlugin(),
new HtmlWebpackPlugin({template: 'index.html.ejs',}),
new Dotenv({
path: './.env.sample',
}),
new Dotenv({
path: './.env',
}),
],
externals: {
'react': 'React',
+19
View File
@@ -0,0 +1,19 @@
{
"posts": [
{
"id": 1,
"title": "json-server",
"author": "typicode"
}
],
"comments": [
{
"id": 1,
"body": "some comment",
"postId": 1
}
],
"profile": {
"name": "typicode"
}
}
+548 -1913
View File
File diff suppressed because it is too large Load Diff
+7 -2
View File
@@ -25,8 +25,9 @@
"start-dev": "webpack-dev-server --config=configs/webpack/dev.js",
"serve": "node express.js",
"start-prod": "npm run build && npm run serve",
"mock-backend": "json-server --watch db.json -p 1234",
"test": "npm-run-all lint test:e2e",
"test:e2e": "npm-run-all -s build -p -r serve test:e2e:run",
"test:e2e": "npm-run-all -s build -p -r mock-backend serve test:e2e:run",
"test:e2e:run": "testcafe -S -s 'tests/testcafe/screenshots' --app-init-delay 2000 chrome tests/testcafe",
"plop": "plop"
},
@@ -51,6 +52,7 @@
"html-webpack-plugin": "^3.2.0",
"husky": "^1.0.0-rc.9",
"image-webpack-loader": "^4.3.0",
"json-server": "^0.14.0",
"node-sass": "^4.9.0",
"npm-run-all": "^4.1.3",
"plop": "^2.0.0",
@@ -76,8 +78,11 @@
"webpack-merge": "^4.1.2"
},
"dependencies": {
"axios": "^0.18.0",
"dotenv-webpack": "^1.5.7",
"mobx": "^5.0.3",
"mobx-react": "^5.2.3"
"mobx-react": "^5.2.3",
"normalize.css": "^8.0.0"
},
"postcss": {}
}
+1 -1
View File
@@ -20,5 +20,5 @@ export interface {{ properCase name }}Props {}
{{#if observer}}
export default (props) => <{{ properCase name }} {{ camelCase store_name }}={ {{ camelCase store_name }} } { ...props } />;
{{else}}
export default <{{ properCase name }} />;
export default {{ properCase name }};
{{/if}}
+41 -5
View File
@@ -1,28 +1,64 @@
import * as React from "react";
import { observer } from "mobx-react";
import "./App.scss";
import appState from "../../stores/AppStore";
import appStore from "../../stores/AppStore";
import Button from "../Button";
import { getPosts, Post as PostInterface } from "../../models/post";
import Post from "../Post";
export interface AppProps {
appState: {
appStore: {
increment: () => string,
counter: number
};
}
@observer class App extends React.Component<AppProps, undefined> {
export interface AppState {
posts: PostInterface[];
error: string | Error;
}
@observer class App extends React.Component<AppProps, AppState> {
constructor(props) {
super(props);
this.fetchPosts = this.fetchPosts.bind(this);
this.state = {
error: "",
posts: [],
};
this.fetchPosts();
}
async fetchPosts() {
try {
const posts = await getPosts();
this.setState({
posts,
});
} catch (err) {
this.setState({
error: "Failed to get posts! Is the mock backend on?",
});
}
}
render() {
return <div className="app__landing">
<h1>Aalto-yliopiston sähköinsinöörikilta!</h1>
<p>Sähköä, viinaa, naisia.</p>
<Button onClick={this.props.appState.increment}>{this.props.appState.counter}</Button>
<div className="app__landing__container--button">
<h2>Increment button (MobX)</h2>
<Button onClick={this.props.appStore.increment}>{this.props.appStore.counter}</Button>
</div>
<div className="app__landing__container--posts">
<h2>Posts from mock server</h2>
{ this.state.posts.map((post, index) => <Post key={index} post={post} />)}
</div>
<div className="app__landing__container--error">
{ this.state.error }
</div>
</div>;
}
}
export default props => <App appState={appState} {...props} />;
export default props => <App appStore={appStore} {...props} />;
+1
View File
@@ -11,6 +11,7 @@
&:hover {
border: 2px solid $dark-blue;
cursor: pointer;
}
&:active,
+6
View File
@@ -0,0 +1,6 @@
.post {
&:hover {
text-decoration: underline;
cursor: pointer;
}
}
+20
View File
@@ -0,0 +1,20 @@
import * as React from "react";
import "./Post.scss";
import { Post as PostInterface } from "../../models/posts";
export interface PostProps {
post: PostInterface;
}
class Post extends React.Component<PostProps, undefined> {
render() {
const { title, author, id } = this.props.post;
return (
<div className="post">
#{ id }: <strong>{ title }</strong> from <i>{ author }</i>
</div>
);
}
}
export default Post;
+2
View File
@@ -0,0 +1,2 @@
import Post from "./Post";
export default Post;
+1
View File
@@ -2,6 +2,7 @@ import * as React from "react";
import {render} from "react-dom";
import {AppContainer} from "react-hot-loader";
import App from "./components/App";
import "normalize.css";
import "./index.scss";
const rootEl = document.getElementById("root");
+19
View File
@@ -0,0 +1,19 @@
import axios from "axios";
const url = `${process.env.API_URL}/posts`;
export interface Post {
id: number;
title: string;
author: string;
}
export async function getPosts(): Promise<Post[]> {
try {
const resp = await axios.get(url);
return resp.data;
} catch (err) {
console.error(err);
throw err;
}
}
+10
View File
@@ -35,3 +35,13 @@ test("Increment button functions correctly", async t => {
await t.click(button);
await t.expect(button.textContent).contains("1");
});
fixture`Posts from API`.page("http://localhost:3000");
test("At least one post exists", async t => {
/**
* Test if the API responds with at least one post and it is rendered to the page
*/
const post = Selector(".post");
await t.expect(post.exists).ok();
});