import './watcher.js';

describe('Component vnWatcher', () => {
    let $scope;
    let $element;
    let $state;
    let $httpBackend;
    let controller;
    let $attrs;
    let $q;
    let data;

    beforeEach(ngModule('vnCore'));

    beforeEach(inject(($componentController, $rootScope, _$httpBackend_, _$state_, _$q_) => {
        data = {
            id: 1,
            foo: 'bar'
        };

        $scope = $rootScope.$new();
        $element = angular.element('<div></div>');
        $state = _$state_;
        $q = _$q_;
        $httpBackend = _$httpBackend_;
        $attrs = {
            save: 'patch'
        };
        controller = $componentController('vnWatcher', {$scope, $element, $state, $httpBackend, $attrs, $q});
    }));

    describe('$onInit()', () => {
        it('should set data empty by default', () => {
            controller.$onInit();

            expect(controller.data).toBeUndefined();
        });

        it('should set new data when insert mode is enabled', () => {
            controller.insertMode = true;
            controller.data = data;

            controller.$onInit();

            expect(controller.orgData).toEqual(data);
            expect(controller.orgData).toEqual(data);
            expect(controller.isNew).toBeTruthy();
        });

        it('should call backend and fetch data if url and idValue properties are defined', () => {
            controller.url = 'Foos';
            controller.idValue = 1;

            $httpBackend.expectGET('Foos/1').respond(data);
            controller.$onInit();
            $httpBackend.flush();

            expect(controller.orgData).toEqual(data);
            expect(controller.orgData).toEqual(data);
            expect(controller.orgData).not.toBe(controller.data);
        });
    });

    describe('fetch()', () => {
        it(`should perform a query then store the received data into data property and make an snapshot into orgData`, () => {
            controller.url = 'Bars';
            controller.idValue = 1;

            $httpBackend.expectGET('Bars/1').respond(data);
            controller.$onInit();
            $httpBackend.flush();

            expect(controller.orgData).toEqual(data);
            expect(controller.orgData).toEqual(data);
            expect(controller.orgData).not.toBe(controller.data);
        });
    });

    describe('submitBack()', () => {
        it('should call controller.window.history.back() function after calling controllers submit() function', done => {
            jest.spyOn(controller, 'submit').mockReturnValue(Promise.resolve());
            jest.spyOn(controller.window.history, 'back');
            controller.submitBack()
                .then(() => {
                    expect(controller.submit).toHaveBeenCalledWith();
                    expect(controller.window.history.back).toHaveBeenCalledWith();
                    done();
                }).catch(done.fail);
        });
    });

    describe('submitGo()', () => {
        it('should call controller.$state.go() function after calling controllers submit() function', done => {
            jest.spyOn(controller, 'submit').mockReturnValue(Promise.resolve());
            jest.spyOn(controller.$state, 'go');
            let state = 'the.State';
            controller.submitGo(state)
                .then(() => {
                    expect(controller.submit).toHaveBeenCalledWith();
                    expect(controller.$state.go).toHaveBeenCalledWith(state, {});
                    done();
                }).catch(done.fail);
        });
    });

    describe('check()', () => {
        it(`should throw error if controller.form is invalid`, () => {
            controller.form = {$invalid: true};

            expect(function() {
                controller.check();
            }).toThrowError();
        });
    });

    describe('realSubmit()', () => {
        describe('when controller.form', () => {
            it(`should call controller.form.setSubmited if controller.form is defined`, () => {
                controller.form = {
                    $setSubmitted: () => {},
                    $setPristine: () => {}
                };
                jest.spyOn(controller.form, '$setSubmitted');
                controller.realSubmit();

                expect(controller.form.$setSubmitted).toHaveBeenCalledWith();
            });
        });

        describe('when controller.save()', () => {
            it(`should set controller.save.model property`, () => {
                controller.save = {accept: () => $q.resolve()};
                controller.data = {originalInfo: 'original data', info: 'new data'};
                controller.orgData = {originalInfo: 'original data'};
                controller.realSubmit();

                expect(controller.save.model).toEqual({info: 'new data'});
            });
        });

        describe('should perform a PATCH query and save the data', () => {
            it(`should perform a query then call controller.writeData()`, () => {
                controller.url = 'Foos';
                controller.data = data;

                const changedData = {baz: 'value'};
                Object.assign(controller.data, changedData);

                $httpBackend.expectPATCH('Foos/1', changedData).respond({newProp: 'some'});
                controller.realSubmit();
                $httpBackend.flush();

                expect(controller.data).toEqual(Object.assign({}, data, changedData));
            });
        });

        it(`should perform a POST query and save the data`, () => {
            controller.insertMode = true;
            controller.url = 'Foos';
            controller.data = data;

            const changedData = {baz: 'value'};
            Object.assign(controller.data, changedData);

            $httpBackend.expectPOST('Foos', controller.data).respond({newProp: 'some'});
            controller.realSubmit();
            $httpBackend.flush();

            expect(controller.data).toEqual(Object.assign({}, data, changedData));
        });

        describe('should perform a DELETE query and save empty data', () => {
            it(`should perform a query then call controller.writeData()`, () => {
                controller.url = 'Foos';
                controller.data = data;
                controller.delete();

                $httpBackend.expectDELETE('Foos/1').respond();
                controller.realSubmit();
                $httpBackend.flush();

                expect(controller.data).toBeNull();
            });
        });
    });

    describe('writeData()', () => {
        it(`should save data into orgData`, () => {
            controller.data = data;
            Object.assign(controller.data, {baz: 'value'});

            controller.writeData({});

            expect(controller.data).toEqual(controller.orgData);
        });
    });

    describe('callback()', () => {
        describe(`when dataChanged() returns true and there's no state in the controller`, () => {
            it(`should define controller.state, call controller.$.confirm.show() and return false`, () => {
                $scope.confirm = {show: jasmine.createSpy('show')};
                controller.dataChanged = () => {
                    return true;
                };
                controller.state = undefined;
                let transition = {to: () => {
                    return {name: 'Batman'};
                }};
                let result = controller.callback(transition);

                expect(controller.state).toEqual('Batman');
                expect(controller.$.confirm.show).toHaveBeenCalledWith();
                expect(result).toBeFalsy();
            });
        });

        describe(`when dataChanged() returns false and/or there's a state in the controller`, () => {
            it(`should return true`, () => {
                $scope.confirm = {show: jasmine.createSpy('show')};
                controller.dataChanged = () => {
                    return false;
                };
                controller.state = 'the state';
                let transition = {to: () => {
                    return {name: 'Batman'};
                }};
                let result = controller.callback(transition);

                expect(result).toBeTruthy();
            });
        });
    });

    describe(`onConfirmResponse()`, () => {
        describe(`when response is accept`, () => {
            it(`should reset data them go to state`, () => {
                let data = {key: 'value'};
                controller.data = data;
                data.foo = 'bar';
                controller.$state = {go: jasmine.createSpy('go')};
                controller.state = 'foo.bar';
                controller.onConfirmResponse('accept');

                expect(controller.data).toEqual({key: 'value'});
                expect(controller.$state.go).toHaveBeenCalledWith(controller.state);
            });
        });

        describe(`when response is not accept`, () => {
            it(`should set controller.state to null`, () => {
                controller.state = 'Batman';
                controller.onConfirmResponse('cancel');

                expect(controller.state).toBeFalsy();
            });
        });
    });

    describe(`reset()`, () => {
        it(`should reset data as it was before changing it`, () => {
            let data = {key: 'value'};

            controller.data = data;
            data.foo = 'bar';
            controller.reset();

            expect(data).toEqual({key: 'value'});
        });
    });
});