Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rr4 demo #2

Open
wants to merge 6 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
15 changes: 4 additions & 11 deletions client/main.js
Original file line number Diff line number Diff line change
@@ -1,25 +1,18 @@
import { Meteor } from 'meteor/meteor';
import ReactDOM from 'react-dom';
import React from 'react';
import { Tracker } from 'meteor/tracker';
import { Session } from 'meteor/session';
import { browserHistory } from 'react-router';

import { routes, onAuthChange } from '../imports/routes/routes';
import { AppRouter, history, onAuthChange } from '../imports/routes/AppRouter';
import '../imports/startup/simple-schema-configuration.js';

Tracker.autorun(() => {
const isAuthenticated = !!Meteor.userId();
const currentPagePrivacy = Session.get('currentPagePrivacy');

onAuthChange(isAuthenticated, currentPagePrivacy);
});

Tracker.autorun(() => {
const selectedNoteId = Session.get('selectedNoteId');
Session.set('isNavOpen', false);

if (selectedNoteId) {
browserHistory.replace(`/dashboard/${selectedNoteId}`);
history.replace(`/dashboard/${selectedNoteId}`);
}
});

Expand All @@ -32,5 +25,5 @@ Tracker.autorun(() => {
Meteor.startup(() => {
Session.set('selectedNoteId', undefined);
Session.set('isNavOpen', false);
ReactDOM.render(routes, document.getElementById('app'));
ReactDOM.render(<AppRouter />, document.getElementById('app'));
});
26 changes: 26 additions & 0 deletions imports/routes/AppRouter.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { Meteor } from 'meteor/meteor';
import React from 'react';
import { Router, Route, Switch } from 'react-router-dom';
import createHistory from 'history/createBrowserHistory';
import { Session } from 'meteor/session';

import PrivateRoute from './PrivateRoute';
import PublicRoute from './PublicRoute';
import Signup from '../ui/Signup';
import Dashboard from '../ui/Dashboard';
import NotFound from '../ui/NotFound';
import Login from '../ui/Login';

export const history = createHistory()

export const AppRouter = () => (
<Router history={history}>
<Switch>
<PublicRoute path="/" component={Login} exact={true} />
<PublicRoute path="/signup" component={Signup} />
<PrivateRoute path="/dashboard" component={Dashboard} exact={true} />
<PrivateRoute path="/dashboard/:id" component={Dashboard} />
<Route path="*" component={NotFound} />
</Switch>
</Router>
);
21 changes: 21 additions & 0 deletions imports/routes/PrivateRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Route, Redirect } from 'react-router-dom';

export const PrivateRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route {...rest} render={(props) => (
isAuthenticated ? (
<Component {...props} />
) : (
<Redirect to="/" />
)
)} />
);

export default createContainer(() => ({
isAuthenticated: !!Meteor.userId()
}), PrivateRoute);
21 changes: 21 additions & 0 deletions imports/routes/PublicRoute.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import React from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Route, Redirect } from 'react-router-dom';

export const PublicRoute = ({
isAuthenticated,
component: Component,
...rest
}) => (
<Route {...rest} render={(props) => (
isAuthenticated ? (
<Redirect to="/dashboard" />
) : (
<Component {...props} />
)
)} />
);

export default createContainer(() => ({
isAuthenticated: !!Meteor.userId()
}), PublicRoute);
44 changes: 0 additions & 44 deletions imports/routes/routes.js

This file was deleted.

24 changes: 15 additions & 9 deletions imports/ui/Editor.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import React from 'react';
import { createContainer } from 'meteor/react-meteor-data';
import { Session } from 'meteor/session';
import { Meteor } from 'meteor/meteor';
import { browserHistory } from 'react-router';
import { withRouter } from 'react-router-dom';

import { Notes } from '../api/notes';

Expand All @@ -24,9 +24,9 @@ export class Editor extends React.Component {
this.setState({ title });
this.props.call('notes.update', this.props.note._id, { title });
}
handleRemoval(){
handleRemoval() {
this.props.call('notes.remove', this.props.note._id);
this.props.browserHistory.push('/dashboard');
this.props.history.push('/dashboard');
}
componentDidUpdate(prevProps, prevState) {
const currentNoteId = this.props.note ? this.props.note._id : undefined;
Expand All @@ -39,11 +39,16 @@ export class Editor extends React.Component {
});
}
}
componentDidMount() {
if (this.props.match) {
this.props.Session.set('selectedNoteId', this.props.match.params.id)
}
}
render() {
if (this.props.note) {
return (
<div className="editor">
<input className="editor__title" value={this.state.title} placeholder="Untitled Note" onChange={this.handleTitleChange.bind(this)}/>
<input className="editor__title" value={this.state.title} placeholder="Untitled Note" onChange={this.handleTitleChange.bind(this)} />
<textarea className="editor__body" value={this.state.body} placeholder="Your note here" onChange={this.handleBodyChange.bind(this)}></textarea>
<div>
<button className="button button--secondary" onClick={this.handleRemoval.bind(this)}>Delete Note</button>
Expand All @@ -54,7 +59,7 @@ export class Editor extends React.Component {
return (
<div className="editor">
<p className="editor__message">
{ this.props.selectedNoteId ? 'Note not found.' : 'Pick or create a note to get started.'}
{this.props.selectedNoteId ? 'Note not found.' : 'Pick or create a note to get started.'}
</p>
</div>
);
Expand All @@ -66,16 +71,17 @@ Editor.propTypes = {
note: React.PropTypes.object,
selectedNoteId: React.PropTypes.string,
call: React.PropTypes.func.isRequired,
browserHistory: React.PropTypes.object.isRequired
history: React.PropTypes.object.isRequired,
Session: React.PropTypes.object.isRequired
};

export default createContainer(() => {
export default withRouter(createContainer(() => {
const selectedNoteId = Session.get('selectedNoteId');

return {
selectedNoteId,
note: Notes.findOne(selectedNoteId),
call: Meteor.call,
browserHistory
Session
};
}, Editor);
}, Editor));
24 changes: 14 additions & 10 deletions imports/ui/Editor.test.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,38 +8,42 @@ import { notes } from '../fixtures/fixtures';

if (Meteor.isClient) {
describe('Editor', function () {
let browserHistory;
let history;
let call;
let Session;

beforeEach(function () {
call = expect.createSpy();
browserHistory = {
history = {
push: expect.createSpy()
};
Session = {
set: expect.createSpy()
};
});

it('should render pick note mesesage', function () {
const wrapper = mount(<Editor browserHistory={browserHistory} call={call}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} />);
expect(wrapper.find('p').text()).toBe('Pick or create a note to get started.');
});

it('should render not found message', function () {
const wrapper = mount(<Editor browserHistory={browserHistory} call={call} selectedNoteId={notes[0]._id}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} selectedNoteId={notes[0]._id} />);
expect(wrapper.find('p').text()).toBe('Note not found.');
});

it('should remove note', function () {
const wrapper = mount(<Editor browserHistory={browserHistory} call={call} selectedNoteId={notes[0]._id} note={notes[0]}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} selectedNoteId={notes[0]._id} note={notes[0]} />);

wrapper.find('button').simulate('click');

expect(browserHistory.push).toHaveBeenCalledWith('/dashboard');
expect(history.push).toHaveBeenCalledWith('/dashboard');
expect(call).toHaveBeenCalledWith('notes.remove', notes[0]._id);
});

it('should update the note body on textarea change', function () {
const newBody = 'This is my new body text';
const wrapper = mount(<Editor browserHistory={browserHistory} call={call} selectedNoteId={notes[0]._id} note={notes[0]}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} selectedNoteId={notes[0]._id} note={notes[0]} />);

wrapper.find('textarea').simulate('change', {
target: {
Expand All @@ -53,7 +57,7 @@ if (Meteor.isClient) {

it('should update the note title on input change', function () {
const newTitle = 'This is my new title text';
const wrapper = mount(<Editor browserHistory={browserHistory} call={call} selectedNoteId={notes[0]._id} note={notes[0]}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} selectedNoteId={notes[0]._id} note={notes[0]} />);

wrapper.find('input').simulate('change', {
target: {
Expand All @@ -66,7 +70,7 @@ if (Meteor.isClient) {
});

it('should set state for new note', function () {
const wrapper = mount(<Editor browserHistory={browserHistory} call={call}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} />);

wrapper.setProps({
selectedNoteId: notes[0]._id,
Expand All @@ -78,7 +82,7 @@ if (Meteor.isClient) {
});

it('should not set state if note prop not provided', function () {
const wrapper = mount(<Editor browserHistory={browserHistory} call={call}/>);
const wrapper = mount(<Editor history={history} call={call} Session={Session} />);

wrapper.setProps({
selectedNoteId: notes[0]._id
Expand Down
27 changes: 17 additions & 10 deletions imports/ui/Login.js
Original file line number Diff line number Diff line change
@@ -1,29 +1,36 @@
import React from 'react';
import { Link } from 'react-router';
import { Link } from 'react-router-dom';
import { Meteor } from 'meteor/meteor';
import { createContainer } from 'meteor/react-meteor-data';

export class Login extends React.Component {
constructor(props) {
super(props);
this.state = {
error: ''
error: '',
email: '',
password: ''
};
}
onSubmit(e) {
e.preventDefault();
let { email, password } = this.state;

let email = this.refs.email.value.trim();
let password = this.refs.password.value.trim();
e.preventDefault();

this.props.loginWithPassword({email}, password, (err) => {
this.props.loginWithPassword({ email }, password, (err) => {
if (err) {
this.setState({error: 'Unable to login. Check email and password.'});
this.setState({ error: 'Unable to login. Check email and password.' });
} else {
this.setState({error: ''});
this.setState({ error: '' });
}
});
}
onEmailChange(e) {
this.setState({ email: e.target.value.trim() })
}
onPasswordChange(e) {
this.setState({ password: e.target.value.trim() })
}
render() {
return (
<div className="boxed-view">
Expand All @@ -33,8 +40,8 @@ export class Login extends React.Component {
{this.state.error ? <p>{this.state.error}</p> : undefined}

<form onSubmit={this.onSubmit.bind(this)} noValidate className="boxed-view__form">
<input type="email" ref="email" name="email" placeholder="Email"/>
<input type="password" ref="password" name="password" placeholder="Password"/>
<input type="email" name="email" placeholder="Email" onChange={this.onEmailChange.bind(this)} value={this.state.email} />
<input type="password" name="password" placeholder="Password" onChange={this.onPasswordChange.bind(this)} value={this.state.password} />
<button className="button">Login</button>
</form>

Expand Down
15 changes: 7 additions & 8 deletions imports/ui/Login.test.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Meteor } from 'meteor/meteor';
import React from 'react';
import expect from 'expect';
import { mount } from 'enzyme';
import { shallow } from 'enzyme';

import { Login } from './Login';

Expand All @@ -10,7 +10,7 @@ if (Meteor.isClient) {

it('should show error messages', function () {
const error = 'This is not working';
const wrapper = mount(<Login loginWithPassword={() => {}}/>);
const wrapper = shallow(<Login loginWithPassword={() => { }} />);

wrapper.setState({ error });
expect(wrapper.find('p').text()).toBe(error);
Expand All @@ -23,21 +23,20 @@ if (Meteor.isClient) {
const email = '[email protected]';
const password = 'password123';
const spy = expect.createSpy();
const wrapper = mount(<Login loginWithPassword={spy}/>);
const wrapper = shallow(<Login loginWithPassword={spy} />);

wrapper.ref('email').node.value = email;
wrapper.ref('password').node.value = password;
wrapper.find('form').simulate('submit');
wrapper.setState({ email, password })
wrapper.find('form').simulate('submit', { preventDefault: () => { } });

expect(spy.calls[0].arguments[0]).toEqual({ email });
expect(spy.calls[0].arguments[1]).toBe(password);
});

it('should set loginWithPassword callback errors', function () {
const spy = expect.createSpy();
const wrapper = mount(<Login loginWithPassword={spy}/>);
const wrapper = shallow(<Login loginWithPassword={spy} />);

wrapper.find('form').simulate('submit');
wrapper.find('form').simulate('submit', { preventDefault: () => { } });

spy.calls[0].arguments[2]({});
expect(wrapper.state('error').length).toNotBe(0);
Expand Down
Loading