Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
23 commits
Select commit Hold shift + click to select a range
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
5 changes: 5 additions & 0 deletions main.js
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,11 @@ app.on('ready', function(){
label: 'Paste',
accelerator: 'Command+V',
selector: 'paste:'
},
{
label: 'Select All',
accelerator: 'Command+A',
selector: 'selectAll:'
}
]
}];
Expand Down
2 changes: 2 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -71,8 +71,10 @@
"src/js/components/repository.js": true,
"src/js/components/settings.js": true,
"src/js/components/footer.js": true,
"src/js/components/search-input.js": true,
"src/js/stores/auth.js": true,
"src/js/stores/notifications.js": true,
"src/js/stores/search.js": true,
"src/js/stores/settings.js": true
},
"unmockedModulePathPatterns": [
Expand Down
13 changes: 13 additions & 0 deletions src/js/__tests__/components/footer.js
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,19 @@ describe('Test for Footer', function () {

var Actions, Footer;

window.localStorage = {
item: false,
setItem: function (item) {
this.item = item;
},
getItem: function () {
return this.item;
},
clear: function () {
this.item = false;
}
};

beforeEach(function () {
// Mock Electron's window.require
// and remote.require('shell')
Expand Down
41 changes: 41 additions & 0 deletions src/js/__tests__/components/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,47 @@ describe('Test for Notifications Component', function () {
errors = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'errored');
expect(errors.length).toBe(1);

expect(instance.areIn('ekonstantinidis/gitify', 'gitify')).toBeTruthy();
expect(instance.areIn('ekonstantinidis/gitify', 'hello')).toBeFalsy();

instance.state.searchTerm = 'hello';
var matches = instance.matchesSearchTerm(response[0]);
expect(matches).toBeFalsy();

instance.state.searchTerm = 'gitify';
matches = instance.matchesSearchTerm(response[0]);
expect(matches).toBeTruthy();
});

it('Should only render repos that match the search term', function () {
AuthStore.authStatus = function () {
return true;
};

var instance = TestUtils.renderIntoDocument(<Notifications />);

var response = [[{
'repository': {
'full_name': 'ekonstantinidis/gitify',
'owner': {
'avatar_url': 'http://avatar.url'
}
},
'subject': {
'type': 'Issue'
}
}]];

NotificationsStore.trigger(response);

var notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'repository');
expect(notifications.length).toBe(1);

instance.state.searchTerm = 'hello';
instance.forceUpdate();

notifications = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'repository');
expect(notifications.length).toBe(0);
});

});
87 changes: 87 additions & 0 deletions src/js/__tests__/components/search-input.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/* global jest, describe, beforeEach, it, expect, spyOn */

jest.dontMock('reflux');
jest.dontMock('../../actions/actions.js');
jest.dontMock('../../components/search-input.js');
jest.dontMock('../../stores/auth.js');

var React = require('react/addons');
var TestUtils = React.addons.TestUtils;

describe('Test for Search Input Component', function () {

var Actions, AuthStore, SearchInput;

beforeEach(function () {
// Mock Electron's window.require
// and remote.require('shell')
window.require = function () {
return {
require: function () {
return {
openExternal: function () {
return {};
}
};
}
};
};

// Mock localStorage
window.localStorage = {
item: false,
getItem: function () {
return this.item;
}
};

Actions = require('../../actions/actions.js');
AuthStore = require('../../stores/auth.js');
SearchInput = require('../../components/search-input.js');
});

it('Should make a search', function () {

spyOn(Actions, 'updateSearchTerm');
spyOn(Actions, 'clearSearchTerm');

var instance = TestUtils.renderIntoDocument(<SearchInput />);

var wrapper = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'search-wrapper');
expect(wrapper.length).toBe(1);

instance.clearSearch();

instance.onChange({
target: {
value: 'hello'
}
});

expect(Actions.updateSearchTerm).toHaveBeenCalledWith('hello');
});

it('Should clear the search', function () {
spyOn(Actions, 'clearSearchTerm');

var instance = TestUtils.renderIntoDocument(<SearchInput />);
expect(Actions.clearSearchTerm).not.toHaveBeenCalled();

instance.clearSearch();
expect(Actions.clearSearchTerm).toHaveBeenCalled();
});

it('Should only render clear button if search term is not empty', function () {
var instance = TestUtils.renderIntoDocument(<SearchInput />);

var clearButton = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'octicon-x');
expect(clearButton.length).toBe(0);

instance.state.searchTerm = 'hello';
instance.forceUpdate();

clearButton = TestUtils.scryRenderedDOMComponentsWithClass(instance, 'octicon-x');
expect(clearButton.length).toBe(1);
});

});
31 changes: 31 additions & 0 deletions src/js/__tests__/stores/search.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/*global jest, describe, it, expect, beforeEach */

'use strict';

jest.dontMock('reflux');
jest.dontMock('../../stores/search');
jest.dontMock('../../actions/actions');

describe('Tests for AuthStore', function () {

var SearchStore, Actions;

beforeEach(function () {
Actions = require('../../actions/actions');
SearchStore = require('../../stores/search');
});

it('should login - store the token', function () {
var searchTerm = 'test';
SearchStore.onUpdateSearchTerm(searchTerm);
expect(SearchStore._searchTerm).toEqual(searchTerm);
expect(SearchStore.searchTerm()).toEqual(searchTerm);
});

it('should logout - remove the token', function () {
SearchStore.onClearSearchTerm();
expect(SearchStore._searchTerm).toEqual(undefined);
expect(SearchStore.searchTerm()).toEqual(undefined);
});

});
2 changes: 2 additions & 0 deletions src/js/actions/actions.js
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,8 @@ var Actions = Reflux.createActions({
'login': {},
'logout': {},
'getNotifications': {asyncResult: true},
'updateSearchTerm': {},
'clearSearchTerm': {},
'setSetting': {}

});
Expand Down
27 changes: 23 additions & 4 deletions src/js/components/footer.js
Original file line number Diff line number Diff line change
@@ -1,22 +1,41 @@
var React = require('react');
var Reflux = require('reflux');
var remote = window.require('remote');
var shell = remote.require('shell');
var SearchInput = require('./search-input');
var AuthStore = require('../stores/auth');

var Footer = React.createClass({
mixins: [
Reflux.connect(AuthStore, 'authStatus')
],

openRepoBrowser: function () {
shell.openExternal('http://www.github.com/ekonstantinidis/gitify');
},

getInitialState: function () {
return {
authStatus: AuthStore.authStatus()
};
},

render: function () {
return (
<div className='container-fluid footer'>
<div className='row'>
<div
className='col-xs-12 right'
onClick={this.openRepoBrowser}>
<div className="col-xs-6">
{
this.state.authStatus ? (
<SearchInput />
) : undefined
}
</div>
<div className='col-xs-6 right'>
<span className='github-link' onClick={this.openRepoBrowser}>
Fork me on <span className="octicon octicon-mark-github"/>
</div>
</span>
</div>
</div>
</div>
);
Expand Down
56 changes: 44 additions & 12 deletions src/js/components/notifications.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,16 +2,28 @@ var React = require('react');
var Reflux = require('reflux');
var Loading = require('reloading');
var _ = require('underscore');
var remote = window.require('remote');
var shell = remote.require('shell');

var Actions = require('../actions/actions');
var NotificationsStore = require('../stores/notifications');
var SearchStore = require('../stores/search');
var Repository = require('../components/repository');

var Notifications = React.createClass({
areIn: function (repoFullName, searchTerm) {
return repoFullName.toLowerCase().indexOf(searchTerm.toLowerCase()) >= 0;
},

matchesSearchTerm: function (obj) {
var repoFullName = obj[0].repository.full_name;
var searchTerm = this.state.searchTerm.replace(/^\s+/, '').replace(/\s+$/, '');
var searchTerms = searchTerm.split(/\s+/);

return _.all(searchTerms, this.areIn.bind(null, repoFullName));
},

mixins: [
Reflux.connect(NotificationsStore, 'notifications'),
Reflux.connect(SearchStore, 'searchTerm'),
Reflux.listenTo(Actions.getNotifications.completed, 'completedNotifications'),
Reflux.listenTo(Actions.getNotifications.failed, 'failedNotifications')
],
Expand Down Expand Up @@ -45,9 +57,9 @@ var Notifications = React.createClass({
render: function () {
var notifications, errors;
var wrapperClass = 'container-fluid main-container notifications';
var notificationsEmpty = _.isEmpty(this.state.notifications);

if (this.state.errors) {
wrapperClass += ' errored';
errors = (
<div>
<h3>Oops something went wrong.</h3>
Expand All @@ -56,8 +68,7 @@ var Notifications = React.createClass({
</div>
);
} else {
if (_.isEmpty(this.state.notifications)) {
wrapperClass += ' all-read';
if (notificationsEmpty) {
notifications = (
<div>
<h2>There are no notifications for you.</h2>
Expand All @@ -66,17 +77,38 @@ var Notifications = React.createClass({
</div>
);
} else {
notifications = (
this.state.notifications.map(function (obj) {
var repoFullName = obj[0].repository.full_name;
return <Repository repo={obj} repoName={repoFullName} key={repoFullName} />;
})
);
if (this.state.searchTerm) {
notifications = _.filter(this.state.notifications, this.matchesSearchTerm);
} else {
notifications = this.state.notifications;
}

if (notifications.length) {
notifications = (
notifications.map(function (obj) {
var repoFullName = obj[0].repository.full_name;
return <Repository repo={obj} repoName={repoFullName} key={repoFullName} />;
})
);
} else {
notificationsEmpty = true;
errors = (
<div>
<h3>No Search Results.</h3>
<h4>No Organisations or Repositories match your search term.</h4>
<img className='img-responsive emoji' src='images/all-read.png' />
</div>
);
}
}
}

return (
<div className={wrapperClass}>
<div className={
wrapperClass +
(this.state.errors ? ' errored' : '') +
(notificationsEmpty ? ' all-read' : '')
}>
<Loading className='loading-container' shouldShow={this.state.loading}>
<div className='loading-text'>working on it</div>
</Loading>
Expand Down
3 changes: 1 addition & 2 deletions src/js/components/repository.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
var React = require('react');
var _ = require('underscore');
var remote = window.require('remote');
var shell = remote.require('shell');

Expand All @@ -24,7 +23,7 @@ var Repository = React.createClass({
<div className='col-xs-10 name' onClick={this.openBrowser}>{this.props.repoName}</div>
</div>

{this.props.repo.map(function (obj, i) {
{this.props.repo.map(function (obj) {
return <SingleNotification notification={obj} key={obj.id} />;
})}

Expand Down
Loading