Skip to content
Snippets Groups Projects
Commit e31fae00 authored by Mathias Picker's avatar Mathias Picker
Browse files

Solved tasks

parent 020264ff
No related branches found
No related tags found
No related merge requests found
...@@ -56,6 +56,7 @@ export class TaskDetails extends Component<{ match: { params: { id: number } } } ...@@ -56,6 +56,7 @@ export class TaskDetails extends Component<{ match: { params: { id: number } } }
</Row> </Row>
<Row> <Row>
<Column width={2}>Description:</Column> <Column width={2}>Description:</Column>
<Column width={2}>{this.task.description}</Column>
</Row> </Row>
<Row> <Row>
<Column width={2}>Done:</Column> <Column width={2}>Done:</Column>
...@@ -85,7 +86,25 @@ export class TaskDetails extends Component<{ match: { params: { id: number } } } ...@@ -85,7 +86,25 @@ export class TaskDetails extends Component<{ match: { params: { id: number } } }
* Renders form to edit a specific task. * Renders form to edit a specific task.
*/ */
export class TaskEdit extends Component<{ match: { params: { id: number } } }> { export class TaskEdit extends Component<{ match: { params: { id: number } } }> {
task: Task = { id: 0, title: '', done: false }; task: Task = { id: 0, title: '', description: '', done: false };
editTask(task: Task) {
taskService
.update(task)
.then((res) => {
if (res.affectedRows) {
Alert.success('Task succesfully updated')
}
})
.catch((error: Error) => Alert.danger('Error updating task: ' + error.message))
}
deleteTask(id: number) {
taskService
.delete(id)
.then((res) => history.push('/tasks/'))
.catch((error: Error) => Alert.danger('Error deleting task: ' + error.message))
}
render() { render() {
return ( return (
...@@ -108,7 +127,7 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> { ...@@ -108,7 +127,7 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> {
<Form.Label>Description:</Form.Label> <Form.Label>Description:</Form.Label>
</Column> </Column>
<Column> <Column>
<Form.Textarea value="" onChange={() => {}} rows={10} disabled /> <Form.Textarea value={this.task.description} onChange={(event) => (this.task.description = event.currentTarget.value)} rows={10} />
</Column> </Column>
</Row> </Row>
<Row> <Row>
...@@ -123,10 +142,10 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> { ...@@ -123,10 +142,10 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> {
</Card> </Card>
<Row> <Row>
<Column> <Column>
<Button.Success onClick={() => Alert.info('Not yet implemented')}>Save</Button.Success> <Button.Success onClick={() => this.editTask(this.task)}>Save</Button.Success>
</Column> </Column>
<Column right> <Column right>
<Button.Danger onClick={() => Alert.info('Not yet implemented')}>Delete</Button.Danger> <Button.Danger onClick={() => this.deleteTask(this.task.id)}>Delete</Button.Danger>
</Column> </Column>
</Row> </Row>
</> </>
...@@ -145,7 +164,10 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> { ...@@ -145,7 +164,10 @@ export class TaskEdit extends Component<{ match: { params: { id: number } } }> {
* Renders form to create new task. * Renders form to create new task.
*/ */
export class TaskNew extends Component { export class TaskNew extends Component {
title = ''; task = {
title: '',
description: ''
}
render() { render() {
return ( return (
...@@ -158,8 +180,8 @@ export class TaskNew extends Component { ...@@ -158,8 +180,8 @@ export class TaskNew extends Component {
<Column> <Column>
<Form.Input <Form.Input
type="text" type="text"
value={this.title} value={this.task.title}
onChange={(event) => (this.title = event.currentTarget.value)} onChange={(event) => (this.task.title = event.currentTarget.value)}
/> />
</Column> </Column>
</Row> </Row>
...@@ -168,14 +190,14 @@ export class TaskNew extends Component { ...@@ -168,14 +190,14 @@ export class TaskNew extends Component {
<Form.Label>Description:</Form.Label> <Form.Label>Description:</Form.Label>
</Column> </Column>
<Column> <Column>
<Form.Textarea value="" onChange={() => {}} rows={10} disabled /> <Form.Textarea value={this.task.description} onChange={(event) => { this.task.description = event.currentTarget.value }} rows={10} />
</Column> </Column>
</Row> </Row>
</Card> </Card>
<Button.Success <Button.Success
onClick={() => { onClick={() => {
taskService taskService
.create(this.title) .create(this.task)
.then((id) => history.push('/tasks/' + id)) .then((id) => history.push('/tasks/' + id))
.catch((error: Error) => Alert.danger('Error creating task: ' + error.message)); .catch((error: Error) => Alert.danger('Error creating task: ' + error.message));
}} }}
......
...@@ -24,14 +24,26 @@ class TaskService { ...@@ -24,14 +24,26 @@ class TaskService {
return axios.get<Task[]>('/tasks').then((response) => response.data); return axios.get<Task[]>('/tasks').then((response) => response.data);
} }
update(task: object) {
return axios
.post<{}>('/tasks/update/' + task.id, { title: task.title, description: task.description, done: task.done })
.then((response) => response.data)
}
delete(id: number) {
return axios
.delete<{}>('tasks/' + id)
.then(response => response.data)
}
/** /**
* Create new task having the given title. * Create new task having the given title.
* *
* Resolves the newly created task id. * Resolves the newly created task id.
*/ */
create(title: string) { create(task: object) {
return axios return axios
.post<{}, { id: number }>('/tasks', { title: title }) .post<{}, { id: number }>('/tasks', { title: task.title, description: task.description })
.then((response) => response.data.id); .then((response) => response.data.id);
} }
} }
......
// Jest Snapshot v1, https://goo.gl/fbAQLP
exports[`Task component tests TaskDetails draws task correctly - snapshot 1`] = `
<Fragment>
<Card
title="Task"
>
<Row>
<Column
width={2}
>
Title:
</Column>
<Column>
Les leksjon
</Column>
</Row>
<Row>
<Column
width={2}
>
Description:
</Column>
<Column
width={2}
>
Må huske å lese leksjonen!
</Column>
</Row>
<Row>
<Column
width={2}
>
Done:
</Column>
<Column>
<FormCheckbox
checked={false}
disabled={true}
onChange={[Function]}
/>
</Column>
</Row>
</Card>
<ButtonSuccess
onClick={[Function]}
>
Edit
</ButtonSuccess>
</Fragment>
`;
exports[`Task component tests TaskEdit draws task correctly - snapshot 1`] = `
<Fragment>
<Card
title="Edit task"
>
<Row>
<Column
width={2}
>
<FormLabel>
Title:
</FormLabel>
</Column>
<Column>
<FormInput
onChange={[Function]}
type="text"
value="Les leksjon"
/>
</Column>
</Row>
<Row>
<Column
width={2}
>
<FormLabel>
Description:
</FormLabel>
</Column>
<Column>
<FormTextarea
onChange={[Function]}
rows={10}
value="Må huske å lese leksjonen!"
/>
</Column>
</Row>
<Row>
<Column
width={2}
>
Done:
</Column>
<Column>
<FormCheckbox
checked={false}
onChange={[Function]}
/>
</Column>
</Row>
</Card>
<Row>
<Column>
<ButtonSuccess
onClick={[Function]}
>
Save
</ButtonSuccess>
</Column>
<Column
right={true}
>
<ButtonDanger
onClick={[Function]}
>
Delete
</ButtonDanger>
</Column>
</Row>
</Fragment>
`;
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { Component } from 'react-simplified';
import { Alert } from '../src/widgets.js'; import { Alert } from '../src/widgets.js';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
class ThreeAlerts extends Component {
render() {
return (
<div>
<Alert.danger text="Error" />
<Alert.success text="Success" />
<Alert.info text="Info" />
</div>
);
}
}
describe('Alert tests', () => { describe('Alert tests', () => {
test('No alerts initially', () => { test('No alerts initially', () => {
const wrapper = shallow(<Alert />); const wrapper = shallow(<Alert />);
...@@ -56,4 +69,51 @@ describe('Alert tests', () => { ...@@ -56,4 +69,51 @@ describe('Alert tests', () => {
done(); done();
}); });
}); });
test('Open 3 alerts and close the second', (done) => {
const wrapper = shallow(<Alert />);
Alert.danger('error-test');
Alert.success('success-test');
Alert.info('info-test');
// Wait for events to complete
setTimeout(() => {
expect(
wrapper.containsMatchingElement(
<>
<div>
error-test<button>&times;</button>
</div>
<div>
success-test<button>&times;</button>
</div>
<div>
info-test<button>&times;</button>
</div>
</>
)
).toEqual(true);
wrapper.find('.alert-success').find('button.close').simulate('click');
expect(
wrapper.containsMatchingElement(
<>
<div>
error-test<button>&times;</button>
</div>
<div>
info-test<button>&times;</button>
</div>
</>
)
).toEqual(true);
done();
});
});
}); });
// @flow
import * as React from 'react';
import { Component } from 'react-simplified';
import { shallow } from 'enzyme';
class Hello extends Component {
render() {
return (
<div>
<b>Hello</b>
</div>
);
}
}
describe('Hello tests', () => {
test('Draws correctly', () => {
const wrapper = shallow(<Hello />);
expect(
wrapper.matchesElement(
<div>
<b>Hello</b>
</div>
)
).toEqual(true);
});
});
\ No newline at end of file
// @flow // @flow
import * as React from 'react'; import * as React from 'react';
import { TaskList, TaskNew } from '../src/task-components'; import { TaskList, TaskNew, TaskDetails, TaskEdit } from '../src/task-components';
import { type Task } from '../src/task-service'; import { type Task } from '../src/task-service';
import { shallow } from 'enzyme'; import { shallow } from 'enzyme';
import { Form, Button, Column } from '../src/widgets'; import { Form, Button, Column, Alert, Card, Row, NavBar } from '../src/widgets';
import { NavLink } from 'react-router-dom'; import { NavLink } from 'react-router-dom';
jest.mock('../src/task-service', () => { jest.mock('../src/task-service', () => {
...@@ -17,9 +17,23 @@ jest.mock('../src/task-service', () => { ...@@ -17,9 +17,23 @@ jest.mock('../src/task-service', () => {
]); ]);
} }
get(id: number) {
return Promise.resolve(
{ id: id, title: "Les leksjon", description: "Må huske å lese leksjonen!", done: false }
)
}
update(task: object) {
return Promise.resolve({ affectedRows: 1 })
}
create(title: string) { create(title: string) {
return Promise.resolve(4); // Same as: return new Promise((resolve) => resolve(4)); return Promise.resolve(4); // Same as: return new Promise((resolve) => resolve(4));
} }
delete(id: number) {
return Promise.resolve();
}
} }
return new TaskService(); return new TaskService();
}); });
...@@ -55,4 +69,131 @@ describe('Task component tests', () => { ...@@ -55,4 +69,131 @@ describe('Task component tests', () => {
done(); done();
}); });
}); });
test("TaskDetails draws task correctly", (done) => {
const wrapper = shallow(<TaskDetails match={{ params: { id: 1 } }} />);
setTimeout(() => {
expect(wrapper.containsMatchingElement(
<>
<Card title="Task">
<Row>
<Column>Title:</Column>
<Column>Les leksjon</Column>
</Row>
<Row>
<Column>Description:</Column>
<Column> huske å lese leksjonen!</Column>
</Row>
<Row>
<Column>Done:</Column>
<Column>
<Form.Checkbox checked={false} />
</Column>
</Row>
</Card>
<Button.Success>
Edit
</Button.Success>
</>
))
.toEqual(true);
wrapper.find(Button.Success).simulate('click');
expect(location.hash).toEqual('#/tasks/1/edit');
done();
});
})
test("TaskDetails draws task correctly - snapshot", (done) => {
const wrapper = shallow(<TaskDetails match={{ params: { id: 1 } }} />);
setTimeout(() => {
expect(wrapper).toMatchSnapshot();
wrapper.find(Button.Success).simulate('click');
expect(location.hash).toEqual('#/tasks/1/edit');
done();
});
})
test("TaskEdit draws task correctly - snapshot", (done) => {
const wrapper = shallow(<TaskEdit match={{ params: { id: 1 } }} />);
setTimeout(() => {
expect(wrapper).toMatchSnapshot();
done();
});
})
test("TaskEdit draws task correctly and has functionality", (done) => {
const wrapper = shallow(<TaskEdit match={{ params: { id: 1 } }} />);
setTimeout(() => {
expect(wrapper.containsMatchingElement(
<>
<Card title="Edit task">
<Row>
<Column>
<Form.Label>Title:</Form.Label>
</Column>
<Column>
<Form.Input
value="Les leksjon"
/>
</Column>
</Row>
<Row>
<Column>
<Form.Label>Description:</Form.Label>
</Column>
<Column>
<Form.Textarea value="Må huske å lese leksjonen!" />
</Column>
</Row>
<Row>
<Column>Done:</Column>
<Column>
<Form.Checkbox checked={false} />
</Column>
</Row>
</Card>
<Row>
<Column>
<Button.Success>Save</Button.Success>
</Column>
<Column>
<Button.Danger>Delete</Button.Danger>
</Column>
</Row>
</>
))
.toEqual(true);
wrapper.find(Form.Input).simulate('change', { currentTarget: { value: 'Joohoo' } });
wrapper.find(Form.Textarea).simulate('change', { currentTarget: { value: 'Har lest leksjon nå :)' } });
wrapper.find(Form.Checkbox).simulate('click');
expect(wrapper.containsMatchingElement(<Form.Input value="Joohoo" />));
expect(wrapper.containsMatchingElement(<Form.Textarea value="Har lest leksjon nå :)" />));
expect(wrapper.containsMatchingElement(<Form.Checkbox checked={true} />));
wrapper.find(Button.Success).simulate('click');
expect(wrapper.containsMatchingElement(<Form.Input value="Joohoo" />));
expect(wrapper.containsMatchingElement(<Form.Textarea value="Har lest leksjon nå :)" />));
expect(wrapper.containsMatchingElement(<Alert />));
wrapper.find(Button.Danger).simulate('click');
setTimeout(() => {
expect(location.hash).toEqual('#/tasks/');
done();
});
});
})
}); });
...@@ -28,12 +28,32 @@ router.post('/tasks', (request, response) => { ...@@ -28,12 +28,32 @@ router.post('/tasks', (request, response) => {
const data = request.body; const data = request.body;
if (data && typeof data.title == 'string' && data.title.length != 0) if (data && typeof data.title == 'string' && data.title.length != 0)
taskService taskService
.create(data.title) .create(data)
.then((id) => response.send({ id: id })) .then((id) => response.send({ id: id }))
.catch((error: Error) => response.status(500).send(error)); .catch((error: Error) => response.status(500).send(error));
else response.status(400).send('Missing task title'); else response.status(400).send('Missing task title');
}); });
router.post('/tasks/update/:id', (request, response) => {
const data = request.body;
const dataIsValid = data && typeof data.title === "string" && data.title.length !== 0;
const id = Number(request.params.id)
const updateTaskObject = {
id: id,
title: data.title,
description: data.description || '', // We allow no description
done: data.done
};
if (dataIsValid) {
taskService.update(updateTaskObject)
.then((task) => (task ? response.send(task) : response.status(404).send('Task not found')))
.catch((error: Error) => response.status(500).send(error));
} else {
response.status(400).send('Missing task title')
}
})
router.delete('/tasks/:id', (request, response) => { router.delete('/tasks/:id', (request, response) => {
taskService taskService
.delete(Number(request.params.id)) .delete(Number(request.params.id))
......
...@@ -40,9 +40,9 @@ class TaskService { ...@@ -40,9 +40,9 @@ class TaskService {
* *
* Resolves the newly created task id. * Resolves the newly created task id.
*/ */
create(title: string) { create(data: object) {
return new Promise<number>((resolve, reject) => { return new Promise<number>((resolve, reject) => {
pool.query('INSERT INTO Tasks SET title=?', [title], (error, results) => { pool.query('INSERT INTO Tasks SET title=?, description=?', [data.title, data.description], (error, results) => {
if (error) return reject(error); if (error) return reject(error);
if (!results.insertId) return reject(new Error('No row inserted')); if (!results.insertId) return reject(new Error('No row inserted'));
...@@ -51,6 +51,15 @@ class TaskService { ...@@ -51,6 +51,15 @@ class TaskService {
}); });
} }
update(data: object) {
return new Promise<object>((resolve, reject) => {
pool.query('UPDATE Tasks SET title=?, description=?, done=? WHERE id=?', [data.title, data.description, data.done, data.id], (error, results) => {
if (error) return reject(error);
resolve(results)
})
})
}
/** /**
* Delete task with given id. * Delete task with given id.
*/ */
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment