<commit_before>describe('The dp-map directive', function () {
    let $compile,
        $rootScope,
        L,
        layers,
        highlight,
        panning,
        zoom,
        drawTool,
        onMapClick,
        mockedMapState,
        mockedLeafletMap,
        mockedMarkers,
        DRAW_TOOL_CONFIG;

    beforeEach(function () {
        angular.mock.module(
            'dpMap',
            {
                store: {
                    dispatch: angular.noop,
                    subscribe: angular.noop
                },
                leafletMap: {
                    invalidateSize: angular.noop
                },
                mapConfig: {
                    MAP_OPTIONS: {
                        doThisThing: false,
                        someVariable: 4
                    }
                },
                highlight: {
                    initialize: angular.noop,
                    addMarker: angular.noop,
                    removeMarker: angular.noop,
                    setCluster: angular.noop,
                    clearCluster: angular.noop
                },
                panning: {
                    initialize: angular.noop,
                    panTo: angular.noop,
                    setOption: angular.noop
                },
                zoom: {
                    initialize: angular.noop,
                    setZoom: angular.noop
                },
                onMapClick: {
                    initialize: angular.noop
                },
                user: {
                    getAuthorizationLevel: angular.noop
                },
                overlays: {
                    SOURCES: {
                        'some_overlay': 'some_overlay',
                        'some_other_overlay': 'some_other_overlay'
                    }
                }
            },

            function ($provide) {
                $provide.factory('dpLinkDirective', function () {
                    return {};
                });

                $provide.factory('dpToggleLayerSelectionDirective', function () {
                    return {};
                });

                $provide.factory('dpDrawToolDirective' + '', function () {
                    return {};
                });

                $provide.factory('dpActiveOverlaysDirective', function () {
                    return {};
                });

                $provide.factory('dpToggleFullscreenDirective', function () {
                    return {};
                });
            }
        );
        mockedLeafletMap = {
            invalidateSize: angular.noop
        };

        angular.mock.inject(function (
            _$compile_,
            _$rootScope_,
            _L_,
            _layers_,
            _highlight_,
            _panning_,
            _zoom_,
            _drawTool_,
            _onMapClick_,
            _DRAW_TOOL_CONFIG_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            L = _L_;
            layers = _layers_;
            highlight = _highlight_;
            panning = _panning_;
            zoom = _zoom_;
            drawTool = _drawTool_;
            onMapClick = _onMapClick_;
            DRAW_TOOL_CONFIG = _DRAW_TOOL_CONFIG_;
        });
        spyOn(L, 'map').and.returnValue(mockedLeafletMap);

        spyOn(mockedLeafletMap, 'invalidateSize');
        spyOn(layers, 'setBaseLayer');
        spyOn(layers, 'addOverlay');
        spyOn(layers, 'removeOverlay');
        spyOn(highlight, 'initialize');
        spyOn(highlight, 'addMarker');
        spyOn(highlight, 'removeMarker');
        spyOn(highlight, 'setCluster').and.callThrough();
        spyOn(highlight, 'clearCluster');

        spyOn(panning, 'initialize');
        spyOn(panning, 'panTo');
        spyOn(panning, 'setOption');
        spyOn(zoom, 'initialize');
        spyOn(zoom, 'setZoom');
        spyOn(drawTool, 'initialize').and.callThrough();
        spyOn(drawTool, 'enable');
        spyOn(drawTool, 'disable');
        spyOn(onMapClick, 'initialize');

        mockedMapState = {
            baseLayer: 'topografie',
            overlays: [],
            viewCenter: [52.789, 4.123],
            isFullscreen: false,
            zoom: 12
        };

        mockedMarkers = {
            regular: [],
            clustered: []
        };
    });

    function getDirective (mapState, showLayerSelection, markers, useRootScopeApply, resize) {
        const element = document.createElement('dp-map');
        element.setAttribute('map-state', 'mapState');
        element.setAttribute('markers', 'markers');
        element.setAttribute('show-layer-selection', 'showLayerSelection');
        element.setAttribute('resize', 'resize');

        const scope = $rootScope.$new();
        scope.mapState = mapState;
        scope.markers = markers;
        scope.resize = resize;
        scope.showLayerSelection = showLayerSelection;

        const directive = $compile(element)(scope);

        scope.$apply();

        if (angular.isUndefined(useRootScopeApply) || useRootScopeApply) {
            $rootScope.$apply();
        }

        return directive;
    }

    it('doesn\'t initialize until the next digest cycle', function () {
        /**
         * This is needed to ensure that the map has a width. To have a width it needs to be appended to the DOM. And
         * adding to the DOM happens the next digest cycle.
         */
        getDirective(mockedMapState, false, mockedMarkers, false);
        expect(L.map).not.toHaveBeenCalled();

        $rootScope.$apply();
        expect(L.map).toHaveBeenCalled();
    });

    it('creates a Leaflet map with options based on both the map state and mapConfig', function () {
        const directive = getDirective(mockedMapState, false, mockedMarkers);
        const element = directive.find('.qa-leaflet-map');

        expect(L.map).toHaveBeenCalledWith(element[0], {
            center: [52.789, 4.123],
            zoom: 12,
            doThisThing: false,
            someVariable: 4
        });
    });

    describe('has a base layer which', function () {
        it('is set on initialization', function () {
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.setBaseLayer).toHaveBeenCalledWith(mockedLeafletMap, 'topografie');
        });

        it('changes when the mapState changes', function () {
            getDirective(mockedMapState, false, mockedMarkers);
            expect(layers.setBaseLayer).toHaveBeenCalledTimes(1);

            mockedMapState.baseLayer = 'luchtfoto_2015';
            $rootScope.$apply();

            expect(layers.setBaseLayer).toHaveBeenCalledTimes(2);
            expect(layers.setBaseLayer).toHaveBeenCalledWith(mockedLeafletMap, 'luchtfoto_2015');
        });
    });

    describe('has overlays which', function () {
        let user,
            overlays;

        beforeEach(function () {
            angular.mock.inject(function (_overlays_, _user_) {
                overlays = _overlays_;
                user = _user_;
            });
        });

        it('can be added on initialization', function () {
            mockedMapState.overlays = [{id: 'some_overlay', isVisible: true}];
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
        });

        it('can be added when the mapState changes', function () {
            getDirective(mockedMapState, false, mockedMarkers);
            expect(layers.addOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            $rootScope.$apply();

            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('can be removed when the mapState changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('is updated when the user authorization level changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            spyOn(user, 'getAuthorizationLevel').and.returnValue(1);

            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            user.getAuthorizationLevel.and.returnValue(2);
            overlays.SOURCES = {
                'some_overlay': 'some_overlay'  // the other overlay is removed for  this auth level
            };

            $rootScope.$digest();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('can be removed when isVisible changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            getDirective(mockedMapState, false, mockedMarkers);

            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: false},
                {id: 'some_other_overlay', isVisible: true}
            ];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.removeOverlay).not.toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });
    });

    describe('has highlight options', function () {
        it('that gets a call to .initialize() so it can configure Leaflet variables', function () {
            expect(highlight.initialize).not.toHaveBeenCalled();

            getDirective(mockedMapState, false, mockedMarkers);
            expect(highlight.initialize).toHaveBeenCalled();
        });

        describe('that manage individual markers', function () {
            it('can be added on initialisation', function () {
                getDirective(mockedMapState, false, {
                    regular: [{id: 'FAKE_HIGHLIGHT_ITEM_A'}, {id: 'FAKE_HIGHLIGHT_ITEM_B'}],
                    clustered: []
                });

                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_A'});
                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_B'});
            });

            it('can be added by changing the input', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A'},
                        {id: 'FAKE_HIGHLIGHT_ITEM_B'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                highlightItems.regular.push({id: 'FAKE_HIGHLIGHT_ITEM_C'});
                $rootScope.$apply();

                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_C'});
            });

            it('can be removed from the map', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A'},
                        {id: 'FAKE_HIGHLIGHT_ITEM_B'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                highlightItems.regular.pop();
                $rootScope.$apply();

                expect(highlight.removeMarker).toHaveBeenCalledWith(
                    mockedLeafletMap,
                    {
                        id: 'FAKE_HIGHLIGHT_ITEM_B'
                    }
                );
            });

            it('deletes and re-adds changed icons', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A', geometry: 'FAKE_GEOMETRY_A'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                expect(highlight.addMarker).toHaveBeenCalledTimes(1);
                expect(highlight.addMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_A'
                });
                expect(highlight.removeMarker).not.toHaveBeenCalled();

                // Change the marker
                highlightItems.regular.length = 0;
                highlightItems.regular.push({
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_B'
                });
                $rootScope.$apply();

                expect(highlight.removeMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_A'
                });

                expect(highlight.addMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_B'
                });
            });
        });

        describe('that manages clustered markers', function () {
            it('can add a group of clustered markers', function () {
                // Start without any clustered markers
                const highlightItems = {
                    regular: [],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);
                $rootScope.$apply();
                expect(highlight.setCluster).not.toHaveBeenCalled();

                // Add one marker
                highlightItems.clustered.push([52.1, 4.1], [52.2, 4.1]);
                $rootScope.$apply();
                expect(highlight.setCluster).toHaveBeenCalledWith(
                    mockedLeafletMap,
                    [
                        [52.1, 4.1],
                        [52.2, 4.1]
                    ]
                );
            });

            it('can remove a group of clustered markers', function () {
                const highlightItems = {
                    regular: [],
                    clustered: [
                        [52.1, 4.1],
                        [52.2, 4.1]
                    ]
                };

                getDirective(mockedMapState, false, highlightItems);
                $rootScope.$apply();

                // Now remove the clustered markers
                highlightItems.clustered.length = 0;
                $rootScope.$apply();

                expect(highlight.clearCluster).toHaveBeenCalledWith(mockedLeafletMap);
            });
        });
    });

    describe('panning factory', function () {
        beforeEach(function () {
            getDirective(mockedMapState, false, mockedMarkers);
        });

        it('is initialized', function () {
            expect(panning.initialize).toHaveBeenCalledWith(mockedLeafletMap);
        });

        it('is called whenever mapState.viewCenter changes', function () {
            expect(panning.panTo).toHaveBeenCalledTimes(1);
            expect(panning.panTo).toHaveBeenCalledWith(mockedLeafletMap, [52.789, 4.123]);

            mockedMapState.viewCenter = [53, 5];
            $rootScope.$apply();

            expect(panning.panTo).toHaveBeenCalledTimes(2);
            expect(panning.panTo).toHaveBeenCalledWith(mockedLeafletMap, [53, 5]);
        });
    });

    describe('zoom factory', function () {
        beforeEach(function () {
            getDirective(mockedMapState, false, mockedMarkers);
        });

        it('is initialized', function () {
            expect(zoom.initialize).toHaveBeenCalledWith(mockedLeafletMap);
        });

        it('is called whenever mapState.zoom changes', function () {
            expect(zoom.setZoom).toHaveBeenCalledTimes(1);
            expect(zoom.setZoom).toHaveBeenCalledWith(mockedLeafletMap, 12);

            mockedMapState.zoom = 11;
            $rootScope.$apply();

            expect(zoom.setZoom).toHaveBeenCalledTimes(2);
            expect(zoom.setZoom).toHaveBeenCalledWith(mockedLeafletMap, 11);
        });
    });

    it('initializes the onMapClick factory', function () {
        getDirective(mockedMapState, false, mockedMarkers);

        expect(onMapClick.initialize).toHaveBeenCalledWith(mockedLeafletMap);
    });

    describe('resize state', function () {
        it('invalidateSize when resize state changes', function () {
            const mockedResizeArray = ['1', '2'];

            getDirective(mockedMapState, true, mockedMarkers, true, mockedResizeArray);

            expect(mockedLeafletMap.invalidateSize).not.toHaveBeenCalled();

            mockedResizeArray.push('3');
            $rootScope.$apply();

            expect(mockedLeafletMap.invalidateSize).toHaveBeenCalled();
        });
    });

    describe('draw state', () => {
        it('should set the draw mode to none when drawing and editing are not active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.NONE;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-none');
        });

        it('should set the draw mode to draw when drawing is active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.DRAW;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-draw');
        });

        it('should set the draw mode to draw when editing is active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.EDIT;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-edit');
        });
    });
});
<commit_msg>added unit test tg-2688<commit_after>describe('The dp-map directive', function () {
    let $compile,
        $rootScope,
        L,
        layers,
        highlight,
        panning,
        zoom,
        drawTool,
        onMapClick,
        mockedMapState,
        mockedLeafletMap,
        mockedMarkers,
        DRAW_TOOL_CONFIG;

    beforeEach(function () {
        angular.mock.module(
            'dpMap',
            {
                store: {
                    dispatch: angular.noop,
                    subscribe: angular.noop
                },
                leafletMap: {
                    invalidateSize: angular.noop
                },
                mapConfig: {
                    MAP_OPTIONS: {
                        doThisThing: false,
                        someVariable: 4
                    }
                },
                highlight: {
                    initialize: angular.noop,
                    addMarker: angular.noop,
                    removeMarker: angular.noop,
                    setCluster: angular.noop,
                    clearCluster: angular.noop
                },
                panning: {
                    initialize: angular.noop,
                    panTo: angular.noop,
                    setOption: angular.noop
                },
                zoom: {
                    initialize: angular.noop,
                    setZoom: angular.noop
                },
                onMapClick: {
                    initialize: angular.noop
                },
                user: {
                    getAuthorizationLevel: angular.noop
                },
                overlays: {
                    SOURCES: {
                        'some_overlay': 'some_overlay',
                        'some_other_overlay': 'some_other_overlay'
                    }
                }
            },

            function ($provide) {
                $provide.factory('dpLinkDirective', function () {
                    return {};
                });

                $provide.factory('dpToggleLayerSelectionDirective', function () {
                    return {};
                });

                $provide.factory('dpDrawToolDirective' + '', function () {
                    return {};
                });

                $provide.factory('dpActiveOverlaysDirective', function () {
                    return {};
                });

                $provide.factory('dpToggleFullscreenDirective', function () {
                    return {};
                });
            }
        );
        mockedLeafletMap = {
            invalidateSize: angular.noop
        };

        angular.mock.inject(function (
            _$compile_,
            _$rootScope_,
            _L_,
            _layers_,
            _highlight_,
            _panning_,
            _zoom_,
            _drawTool_,
            _onMapClick_,
            _DRAW_TOOL_CONFIG_) {
            $compile = _$compile_;
            $rootScope = _$rootScope_;
            L = _L_;
            layers = _layers_;
            highlight = _highlight_;
            panning = _panning_;
            zoom = _zoom_;
            drawTool = _drawTool_;
            onMapClick = _onMapClick_;
            DRAW_TOOL_CONFIG = _DRAW_TOOL_CONFIG_;
        });
        spyOn(L, 'map').and.returnValue(mockedLeafletMap);

        spyOn(mockedLeafletMap, 'invalidateSize');
        spyOn(layers, 'setBaseLayer');
        spyOn(layers, 'addOverlay');
        spyOn(layers, 'removeOverlay');
        spyOn(highlight, 'initialize');
        spyOn(highlight, 'addMarker');
        spyOn(highlight, 'removeMarker');
        spyOn(highlight, 'setCluster').and.callThrough();
        spyOn(highlight, 'clearCluster');

        spyOn(panning, 'initialize');
        spyOn(panning, 'panTo');
        spyOn(panning, 'setOption');
        spyOn(zoom, 'initialize');
        spyOn(zoom, 'setZoom');
        spyOn(drawTool, 'initialize').and.callThrough();
        spyOn(drawTool, 'enable');
        spyOn(drawTool, 'disable');
        spyOn(onMapClick, 'initialize');

        mockedMapState = {
            baseLayer: 'topografie',
            overlays: [],
            viewCenter: [52.789, 4.123],
            isFullscreen: false,
            zoom: 12
        };

        mockedMarkers = {
            regular: [],
            clustered: []
        };
    });

    function getDirective (mapState, showLayerSelection, markers, useRootScopeApply, resize) {
        const element = document.createElement('dp-map');
        element.setAttribute('map-state', 'mapState');
        element.setAttribute('markers', 'markers');
        element.setAttribute('show-layer-selection', 'showLayerSelection');
        element.setAttribute('resize', 'resize');

        const scope = $rootScope.$new();
        scope.mapState = mapState;
        scope.markers = markers;
        scope.resize = resize;
        scope.showLayerSelection = showLayerSelection;

        const directive = $compile(element)(scope);

        scope.$apply();

        if (angular.isUndefined(useRootScopeApply) || useRootScopeApply) {
            $rootScope.$apply();
        }

        return directive;
    }

    it('doesn\'t initialize until the next digest cycle', function () {
        /**
         * This is needed to ensure that the map has a width. To have a width it needs to be appended to the DOM. And
         * adding to the DOM happens the next digest cycle.
         */
        getDirective(mockedMapState, false, mockedMarkers, false);
        expect(L.map).not.toHaveBeenCalled();

        $rootScope.$apply();
        expect(L.map).toHaveBeenCalled();
    });

    it('creates a Leaflet map with options based on both the map state and mapConfig', function () {
        const directive = getDirective(mockedMapState, false, mockedMarkers);
        const element = directive.find('.qa-leaflet-map');

        expect(L.map).toHaveBeenCalledWith(element[0], {
            center: [52.789, 4.123],
            zoom: 12,
            doThisThing: false,
            someVariable: 4
        });
    });

    describe('has a base layer which', function () {
        it('is set on initialization', function () {
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.setBaseLayer).toHaveBeenCalledWith(mockedLeafletMap, 'topografie');
        });

        it('changes when the mapState changes', function () {
            getDirective(mockedMapState, false, mockedMarkers);
            expect(layers.setBaseLayer).toHaveBeenCalledTimes(1);

            mockedMapState.baseLayer = 'luchtfoto_2015';
            $rootScope.$apply();

            expect(layers.setBaseLayer).toHaveBeenCalledTimes(2);
            expect(layers.setBaseLayer).toHaveBeenCalledWith(mockedLeafletMap, 'luchtfoto_2015');
        });
    });

    describe('has overlays which', function () {
        let user,
            overlays;

        beforeEach(function () {
            angular.mock.inject(function (_overlays_, _user_) {
                overlays = _overlays_;
                user = _user_;
            });
        });

        it('can be added on initialization', function () {
            mockedMapState.overlays = [{id: 'some_overlay', isVisible: true}];
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
        });

        it('can be removed on initialization', function () {
            mockedMapState.overlays = [{id: 'some_overlay', isVisible: false}];
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
        });

        it('can be added when the mapState changes', function () {
            getDirective(mockedMapState, false, mockedMarkers);
            expect(layers.addOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            $rootScope.$apply();

            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.addOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('can be removed when the mapState changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            mockedMapState.overlays = [];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('is updated when the user authorization level changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            spyOn(user, 'getAuthorizationLevel').and.returnValue(1);

            getDirective(mockedMapState, false, mockedMarkers);

            expect(layers.removeOverlay).not.toHaveBeenCalled();

            user.getAuthorizationLevel.and.returnValue(2);
            overlays.SOURCES = {
                'some_overlay': 'some_overlay'  // the other overlay is removed for  this auth level
            };

            $rootScope.$digest();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });

        it('can be removed when isVisible changes', function () {
            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: true},
                {id: 'some_other_overlay', isVisible: true}
            ];
            getDirective(mockedMapState, false, mockedMarkers);

            mockedMapState.overlays = [
                {id: 'some_overlay', isVisible: false},
                {id: 'some_other_overlay', isVisible: true}
            ];
            $rootScope.$apply();

            expect(layers.removeOverlay).toHaveBeenCalledWith(mockedLeafletMap, 'some_overlay');
            expect(layers.removeOverlay).not.toHaveBeenCalledWith(mockedLeafletMap, 'some_other_overlay');
        });
    });

    describe('has highlight options', function () {
        it('that gets a call to .initialize() so it can configure Leaflet variables', function () {
            expect(highlight.initialize).not.toHaveBeenCalled();

            getDirective(mockedMapState, false, mockedMarkers);
            expect(highlight.initialize).toHaveBeenCalled();
        });

        describe('that manage individual markers', function () {
            it('can be added on initialisation', function () {
                getDirective(mockedMapState, false, {
                    regular: [{id: 'FAKE_HIGHLIGHT_ITEM_A'}, {id: 'FAKE_HIGHLIGHT_ITEM_B'}],
                    clustered: []
                });

                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_A'});
                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_B'});
            });

            it('can be added by changing the input', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A'},
                        {id: 'FAKE_HIGHLIGHT_ITEM_B'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                highlightItems.regular.push({id: 'FAKE_HIGHLIGHT_ITEM_C'});
                $rootScope.$apply();

                expect(highlight.addMarker)
                    .toHaveBeenCalledWith(mockedLeafletMap, {id: 'FAKE_HIGHLIGHT_ITEM_C'});
            });

            it('can be removed from the map', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A'},
                        {id: 'FAKE_HIGHLIGHT_ITEM_B'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                highlightItems.regular.pop();
                $rootScope.$apply();

                expect(highlight.removeMarker).toHaveBeenCalledWith(
                    mockedLeafletMap,
                    {
                        id: 'FAKE_HIGHLIGHT_ITEM_B'
                    }
                );
            });

            it('deletes and re-adds changed icons', function () {
                const highlightItems = {
                    regular: [
                        {id: 'FAKE_HIGHLIGHT_ITEM_A', geometry: 'FAKE_GEOMETRY_A'}
                    ],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);

                expect(highlight.addMarker).toHaveBeenCalledTimes(1);
                expect(highlight.addMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_A'
                });
                expect(highlight.removeMarker).not.toHaveBeenCalled();

                // Change the marker
                highlightItems.regular.length = 0;
                highlightItems.regular.push({
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_B'
                });
                $rootScope.$apply();

                expect(highlight.removeMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_A'
                });

                expect(highlight.addMarker).toHaveBeenCalledWith(mockedLeafletMap, {
                    id: 'FAKE_HIGHLIGHT_ITEM_A',
                    geometry: 'FAKE_GEOMETRY_B'
                });
            });
        });

        describe('that manages clustered markers', function () {
            it('can add a group of clustered markers', function () {
                // Start without any clustered markers
                const highlightItems = {
                    regular: [],
                    clustered: []
                };

                getDirective(mockedMapState, false, highlightItems);
                $rootScope.$apply();
                expect(highlight.setCluster).not.toHaveBeenCalled();

                // Add one marker
                highlightItems.clustered.push([52.1, 4.1], [52.2, 4.1]);
                $rootScope.$apply();
                expect(highlight.setCluster).toHaveBeenCalledWith(
                    mockedLeafletMap,
                    [
                        [52.1, 4.1],
                        [52.2, 4.1]
                    ]
                );
            });

            it('can remove a group of clustered markers', function () {
                const highlightItems = {
                    regular: [],
                    clustered: [
                        [52.1, 4.1],
                        [52.2, 4.1]
                    ]
                };

                getDirective(mockedMapState, false, highlightItems);
                $rootScope.$apply();

                // Now remove the clustered markers
                highlightItems.clustered.length = 0;
                $rootScope.$apply();

                expect(highlight.clearCluster).toHaveBeenCalledWith(mockedLeafletMap);
            });
        });
    });

    describe('panning factory', function () {
        beforeEach(function () {
            getDirective(mockedMapState, false, mockedMarkers);
        });

        it('is initialized', function () {
            expect(panning.initialize).toHaveBeenCalledWith(mockedLeafletMap);
        });

        it('is called whenever mapState.viewCenter changes', function () {
            expect(panning.panTo).toHaveBeenCalledTimes(1);
            expect(panning.panTo).toHaveBeenCalledWith(mockedLeafletMap, [52.789, 4.123]);

            mockedMapState.viewCenter = [53, 5];
            $rootScope.$apply();

            expect(panning.panTo).toHaveBeenCalledTimes(2);
            expect(panning.panTo).toHaveBeenCalledWith(mockedLeafletMap, [53, 5]);
        });
    });

    describe('zoom factory', function () {
        beforeEach(function () {
            getDirective(mockedMapState, false, mockedMarkers);
        });

        it('is initialized', function () {
            expect(zoom.initialize).toHaveBeenCalledWith(mockedLeafletMap);
        });

        it('is called whenever mapState.zoom changes', function () {
            expect(zoom.setZoom).toHaveBeenCalledTimes(1);
            expect(zoom.setZoom).toHaveBeenCalledWith(mockedLeafletMap, 12);

            mockedMapState.zoom = 11;
            $rootScope.$apply();

            expect(zoom.setZoom).toHaveBeenCalledTimes(2);
            expect(zoom.setZoom).toHaveBeenCalledWith(mockedLeafletMap, 11);
        });
    });

    it('initializes the onMapClick factory', function () {
        getDirective(mockedMapState, false, mockedMarkers);

        expect(onMapClick.initialize).toHaveBeenCalledWith(mockedLeafletMap);
    });

    describe('resize state', function () {
        it('invalidateSize when resize state changes', function () {
            const mockedResizeArray = ['1', '2'];

            getDirective(mockedMapState, true, mockedMarkers, true, mockedResizeArray);

            expect(mockedLeafletMap.invalidateSize).not.toHaveBeenCalled();

            mockedResizeArray.push('3');
            $rootScope.$apply();

            expect(mockedLeafletMap.invalidateSize).toHaveBeenCalled();
        });
    });

    describe('draw state', () => {
        it('should set the draw mode to none when drawing and editing are not active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.NONE;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-none');
        });

        it('should set the draw mode to draw when drawing is active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.DRAW;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-draw');
        });

        it('should set the draw mode to draw when editing is active', () => {
            mockedMapState.drawingMode = DRAW_TOOL_CONFIG.DRAWING_MODE.EDIT;

            const directive = getDirective(mockedMapState, false, mockedMarkers);
            const element = directive.find('.qa-map');

            expect(element.attr('class')).toContain('c-map--drawing-mode-edit');
        });
    });
});
<|endoftext|>


<commit_before>import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Button, Col, ControlLabel, Form, FormControl, FormGroup, Grid, Panel, Row } from 'react-bootstrap';

const InputField = ({ name, label, id=null, type='text' }) => (
	<FormGroup controlId={id || name}>
		<Col componentClass={ControlLabel} md={4}>
			{label}:
		</Col>
		<Col md={4}>
			<FormControl type={type} name={name} value="" />
		</Col>
	</FormGroup>
);

const Register = () => (
	<Grid fluid>
		<Row>
			<Col md={8} mdOffset={2}>
				<Panel header="Register" bsStyle="default">

					<Form horizontal>
						<InputField label="Name" name="name" />
						<InputField label="Email" name="email" />
						<InputField label="Password" name="password" type="password" />
						<InputField label="Confirm Password" name="password_confirmation" type="password" />
						<FormGroup>
							<Col md={4} mdOffset={4}>
								<Button type="submit">Register</Button>
							</Col>
						</FormGroup>
					</Form>
				</Panel>
			</Col>
		</Row>
	</Grid>
);

export default Register;
<commit_msg>InputField: remove value attribute<commit_after>import React from 'react';
import { Field, reduxForm } from 'redux-form';
import { Button, Col, ControlLabel, Form, FormControl, FormGroup, Grid, Panel, Row } from 'react-bootstrap';

const InputField = ({ name, label, id=null, type='text' }) => (
	<FormGroup controlId={id || name}>
		<Col componentClass={ControlLabel} md={4}>
			{label}:
		</Col>
		<Col md={4}>
			<FormControl type={type} name={name} />
		</Col>
	</FormGroup>
);

const Register = () => (
	<Grid fluid>
		<Row>
			<Col md={8} mdOffset={2}>
				<Panel header="Register" bsStyle="default">

					<Form horizontal>
						<InputField label="Name" name="name" />
						<InputField label="Email" name="email" type="email" />
						<InputField label="Password" name="password" type="password" />
						<InputField label="Confirm Password" name="password_confirmation" type="password" />
						<FormGroup>
							<Col md={4} mdOffset={4}>
								<Button type="submit">Register</Button>
							</Col>
						</FormGroup>
					</Form>
				</Panel>
			</Col>
		</Row>
	</Grid>
);

export default Register;
<|endoftext|>


<commit_before>friendFetcher = {
	methods: {
		getMixerId: function(username) {
		// This gets a channel id using a mixer username.
			return new Promise(function(resolve, reject) {

				var request = new XMLHttpRequest();
				request.open('GET', 'https://mixer.com/api/v1/channels/'+username+'?fields=userId', true);

				request.onload = function() {
					if (request.status >= 200 && request.status < 400) {
					// Success!
						var data = JSON.parse(request.responseText);
						resolve(data.userId);
					} else {
					// We reached our target server, but it returned an error
					// TODO: Display a message in the extension.
						reject(JSON.parse(request.responseText.message));
					}
				};

				request.onerror = function() {
				// There was a connection error of some sort
					reject('Error getting userId');
				};

				request.send();
			});
		},
		getMixerFollows: function(userId, page, followList){
		// This will get 50 follows on a specific page.
			return new Promise(function(resolve, reject) {
			// To test a lot of follows, uncomment the line below.
			// var userId = 313842;

				var request = new XMLHttpRequest();
				request.open('GET', 'https://mixer.com/api/v1/users/'+userId+'/follows?fields=id,online,name,token,viewersCurrent,partnered,costreamId,interactive,type&where=online:eq:true&order=token:ASC&limit=250&page='+page, true);

				request.onload = function() {
					if (request.status >= 200 && request.status < 400) {
					// Success!
						var data = JSON.parse(request.responseText);

						// Loop through data and throw in array.
						for (friend of data){
							// Set defaults not included in mixer data.
							friend.watchVideo = false;
							friend.offlineStrikes = 0;

							// Add to array
							followList.push(friend);
						}

						resolve(followList);

					} else {
					// We reached our target server, but it returned an error
						reject('Error getting followed channels.');
					}
				};

				request.onerror = function() {
				// There was a connection error of some sort
					reject('Error while getting followed channels.');
				};

				request.send();
			});
		},
		outputMixerFollows: function(username){
			var app = this;
			// This combines two functions so that we can get a full list of online followed channels with a username.
			return new Promise((resolve, reject) => {
				var page = 0;
				app.getMixerId(username)
					.then((userId) =>{
						app.getMixerFollows(userId, page, [])
							.then((followList) =>{
								resolve(followList);
							})
							.catch((err) => {
								reject(err);
							});
					})
					.catch((err) => {
						reject(err);
					});
			});
		}
	}
};<commit_msg>Pass client ID.<commit_after>friendFetcher = {
	methods: {
		getMixerId: function(username) {
		// This gets a channel id using a mixer username.
			return new Promise(function(resolve, reject) {

				var request = new XMLHttpRequest();
				request.open('GET', 'https://mixer.com/api/v1/channels/'+username+'?fields=userId', true);
				request.setRequestHeader("Client-ID", "8682f64ae59cbcba5cd701c205b54b04a424b46ca064e563");

				request.onload = function() {
					if (request.status >= 200 && request.status < 400) {
					// Success!
						var data = JSON.parse(request.responseText);
						resolve(data.userId);
					} else {
					// We reached our target server, but it returned an error
					// TODO: Display a message in the extension.
						reject(JSON.parse(request.responseText.message));
					}
				};

				request.onerror = function() {
				// There was a connection error of some sort
					reject('Error getting userId');
				};

				request.send();
			});
		},
		getMixerFollows: function(userId, page, followList){
		// This will get 50 follows on a specific page.
			return new Promise(function(resolve, reject) {
			// To test a lot of follows, uncomment the line below.
			// var userId = 313842;

				var request = new XMLHttpRequest();
				request.open('GET', 'https://mixer.com/api/v1/users/'+userId+'/follows?fields=id,online,name,token,viewersCurrent,partnered,costreamId,interactive,type&where=online:eq:true&order=token:ASC&limit=250&page='+page, true);
				request.setRequestHeader("Client-ID", "8682f64ae59cbcba5cd701c205b54b04a424b46ca064e563");
				request.onload = function() {
					if (request.status >= 200 && request.status < 400) {
					// Success!
						var data = JSON.parse(request.responseText);

						// Loop through data and throw in array.
						for (friend of data){
							// Set defaults not included in mixer data.
							friend.watchVideo = false;
							friend.offlineStrikes = 0;

							// Add to array
							followList.push(friend);
						}

						resolve(followList);

					} else {
					// We reached our target server, but it returned an error
						reject('Error getting followed channels.');
					}
				};

				request.onerror = function() {
				// There was a connection error of some sort
					reject('Error while getting followed channels.');
				};

				request.send();
			});
		},
		outputMixerFollows: function(username){
			var app = this;
			// This combines two functions so that we can get a full list of online followed channels with a username.
			return new Promise((resolve, reject) => {
				var page = 0;
				app.getMixerId(username)
					.then((userId) =>{
						app.getMixerFollows(userId, page, [])
							.then((followList) =>{
								resolve(followList);
							})
							.catch((err) => {
								reject(err);
							});
					})
					.catch((err) => {
						reject(err);
					});
			});
		}
	}
};<|endoftext|>


<commit_before>'use strict';

const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const OperationBaseV2 = require('./operation').OperationBaseV2;
const decorateWithCollation = require('../utils').decorateWithCollation;
const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
const executeCommand = require('./db_ops').executeCommand;
const handleCallback = require('../utils').handleCallback;
const resolveReadPreference = require('../utils').resolveReadPreference;

/**
 * Return a list of distinct values for the given key across a collection.
 *
 * @class
 * @property {Collection} a Collection instance.
 * @property {string} key Field of the document to find distinct values for.
 * @property {object} query The query for filtering the set of documents to which we apply the distinct filter.
 * @property {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
 */
class DistinctOperation extends OperationBaseV2 {
  /**
   * Construct a Distinct operation.
   *
   * @param {Collection} a Collection instance.
   * @param {string} key Field of the document to find distinct values for.
   * @param {object} query The query for filtering the set of documents to which we apply the distinct filter.
   * @param {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
   */
  constructor(collection, key, query, options) {
    super(collection, options);

    this.collection = collection;
    this.key = key;
    this.query = query;
  }

  /**
   * Execute the operation.
   *
   * @param {Collection~resultCallback} [callback] The command result callback
   */
  execute(callback) {
    const coll = this.collection;
    const key = this.key;
    const query = this.query;
    let options = this.options;

    // maxTimeMS option
    const maxTimeMS = options.maxTimeMS;

    // Distinct command
    const cmd = {
      distinct: coll.collectionName,
      key: key,
      query: query
    };

    options = Object.assign({}, options);
    // Ensure we have the right read preference inheritance
    options.readPreference = resolveReadPreference(coll, options);

    // Add maxTimeMS if defined
    if (typeof maxTimeMS === 'number') cmd.maxTimeMS = maxTimeMS;

    // Do we have a readConcern specified
    decorateWithReadConcern(cmd, coll, options);

    // Have we specified collation
    try {
      decorateWithCollation(cmd, coll, options);
    } catch (err) {
      return callback(err, null);
    }

    // Execute the command
    executeCommand(coll.s.db, cmd, options, (err, result) => {
      if (err) return handleCallback(callback, err);
      handleCallback(callback, null, result.values);
    });
  }
}

defineAspects(DistinctOperation, Aspect.READ_OPERATION);

module.exports = DistinctOperation;
<commit_msg>refactor(distinct): use selection for the distinct operation<commit_after>'use strict';

const Aspect = require('./operation').Aspect;
const defineAspects = require('./operation').defineAspects;
const OperationBaseV2 = require('./operation').OperationBaseV2;
const decorateWithCollation = require('../utils').decorateWithCollation;
const decorateWithReadConcern = require('../utils').decorateWithReadConcern;
const handleCallback = require('../utils').handleCallback;

/**
 * Return a list of distinct values for the given key across a collection.
 *
 * @class
 * @property {Collection} a Collection instance.
 * @property {string} key Field of the document to find distinct values for.
 * @property {object} query The query for filtering the set of documents to which we apply the distinct filter.
 * @property {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
 */
class DistinctOperation extends OperationBaseV2 {
  /**
   * Construct a Distinct operation.
   *
   * @param {Collection} a Collection instance.
   * @param {string} key Field of the document to find distinct values for.
   * @param {object} query The query for filtering the set of documents to which we apply the distinct filter.
   * @param {object} [options] Optional settings. See Collection.prototype.distinct for a list of options.
   */
  constructor(collection, key, query, options) {
    super(collection, options);

    this.collection = collection;
    this.key = key;
    this.query = query;
  }

  /**
   * Execute the operation.
   *
   * @param {Collection~resultCallback} [callback] The command result callback
   */
  execute(server, callback) {
    const coll = this.collection;
    const key = this.key;
    const query = this.query;
    const options = this.options;

    // Distinct command
    const cmd = {
      distinct: coll.collectionName,
      key: key,
      query: query
    };

    // Add maxTimeMS if defined
    if (typeof options.maxTimeMS === 'number') {
      cmd.maxTimeMS = options.maxTimeMS;
    }

    // Do we have a readConcern specified
    decorateWithReadConcern(cmd, coll, options);

    // Have we specified collation
    try {
      decorateWithCollation(cmd, coll, options);
    } catch (err) {
      return callback(err, null);
    }

    const ns =
      typeof coll.s.db.namespace === 'string'
        ? `${coll.s.db.namespace}.$cmd`
        : coll.s.db.namespace.withCollection('$cmd');

    server.command(ns, cmd, options, (err, result) => {
      if (err) return handleCallback(callback, err);

      result = result.result;
      handleCallback(callback, null, result.values);
    });
  }
}

defineAspects(DistinctOperation, [
  Aspect.READ_OPERATION,
  Aspect.RETRYABLE,
  Aspect.EXECUTE_WITH_SELECTION
]);

module.exports = DistinctOperation;
<|endoftext|>


<commit_before>/**
 * This file constructs the XMOT.util namespace and adds miscellaneous utilities.
 */
(function() {

    if(!XMOT.util)
        XMOT.util = {};

    var u = XMOT.util;

    /**
     * Can be used to wrap the given method into a closure that preserves the
     * this pointer inside the given function. Internally an object
     * __callbacks will be attached to the given object. This way several calls
     * to this function with the same function will return the same wrapper.
     * This is needed to successfully unregister callbacks again.
     *
     * @param {!Object} obj the object on which to call the method f
     * @param {!function()} fn the method to be wrapped
     *
     * @return {function()} the wrapped function
     */
    u.wrapCallback = function(obj, fn)
    {

        if(!obj.__callbacks)
            obj.__callbacks = {};

        if(!obj.__callbacks[fn])
        {
            var method = fn;
            var self = obj;

            obj.__callbacks[fn] = function() {
                return method.apply(self, arguments);
            };
        }

        return obj.__callbacks[fn];
    };

    /**
     * Retrieve the world bounding box of a given node
     *
     * @param {!Object} node
     * @return {XML3DBox}
     */
    u.getWorldBBox = function(node)
    {
        if(!node.getBoundingBox)
            return new window.XML3DBox();

        var bbox = node.getBoundingBox();

        if(node.parentNode.getWorldMatrix)
        {
            var parentGlobMat = node.parentNode.getWorldMatrix();
            bbox.transform(parentGlobMat);
        }

        return bbox;
    };

    /**
     * Retrieve the bounding box of the children of a given node.
     *
     * @param {!Object} node
     * @return {XML3DBox}
     */
    u.getChildrenBBox = function(node)
    {
        var bbox = new window.XML3DBox();

        var curChild = node.firstChild;
        while(curChild)
        {
            if(curChild.getBoundingBox)
                bbox.extend(curChild.getBoundingBox());

            curChild = curChild.nextSibling;
        }

        return bbox;
    };

    /** Returns the xml3d element in which the given element is contained.
     * If none is found, null is returned.
     *
     * @param {!Object} el
     * @return {Object}
     */
    u.getXml3dRoot = function(el)
    {
        if(!el)
            return null;

        if(el.tagName == "xml3d")
            return el;

        if(el.parentNode)
            return u.getXml3dRoot(el.parentNode);

        return null;
    };

    /** Internal helper method. Gets and/or sets a reference pointed to by attrName
     *  of the element el. For more information see XMOT.util.transform()
     *  or XMOT.util.shader().
     *
     *  @param {!Object} el
     *  @param {!string} attrName
     *  @param {Object} newRefNode
     *
     *  @return {Object}
     */
    function getOrSetRefNode(el, attrName, newRefNode)
    {
        var oldRefNode = null;

        var oldRef = el.getAttribute(attrName);
        if(oldRef && oldRef.length > 0)
            oldRefNode = XML3D.URIResolver.resolveLocal(oldRef);

        if(newRefNode)
        {
            var newRef = newRefNode.getAttribute("id");
            if(newRef && newRef.length > 0)
            {
                newRef = "#" + newRef;
                el.setAttribute(attrName, newRef);
            }
        }

        return oldRefNode;
    };

    /**
     * Retrieve or set the transform node of a given group node.
     *
     * @param {!Object} grp the desired group node
     * @param {Object} xfm (optional) the transform element to which grp's
     *  transform attribute should be set. The transform has to have an
     *  id attribute.
     *
     * @return {Object} if one argument is given, it returns the current transform
     *  element of el. If two arguments are given, it returns the transform element set
     *  before it was overridden by xfm.
     */
    u.transform = function(grp, xfm)
    {
        if(grp.tagName !== "group")
            throw "XMOT.util.transform(): given element is not a group.";

        return getOrSetRefNode(grp, "transform", xfm);
    };

    /** Retrieve or set the shader of the given group node.
     *
     * @param {!Object} grp the group of which to retrieve the shader.
     * @param {Object} sh (optional) the shader element to which el's shader
     *  attribute should be set. The shader has to have an id attribute.
     *
     * @return {Object} if one argument is given, it returns the current shader of el. If
     *  two arguments are given, it returns the shader set before it was overridden by sh.
     */
    u.shader = function(grp, sh)
    {
        if(grp.tagName !== "group")
            throw "XMOT.util.shader(): given element is not a group.";

        return getOrSetRefNode(grp, "shader", sh);
    };

    /**
     * Returns the first defs element of the given xml3d element. If none exists,
     * a corresponding element is created, appended to xml3d and returned.
     *
     * @param {!Object} xml3d the xml3d element of which to return the defs element
     *
     * @return {Object} the first defs element of the given xml3d element
     */
    u.getOrCreateDefs = function(xml3d)
    {
        var defs = XML3D.util.evaluateXPathExpr(
                xml3d, './/xml3d:defs[1]').singleNodeValue;

        if(!defs)
        {
            defs = XMOT.creation.element("defs");
            xml3d.appendChild(defs);
        }

        return defs;
    };

    /**
     * Returns the transform element corresponding to the given
     * group. If it doesn't exist, one will be created with the
     * id newId, appended to the defs section and targetGrp's
     * transform reference will be set to the newly created
     * element.
     *
     * @param {!Object} targetGrp the group to retrieve the transform from
     * @param {string} newId the id attribute of the transform to be created
     *
     * @return {Object} the transform corresponding to targetGrp
     */
    u.getOrCreateTransform = function(targetGrp, newId)
    {
        var t = XMOT.util.transform(targetGrp);

        if(t) // found it, just return
            return t;

        var xml3d = u.getXml3dRoot(targetGrp);
        if(!xml3d)
            throw "XMOT.util.getOrCreateTransform(): target group does have no xml3d root element!";
        var defs = u.getOrCreateDefs(xml3d);

        // create transform
        t = XMOT.creation.element("transform", {id: newId});
        defs.appendChild(t);
        targetGrp.setAttribute("transform", "#" + newId);

        return t;
    };

    /** Return the first child element of tarNode, whose "name" attribute
     * is equal to the given one. Useful for retrieving shader attributes.
     *
     * @param {!Object} tarNode
     * @param {string} name
     * @return {Object} the found node
     */
    u.getNamedChild = function(tarNode, name)
    {
        var nodes = document.getElementsByName(name);

        for(var i = 0; i < nodes.length; i++)
        {
            if(nodes[i].parentNode === tarNode)
                return nodes[i];
        }

        return null;
    };

    /** Get the children of the given node that reside in the XML3D namespace
     *
     *  @param {!Object} node
     *  @return {Array.<Object>}
     */
    u.getXML3DChildren = function(node)
    {
        var children = [];

        var n = node.firstChild;
        while(n)
        {
            if(n.namespaceURI === XML3D.xml3dNS)
                children.push(n);

            n = n.nextSibling;
        }

        return children;
    };
}());
<commit_msg>util: added isDefined() and getParentWorldMatrix()<commit_after>/**
 * This file constructs the XMOT.util namespace and adds miscellaneous utilities.
 */
(function() {

    if(!XMOT.util)
        XMOT.util = {};

    var u = XMOT.util;

    /**
     * Returns whether the object is actually given as argument. If it is it has
     * to be not undefined and not null.
     *
     * @param {Object=} obj
     * @return {boolean} true if the object is actually defined
     */
    u.isDefined = function(obj)
    {
        return (obj !== undefined && obj !== null);
    };

    /**
     * Can be used to wrap the given method into a closure that preserves the
     * this pointer inside the given function. Internally an object
     * __callbacks will be attached to the given object. This way several calls
     * to this function with the same function will return the same wrapper.
     * This is needed to successfully unregister callbacks again.
     *
     * @param {!Object} obj the object on which to call the method f
     * @param {!function()} fn the method to be wrapped
     *
     * @return {function()} the wrapped function
     */
    u.wrapCallback = function(obj, fn)
    {

        if(!obj.__callbacks)
            obj.__callbacks = {};

        if(!obj.__callbacks[fn])
        {
            var method = fn;
            var self = obj;

            obj.__callbacks[fn] = function() {
                return method.apply(self, arguments);
            };
        }

        return obj.__callbacks[fn];
    };

    /**
     * Retrieve the world bounding box of a given node
     *
     * @param {!Object} node
     * @return {XML3DBox}
     */
    u.getWorldBBox = function(node)
    {
        if(!node.getBoundingBox)
            return new window.XML3DBox();

        var bbox = node.getBoundingBox();

        if(node.parentNode.getWorldMatrix)
        {
            var parentGlobMat = node.parentNode.getWorldMatrix();
            bbox.transform(parentGlobMat);
        }

        return bbox;
    };

    /**
     * Returns the given node's parent world matrix. If no parent is present
     * or doesn't have a getWorldMatrix() method the identity matrix is returned.
     *
     * @param {Object} node
     * @return {window.XML3DMatrix}
     */
    u.getParentWorldMatrix = function(node)
    {
        if(!XMOT.util.isDefined(node.parentNode)
        || !XMOT.util.isDefined(node.parentNode.getWorldMatrix))
            return new window.XML3DMatrix();

        return node.parentNode.getWorldMatrix();
    };

    /**
     * Retrieve the bounding box of the children of a given node.
     *
     * @param {!Object} node
     * @return {XML3DBox}
     */
    u.getChildrenBBox = function(node)
    {
        var bbox = new window.XML3DBox();

        var curChild = node.firstChild;
        while(curChild)
        {
            if(curChild.getBoundingBox)
                bbox.extend(curChild.getBoundingBox());

            curChild = curChild.nextSibling;
        }

        return bbox;
    };

    /** Returns the xml3d element in which the given element is contained.
     * If none is found, null is returned.
     *
     * @param {!Object} el
     * @return {Object}
     */
    u.getXml3dRoot = function(el)
    {
        if(!el)
            return null;

        if(el.tagName == "xml3d")
            return el;

        if(el.parentNode)
            return u.getXml3dRoot(el.parentNode);

        return null;
    };

    /** Internal helper method. Gets and/or sets a reference pointed to by attrName
     *  of the element el. For more information see XMOT.util.transform()
     *  or XMOT.util.shader().
     *
     *  @param {!Object} el
     *  @param {!string} attrName
     *  @param {Object} newRefNode
     *
     *  @return {Object}
     */
    function getOrSetRefNode(el, attrName, newRefNode)
    {
        var oldRefNode = null;

        var oldRef = el.getAttribute(attrName);
        if(oldRef && oldRef.length > 0)
            oldRefNode = XML3D.URIResolver.resolveLocal(oldRef);

        if(newRefNode)
        {
            var newRef = newRefNode.getAttribute("id");
            if(newRef && newRef.length > 0)
            {
                newRef = "#" + newRef;
                el.setAttribute(attrName, newRef);
            }
        }

        return oldRefNode;
    };

    /**
     * Retrieve or set the transform node of a given group node.
     *
     * @param {!Object} grp the desired group node
     * @param {Object} xfm (optional) the transform element to which grp's
     *  transform attribute should be set. The transform has to have an
     *  id attribute.
     *
     * @return {Object} if one argument is given, it returns the current transform
     *  element of el. If two arguments are given, it returns the transform element set
     *  before it was overridden by xfm.
     */
    u.transform = function(grp, xfm)
    {
        if(grp.tagName !== "group")
            throw "XMOT.util.transform(): given element is not a group.";

        return getOrSetRefNode(grp, "transform", xfm);
    };

    /** Retrieve or set the shader of the given group node.
     *
     * @param {!Object} grp the group of which to retrieve the shader.
     * @param {Object} sh (optional) the shader element to which el's shader
     *  attribute should be set. The shader has to have an id attribute.
     *
     * @return {Object} if one argument is given, it returns the current shader of el. If
     *  two arguments are given, it returns the shader set before it was overridden by sh.
     */
    u.shader = function(grp, sh)
    {
        if(grp.tagName !== "group")
            throw "XMOT.util.shader(): given element is not a group.";

        return getOrSetRefNode(grp, "shader", sh);
    };

    /**
     * Returns the first defs element of the given xml3d element. If none exists,
     * a corresponding element is created, appended to xml3d and returned.
     *
     * @param {!Object} xml3d the xml3d element of which to return the defs element
     *
     * @return {Object} the first defs element of the given xml3d element
     */
    u.getOrCreateDefs = function(xml3d)
    {
        var defs = XML3D.util.evaluateXPathExpr(
                xml3d, './/xml3d:defs[1]').singleNodeValue;

        if(!defs)
        {
            defs = XMOT.creation.element("defs");
            xml3d.appendChild(defs);
        }

        return defs;
    };

    /**
     * Returns the transform element corresponding to the given
     * group. If it doesn't exist, one will be created with the
     * id newId, appended to the defs section and targetGrp's
     * transform reference will be set to the newly created
     * element.
     *
     * @param {!Object} targetGrp the group to retrieve the transform from
     * @param {string} newId the id attribute of the transform to be created
     *
     * @return {Object} the transform corresponding to targetGrp
     */
    u.getOrCreateTransform = function(targetGrp, newId)
    {
        var t = XMOT.util.transform(targetGrp);

        if(t) // found it, just return
            return t;

        var xml3d = u.getXml3dRoot(targetGrp);
        if(!xml3d)
            throw "XMOT.util.getOrCreateTransform(): target group does have no xml3d root element!";
        var defs = u.getOrCreateDefs(xml3d);

        // create transform
        t = XMOT.creation.element("transform", {id: newId});
        defs.appendChild(t);
        targetGrp.setAttribute("transform", "#" + newId);

        return t;
    };

    /** Return the first child element of tarNode, whose "name" attribute
     * is equal to the given one. Useful for retrieving shader attributes.
     *
     * @param {!Object} tarNode
     * @param {string} name
     * @return {Object} the found node
     */
    u.getNamedChild = function(tarNode, name)
    {
        var nodes = document.getElementsByName(name);

        for(var i = 0; i < nodes.length; i++)
        {
            if(nodes[i].parentNode === tarNode)
                return nodes[i];
        }

        return null;
    };

    /** Get the children of the given node that reside in the XML3D namespace
     *
     *  @param {!Object} node
     *  @return {Array.<Object>}
     */
    u.getXML3DChildren = function(node)
    {
        var children = [];

        var n = node.firstChild;
        while(n)
        {
            if(n.namespaceURI === XML3D.xml3dNS)
                children.push(n);

            n = n.nextSibling;
        }

        return children;
    };
}());
<|endoftext|>


<commit_before>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class therock extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'therock',
            'name': 'TheRockTrading',
            'countries': [ 'MT' ],
            'rateLimit': 1000,
            'version': 'v1',
            'has': {
                'CORS': false,
                'fetchTickers': true,
                'fetchMyTrades': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg',
                'api': 'https://api.therocktrading.com',
                'www': 'https://therocktrading.com',
                'doc': [
                    'https://api.therocktrading.com/doc/v1/index.html',
                    'https://api.therocktrading.com/doc/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'funds',
                        'funds/{id}/orderbook',
                        'funds/{id}/ticker',
                        'funds/{id}/trades',
                        'funds/tickers',
                    ],
                },
                'private': {
                    'get': [
                        'balances',
                        'balances/{id}',
                        'discounts',
                        'discounts/{id}',
                        'funds',
                        'funds/{id}',
                        'funds/{id}/trades',
                        'funds/{fund_id}/orders',
                        'funds/{fund_id}/orders/{id}',
                        'funds/{fund_id}/position_balances',
                        'funds/{fund_id}/positions',
                        'funds/{fund_id}/positions/{id}',
                        'transactions',
                        'transactions/{id}',
                        'withdraw_limits/{id}',
                        'withdraw_limits',
                    ],
                    'post': [
                        'atms/withdraw',
                        'funds/{fund_id}/orders',
                    ],
                    'delete': [
                        'funds/{fund_id}/orders/{id}',
                        'funds/{fund_id}/orders/remove_all',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'tierBased': false,
                    'percentage': false,
                    'withdraw': {
                        'BTC': 0.0005,
                        'BCH': 0.0005,
                        'PPC': 0.02,
                        'ETH': 0.001,
                        'ZEC': 0.001,
                        'LTC': 0.002,
                        'EUR': 2.5,  // worst-case scenario: https://therocktrading.com/en/pages/fees
                    },
                    'deposit': {
                        'BTC': 0,
                        'BCH': 0,
                        'PPC': 0,
                        'ETH': 0,
                        'ZEC': 0,
                        'LTC': 0,
                        'EUR': 0,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let response = await this.publicGetFunds ();
        //
        //     { funds: [ {                      id:   "BTCEUR",
        //                              description:   "Trade Bitcoin with Euro",
        //                                     type:   "currency",
        //                            base_currency:   "EUR",
        //                           trade_currency:   "BTC",
        //                                  buy_fee:    0.2,
        //                                 sell_fee:    0.2,
        //                      minimum_price_offer:    0.01,
        //                   minimum_quantity_offer:    0.0005,
        //                   base_currency_decimals:    2,
        //                  trade_currency_decimals:    4,
        //                                leverages: []                           },
        //                {                      id:   "LTCEUR",
        //                              description:   "Trade Litecoin with Euro",
        //                                     type:   "currency",
        //                            base_currency:   "EUR",
        //                           trade_currency:   "LTC",
        //                                  buy_fee:    0.2,
        //                                 sell_fee:    0.2,
        //                      minimum_price_offer:    0.01,
        //                   minimum_quantity_offer:    0.01,
        //                   base_currency_decimals:    2,
        //                  trade_currency_decimals:    2,
        //                                leverages: []                            } ] }
        //
        let markets = this.safeValue (response, 'funds');
        let result = [];
        if (markets === undefined) {
            throw new ExchangeError (this.id + ' fetchMarkets got an unexpected response');
        } else {
            for (let i = 0; i < markets.length; i++) {
                let market = markets[i];
                let id = this.safeString (market, 'id');
                let baseId = this.safeString (market, 'trade_currency');
                let quoteId = this.safeString (market, 'base_currency');
                let base = this.commonCurrencyCode (baseId);
                let quote = this.commonCurrencyCode (quoteId);
                let symbol = base + '/' + quote;
                let buy_fee = this.safeFloat (market, 'buy_fee');
                let sell_fee = this.safeFloat (market, 'sell_fee');
                let taker = Math.max (buy_fee, sell_fee);
                taker = taker / 100;
                let maker = taker;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': market,
                    'active': true,
                    'maker': maker,
                    'taker': taker,
                    'precision': {
                        'amount': this.safeInteger (market, 'trade_currency_decimals'),
                        'price': this.safeInteger (market, 'base_currency_decimals'),
                    },
                    'limits': {
                        'amount': {
                            'min': this.safeFloat (market, 'minimum_quantity_offer'),
                            'max': undefined,
                        },
                        'price': {
                            'min': this.safeFloat (market, 'minimum_price_offer'),
                            'max': undefined,
                        },
                        'cost': {
                            'min': undefined,
                            'max': undefined,
                        },
                    },
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetBalances ();
        let balances = response['balances'];
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let free = balance['trading_balance'];
            let total = balance['balance'];
            let used = total - free;
            let account = {
                'free': free,
                'used': used,
                'total': total,
            };
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetFundsIdOrderbook (this.extend ({
            'id': this.marketId (symbol),
        }, params));
        let timestamp = this.parse8601 (orderbook['date']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.parse8601 (ticker['date']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': this.safeFloat (ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': this.safeFloat (ticker, 'close'), // previous day close, if any
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'volume_traded'),
            'quoteVolume': this.safeFloat (ticker, 'volume'),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetFundsTickers (params);
        let tickers = this.indexBy (response['tickers'], 'fund_id');
        let ids = Object.keys (tickers);
        let result = {};
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            let ticker = tickers[id];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetFundsIdTicker (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        if (!market)
            market = this.markets_by_id[trade['fund_id']];
        let timestamp = this.parse8601 (trade['date']);
        return {
            'info': trade,
            'id': trade['id'].toString (),
            'order': undefined,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['side'],
            'price': trade['price'],
            'amount': trade['amount'],
        };
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.privateGetFundsIdTrades (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTrades (response['trades'], market, since, limit);
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetFundsIdTrades (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTrades (response['trades'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type === 'market')
            price = 0;
        let response = await this.privatePostFundsFundIdOrders (this.extend ({
            'fund_id': this.marketId (symbol),
            'side': side,
            'amount': amount,
            'price': price,
        }, params));
        return {
            'info': response,
            'id': response['id'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privateDeleteFundsFundIdOrdersId (this.extend ({
            'id': id,
            'fund_id': this.marketId (symbol),
        }, params));
    }

    parseOrderStatus (status) {
        const statuses = {
            'active': 'open',
            'executed': 'closed',
            'deleted': 'canceled',
            // don't know what this status means
            // 'conditional': '?',
        };
        return this.safeString (statuses, status, status);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = nonce + url;
            headers = {
                'X-TRT-KEY': this.apiKey,
                'X-TRT-NONCE': nonce,
                'X-TRT-SIGN': this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'),
            };
            if (Object.keys (query).length) {
                body = this.json (query);
                headers['Content-Type'] = 'application/json';
            }
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('errors' in response)
            throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }
};
<commit_msg>therock fetchMyTrades guards for undefined symbol<commit_after>'use strict';

//  ---------------------------------------------------------------------------

const Exchange = require ('./base/Exchange');
const { ExchangeError, ArgumentsRequired } = require ('./base/errors');

//  ---------------------------------------------------------------------------

module.exports = class therock extends Exchange {
    describe () {
        return this.deepExtend (super.describe (), {
            'id': 'therock',
            'name': 'TheRockTrading',
            'countries': [ 'MT' ],
            'rateLimit': 1000,
            'version': 'v1',
            'has': {
                'CORS': false,
                'fetchTickers': true,
                'fetchMyTrades': true,
            },
            'urls': {
                'logo': 'https://user-images.githubusercontent.com/1294454/27766869-75057fa2-5ee9-11e7-9a6f-13e641fa4707.jpg',
                'api': 'https://api.therocktrading.com',
                'www': 'https://therocktrading.com',
                'doc': [
                    'https://api.therocktrading.com/doc/v1/index.html',
                    'https://api.therocktrading.com/doc/',
                ],
            },
            'api': {
                'public': {
                    'get': [
                        'funds',
                        'funds/{id}/orderbook',
                        'funds/{id}/ticker',
                        'funds/{id}/trades',
                        'funds/tickers',
                    ],
                },
                'private': {
                    'get': [
                        'balances',
                        'balances/{id}',
                        'discounts',
                        'discounts/{id}',
                        'funds',
                        'funds/{id}',
                        'funds/{id}/trades',
                        'funds/{fund_id}/orders',
                        'funds/{fund_id}/orders/{id}',
                        'funds/{fund_id}/position_balances',
                        'funds/{fund_id}/positions',
                        'funds/{fund_id}/positions/{id}',
                        'transactions',
                        'transactions/{id}',
                        'withdraw_limits/{id}',
                        'withdraw_limits',
                    ],
                    'post': [
                        'atms/withdraw',
                        'funds/{fund_id}/orders',
                    ],
                    'delete': [
                        'funds/{fund_id}/orders/{id}',
                        'funds/{fund_id}/orders/remove_all',
                    ],
                },
            },
            'fees': {
                'trading': {
                    'maker': 0.2 / 100,
                    'taker': 0.2 / 100,
                },
                'funding': {
                    'tierBased': false,
                    'percentage': false,
                    'withdraw': {
                        'BTC': 0.0005,
                        'BCH': 0.0005,
                        'PPC': 0.02,
                        'ETH': 0.001,
                        'ZEC': 0.001,
                        'LTC': 0.002,
                        'EUR': 2.5,  // worst-case scenario: https://therocktrading.com/en/pages/fees
                    },
                    'deposit': {
                        'BTC': 0,
                        'BCH': 0,
                        'PPC': 0,
                        'ETH': 0,
                        'ZEC': 0,
                        'LTC': 0,
                        'EUR': 0,
                    },
                },
            },
        });
    }

    async fetchMarkets () {
        let response = await this.publicGetFunds ();
        //
        //     { funds: [ {                      id:   "BTCEUR",
        //                              description:   "Trade Bitcoin with Euro",
        //                                     type:   "currency",
        //                            base_currency:   "EUR",
        //                           trade_currency:   "BTC",
        //                                  buy_fee:    0.2,
        //                                 sell_fee:    0.2,
        //                      minimum_price_offer:    0.01,
        //                   minimum_quantity_offer:    0.0005,
        //                   base_currency_decimals:    2,
        //                  trade_currency_decimals:    4,
        //                                leverages: []                           },
        //                {                      id:   "LTCEUR",
        //                              description:   "Trade Litecoin with Euro",
        //                                     type:   "currency",
        //                            base_currency:   "EUR",
        //                           trade_currency:   "LTC",
        //                                  buy_fee:    0.2,
        //                                 sell_fee:    0.2,
        //                      minimum_price_offer:    0.01,
        //                   minimum_quantity_offer:    0.01,
        //                   base_currency_decimals:    2,
        //                  trade_currency_decimals:    2,
        //                                leverages: []                            } ] }
        //
        let markets = this.safeValue (response, 'funds');
        let result = [];
        if (markets === undefined) {
            throw new ExchangeError (this.id + ' fetchMarkets got an unexpected response');
        } else {
            for (let i = 0; i < markets.length; i++) {
                let market = markets[i];
                let id = this.safeString (market, 'id');
                let baseId = this.safeString (market, 'trade_currency');
                let quoteId = this.safeString (market, 'base_currency');
                let base = this.commonCurrencyCode (baseId);
                let quote = this.commonCurrencyCode (quoteId);
                let symbol = base + '/' + quote;
                let buy_fee = this.safeFloat (market, 'buy_fee');
                let sell_fee = this.safeFloat (market, 'sell_fee');
                let taker = Math.max (buy_fee, sell_fee);
                taker = taker / 100;
                let maker = taker;
                result.push ({
                    'id': id,
                    'symbol': symbol,
                    'base': base,
                    'quote': quote,
                    'baseId': baseId,
                    'quoteId': quoteId,
                    'info': market,
                    'active': true,
                    'maker': maker,
                    'taker': taker,
                    'precision': {
                        'amount': this.safeInteger (market, 'trade_currency_decimals'),
                        'price': this.safeInteger (market, 'base_currency_decimals'),
                    },
                    'limits': {
                        'amount': {
                            'min': this.safeFloat (market, 'minimum_quantity_offer'),
                            'max': undefined,
                        },
                        'price': {
                            'min': this.safeFloat (market, 'minimum_price_offer'),
                            'max': undefined,
                        },
                        'cost': {
                            'min': undefined,
                            'max': undefined,
                        },
                    },
                });
            }
        }
        return result;
    }

    async fetchBalance (params = {}) {
        await this.loadMarkets ();
        let response = await this.privateGetBalances ();
        let balances = response['balances'];
        let result = { 'info': response };
        for (let b = 0; b < balances.length; b++) {
            let balance = balances[b];
            let currency = balance['currency'];
            let free = balance['trading_balance'];
            let total = balance['balance'];
            let used = total - free;
            let account = {
                'free': free,
                'used': used,
                'total': total,
            };
            result[currency] = account;
        }
        return this.parseBalance (result);
    }

    async fetchOrderBook (symbol, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let orderbook = await this.publicGetFundsIdOrderbook (this.extend ({
            'id': this.marketId (symbol),
        }, params));
        let timestamp = this.parse8601 (orderbook['date']);
        return this.parseOrderBook (orderbook, timestamp, 'bids', 'asks', 'price', 'amount');
    }

    parseTicker (ticker, market = undefined) {
        let timestamp = this.parse8601 (ticker['date']);
        let symbol = undefined;
        if (market)
            symbol = market['symbol'];
        let last = this.safeFloat (ticker, 'last');
        return {
            'symbol': symbol,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'high': this.safeFloat (ticker, 'high'),
            'low': this.safeFloat (ticker, 'low'),
            'bid': this.safeFloat (ticker, 'bid'),
            'bidVolume': undefined,
            'ask': this.safeFloat (ticker, 'ask'),
            'askVolume': undefined,
            'vwap': undefined,
            'open': this.safeFloat (ticker, 'open'),
            'close': last,
            'last': last,
            'previousClose': this.safeFloat (ticker, 'close'), // previous day close, if any
            'change': undefined,
            'percentage': undefined,
            'average': undefined,
            'baseVolume': this.safeFloat (ticker, 'volume_traded'),
            'quoteVolume': this.safeFloat (ticker, 'volume'),
            'info': ticker,
        };
    }

    async fetchTickers (symbols = undefined, params = {}) {
        await this.loadMarkets ();
        let response = await this.publicGetFundsTickers (params);
        let tickers = this.indexBy (response['tickers'], 'fund_id');
        let ids = Object.keys (tickers);
        let result = {};
        for (let i = 0; i < ids.length; i++) {
            let id = ids[i];
            let market = this.markets_by_id[id];
            let symbol = market['symbol'];
            let ticker = tickers[id];
            result[symbol] = this.parseTicker (ticker, market);
        }
        return result;
    }

    async fetchTicker (symbol, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let ticker = await this.publicGetFundsIdTicker (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTicker (ticker, market);
    }

    parseTrade (trade, market = undefined) {
        if (!market)
            market = this.markets_by_id[trade['fund_id']];
        let timestamp = this.parse8601 (trade['date']);
        return {
            'info': trade,
            'id': trade['id'].toString (),
            'order': undefined,
            'timestamp': timestamp,
            'datetime': this.iso8601 (timestamp),
            'symbol': market['symbol'],
            'type': undefined,
            'side': trade['side'],
            'price': trade['price'],
            'amount': trade['amount'],
        };
    }

    async fetchMyTrades (symbol = undefined, since = undefined, limit = undefined, params = {}) {
        if (symbol === undefined) {
            throw new ArgumentsRequired (this.id + ' fetchMyTrades requires a symbol argument');
        }
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.privateGetFundsIdTrades (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTrades (response['trades'], market, since, limit);
    }

    async fetchTrades (symbol, since = undefined, limit = undefined, params = {}) {
        await this.loadMarkets ();
        let market = this.market (symbol);
        let response = await this.publicGetFundsIdTrades (this.extend ({
            'id': market['id'],
        }, params));
        return this.parseTrades (response['trades'], market, since, limit);
    }

    async createOrder (symbol, type, side, amount, price = undefined, params = {}) {
        await this.loadMarkets ();
        if (type === 'market')
            price = 0;
        let response = await this.privatePostFundsFundIdOrders (this.extend ({
            'fund_id': this.marketId (symbol),
            'side': side,
            'amount': amount,
            'price': price,
        }, params));
        return {
            'info': response,
            'id': response['id'].toString (),
        };
    }

    async cancelOrder (id, symbol = undefined, params = {}) {
        await this.loadMarkets ();
        return await this.privateDeleteFundsFundIdOrdersId (this.extend ({
            'id': id,
            'fund_id': this.marketId (symbol),
        }, params));
    }

    parseOrderStatus (status) {
        const statuses = {
            'active': 'open',
            'executed': 'closed',
            'deleted': 'canceled',
            // don't know what this status means
            // 'conditional': '?',
        };
        return this.safeString (statuses, status, status);
    }

    sign (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let url = this.urls['api'] + '/' + this.version + '/' + this.implodeParams (path, params);
        let query = this.omit (params, this.extractParams (path));
        if (api === 'private') {
            this.checkRequiredCredentials ();
            let nonce = this.nonce ().toString ();
            let auth = nonce + url;
            headers = {
                'X-TRT-KEY': this.apiKey,
                'X-TRT-NONCE': nonce,
                'X-TRT-SIGN': this.hmac (this.encode (auth), this.encode (this.secret), 'sha512'),
            };
            if (Object.keys (query).length) {
                body = this.json (query);
                headers['Content-Type'] = 'application/json';
            }
        }
        return { 'url': url, 'method': method, 'body': body, 'headers': headers };
    }

    async request (path, api = 'public', method = 'GET', params = {}, headers = undefined, body = undefined) {
        let response = await this.fetch2 (path, api, method, params, headers, body);
        if ('errors' in response)
            throw new ExchangeError (this.id + ' ' + this.json (response));
        return response;
    }
};
<|endoftext|>


<commit_before>import { addFetchListener } from 'ember-service-worker/service-worker';

function invertPromise(promise) {
  return new Promise(
    (resolve, reject) =>
      promise.then(reject, resolve)
  );
}

function raceToSuccess(promises) {
  return invertPromise(
    Promise.all(
      promises.map(invertPromise)));
}

addFetchListener(function(event) {
  const cachedVersion = caches.match(event.request);
  const fetchedVersion = fetch(event.request);

  return raceToSuccess([ cachedVersion, fetchedVersion ]);
});
<commit_msg>Serve fastest from cache and fetch, update with fetch<commit_after>import { addFetchListener, PROJECT_REVISION } from 'ember-service-worker/service-worker';

const CACHE_KEY_PREFIX = 'esw-race-';
const CACHE_NAME = `${CACHE_KEY_PREFIX}${PROJECT_REVISION}`;

function invertPromise(promise) {
  return new Promise(
    (resolve, reject) =>
      promise.then(reject, resolve)
  );
}

function raceToSuccess(promises) {
  return invertPromise(
    Promise.all(
      promises.map(invertPromise)));
}

function updateCache(fetchPromise, event) {
  return new Promise((resolve, reject) => {
    try {
      fetchPromise.then((response) => {
        caches.open(CACHE_NAME).then((cache) => {
          console.log('Cache was updated', event.request, response);
          cache.put(event.request, response);
          resolve();
        });
      });
    } catch(_) { reject(); }
  });
}

function fetchCacheRace(fetchedVersion, cachedVersion) {
  return new Promise((resolve, reject) => {
      try {
        raceToSuccess([ cachedVersion, fetchedVersion.catch(_ => cachedVersion) ]).then((response) => {
          if (!response) {
            fetchedVersion.then((r) => {
              return resolve(r);
            });
          } else {
            return resolve(response);
          }
        });

      } catch(_) {
        return reject(new Response(null, {status: 404}));
      }
    });
}

addFetchListener(function(event) {
  const cachedVersion = caches.match(event.request);
  const fetchedVersion = fetch(event.request);
  const fetchedVersionCopy = fetchedVersion.then(response => response.clone());

  event.waitUntil(updateCache(fetchedVersionCopy, event));

  return fetchCacheRace(fetchedVersion, cachedVersion);
});

<|endoftext|>