<commit_before>describe('The atlas-search directive', function () {
    var $compile,
        $rootScope,
        $timeout,
        $q,
        store,
        ACTIONS,
        autocompleteData,
        fakeAutocompleteData,
        finishApiCall,
        promises = [];

    beforeEach(function () {
        angular.mock.module(
            'atlasHeader',
            {
                store: {
                    dispatch: function () {}
                },
                autocompleteData: {
                    search: function (query) {
                        var q = $q.defer();
                        promises.push(q);
                        finishApiCall = function() {
                            var q = promises.shift();
                            if (q) {
                                q.resolve(fakeAutocompleteData[query]);
                                $rootScope.$apply();
                            }
                        };

                        return q.promise;
                    },
                    cancel: function () {},
                    getSuggestionByIndex: function (searchResults, index) {
                        var fakeSuggestions = [
                            {
                                _display: 'Suggestion A1',
                                uri: 'blah-blah/1'
                            }, {
                                _display: 'Suggestion A2',
                                uri: 'blah-blah/2'
                            }, {
                                _display: 'Suggestion B1',
                                uri: 'something/789'
                            }
                        ];

                        return fakeSuggestions[index];
                    }
                },
                environment: {
                    API_ROOT: 'http://api.example.com/'
                 }
            }
        );

        angular.mock.inject(
            function (_$compile_, _$rootScope_, _$timeout_, _$q_, _store_, _ACTIONS_, _autocompleteData_) {
                $compile = _$compile_;
                $rootScope = _$rootScope_;
                $timeout = _$timeout_;
                $q = _$q_;
                store = _store_;
                ACTIONS = _ACTIONS_;
                autocompleteData = _autocompleteData_;
            }
        );

        fakeAutocompleteData = {
            'query without suggestions': {
                count: 0,
                data: [],
                query: 'query without suggestions'
            },
            'query with suggestions': {
                count: 3,
                data: [
                    {
                        label: 'Category A',
                        content: [
                            {
                                _display: 'Suggestion A1',
                                uri: 'blah-blah/1',
                                index: 0
                            }, {
                                _display: 'Suggestion A2',
                                uri: 'blah-blah/2',
                                index: 1
                            }
                        ]
                    }, {
                        label: 'Category B',
                        content: [
                            {
                                _display: 'Suggestion B1',
                                uri: 'something/789',
                                index: 2
                            }
                        ]
                    }
                ],
                query: 'query with suggestions'
            }
        };

        promises = [];
    });

    function getDirective (query) {
        var directive,
            element,
            scope;

        element = document.createElement('atlas-search');
        element.setAttribute('query', query);

        scope = $rootScope.$new();

        directive = $compile(element)(scope);
        scope.$apply();

        return directive;
    }

    it('optionally fills the searchbox with a query', function () {
        var directive;

        //Without a query
        directive = getDirective('');
        expect(directive.find('.js-search-input')[0].value).toBe('');

        //With a query
        directive = getDirective('query without suggestions');
        expect(directive.find('.js-search-input')[0].value).toBe('query without suggestions');
    });

    it('can search (without using a suggestion from autocomplete)', function () {
        var directive = getDirective('');

        spyOn(store, 'dispatch');

        //Set a query
        directive.find('.js-search-input')[0].value = 'query without suggestions';
        directive.find('.js-search-input').trigger('change');

        //Submit the form
        directive.find('.c-search-form').trigger('submit');

        expect(store.dispatch).toHaveBeenCalledWith({
            type: ACTIONS.SHOW_SEARCH_RESULTS_BY_QUERY,
            payload: 'query without suggestions'
        });
    });

    it('has a title attribute and text fallback for the search icon (submit button)', function () {
        var directive = getDirective('');

        expect(directive.find('.c-search-form__submit').attr('title')).toBe('Zoeken');
        expect(directive.find('.c-search-form__submit .u-sr-only').text()).toBe('Zoeken');
    });

    describe('has autocomplete suggestions', function () {
        it('which are loaded when typing', function () {
            var directive = getDirective('');

            //No query and no suggestions
            expect(directive.find('.c-autocomplete').length).toBe(0);

            //A query without suggestions
            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(0);

            //A query with suggestions
            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(1);
            expect(directive.find('.c-autocomplete div:nth-of-type(1) h4').text()).toBe('Category A');
            expect(directive.find('.c-autocomplete div:nth-of-type(1) li:nth-child(1)').text().trim())
                .toBe('Suggestion A1');
            expect(directive.find('.c-autocomplete div:nth-of-type(1) li:nth-child(2)').text().trim())
                .toBe('Suggestion A2');

            expect(directive.find('.c-autocomplete div:nth-of-type(2) h4').text()).toBe('Category B');
            expect(directive.find('.c-autocomplete div:nth-of-type(2) li:nth-child(1)').text().trim())
                .toBe('Suggestion B1');
        });

        it('won\'t try to fetch suggestions if there is no query', function () {
            var directive = getDirective('');

            spyOn(autocompleteData, 'search').and.callThrough();

            //With a query
            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(autocompleteData.search).toHaveBeenCalledTimes(1);
            expect(autocompleteData.search).toHaveBeenCalledWith('query without suggestions');

            //Without a query;
            directive.find('.js-search-input')[0].value = '';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(autocompleteData.search).toHaveBeenCalledTimes(1);
        });

        it('Only show relevat autocomplete suggestions', function() {
            var directive = getDirective('');

            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');

            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');

            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(0);
        });

        describe('with mouse support', function () {
            it('opens a detail page when clicking a suggestion', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                spyOn(store, 'dispatch');

                //First suggestion
                directive.find('.c-autocomplete button').eq(0).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/blah-blah/1'
                });

                //Second suggestion
                directive.find('.c-autocomplete button').eq(1).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/blah-blah/2'
                });

                //Third suggestion
                directive.find('.c-autocomplete button').eq(2).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/something/789'
                });
            });

            it('hides the suggestions when choosing a suggestion', function () {
                /**
                 * Clicking on a suggestion link triggers the blur event on the searchbox.
                 */
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                expect(directive.find('.c-autocomplete').length).toBe(1);

                //Click a suggestion
                directive.find('.c-autocomplete button').eq(0).click();
                directive.find('.js-search-input').trigger('blur');
                $timeout.flush();

                expect(directive.find('.c-autocomplete').length).toBe(0);
            });

            it('clears the input when clicking a suggestion', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //First suggestion
                directive.find('.c-autocomplete button').eq(0).click();

                expect(directive.find('.js-search-input')[0].value).toBe('');
            });
        });

        describe('with keyboard support', function () {
            function triggerKeyDownEvent (element, keyCode) {
                var event;

                event = angular.element.Event('keydown');
                event.keyCode = keyCode;

                element.trigger(event);
            }

            it('can select a query by navigating with the UP and DOWN arrows', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Make sure no suggestion is highlighted by default
                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                /**
                 * Press the DOWN ARROW for the first time
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                //Highlight the active suggestion in the list with suggestions
                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                //Show the highlighted suggestion in the searchbox
                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A1');

                /**
                 * Press the DOWN ARROW for the second time
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A2');

                /**
                 * Press the DOWN ARROW again (making sure the 2nd category of suggestions is working as well)
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion B1');

                /**
                 * Press the UP arrow
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 38);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A2');
            });

            it('restores the original query if the UP is used to remove focus from the suggestions', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Navigate to a suggestion
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A1');

                //Deselect all suggestion in the autocomplete, by navigating back with the UP ARROW
                triggerKeyDownEvent(directive.find('.js-search-input'), 38);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('query with suggestions');
            });

            it('restores the original query and hides the suggestion when pressing ESCAPE', function () {
                var directive = getDirective();

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Navigate to the third suggestion
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion B1');
                expect(directive.find('.c-autocomplete').length).toBe(1);

                //Press ESCAPE
                triggerKeyDownEvent(directive.find('.js-search-input'), 27);

                expect(directive.find('.js-search-input')[0].value).toBe('query with suggestions');
                expect(directive.find('.c-autocomplete').length).toBe(0);
            });

            describe('submitting the form (triggered by ENTER) with a highlighted suggestion', function () {
                var directive;

                beforeEach(function () {
                    spyOn(store, 'dispatch');

                    directive = getDirective('');

                    //Load suggestions
                    directive.find('.js-search-input')[0].value = 'query with suggestions';
                    directive.find('.js-search-input').trigger('change');
                    finishApiCall();

                    //Navigate to the second suggestion
                    triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                    triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                    $rootScope.$apply();

                    //Trigger the submit
                    directive.find('.c-search-form').trigger('submit');
                });

                it('opens the selected suggestion', function () {
                    expect(store.dispatch).toHaveBeenCalledWith({
                        type: ACTIONS.FETCH_DETAIL,
                        payload: 'http://api.example.com/blah-blah/2'
                    });
                });

                it('clears the active suggestion in the searchbox', function () {
                    expect(directive.find('.js-search-input')[0].value).toBe('');
                });

                it('hides the suggestions when submitting the form', function () {
                    expect(directive.find('.c-autocomplete').length).toBe(0);
                });
            });
        });

        it('hides the suggestions when the searchbox loses focus', function () {
            var directive = getDirective('');

            //Load suggestions
            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();

            expect(directive.find('.c-autocomplete').length).toBe(1);

            //Lose focus
            directive.find('.js-search-input').trigger('blur');
            $timeout.flush();

            expect(directive.find('.c-autocomplete').length).toBe(0);
        });
    });
});
<commit_msg>tg-777 using actual search input value change to mock change in scope query value<commit_after>describe('The atlas-search directive', function () {
    var $compile,
        $rootScope,
        $timeout,
        $q,
        store,
        ACTIONS,
        autocompleteData,
        fakeAutocompleteData,
        finishApiCall,
        promises = [];

    beforeEach(function () {
        angular.mock.module(
            'atlasHeader',
            {
                store: {
                    dispatch: function () {}
                },
                autocompleteData: {
                    search: function (query) {
                        var q = $q.defer();
                        promises.push({query: query, q: q});
                        finishApiCall = function() {
                            var prom = promises.shift();
                            if (prom) {
                                prom.q.resolve(fakeAutocompleteData[prom.query]);
                                $rootScope.$apply();
                            }
                        };

                        return q.promise;
                    },
                    cancel: function () {},
                    getSuggestionByIndex: function (searchResults, index) {
                        var fakeSuggestions = [
                            {
                                _display: 'Suggestion A1',
                                uri: 'blah-blah/1'
                            }, {
                                _display: 'Suggestion A2',
                                uri: 'blah-blah/2'
                            }, {
                                _display: 'Suggestion B1',
                                uri: 'something/789'
                            }
                        ];

                        return fakeSuggestions[index];
                    }
                },
                environment: {
                    API_ROOT: 'http://api.example.com/'
                 }
            }
        );

        angular.mock.inject(
            function (_$compile_, _$rootScope_, _$timeout_, _$q_, _store_, _ACTIONS_, _autocompleteData_) {
                $compile = _$compile_;
                $rootScope = _$rootScope_;
                $timeout = _$timeout_;
                $q = _$q_;
                store = _store_;
                ACTIONS = _ACTIONS_;
                autocompleteData = _autocompleteData_;
            }
        );

        fakeAutocompleteData = {
            'query without suggestions': {
                count: 0,
                data: [],
                query: 'query without suggestions'
            },
            'query with suggestions': {
                count: 3,
                data: [
                    {
                        label: 'Category A',
                        content: [
                            {
                                _display: 'Suggestion A1',
                                uri: 'blah-blah/1',
                                index: 0
                            }, {
                                _display: 'Suggestion A2',
                                uri: 'blah-blah/2',
                                index: 1
                            }
                        ]
                    }, {
                        label: 'Category B',
                        content: [
                            {
                                _display: 'Suggestion B1',
                                uri: 'something/789',
                                index: 2
                            }
                        ]
                    }
                ],
                query: 'query with suggestions'
            }
        };

        promises = [];
    });

    function getDirective (query) {
        var directive,
            element,
            scope;

        element = document.createElement('atlas-search');
        element.setAttribute('query', query);

        scope = $rootScope.$new();

        directive = $compile(element)(scope);
        scope.$apply();

        return directive;
    }

    it('optionally fills the searchbox with a query', function () {
        var directive;

        //Without a query
        directive = getDirective('');
        expect(directive.find('.js-search-input')[0].value).toBe('');

        //With a query
        directive = getDirective('query without suggestions');
        expect(directive.find('.js-search-input')[0].value).toBe('query without suggestions');
    });

    it('can search (without using a suggestion from autocomplete)', function () {
        var directive = getDirective('');

        spyOn(store, 'dispatch');

        //Set a query
        directive.find('.js-search-input')[0].value = 'query without suggestions';
        directive.find('.js-search-input').trigger('change');

        //Submit the form
        directive.find('.c-search-form').trigger('submit');

        expect(store.dispatch).toHaveBeenCalledWith({
            type: ACTIONS.SHOW_SEARCH_RESULTS_BY_QUERY,
            payload: 'query without suggestions'
        });
    });

    it('has a title attribute and text fallback for the search icon (submit button)', function () {
        var directive = getDirective('');

        expect(directive.find('.c-search-form__submit').attr('title')).toBe('Zoeken');
        expect(directive.find('.c-search-form__submit .u-sr-only').text()).toBe('Zoeken');
    });

    describe('has autocomplete suggestions', function () {
        it('which are loaded when typing', function () {
            var directive = getDirective('');

            //No query and no suggestions
            expect(directive.find('.c-autocomplete').length).toBe(0);

            //A query without suggestions
            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(0);

            //A query with suggestions
            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(1);
            expect(directive.find('.c-autocomplete div:nth-of-type(1) h4').text()).toBe('Category A');
            expect(directive.find('.c-autocomplete div:nth-of-type(1) li:nth-child(1)').text().trim())
                .toBe('Suggestion A1');
            expect(directive.find('.c-autocomplete div:nth-of-type(1) li:nth-child(2)').text().trim())
                .toBe('Suggestion A2');

            expect(directive.find('.c-autocomplete div:nth-of-type(2) h4').text()).toBe('Category B');
            expect(directive.find('.c-autocomplete div:nth-of-type(2) li:nth-child(1)').text().trim())
                .toBe('Suggestion B1');
        });

        it('won\'t try to fetch suggestions if there is no query', function () {
            var directive = getDirective('');

            spyOn(autocompleteData, 'search').and.callThrough();

            //With a query
            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(autocompleteData.search).toHaveBeenCalledTimes(1);
            expect(autocompleteData.search).toHaveBeenCalledWith('query without suggestions');

            //Without a query;
            directive.find('.js-search-input')[0].value = '';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();
            expect(autocompleteData.search).toHaveBeenCalledTimes(1);
        });

        it('Only show relevat autocomplete suggestions', function() {
            var directive = getDirective('');

            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');

            directive.find('.js-search-input')[0].value = 'query without suggestions';
            directive.find('.js-search-input').trigger('change');

            finishApiCall();
            expect(directive.find('.c-autocomplete').length).toBe(0);
        });

        describe('with mouse support', function () {
            it('opens a detail page when clicking a suggestion', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                spyOn(store, 'dispatch');

                //First suggestion
                directive.find('.c-autocomplete button').eq(0).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/blah-blah/1'
                });

                //Second suggestion
                directive.find('.c-autocomplete button').eq(1).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/blah-blah/2'
                });

                //Third suggestion
                directive.find('.c-autocomplete button').eq(2).click();

                expect(store.dispatch).toHaveBeenCalledWith({
                    type: ACTIONS.FETCH_DETAIL,
                    payload: 'http://api.example.com/something/789'
                });
            });

            it('hides the suggestions when choosing a suggestion', function () {
                /**
                 * Clicking on a suggestion link triggers the blur event on the searchbox.
                 */
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                expect(directive.find('.c-autocomplete').length).toBe(1);

                //Click a suggestion
                directive.find('.c-autocomplete button').eq(0).click();
                directive.find('.js-search-input').trigger('blur');
                $timeout.flush();

                expect(directive.find('.c-autocomplete').length).toBe(0);
            });

            it('clears the input when clicking a suggestion', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //First suggestion
                directive.find('.c-autocomplete button').eq(0).click();

                expect(directive.find('.js-search-input')[0].value).toBe('');
            });
        });

        describe('with keyboard support', function () {
            function triggerKeyDownEvent (element, keyCode) {
                var event;

                event = angular.element.Event('keydown');
                event.keyCode = keyCode;

                element.trigger(event);
            }

            it('can select a query by navigating with the UP and DOWN arrows', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Make sure no suggestion is highlighted by default
                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                /**
                 * Press the DOWN ARROW for the first time
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                //Highlight the active suggestion in the list with suggestions
                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                //Show the highlighted suggestion in the searchbox
                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A1');

                /**
                 * Press the DOWN ARROW for the second time
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A2');

                /**
                 * Press the DOWN ARROW again (making sure the 2nd category of suggestions is working as well)
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion B1');

                /**
                 * Press the UP arrow
                 */
                triggerKeyDownEvent(directive.find('.js-search-input'), 38);
                $rootScope.$apply();

                expect(directive.find('.c-autocomplete li').eq(0).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);
                expect(directive.find('.c-autocomplete li').eq(1).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(true);
                expect(directive.find('.c-autocomplete li').eq(2).find('button')
                    .hasClass('c-autocomplete__category__suggestion--active'))
                    .toBe(false);

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A2');
            });

            it('restores the original query if the UP is used to remove focus from the suggestions', function () {
                var directive = getDirective('');

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Navigate to a suggestion
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion A1');

                //Deselect all suggestion in the autocomplete, by navigating back with the UP ARROW
                triggerKeyDownEvent(directive.find('.js-search-input'), 38);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('query with suggestions');
            });

            it('restores the original query and hides the suggestion when pressing ESCAPE', function () {
                var directive = getDirective();

                //Load suggestions
                directive.find('.js-search-input')[0].value = 'query with suggestions';
                directive.find('.js-search-input').trigger('change');
                finishApiCall();

                //Navigate to the third suggestion
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                $rootScope.$apply();

                expect(directive.find('.js-search-input')[0].value).toBe('Suggestion B1');
                expect(directive.find('.c-autocomplete').length).toBe(1);

                //Press ESCAPE
                triggerKeyDownEvent(directive.find('.js-search-input'), 27);

                expect(directive.find('.js-search-input')[0].value).toBe('query with suggestions');
                expect(directive.find('.c-autocomplete').length).toBe(0);
            });

            describe('submitting the form (triggered by ENTER) with a highlighted suggestion', function () {
                var directive;

                beforeEach(function () {
                    spyOn(store, 'dispatch');

                    directive = getDirective('');

                    //Load suggestions
                    directive.find('.js-search-input')[0].value = 'query with suggestions';
                    directive.find('.js-search-input').trigger('change');
                    finishApiCall();

                    //Navigate to the second suggestion
                    triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                    triggerKeyDownEvent(directive.find('.js-search-input'), 40);
                    $rootScope.$apply();

                    //Trigger the submit
                    directive.find('.c-search-form').trigger('submit');
                });

                it('opens the selected suggestion', function () {
                    expect(store.dispatch).toHaveBeenCalledWith({
                        type: ACTIONS.FETCH_DETAIL,
                        payload: 'http://api.example.com/blah-blah/2'
                    });
                });

                it('clears the active suggestion in the searchbox', function () {
                    expect(directive.find('.js-search-input')[0].value).toBe('');
                });

                it('hides the suggestions when submitting the form', function () {
                    expect(directive.find('.c-autocomplete').length).toBe(0);
                });
            });
        });

        it('hides the suggestions when the searchbox loses focus', function () {
            var directive = getDirective('');

            //Load suggestions
            directive.find('.js-search-input')[0].value = 'query with suggestions';
            directive.find('.js-search-input').trigger('change');
            finishApiCall();

            expect(directive.find('.c-autocomplete').length).toBe(1);

            //Lose focus
            directive.find('.js-search-input').trigger('blur');
            $timeout.flush();

            expect(directive.find('.c-autocomplete').length).toBe(0);
        });
    });
});
<|endoftext|>


<commit_before>const NAV_VISIBLE_CLASS = 'nav-visible';
const SEARCH_VISIBLE_CLASS = 'search-visible';

/* Navigation */
document.getElementById('main-nav-toggle').addEventListener('click', (event) => {
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(NAV_VISIBLE_CLASS)) {
    bodyClasses.remove(NAV_VISIBLE_CLASS);
  } else {
    bodyClasses.add(NAV_VISIBLE_CLASS);
    bodyClasses.remove(SEARCH_VISIBLE_CLASS);
  }
  event.stopPropagation();
}, false);

document.getElementById('main-nav').addEventListener('click', (event) => {
  // Prevent events propagating above the navigation element
  event.stopPropagation();
}, false);

document.getElementsByTagName('body')[0].addEventListener('click', () => {
  // Make sure the navigation is closed when area outside navigation is clicked
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(NAV_VISIBLE_CLASS)) {
    bodyClasses.remove(NAV_VISIBLE_CLASS);
  }
}, false);

/* Site search */
const searchBlock = document.getElementById('search-block');
const searchButton = document.getElementById('search-button');
const searchText = document.getElementById('search-text');
const headerSearchButton = document.getElementById('header-button-search');
const mobileSearchButton = document.getElementById('main-nav-mobile-search');

if (searchBlock) {
  searchButton.addEventListener('click', () => {
    if (searchText.value !== '') {
      searchBlock.submit();
    }
    searchBlock.classList.add('search-block--active');
    searchText.placeholder = '';
    searchText.focus();
  });
}

function toggleSearchDisplay() {
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(SEARCH_VISIBLE_CLASS)) {
    bodyClasses.remove(SEARCH_VISIBLE_CLASS);
  } else {
    bodyClasses.add(SEARCH_VISIBLE_CLASS);
    bodyClasses.remove(NAV_VISIBLE_CLASS);
    // set focus on search and ensure cursor is at the end of the search string
    const searchString = searchText.value;
    searchText.value = '';
    searchText.focus();
    searchText.value = searchString;
  }
}

if (headerSearchButton) {
  headerSearchButton.addEventListener('click', toggleSearchDisplay);
}

if (mobileSearchButton) {
  mobileSearchButton.addEventListener('click', toggleSearchDisplay);
}
<commit_msg>Disable top level menu item links on large screen touch devices<commit_after>const NAV_VISIBLE_CLASS = 'nav-visible';
const SEARCH_VISIBLE_CLASS = 'search-visible';

/* Navigation */
function deactivateMainNavParentLinks() {
  const mainNavParentItems = Array.from(document.getElementsByClassName('main-nav__parent'));
  mainNavParentItems.forEach((parentNavItem) => {
    parentNavItem.classList.add('disabled');
    parentNavItem.href = '';
    parentNavItem.addEventListener('click', (event) => {
      event.preventDefault();
    });
  });
}

function onFirstTouch() {
  // detect if the user is touching
  // https://codeburst.io/the-only-way-to-detect-touch-with-javascript-7791a3346685
  // alert('touch5');
  document.body.classList.add('user-is-touching');
  const width = Math.max(document.documentElement.clientWidth, window.innerWidth || 0);
  // deactivate parent nav links if touch device and full navigation
  if (width >= 850) {
    deactivateMainNavParentLinks();
  }
  window.removeEventListener('touchstart', onFirstTouch, false);
}

window.addEventListener('touchstart', onFirstTouch, false);

document.getElementById('main-nav-toggle').addEventListener('click', (event) => {
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(NAV_VISIBLE_CLASS)) {
    bodyClasses.remove(NAV_VISIBLE_CLASS);
  } else {
    bodyClasses.add(NAV_VISIBLE_CLASS);
    bodyClasses.remove(SEARCH_VISIBLE_CLASS);
  }
  event.stopPropagation();
}, false);

document.getElementById('main-nav').addEventListener('click', (event) => {
  // Prevent events propagating above the navigation element
  event.stopPropagation();
}, false);

document.getElementsByTagName('body')[0].addEventListener('click', () => {
  // Make sure the navigation is closed when area outside navigation is clicked
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(NAV_VISIBLE_CLASS)) {
    bodyClasses.remove(NAV_VISIBLE_CLASS);
  }
}, false);

/* Site search */
const searchBlock = document.getElementById('search-block');
const searchButton = document.getElementById('search-button');
const searchText = document.getElementById('search-text');
const headerSearchButton = document.getElementById('header-button-search');
const mobileSearchButton = document.getElementById('main-nav-mobile-search');

if (searchBlock) {
  searchButton.addEventListener('click', () => {
    if (searchText.value !== '') {
      searchBlock.submit();
    }
    searchBlock.classList.add('search-block--active');
    searchText.placeholder = '';
    searchText.focus();
  });
}

function toggleSearchDisplay() {
  const bodyClasses = document.body.classList;
  if (bodyClasses.contains(SEARCH_VISIBLE_CLASS)) {
    bodyClasses.remove(SEARCH_VISIBLE_CLASS);
  } else {
    bodyClasses.add(SEARCH_VISIBLE_CLASS);
    bodyClasses.remove(NAV_VISIBLE_CLASS);
    // set focus on search and ensure cursor is at the end of the search string
    const searchString = searchText.value;
    searchText.value = '';
    searchText.focus();
    searchText.value = searchString;
  }
}

if (headerSearchButton) {
  headerSearchButton.addEventListener('click', toggleSearchDisplay);
}

if (mobileSearchButton) {
  mobileSearchButton.addEventListener('click', toggleSearchDisplay);
}
<|endoftext|>


<commit_before>import stubCompiler from '../src/stubCompiler';

fdescribe("The stubCompiler's", () => {

  describe("parse function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.parse()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake ast", (done) => {
      var promise = stubCompiler.parse();
      promise.then(ast => {
        expect(ast).toBe('some fake ast');
        done();
      });
    });
  });

  describe("compile function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.compile()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake bytecode", (done) => {
      var promise = stubCompiler.compile();
      promise.then(bytecode => {
        expect(bytecode).toBe('some fake bytecode');
        done();
      });
    });
  });
  describe("execute function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.execute()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake result", (done) => {
      var promise = stubCompiler.execute();
      promise.then(result => {
        expect(result).toBe('some fake result');
        done();
      });
    });
  });
});
<commit_msg>made changes suggested by Paul<commit_after>import stubCompiler from '../src/stubCompiler';

describe("The stubCompiler's", () => {

  describe("parse function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.parse()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake ast", (done) => {
      var promise = stubCompiler.parse();
      promise.then(ast => {
        expect(ast).toBe('some fake ast');
        done();
      });
    });
  });

  describe("compile function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.compile()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake bytecode", (done) => {
      var promise = stubCompiler.compile();
      promise.then(bytecode => {
        expect(bytecode).toBe('some fake bytecode');
        done();
      });
    });
  });

  describe("execute function", () => {

    it("returns a Promise instance", () => {
      expect(stubCompiler.execute()).toEqual(jasmine.any(Promise));
    });

    it("returns a Promise that resolves to some fake result", (done) => {
      var promise = stubCompiler.execute();
      promise.then(result => {
        expect(result).toBe('some fake result');
        done();
      });
    });
  });
});
<|endoftext|>


<commit_before>var config = {
    weather: {
        location: "20170", //Can be zip code, address, street, just about anything you can google
        apikey: "API_KEY", //forcast.io api key goes here
        units: "imperial", //imperial or metric. Default is imperial if incorrect value supplied
        refreshTime: 30,/*How often you want to refresh weather data (In Minuites)
                         *Note: forcast.io FREE accounts have 1000
                        *calls per day. To minimize ur api calls,
                        *keep your refresh rate low  (Highr minuites)
                        *defalt to 30 mins if not provided
                         */
        fiveday: "true" // true or false
    },
    location: {
        location: "20170",
        googleKey: "API_KEY"
    },
    time: {
        format: 12, //Default to 24 if not provided/invalid
        seconds: false
    },
    compliments: {
        interval: 30000,
        fadeInterval:3,
        morning:[
            'Rise and Shine!',
            'Good Morning Handsome!',
            'Lookin\' Good!',
            'Enjoy your day!'
        ],
        afternoon:[
            'Hey Good Lookin\'',
            'WOW, You Look Good',
            'Beauty!'

        ],
        evening:[
            'Good Evening',
            'Looking Fresh!',
            'Date night?',
            'Lookin\' Good!'
        ]
    },
    news: {
        refreshInterval: 30
    }
}<commit_msg>updating example config file<commit_after>var config = {
    weather: {
        location: "LOCATION", //Can be zip code, address, street, just about anything you can google
        apikey: "API_KEY", //forcast.io api key goes here
        units: "imperial", //imperial or metric. Default is imperial if incorrect value supplied
        refreshTime: 30,/*How often you want to refresh weather data (In Minuites)
                        *Note: forcast.io FREE accounts have 1000
                        *calls per day. To minimize ur api calls,
                        *keep your refresh rate low  (Higher minuites)
                        *defalt to 30 mins if not provided
                         */
        fiveday: "true" // true or false
    },
    location: {
        location: "LOCATION",
        googleKey: "API_KEY"
    },
    time: {
        format: 12, //Default to 24 if not provided/invalid
        seconds: false
    },
    compliments: {
        interval: 30000,
        fadeInterval:3,
        morning:[
            'Rise and Shine!',
            'Good Morning Handsome!',
            'Lookin\' Good!',
            'Enjoy your day!'
        ],
        afternoon:[
            'Hey Good Lookin\'',
            'WOW, You Look Good',
            'Beauty!'

        ],
        evening:[
            'Good Evening',
            'Looking Fresh!',
            'Date night?',
            'Lookin\' Good!'
        ]
    },
    news: {
        refreshInterval: 30
    },
    maps: {
        bingAPIKey: "API_KEY",
        avoid:["tolls"],/* Availible options are "highways" and "tolls"
                        * To use both, format like this ex. ["highways","tolls"]
                        * anything else will be ignored
                        */
        origin:"HOME_ADDRESS",/* This should be origin of travel
                                                     * Most likely should be address of
                                                     * current location of the mirror
                                                     * THIS SHOULD BE IN HUMAN READABLE FORM
                                                     * PLEASE NO NOT INCLUDE COMMAS OR ANY SPECIAL
                                                     * CHARACTERS
                                                     * Ex."1600 Pennsylvania Ave NW Washington DC 20500"
                                                     */
        destinations:{
            /* You may add multiple destinations to this list
             * Please format like so;
             * name_of_place:"Human Readable Address"
             * a comma is required if not last in the list
             * name_of_place will be the keyword to descripe that place.
             * for example, saying "traffic to work" will show
             * traffic to "1600 Pennsylvania Ave NW Washington DC 20500"
             * in the example below. Each "name_of_place" needs to be a
             * unique word.
             * */
            work: "1600 PENNSYLVANIA AVE NW  WASHINGTON DC 20500",
            school: "4400 University Dr Fairfax VA 22030"
        },
        method: "Driving" //Driving or Walking supported as of now
    }
}<|endoftext|>


<commit_before>(function(d3, fc) {
    'use strict';

    fc.utilities.simpleDataJoin = function(parent, className, data, dataKey) {
        // "Caution: avoid interpolating to or from the number zero when the interpolator is used to generate
        // a string (such as with attr).
        // Very small values, when stringified, may be converted to scientific notation and
        // cause a temporarily invalid attribute or style property value.
        // For example, the number 0.0000001 is converted to the string "1e-7".
        // This is particularly noticeable when interpolating opacity values.
        // To avoid scientific notation, start or end the transition at 1e-6,
        // which is the smallest value that is not stringified in exponential notation."
        // - https://github.com/mbostock/d3/wiki/Transitions#d3_interpolateNumber
        var effectivelyZero = 1e-6;

        // update
        var updateSelection = parent.selectAll('g.' + className)
            .data(data, dataKey || fc.utilities.fn.index);

        // enter
        // entering elements fade in (from transparent to opaque)
        var enterSelection = updateSelection.enter()
            .append('g')
            .classed(className, true)
            .style('opacity', effectivelyZero);

        // exit
        // exiting elements fade out (from opaque to transparent)
        var exitSelection = d3.transition(updateSelection.exit())
            .style('opacity', effectivelyZero)
            .remove();

        // all properties of the selection (which can be interpolated) will transition
        updateSelection = d3.transition(updateSelection)
            .style('opacity', 1);

        updateSelection.enter = d3.functor(enterSelection);
        updateSelection.exit = d3.functor(exitSelection);
        return updateSelection;
    };
}(d3, fc));<commit_msg>Add selectOrAppend to simpleDataJoin result<commit_after>(function(d3, fc) {
    'use strict';

    function enhanceSelection(enterSelection, updateSelection) {
        updateSelection.selectOrAppend = function(elementName, className) {
            var enterSel = enterSelection.append(elementName)
                .attr('class', className);
            var updateSel = updateSelection.select(elementName + '.' + className);
            updateSel.enter = d3.functor(enterSel);
            enhanceSelection(enterSel, updateSel);
            return updateSel;
        };
    }

    fc.utilities.simpleDataJoin = function(parent, className, data, dataKey) {
        // "Caution: avoid interpolating to or from the number zero when the interpolator is used to generate
        // a string (such as with attr).
        // Very small values, when stringified, may be converted to scientific notation and
        // cause a temporarily invalid attribute or style property value.
        // For example, the number 0.0000001 is converted to the string "1e-7".
        // This is particularly noticeable when interpolating opacity values.
        // To avoid scientific notation, start or end the transition at 1e-6,
        // which is the smallest value that is not stringified in exponential notation."
        // - https://github.com/mbostock/d3/wiki/Transitions#d3_interpolateNumber
        var effectivelyZero = 1e-6;

        // update
        var updateSelection = parent.selectAll('g.' + className)
            .data(data, dataKey || fc.utilities.fn.index);

        // enter
        // entering elements fade in (from transparent to opaque)
        var enterSelection = updateSelection.enter()
            .append('g')
            .classed(className, true)
            .style('opacity', effectivelyZero);

        // exit
        // exiting elements fade out (from opaque to transparent)
        var exitSelection = d3.transition(updateSelection.exit())
            .style('opacity', effectivelyZero)
            .remove();

        // all properties of the selection (which can be interpolated) will transition
        updateSelection = d3.transition(updateSelection)
            .style('opacity', 1);

        updateSelection.enter = d3.functor(enterSelection);
        updateSelection.exit = d3.functor(exitSelection);

        enhanceSelection(enterSelection, updateSelection);

        return updateSelection;
    };
}(d3, fc));<|endoftext|>


<commit_before>var util = require('util');

var _ = require('lodash');
var async = require('async');

var marshallers = require('../marshallers');
var utils = require('../utils');

var marshaller = _.merge({}, marshallers.JSONMarshaller, {
  serializers: {
    date: utils.serializeIntegerDate,
    // Use the raw boolean value in cassandra.
    boolean: marshallers.noop
  },
  unserializers: {
    datetime: marshallers.noop,
    date: utils.unserializeIntegerDate
  }
});

function makeParens(values, quote) {
  if (quote) {values = _.map(values, quote);}
  return '(' + values.join(',') + ')';
};

function makeParams(amount) {
  return makeParens(_.times(amount, function() {return '?';}));
};

function makeNames(values) {
  return values.join(',');
}

function quote(text) {
  return '"' + text + '"';
}

/*
CassandraDatastore.generateSetPlaceholder = function(attributes) {
  return _.map(attributes, function(attribute) {
    return addQuotes(attribute) + ' = ?'
  }).join(',');
};
 */

function CassandraDatastore(options) {
  this.client = options.client;
  this.tables = options.tables;
  this.queries = [];
  this.scheduledBatch = false;
  this.schema = {};
}

/**
 * Schema generation methods
 */
CassandraDatastore.prototype.register = function(cf, type, options) {
  if (type === 'cf') {
    this.schema[options.cf] = {};
  } else if (type === 'column') {

  } else if (type === 'index') {
    // pk, v
    // options.table + '_by_' + options.index
  }
  // row key type
  // row key validator (sort?)
  // static columns
  //   name: type
  // dynamic columns
  //   construction: comparator
};

CassandraDatastore.prototype.getSchema = function() {
  return this.schema;
};

/**
 * Datastore implementation methods
 */
CassandraDatastore.prototype.find = function(options, cb) {
  throw new Error('indexes are not yet implemented for CassandraDatastore');
  /*
  var indexFamily = options.table + '_by_' + options.index;
  var query = util.format('SELECT pk FROM %s WHERE v=?', indexFamily);
  this._execute(query, [options.value], function(err, results) {
    if (err) {return cb(err);}
    if (results.rows.length > 0) {
      cb(null, results.rows[0].pk);
    } else {
      cb();
    }
  });*/
};

CassandraDatastore.prototype.save = function(options, cb) {
  var self = this;

  var nullAttrs = _.filter(_.keys(options.data), function(name) {
    return options.data[name] === null;
  });

  async.series([
    /*function(cb) {
      // Check for duplicate indexes.
      async.map(options.indexes, function(index, cb) {
        var query = util.format('SELECT KEY FROM %s WHERE %s = ?',
          options.table, index);
        self._execute(query, [options.data[index]], cb);
      }, function(err, results) {
        if (err) {return cb(err);}
        if (_.all(results, _.identity)) {
          cb();
        } else {
          cb('index already exists');
        }
      }, cb);
    },*/
    function(cb) {
      if (!options.data) {return cb();}

      // Serialize the attributes before save.
      var data = marshallers.serialize(
        marshaller, _.omit(options.data, nullAttrs), options.types
      );

      if (_.isEmpty(data)) {return cb();}

      var queryValues = _.zip(_.map(_.keys(data), function(attribute) {
        var value = data[attribute];
        return [quote(attribute) + ' = ?', value];
      }));

      // Save the row data.
      var query = util.format('UPDATE "%s" SET %s WHERE id = ?', options.table,
        queryValues[0].join(','));
      self._execute(query, queryValues[1].concat([options.id]), cb);
    },
    function(cb) {
      // Delete any null values.
      if (nullAttrs.length > 0) {
        var query = util.format('DELETE %s FROM "%s" WHERE id = ?',
          makeNames(_.map(nullAttrs, quote)), options.table);
        self._execute(query, [options.id], cb);
      } else {
        cb();
      }
    }
  ], cb);
};

CassandraDatastore.prototype.fetch = function(options, cb) {
  var self = this;

  async.map(options.ids, function(id, cb) {
    var query = util.format('SELECT %s FROM "%s" WHERE id IN %s',
      makeNames(_.map(options.attributes, quote)), options.table,
      makeParams(options.ids.length));
    self._execute(query, options.ids, function(err, data) {
      if (err) {return cb(err);}
      if (data.rows.length == 0) {
        cb(null, [id, null]);
      } else {
        var row = data.rows[0];
        var attrs = _.filter(_.keys(row), function(key) {
          return _.contains(options.attributes, key) && !_.isNull(row[key]);
        });
        cb(null, [id, marshallers.unserialize(
          marshaller, _.pick(row, attrs), options.types
        )]);
      }
    });
  }, function(err, data) {
    if (err) {return cb(err);}
    cb(null, _.object(data));
  });
};

CassandraDatastore.prototype.destroy = function(options, cb) {
  var query = util.format('DELETE FROM "%s" WHERE id IN %s', options.table,
    makeParams(options.ids.length));
  this._execute(query, options.ids, cb);
};

CassandraDatastore.prototype.reset = function(cb) {
  var self = this;
  async.each(this.tables, function(table, cb) {
    var query = util.format('TRUNCATE "%s"', table);
    self._execute(query, cb);
  }, cb);
};

CassandraDatastore.prototype._saveIndexes = function(options, values, cb) {
  var self = this;
  var columns = makeParens(columns, ['pk', 'v']);

  async.each(options.indexes, function(index, cb) {
    if (_.has(values, index)) {
      var indexFamily = options.table + '_by_' + index;
      var query = utils.format('INSERT INTO "%s" %s VALUES %s',
        indexFamily, columns, makeParams(2));
      self._execute(query, [values[index], options.id], cb);
    } else {
      cb();
    }
  }, cb);
};

CassandraDatastore.prototype._execute = function() {
  var self = this;
  this.queries.push(_.values(arguments));

  if (!this.scheduledBatch) {
    process.nextTick(function() {
      self._executeQueries(self.queries);
      self.queries = [];
      self.scheduledBatch = false;
    });
    this.scheduledBatch = true;
  }
};

CassandraDatastore.prototype._executeQueries = function(queries) {
  if (queries.length > 1) {
    // Execute queries in batch.
    this.client.executeBatch(_.map(queries, function(query) {
      return {query: query[0], params: query[1]};
    }), function(err, results) {
      _.each(queries, function(query, i) {
        var cb = _.last(query);
        if (err) {return cb(err);}
        cb(null, results[i])
      });
    });
  } else {
    // Execute single query.
    var query = queries[0];
    this.client.execute.apply(this.client, query);
  }
};

module.exports = CassandraDatastore;
<commit_msg>Prepare cql queries<commit_after>var util = require('util');

var _ = require('lodash');
var async = require('async');

var marshallers = require('../marshallers');
var utils = require('../utils');

var marshaller = _.merge({}, marshallers.JSONMarshaller, {
  serializers: {
    date: utils.serializeIntegerDate,
    // Use the raw boolean value in cassandra.
    boolean: marshallers.noop
  },
  unserializers: {
    datetime: marshallers.noop,
    date: utils.unserializeIntegerDate
  }
});

function makeParens(values, quote) {
  if (quote) {values = _.map(values, quote);}
  return '(' + values.join(',') + ')';
};

function makeParams(amount) {
  return makeParens(_.times(amount, function() {return '?';}));
};

function makeNames(values) {
  return values.join(',');
}

function quote(text) {
  return '"' + text + '"';
}

/*
CassandraDatastore.generateSetPlaceholder = function(attributes) {
  return _.map(attributes, function(attribute) {
    return addQuotes(attribute) + ' = ?'
  }).join(',');
};
 */

function CassandraDatastore(options) {
  this.client = options.client;
  this.tables = options.tables;
  this.queries = [];
  this.scheduledBatch = false;
  this.schema = {};
}

/**
 * Schema generation methods
 */
CassandraDatastore.prototype.register = function(cf, type, options) {
  if (type === 'cf') {
    this.schema[options.cf] = {};
  } else if (type === 'column') {

  } else if (type === 'index') {
    // pk, v
    // options.table + '_by_' + options.index
  }
  // row key type
  // row key validator (sort?)
  // static columns
  //   name: type
  // dynamic columns
  //   construction: comparator
};

CassandraDatastore.prototype.getSchema = function() {
  return this.schema;
};

/**
 * Datastore implementation methods
 */
CassandraDatastore.prototype.find = function(options, cb) {
  throw new Error('indexes are not yet implemented for CassandraDatastore');
  /*
  var indexFamily = options.table + '_by_' + options.index;
  var query = util.format('SELECT pk FROM %s WHERE v=?', indexFamily);
  this._execute(query, [options.value], function(err, results) {
    if (err) {return cb(err);}
    if (results.rows.length > 0) {
      cb(null, results.rows[0].pk);
    } else {
      cb();
    }
  });*/
};

CassandraDatastore.prototype.save = function(options, cb) {
  var self = this;

  var nullAttrs = _.filter(_.keys(options.data), function(name) {
    return options.data[name] === null;
  });

  async.series([
    /*function(cb) {
      // Check for duplicate indexes.
      async.map(options.indexes, function(index, cb) {
        var query = util.format('SELECT KEY FROM %s WHERE %s = ?',
          options.table, index);
        self._execute(query, [options.data[index]], cb);
      }, function(err, results) {
        if (err) {return cb(err);}
        if (_.all(results, _.identity)) {
          cb();
        } else {
          cb('index already exists');
        }
      }, cb);
    },*/
    function(cb) {
      if (!options.data) {return cb();}

      // Serialize the attributes before save.
      var data = marshallers.serialize(
        marshaller, _.omit(options.data, nullAttrs), options.types
      );

      if (_.isEmpty(data)) {return cb();}

      var queryValues = _.zip(_.map(_.keys(data), function(attribute) {
        var value = data[attribute];
        return [quote(attribute) + ' = ?', value];
      }));

      // Save the row data.
      var query = util.format('UPDATE "%s" SET %s WHERE id = ?', options.table,
        queryValues[0].join(','));
      self._execute(query, queryValues[1].concat([options.id]), {prepare: true}, cb);
    },
    function(cb) {
      // Delete any null values.
      if (nullAttrs.length > 0) {
        var query = util.format('DELETE %s FROM "%s" WHERE id = ?',
          makeNames(_.map(nullAttrs, quote)), options.table);
        self._execute(query, [options.id], {prepare: true}, cb);
      } else {
        cb();
      }
    }
  ], cb);
};

CassandraDatastore.prototype.fetch = function(options, cb) {
  var self = this;

  async.map(options.ids, function(id, cb) {
    var query = util.format('SELECT %s FROM "%s" WHERE id IN %s',
      makeNames(_.map(options.attributes, quote)), options.table,
      makeParams(options.ids.length));
    self._execute(query, options.ids, {prepare: true}, function(err, data) {
      if (err) {return cb(err);}
      if (data.rows.length == 0) {
        cb(null, [id, null]);
      } else {
        var row = data.rows[0];
        var attrs = _.filter(_.keys(row), function(key) {
          return _.contains(options.attributes, key) && !_.isNull(row[key]);
        });
        cb(null, [id, marshallers.unserialize(
          marshaller, _.pick(row, attrs), options.types
        )]);
      }
    });
  }, function(err, data) {
    if (err) {return cb(err);}
    cb(null, _.object(data));
  });
};

CassandraDatastore.prototype.destroy = function(options, cb) {
  var query = util.format('DELETE FROM "%s" WHERE id IN %s', options.table,
    makeParams(options.ids.length));
  this._execute(query, options.ids, {prepare: true}, cb);
};

CassandraDatastore.prototype.reset = function(cb) {
  var self = this;
  async.each(this.tables, function(table, cb) {
    var query = util.format('TRUNCATE "%s"', table);
    self._execute(query, {prepare: true}, cb);
  }, cb);
};

CassandraDatastore.prototype._saveIndexes = function(options, values, cb) {
  var self = this;
  var columns = makeParens(columns, ['pk', 'v']);

  async.each(options.indexes, function(index, cb) {
    if (_.has(values, index)) {
      var indexFamily = options.table + '_by_' + index;
      var query = utils.format('INSERT INTO "%s" %s VALUES %s',
        indexFamily, columns, makeParams(2));
      self._execute(query, [values[index], options.id], {prepare: true}, cb);
    } else {
      cb();
    }
  }, cb);
};

CassandraDatastore.prototype._execute = function() {
  var self = this;
  this.queries.push(_.values(arguments));

  if (!this.scheduledBatch) {
    process.nextTick(function() {
      self._executeQueries(self.queries);
      self.queries = [];
      self.scheduledBatch = false;
    });
    this.scheduledBatch = true;
  }
};

CassandraDatastore.prototype._executeQueries = function(queries) {
  if (queries.length > 1) {
    // Execute queries in batch.
    this.client.executeBatch(_.map(queries, function(query) {
      return {query: query[0], params: query[1]};
    }), function(err, results) {
      _.each(queries, function(query, i) {
        var cb = _.last(query);
        if (err) {return cb(err);}
        cb(null, results[i])
      });
    });
  } else {
    // Execute single query.
    var query = queries[0];
    this.client.execute.apply(this.client, query);
  }
};

module.exports = CassandraDatastore;
<|endoftext|>


<commit_before>/* eslint-env jest */

import mockDatabase from '../../__mocks__/database'
import { DatabaseOperation, OperationType } from '../../../utils/test-utils'
import { updateUserVc, incrementVcBy1 } from '../updateUserVc'
import moment from 'moment'

jest.mock('../../database', () => {
  return mockDatabase
})

jest.mock('../../userLevels/userLevel', () => {
  return {
    getNextLevelFor: jest.fn((level, vc) => {
      return Promise.resolve({
        id: 4,
        hearts: 400
      })
    })
  }
})

function setup () {
  mockDatabase.init()
  return mockDatabase
}

describe('ReferralData class', function () {
  it('user update vc', () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 50,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    return updateUserVc('45bbefbf-63d1-4d36-931e-212fbe2bc3d9', 15)
      .then(data => {
        expect(data).not.toBe(null)
        expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
        expect(data.vcCurrent).toBe(50)
        expect(data.vcAllTime).toBe(190)
        expect(data.heartsUntilNextLevel).toBe(10)
      })
  })

  it('user level up', () => {
    const database = setup()

    const userAfterVcUpdated = new DatabaseOperation(OperationType.UPDATE, (params) => {
      return {
        Attributes: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          level: 2,
          vcCurrent: 50,
          vcAllTime: 300,
          heartsUntilNextLevel: 0
        }
      }
    })

    const userUpdatedAfterLevelUpResolved = new DatabaseOperation(OperationType.UPDATE, (params) => {
      expect(params.ExpressionAttributeValues[':level']).toBe(3)
      expect(params.ExpressionAttributeValues[':nextLevelHearts']).toBe(100)

      return {
        Attributes: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          level: 3,
          vcCurrent: 50,
          vcAllTime: 300,
          heartsUntilNextLevel: 100
        }
      }
    })

    database.pushDatabaseOperation(userAfterVcUpdated)
    database.pushDatabaseOperation(userUpdatedAfterLevelUpResolved)

    return updateUserVc('45bbefbf-63d1-4d36-931e-212fbe2bc3d9', 1)
      .then(data => {
        expect(data).not.toBe(null)
        expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
      })
  })

  it('Should increment vc by 1', () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100
        }
        }
      })
    )

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        expect(params.ExpressionAttributeValues[':val']).toBe(1)
        expect(params.ExpressionAttributeValues[':subval']).toBe(-1)
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 50,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    return incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
      .then(data => {
        expect(data).not.toBe(null)
        expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
        expect(data.vcCurrent).toBe(50)
        expect(data.vcAllTime).toBe(190)
        expect(data.heartsUntilNextLevel).toBe(10)
      })
  })

  it('Should not increment vc by 1 if last updated was less than 2 seconds ago', () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100,
          lastTabTimestamp: moment.utc()
        }
        }
      })
    )

    // How do we know this incrementVcBy1 is not calling to updateUserVc?
    // If this were happening we would get an exception from the database
    // since an update operation that wasn't being expected was invoked.
    return incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
      .then(data => {
        expect(data).not.toBe(null)
        expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
        expect(data.vcCurrent).toBe(100)
      })
  })

  it('Should increment vc by 1 if last updated was more than 2 seconds ago', () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100,
          lastTabTimestamp: moment.utc().subtract(3, 'seconds')
        }
        }
      })
    )

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        expect(params.ExpressionAttributeValues[':val']).toBe(1)
        expect(params.ExpressionAttributeValues[':subval']).toBe(-1)
        const lastTabTimestamp = moment.utc(params.ExpressionAttributeValues[':lastTabTimestamp'])
        var now = moment.utc()
        var diff = now.diff(lastTabTimestamp, 'seconds')
        expect(diff).toBeLessThan(1)
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 100,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    return incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
      .then(data => {
        expect(data).not.toBe(null)
        expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
        expect(data.vcCurrent).toBe(100)
      })
  })
})
<commit_msg>Fix async Jest tests.<commit_after>/* eslint-env jest */

import mockDatabase from '../../__mocks__/database'
import { DatabaseOperation, OperationType } from '../../../utils/test-utils'
import { updateUserVc, incrementVcBy1 } from '../updateUserVc'
import moment from 'moment'

jest.mock('../../database', () => {
  return mockDatabase
})

jest.mock('../../userLevels/userLevel', () => {
  return {
    getNextLevelFor: jest.fn((level, vc) => {
      return Promise.resolve({
        id: 4,
        hearts: 400
      })
    })
  }
})

function setup () {
  mockDatabase.init()
  return mockDatabase
}

describe('ReferralData class', function () {
  it('user update vc', async () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 50,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    const data = await updateUserVc('45bbefbf-63d1-4d36-931e-212fbe2bc3d9', 15)
    expect(data).not.toBe(null)
    expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data.vcCurrent).toBe(50)
    expect(data.vcAllTime).toBe(190)
    expect(data.heartsUntilNextLevel).toBe(10)
  })

  it('user level up', async () => {
    const database = setup()

    const userAfterVcUpdated = new DatabaseOperation(OperationType.UPDATE, (params) => {
      return {
        Attributes: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          level: 2,
          vcCurrent: 50,
          vcAllTime: 300,
          heartsUntilNextLevel: 0
        }
      }
    })

    const userUpdatedAfterLevelUpResolved = new DatabaseOperation(OperationType.UPDATE, (params) => {
      expect(params.ExpressionAttributeValues[':level']).toBe(3)
      expect(params.ExpressionAttributeValues[':nextLevelHearts']).toBe(100)

      return {
        Attributes: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          level: 3,
          vcCurrent: 50,
          vcAllTime: 300,
          heartsUntilNextLevel: 100
        }
      }
    })

    database.pushDatabaseOperation(userAfterVcUpdated)
    database.pushDatabaseOperation(userUpdatedAfterLevelUpResolved)

    const data = await updateUserVc('45bbefbf-63d1-4d36-931e-212fbe2bc3d9', 1)
    expect(data).not.toBe(null)
    expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
  })

  it('Should increment vc by 1', async () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100
        }
        }
      })
    )

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        expect(params.ExpressionAttributeValues[':val']).toBe(1)
        expect(params.ExpressionAttributeValues[':subval']).toBe(-1)
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 50,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    const data = await incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data).not.toBe(null)
    expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data.vcCurrent).toBe(50)
    expect(data.vcAllTime).toBe(190)
    expect(data.heartsUntilNextLevel).toBe(10)
  })

  it('Should not increment vc by 1 if last updated was less than 2 seconds ago', async () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100,
          lastTabTimestamp: moment.utc()
        }
        }
      })
    )

    // How do we know this incrementVcBy1 is not calling to updateUserVc?
    // If this were happening we would get an exception from the database
    // since an update operation that wasn't being expected was invoked.
    const data = await incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data).not.toBe(null)
    expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data.vcCurrent).toBe(100)
  })

  it('Should increment vc by 1 if last updated was more than 2 seconds ago', async () => {
    const database = setup()

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.GET, (params) => {
        return { Item: {
          id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
          username: 'raulchall',
          email: 'raul@tfac.com',
          vcCurrent: 100,
          lastTabTimestamp: moment.utc().subtract(3, 'seconds')
        }
        }
      })
    )

    database.pushDatabaseOperation(
      new DatabaseOperation(OperationType.UPDATE, (params) => {
        expect(params.ExpressionAttributeValues[':val']).toBe(1)
        expect(params.ExpressionAttributeValues[':subval']).toBe(-1)
        const lastTabTimestamp = moment.utc(params.ExpressionAttributeValues[':lastTabTimestamp'])
        var now = moment.utc()
        var diff = now.diff(lastTabTimestamp, 'seconds')
        expect(diff).toBeLessThan(1)
        return {
          Attributes: {
            id: '45bbefbf-63d1-4d36-931e-212fbe2bc3d9',
            vcCurrent: 100,
            vcAllTime: 190,
            heartsUntilNextLevel: 10
          }
        }
      })
    )

    const data = await incrementVcBy1('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data).not.toBe(null)
    expect(data.id).toBe('45bbefbf-63d1-4d36-931e-212fbe2bc3d9')
    expect(data.vcCurrent).toBe(100)
  })
})
<|endoftext|>


<commit_before>import React from 'react';

import {Navbar} from '../containers';
import {ChatNavBody} from '../containers';
import {ChatWindow} from '../containers';

//request function
import {getUserData,getMessages,postMessage,getSessions,getSessionId} from '../../utils';
//credentials function
import {socket} from '../../utils';

import Drawer from 'material-ui/Drawer';
// var debug = require('react-debug');

export default class Chat extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            user: {},
            message :[],
            sessions: [],
            friend: "",
            sessionId:"",
            btnText:"load earier messages",
            open:false
        };
    }

    componentDidMount() {
        this.getData();
        socket.on('online',(user)=>{
            if(this.state.user.friends.filter((item) => {return item._id===user}).length>0)
            getUserData(this.props.user, (userData) => {
                this.setState({
                    user:userData
                });
            });
        })
    }

    componentWillUpdate(){
        socket.removeAllListeners("chat");
    }

    componentDidUpdate(){
        socket.on('chat',()=>{
            this.getSession(this.state.friend._id,(session)=>{
                this.setState({
                    sessionId:session
                },
                ()=>{
                    getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                        this.setState({
                            message:message
                        },()=>{
                            getSessions(this.props.user, (sessions) => {
                                this.setState({
                                    sessions:sessions,
                                    btnText:"load earier messages"
                                });
                            });
                        })
                    });
                })
            });
        });
    }

    getData() {
        getUserData(this.props.user, (userData) => {
            this.setState({
                user:userData
            },()=>{
                getSessions(this.props.user,(sessions)=>{
                    this.setState({
                        sessions:sessions,
                        friend:this.state.user.friends[0]
                    },()=>{
                        this.getSession(this.state.friend._id,(session)=>{
                            this.setState({
                                sessionId:session
                            },
                            ()=>{
                                getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                                    this.setState({
                                        message:message,
                                        btnText:message.length===0?"say hello to your friend!":"load earier messages"
                                    })
                                });
                            });
                        });
                    });
                });
            })
        });
    }

    getSession(friend,callback){
        var sessions = this.state.sessions;
        var result = null;
        sessions.forEach(session=>{
            if(session.users.indexOf(friend)!==-1){
                return result = session._id;
            }
        });
        if(result === null){
            getSessionId(this.props.user,friend,(session)=>{
                return callback(session._id);
            });
        }else callback(result);
    }

    handlePostMessage(message){
        socket.emit('chat',{currUser:this.props.user,friend:this.state.friend._id});
        postMessage(this.state.sessionId, this.props.user, this.state.friend._id ,message, (newMessage)=>{
            this.setState({message:newMessage},()=>{
                getSessions(this.props.user, (sessions) => {
                    this.setState({
                        sessions:sessions,
                        btnText:"load earier messages"
                    })
                });
            });
        });
    }

    handleSwitchFriends(friendData){
        this.setState({friend:friendData},
            ()=>{
                this.getSession(this.state.friend._id,(session)=>{
                    this.setState({
                        sessionId:session
                    },
                    ()=>{
                        getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                            this.setState({
                                message:message,
                                btnText:message.length===0?"say hello to your friend!":"load earier messages"
                            })
                        });
                    });
                });
            }
        );
    }
    handleLoadMessage(e){
        e.preventDefault();
        var time = this.state.message.length===0?(new Date().getTime()):this.state.message[0].date;
        getMessages(time,this.props.user,this.state.sessionId,(messages)=>{
            if(messages.length===0){
                return this.setState({
                    btnText: "nothing more to load"
                })
            }
            var newMessages = messages.concat(this.state.message);
            this.setState({
                message:newMessages
            });
        });
    }

    render() {
        var chatwindow =
        (
            <ChatWindow target={this.state.friend} curUser={this.props.user}
            onPost={(message)=>this.handlePostMessage(message)}
            message={this.state.message}
            onLoad={(e)=>this.handleLoadMessage(e)}
            onExpand={()=>this.setState({open:!this.state.open})}
            btnText={this.state.btnText}>
            </ChatWindow>
        );
        if(this.state.user.friends === undefined? true: this.state.user.friends.length === 0){
            chatwindow = (

                        You don't have any chats yet

            )
        }
        return (

                <Drawer open={this.state.open} width={300} docked={false} onRequestChange={(open) => this.setState({open:open})}>
                    <ChatNavBody sessions={this.state.sessions}
                    userData={this.state.user} activeFriend={this.state.friend._id} switchUser={(id)=>this.handleSwitchFriends(id)}/>
                </Drawer>
                <Navbar chat="active" user={this.state.user}/>

                            <ChatNavBody sessions={this.state.sessions}
                            userData={this.state.user} activeFriend={this.state.friend._id} switchUser={(id)=>this.handleSwitchFriends(id)}/>

                        {chatwindow}

        );
    }
}
<commit_msg>fix online/offline not realtime bug<commit_after>import React from 'react';

import {Navbar} from '../containers';
import {ChatNavBody} from '../containers';
import {ChatWindow} from '../containers';

//request function
import {getUserData,getMessages,postMessage,getSessions,getSessionId} from '../../utils';
//credentials function
import {socket} from '../../utils';

import Drawer from 'material-ui/Drawer';
// var debug = require('react-debug');

export default class Chat extends React.Component {

    constructor(props) {
        super(props);
        this.state = {
            user: {},
            message :[],
            sessions: [],
            friend: "",
            sessionId:"",
            btnText:"load earier messages",
            open:false
        };
    }

    componentDidMount() {
        this.getData();
        socket.on('online',(data)=>{
            var tmp = Object.assign({},this.state.user);
            tmp.friends.forEach((i)=>{if(i._id===data.user)i.online=data.online})
            this.setState({ user:tmp })
        })
    }

    componentWillUpdate(){
        socket.removeAllListeners("chat");
    }

    componentDidUpdate(){
        socket.on('chat',()=>{
            this.getSession(this.state.friend._id,(session)=>{
                this.setState({
                    sessionId:session
                },
                ()=>{
                    getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                        this.setState({
                            message:message
                        },()=>{
                            getSessions(this.props.user, (sessions) => {
                                this.setState({
                                    sessions:sessions,
                                    btnText:"load earier messages"
                                });
                            });
                        })
                    });
                })
            });
        });
    }

    getData() {
        getUserData(this.props.user, (userData) => {
            this.setState({
                user:userData
            },()=>{
                getSessions(this.props.user,(sessions)=>{
                    this.setState({
                        sessions:sessions,
                        friend:this.state.user.friends[0]
                    },()=>{
                        this.getSession(this.state.friend._id,(session)=>{
                            this.setState({
                                sessionId:session
                            },
                            ()=>{
                                getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                                    this.setState({
                                        message:message,
                                        btnText:message.length===0?"say hello to your friend!":"load earier messages"
                                    })
                                });
                            });
                        });
                    });
                });
            })
        });
    }

    getSession(friend,callback){
        var sessions = this.state.sessions;
        var result = null;
        sessions.forEach(session=>{
            if(session.users.indexOf(friend)!==-1){
                return result = session._id;
            }
        });
        if(result === null){
            getSessionId(this.props.user,friend,(session)=>{
                return callback(session._id);
            });
        }else callback(result);
    }

    handlePostMessage(message){
        socket.emit('chat',{currUser:this.props.user,friend:this.state.friend._id});
        postMessage(this.state.sessionId, this.props.user, this.state.friend._id ,message, (newMessage)=>{
            this.setState({message:newMessage},()=>{
                getSessions(this.props.user, (sessions) => {
                    this.setState({
                        sessions:sessions,
                        btnText:"load earier messages"
                    })
                });
            });
        });
    }

    handleSwitchFriends(friendData){
        this.setState({friend:friendData},
            ()=>{
                this.getSession(this.state.friend._id,(session)=>{
                    this.setState({
                        sessionId:session
                    },
                    ()=>{
                        getMessages((new Date().getTime()),this.props.user,this.state.sessionId,(message)=>{
                            this.setState({
                                message:message,
                                btnText:message.length===0?"say hello to your friend!":"load earier messages"
                            })
                        });
                    });
                });
            }
        );
    }
    handleLoadMessage(e){
        e.preventDefault();
        var time = this.state.message.length===0?(new Date().getTime()):this.state.message[0].date;
        getMessages(time,this.props.user,this.state.sessionId,(messages)=>{
            if(messages.length===0){
                return this.setState({
                    btnText: "nothing more to load"
                })
            }
            var newMessages = messages.concat(this.state.message);
            this.setState({
                message:newMessages
            });
        });
    }

    render() {
        var chatwindow =
        (
            <ChatWindow target={this.state.friend} curUser={this.props.user}
            onPost={(message)=>this.handlePostMessage(message)}
            message={this.state.message}
            onLoad={(e)=>this.handleLoadMessage(e)}
            onExpand={()=>this.setState({open:!this.state.open})}
            btnText={this.state.btnText}>
            </ChatWindow>
        );
        if(this.state.user.friends === undefined? true: this.state.user.friends.length === 0){
            chatwindow = (

                        You don't have any chats yet

            )
        }
        return (

                <Drawer open={this.state.open} width={300} docked={false} onRequestChange={(open) => this.setState({open:open})}>
                    <ChatNavBody sessions={this.state.sessions}
                    userData={this.state.user} activeFriend={this.state.friend._id} switchUser={(id)=>this.handleSwitchFriends(id)}/>
                </Drawer>
                <Navbar chat="active" user={this.state.user}/>

                            <ChatNavBody sessions={this.state.sessions}
                            userData={this.state.user} activeFriend={this.state.friend._id} switchUser={(id)=>this.handleSwitchFriends(id)}/>

                        {chatwindow}

        );
    }
}
<|endoftext|>


<commit_before>import React, { Component } from 'react';
import { NavLink, Link} from 'react-router-dom';
import {connect} from 'react-redux';
import store from '../store';

class ArtistDashboard extends Component {

  constructor(props) {
    super(props);
  }

  render(){
    const currentUser = this.props.currentUser;
    if (currentUser.id) {
      return (

Welcome, {currentUser.name}

      );
    } else {
      return (

Welcome

    );
    }
  }
}

const mapStateToProps = ({currentUser}) => ({currentUser});

export default connect(mapStateToProps)(ArtistDashboard);
<commit_msg>removed unused artist-dashboard file<commit_after><|endoftext|>