<commit_before>/* global d3, moment */

window.angular.module('icdsApp').factory('baseControllersService', ['$timeout', function($timeout) {
    var BaseFilterController = function ($scope, $routeParams, $location, dateHelperService, storageService,
                                         navigationService) {
        var vm = this;
        vm.moveToLocation = function(loc, index) {
            if (loc === 'national') {
                $location.search('location_id', '');
                $location.search('selectedLocationLevel', -1);
                $location.search('location_name', '');
            } else {
                $location.search('location_id', loc.location_id);
                $location.search('selectedLocationLevel', index);
                $location.search('location_name', loc.name);
            }
        };
        vm.addAdditionalFilters = function (gender, age) {
            $location.search('gender', gender);
            $location.search('age', age);
        };
        vm.selectedLocationsCount = function() {
            // this method returns selectedLocationLevel
            // TODO: Need to check why selectedLocationLevel is undefined for all location levels except when awc is selected
            // when awc is selected return selected location level as is or count non null elements in selected Locations array
            // selectedLocations array is fixed array of size 5. default value: [all, null,null,null,null]
            // vm.selectedLocations.filter(Boolean) returns array after removing all null entries
            // eg: if block is selected selectedLocations is (state, district, block, all, null)- only 3 levels selected.
            // since 3 levels are selected, selectedLocationLevel is 2 and vm.selectedLocations.filter(Boolean) = 4
            return vm.selectedLocationLevel ? vm.selectedLocationLevel : (vm.selectedLocations.filter(Boolean).length-2);
        };
        vm.filtersOpen = false;
        $scope.$on('openFilterMenu', function () {
            vm.filtersOpen = true;
        });
        $scope.$on('closeFilterMenu', function () {
            vm.filtersOpen = false;
        });
        $scope.$on('mobile_filter_data_changed', function (event, data) {
            vm.filtersOpen = false;
            if (!data.location) {
                vm.moveToLocation('national', -1);

            } else {
                vm.moveToLocation(data.location, data.locationLevel);
            }
            if (data.locationLevel === 4 && $location.path().indexOf('awc_reports') === -1) {
                // jump to AWC reports if an AWC is selected
                $location.path(navigationService.getAWCTabFromPagePath($location.path()));
            }
            dateHelperService.updateSelectedMonth(data['month'], data['year']);
            vm.addAdditionalFilters(data.gender, data.age);
            storageService.setKey('search', $location.search());
            $scope.$emit('filtersChange');
        });
        vm.selectedMonthDisplay = dateHelperService.getSelectedMonthDisplay();
    };
    return {
        BaseController: function ($scope, $routeParams, $location, locationsService, dateHelperService,
                  navigationService, userLocationId, storageService, haveAccessToAllLocations, haveAccessToFeatures,
                  isMobile) {
            BaseFilterController.call(
                this, $scope, $routeParams, $location, dateHelperService, storageService, navigationService
            );
            var vm = this;

            if (Object.keys($location.search()).length === 0) {
                $location.search(storageService.getKey('search'));
            } else {
                storageService.setKey('search', $location.search());
            }
            vm.userLocationId = userLocationId;
            vm.filtersData = $location.search();
            vm.step = $routeParams.step;
            vm.chartData = null;
            vm.top_five = [];
            vm.bottom_five = [];
            vm.selectedLocations = [];
            vm.all_locations = [];
            vm.location_type = null;
            vm.loaded = false;

            vm.haveAccessToFeatures = haveAccessToFeatures;
            vm.message = storageService.getKey('message') || false;

            // variables used for chart rendering. can be overridden by subclasses
            vm.usePercentage = true;
            vm.forceYAxisFromZero = false;

            $scope.$watch(function() {
                return vm.selectedLocations;
            }, function (newValue, oldValue) {
                if (newValue === oldValue || !newValue || newValue.length === 0) {
                    return;
                }
                if (newValue.length === 6) {
                    var parent = newValue[3];
                    $location.search('location_id', parent.location_id);
                    $location.search('selectedLocationLevel', 3);
                    $location.search('location_name', parent.name);
                    storageService.setKey('message', true);
                    setTimeout(function() {
                        storageService.setKey('message', false);
                    }, 3000);
                }
                return newValue;
            }, true);

            vm.getSteps = function (baseRoute) {
                return {
                    'map': {route: baseRoute + 'map', label: 'Map View'},
                    'chart': {route: baseRoute + 'chart', label: isMobile ? 'Rankings' : 'Chart View'},
                };
            };

            vm.getPopupSubheading = function () {
                // if the map popup should have a subheading, then implement this function in a subclass
                // this is inserted before the indicators. see `AWCSCoveredController` for an example usage.
                return '';
            };

            vm.templatePopup = function (loc, row) {
                // subclasses that don't override this must implement vm.getPopupData
                // See UnderweightChildrenReportController for an example
                var popupData = vm.getPopupData(row);
                return vm.createMapPopupTemplate(
                    loc.properties.name,
                    popupData,
                    vm.getPopupSubheading()
                );
            };

            vm.createMapPopupTemplate = function (locationName, lines, subheading) {
                var template = '
' +
                    '
' + locationName + '
';
                if (subheading) {
                    template += '
' + subheading + '
';
                }
                for (var i = 0; i < lines.length; i++) {
                    template += '
' + lines[i]['indicator_name'] + '' + lines[i]['indicator_value'] + '
';
                }
                if (isMobile) {
                    // assume called in the context of either indie-map or map-or-sector-view,
                    // which both have this function.
                    template += 'see more';
                }
                template += '
';
                return template;
            };

            vm.getLocationType = function() {
                if (vm.location) {
                    if (vm.location.location_type === 'supervisor') {
                        return "Sector";
                    } else {
                        return vm.location.location_type.charAt(0).toUpperCase() +
                            vm.location.location_type.slice(1);
                    }
                }
                return 'National';
            };
            vm.setStepsMapLabel = function() {
                var locType = vm.getLocationType();
                if (vm.location && _.contains(['block', 'supervisor', 'awc'], vm.location.location_type)) {
                    vm.mode = 'sector';
                    vm.steps['map'].label = locType + ' View';
                } else {
                    vm.mode = 'map';
                    vm.steps['map'].label = 'Map View: ' + locType;
                }
            };
            vm.loadDataFromResponse = function(usePercentage, forceYAxisFromZero, overrideStep) {
                // if overrideStep is defined use it, else just use the current step
                // mobile dashboard requires this to load data beyond the currently displayed step on some pages
                var currentStep = overrideStep || vm.step;
                var tailsMultiplier = 1;
                if (usePercentage) {
                    tailsMultiplier = 100;
                }
                var parseTails = function(value) {
                    var precision = 0;
                    if (usePercentage) {
                        precision = 2;
                    }
                    return parseFloat((value / tailsMultiplier).toFixed(precision));
                };
                return function(response) {
                    if (currentStep === "map") {
                        vm.data.mapData = response.data.report_data;
                    } else if (currentStep === "chart") {
                        vm.chartData = response.data.report_data.chart_data;
                        vm.all_locations = response.data.report_data.all_locations;
                        vm.top_five = response.data.report_data.top_five;
                        vm.bottom_five = response.data.report_data.bottom_five;
                        vm.location_type = response.data.report_data.location_type;
                        vm.chartTicks = vm.chartData[0].values.map(function (d) { return d.x; });
                        var max = Math.ceil(d3.max(vm.chartData, function (line) {
                            return d3.max(line.values, function (d) {
                                return d.y;
                            });
                        }) * tailsMultiplier);
                        var min = Math.ceil(d3.min(vm.chartData, function (line) {
                            return d3.min(line.values, function (d) {
                                return d.y;
                            });
                        }) * tailsMultiplier);
                        var range = max - min;
                        vm.chartOptions.chart.forceY = [
                            ((min - range / 10) < 0 || forceYAxisFromZero) ? 0 : parseTails(min - range / 10),
                            parseTails(max + range / 10),
                        ];
                    }
                };
            };

            vm.loadData = function () {
                // a default implementation of loadData that is web and mobile friendly.
                // subclasses that don't override this must set vm.serviceDataFunction to a function
                // that takes in the current step and filters and returns the appropriate data from the relevant
                // service. See UnderweightChildrenReportController for an example
                vm.setStepsMapLabel();
                // mobile dashboard requires map data on the chart pages, whereas web just requires the current
                // step's data.
                // note: it would be better to not load this data on both step pages but instead save it in the
                // JS, but doing that now would be a bit complicated and the server-side caching should make the
                // switching relatively painless
                var allSteps = (isMobile && vm.step === 'chart') ? ['map', 'chart'] : [vm.step];
                for (var i = 0; i < allSteps.length; i++) {
                    var currentStep = allSteps[i];
                    vm.myPromise = vm.serviceDataFunction(currentStep, vm.filtersData).then(
                        vm.loadDataFromResponse(vm.usePercentage, vm.forceYAxisFromZero, currentStep)
                    );
                }
            };

            //Creating a promise, which resolves only after map is rendered (wait until canvas is rendered in dom)
            // Reference: https://stackoverflow.com/a/47776379/12839195 (using timeout instead of rafAsync)
            vm.checkElement = function (selector) {
                // if it is not a page where map is displayed, resolve the promise immediately,
                // else wait till canvas (svg in dom) is created
                if (!navigationService.isMapDisplayed($location.path()) && window.Promise) {
                    return window.Promise.resolve(true);
                }
                if (document.querySelector(selector) === null) {
                    return $timeout().then(function () {
                        return vm.checkElement(selector);
                    });
                } else {
                    return window.Promise.resolve(true);
                }
            };

            vm.mapLoadingPromise = vm.checkElement('svg').then(function () {
            });

            vm.init = function() {
                var locationId = vm.filtersData.location_id || vm.userLocationId;
                if (!locationId || ["all", "null", "undefined"].indexOf(locationId) >= 0) {
                    vm.loadData();
                    vm.loaded = true;
                    return;
                }
                locationsService.getLocation(locationId).then(function(location) {
                    vm.location = location;
                    vm.loadData();
                    vm.loaded = true;
                });
            };
            $scope.$on('filtersChange', function() {
                vm.loadData();
            });
            vm.getDisableIndex = function () {
                var i = -1;
                if (!haveAccessToAllLocations) {
                    window.angular.forEach(vm.selectedLocations, function (key, value) {
                        if (key !== null && key.location_id !== 'all' && !key.user_have_access) {
                            i = value;
                        }
                    });
                }
                return i;
            };
            vm.getChartOptions = function(options) {
                return {
                    chart: {
                        type: 'lineChart',
                        height: 450,
                        margin : {
                            top: 20,
                            right: 60,
                            bottom: 60,
                            left: 80,
                        },
                        x: function(d){ return d.x; },
                        y: function(d){ return d.y; },
                        useInteractiveGuideline: true,
                        clipVoronoi: false,
                        tooltips: true,
                        xAxis: {
                            axisLabel: '',
                            showMaxMin: true,
                            tickFormat: function(d) {
                                return d3.time.format(options['xAxisTickFormat'])(new Date(d));
                            },
                            tickValues: function() {
                                return vm.chartTicks;
                            },
                            axisLabelDistance: -100,
                        },

                        yAxis: {
                            axisLabel: '',
                            tickFormat: function(d){
                                return d3.format(options['yAxisTickFormat'])(d);
                            },
                            axisLabelDistance: 20,
                            forceY: [0],
                        },
                        callback: function(chart) {
                            var tooltip = chart.interactiveLayer.tooltip;
                            tooltip.contentGenerator(function (d) {
                                var day = _.find(vm.chartData[0].values, function(num) {
                                    return num['x'] === d.value;
                                });
                                return vm.tooltipContent(d3.time.format('%b %Y')(new Date(d.value)), day);
                            });
                            return chart;
                        },
                    },
                    caption: {
                        enable: true,
                        html: '' + options['captionContent'],
                        css: {
                            'text-align': 'center',
                            'margin': '0 auto',
                            'width': '900px',
                        },
                    },
                };
            };
            vm.createTooltipContent = function(header, lines) {
                var template = "
" + header + "

";
                for (var i = 0; i < lines.length; i++) {
                    template += '
' + lines[i]['indicator_name'] + '' + lines[i]['indicator_value'] + '
';
                }
                return template;
            };

            // mobile dashboard
            // subsection navigation support
            vm.goToRoute = function (route) {
                $location.path(route);
            };
            // month filter support
            vm.selectedMonthDisplay = dateHelperService.getSelectedMonthDisplay();

            // popup support on rankings pages
            vm.displayMobilePopup = function (location) {
                // data is stored in .data for the first three location levels, but then moves to tooltips_data
                var dataSource = vm.data.mapData.data || vm.data.mapData.tooltips_data;
                var locationData = dataSource[location.loc_name];
                var data = vm.getPopupData(locationData);
                vm.mobilePopupLocation = location;
                vm.mobilePopupData = data;
            };
            vm.drilldownToLocationWithName = function (locationName) {
                // todo: this is heavily copied from map-or-sector-view's handling of the map click event
                // but there's not a great place to share the code since they're managed by separate controllers
                var currentLocationId = $location.search().location_id;
                locationsService.getLocationByNameAndParent(locationName, currentLocationId).then(function (locations) {
                    var location = locations[0];
                    $location.search('location_name', location.name);
                    $location.search('location_id', location.location_id);

                    storageService.setKey('search', $location.search());
                    if (location.location_type_name === 'awc') {
                        $location.path(navigationService.getAWCTabFromPagePath($location.path()));
                    }
                });
            };
        },
        BaseFilterController: BaseFilterController,
    };
}]);
<commit_msg>adding space (lint error)<commit_after>/* global d3, moment */

window.angular.module('icdsApp').factory('baseControllersService', ['$timeout', function ($timeout) {
    var BaseFilterController = function ($scope, $routeParams, $location, dateHelperService, storageService,
                                         navigationService) {
        var vm = this;
        vm.moveToLocation = function(loc, index) {
            if (loc === 'national') {
                $location.search('location_id', '');
                $location.search('selectedLocationLevel', -1);
                $location.search('location_name', '');
            } else {
                $location.search('location_id', loc.location_id);
                $location.search('selectedLocationLevel', index);
                $location.search('location_name', loc.name);
            }
        };
        vm.addAdditionalFilters = function (gender, age) {
            $location.search('gender', gender);
            $location.search('age', age);
        };
        vm.selectedLocationsCount = function() {
            // this method returns selectedLocationLevel
            // TODO: Need to check why selectedLocationLevel is undefined for all location levels except when awc is selected
            // when awc is selected return selected location level as is or count non null elements in selected Locations array
            // selectedLocations array is fixed array of size 5. default value: [all, null,null,null,null]
            // vm.selectedLocations.filter(Boolean) returns array after removing all null entries
            // eg: if block is selected selectedLocations is (state, district, block, all, null)- only 3 levels selected.
            // since 3 levels are selected, selectedLocationLevel is 2 and vm.selectedLocations.filter(Boolean) = 4
            return vm.selectedLocationLevel ? vm.selectedLocationLevel : (vm.selectedLocations.filter(Boolean).length-2);
        };
        vm.filtersOpen = false;
        $scope.$on('openFilterMenu', function () {
            vm.filtersOpen = true;
        });
        $scope.$on('closeFilterMenu', function () {
            vm.filtersOpen = false;
        });
        $scope.$on('mobile_filter_data_changed', function (event, data) {
            vm.filtersOpen = false;
            if (!data.location) {
                vm.moveToLocation('national', -1);

            } else {
                vm.moveToLocation(data.location, data.locationLevel);
            }
            if (data.locationLevel === 4 && $location.path().indexOf('awc_reports') === -1) {
                // jump to AWC reports if an AWC is selected
                $location.path(navigationService.getAWCTabFromPagePath($location.path()));
            }
            dateHelperService.updateSelectedMonth(data['month'], data['year']);
            vm.addAdditionalFilters(data.gender, data.age);
            storageService.setKey('search', $location.search());
            $scope.$emit('filtersChange');
        });
        vm.selectedMonthDisplay = dateHelperService.getSelectedMonthDisplay();
    };
    return {
        BaseController: function ($scope, $routeParams, $location, locationsService, dateHelperService,
                  navigationService, userLocationId, storageService, haveAccessToAllLocations, haveAccessToFeatures,
                  isMobile) {
            BaseFilterController.call(
                this, $scope, $routeParams, $location, dateHelperService, storageService, navigationService
            );
            var vm = this;

            if (Object.keys($location.search()).length === 0) {
                $location.search(storageService.getKey('search'));
            } else {
                storageService.setKey('search', $location.search());
            }
            vm.userLocationId = userLocationId;
            vm.filtersData = $location.search();
            vm.step = $routeParams.step;
            vm.chartData = null;
            vm.top_five = [];
            vm.bottom_five = [];
            vm.selectedLocations = [];
            vm.all_locations = [];
            vm.location_type = null;
            vm.loaded = false;

            vm.haveAccessToFeatures = haveAccessToFeatures;
            vm.message = storageService.getKey('message') || false;

            // variables used for chart rendering. can be overridden by subclasses
            vm.usePercentage = true;
            vm.forceYAxisFromZero = false;

            $scope.$watch(function() {
                return vm.selectedLocations;
            }, function (newValue, oldValue) {
                if (newValue === oldValue || !newValue || newValue.length === 0) {
                    return;
                }
                if (newValue.length === 6) {
                    var parent = newValue[3];
                    $location.search('location_id', parent.location_id);
                    $location.search('selectedLocationLevel', 3);
                    $location.search('location_name', parent.name);
                    storageService.setKey('message', true);
                    setTimeout(function() {
                        storageService.setKey('message', false);
                    }, 3000);
                }
                return newValue;
            }, true);

            vm.getSteps = function (baseRoute) {
                return {
                    'map': {route: baseRoute + 'map', label: 'Map View'},
                    'chart': {route: baseRoute + 'chart', label: isMobile ? 'Rankings' : 'Chart View'},
                };
            };

            vm.getPopupSubheading = function () {
                // if the map popup should have a subheading, then implement this function in a subclass
                // this is inserted before the indicators. see `AWCSCoveredController` for an example usage.
                return '';
            };

            vm.templatePopup = function (loc, row) {
                // subclasses that don't override this must implement vm.getPopupData
                // See UnderweightChildrenReportController for an example
                var popupData = vm.getPopupData(row);
                return vm.createMapPopupTemplate(
                    loc.properties.name,
                    popupData,
                    vm.getPopupSubheading()
                );
            };

            vm.createMapPopupTemplate = function (locationName, lines, subheading) {
                var template = '
' +
                    '
' + locationName + '
';
                if (subheading) {
                    template += '
' + subheading + '
';
                }
                for (var i = 0; i < lines.length; i++) {
                    template += '
' + lines[i]['indicator_name'] + '' + lines[i]['indicator_value'] + '
';
                }
                if (isMobile) {
                    // assume called in the context of either indie-map or map-or-sector-view,
                    // which both have this function.
                    template += 'see more';
                }
                template += '
';
                return template;
            };

            vm.getLocationType = function() {
                if (vm.location) {
                    if (vm.location.location_type === 'supervisor') {
                        return "Sector";
                    } else {
                        return vm.location.location_type.charAt(0).toUpperCase() +
                            vm.location.location_type.slice(1);
                    }
                }
                return 'National';
            };
            vm.setStepsMapLabel = function() {
                var locType = vm.getLocationType();
                if (vm.location && _.contains(['block', 'supervisor', 'awc'], vm.location.location_type)) {
                    vm.mode = 'sector';
                    vm.steps['map'].label = locType + ' View';
                } else {
                    vm.mode = 'map';
                    vm.steps['map'].label = 'Map View: ' + locType;
                }
            };
            vm.loadDataFromResponse = function(usePercentage, forceYAxisFromZero, overrideStep) {
                // if overrideStep is defined use it, else just use the current step
                // mobile dashboard requires this to load data beyond the currently displayed step on some pages
                var currentStep = overrideStep || vm.step;
                var tailsMultiplier = 1;
                if (usePercentage) {
                    tailsMultiplier = 100;
                }
                var parseTails = function(value) {
                    var precision = 0;
                    if (usePercentage) {
                        precision = 2;
                    }
                    return parseFloat((value / tailsMultiplier).toFixed(precision));
                };
                return function(response) {
                    if (currentStep === "map") {
                        vm.data.mapData = response.data.report_data;
                    } else if (currentStep === "chart") {
                        vm.chartData = response.data.report_data.chart_data;
                        vm.all_locations = response.data.report_data.all_locations;
                        vm.top_five = response.data.report_data.top_five;
                        vm.bottom_five = response.data.report_data.bottom_five;
                        vm.location_type = response.data.report_data.location_type;
                        vm.chartTicks = vm.chartData[0].values.map(function (d) { return d.x; });
                        var max = Math.ceil(d3.max(vm.chartData, function (line) {
                            return d3.max(line.values, function (d) {
                                return d.y;
                            });
                        }) * tailsMultiplier);
                        var min = Math.ceil(d3.min(vm.chartData, function (line) {
                            return d3.min(line.values, function (d) {
                                return d.y;
                            });
                        }) * tailsMultiplier);
                        var range = max - min;
                        vm.chartOptions.chart.forceY = [
                            ((min - range / 10) < 0 || forceYAxisFromZero) ? 0 : parseTails(min - range / 10),
                            parseTails(max + range / 10),
                        ];
                    }
                };
            };

            vm.loadData = function () {
                // a default implementation of loadData that is web and mobile friendly.
                // subclasses that don't override this must set vm.serviceDataFunction to a function
                // that takes in the current step and filters and returns the appropriate data from the relevant
                // service. See UnderweightChildrenReportController for an example
                vm.setStepsMapLabel();
                // mobile dashboard requires map data on the chart pages, whereas web just requires the current
                // step's data.
                // note: it would be better to not load this data on both step pages but instead save it in the
                // JS, but doing that now would be a bit complicated and the server-side caching should make the
                // switching relatively painless
                var allSteps = (isMobile && vm.step === 'chart') ? ['map', 'chart'] : [vm.step];
                for (var i = 0; i < allSteps.length; i++) {
                    var currentStep = allSteps[i];
                    vm.myPromise = vm.serviceDataFunction(currentStep, vm.filtersData).then(
                        vm.loadDataFromResponse(vm.usePercentage, vm.forceYAxisFromZero, currentStep)
                    );
                }
            };

            //Creating a promise, which resolves only after map is rendered (wait until canvas is rendered in dom)
            // Reference: https://stackoverflow.com/a/47776379/12839195 (using timeout instead of rafAsync)
            vm.checkElement = function (selector) {
                // if it is not a page where map is displayed, resolve the promise immediately,
                // else wait till canvas (svg in dom) is created
                if (!navigationService.isMapDisplayed($location.path()) && window.Promise) {
                    return window.Promise.resolve(true);
                }
                if (document.querySelector(selector) === null) {
                    return $timeout().then(function () {
                        return vm.checkElement(selector);
                    });
                } else {
                    return window.Promise.resolve(true);
                }
            };

            vm.mapLoadingPromise = vm.checkElement('svg').then(function () {
            });

            vm.init = function() {
                var locationId = vm.filtersData.location_id || vm.userLocationId;
                if (!locationId || ["all", "null", "undefined"].indexOf(locationId) >= 0) {
                    vm.loadData();
                    vm.loaded = true;
                    return;
                }
                locationsService.getLocation(locationId).then(function(location) {
                    vm.location = location;
                    vm.loadData();
                    vm.loaded = true;
                });
            };
            $scope.$on('filtersChange', function() {
                vm.loadData();
            });
            vm.getDisableIndex = function () {
                var i = -1;
                if (!haveAccessToAllLocations) {
                    window.angular.forEach(vm.selectedLocations, function (key, value) {
                        if (key !== null && key.location_id !== 'all' && !key.user_have_access) {
                            i = value;
                        }
                    });
                }
                return i;
            };
            vm.getChartOptions = function(options) {
                return {
                    chart: {
                        type: 'lineChart',
                        height: 450,
                        margin : {
                            top: 20,
                            right: 60,
                            bottom: 60,
                            left: 80,
                        },
                        x: function(d){ return d.x; },
                        y: function(d){ return d.y; },
                        useInteractiveGuideline: true,
                        clipVoronoi: false,
                        tooltips: true,
                        xAxis: {
                            axisLabel: '',
                            showMaxMin: true,
                            tickFormat: function(d) {
                                return d3.time.format(options['xAxisTickFormat'])(new Date(d));
                            },
                            tickValues: function() {
                                return vm.chartTicks;
                            },
                            axisLabelDistance: -100,
                        },

                        yAxis: {
                            axisLabel: '',
                            tickFormat: function(d){
                                return d3.format(options['yAxisTickFormat'])(d);
                            },
                            axisLabelDistance: 20,
                            forceY: [0],
                        },
                        callback: function(chart) {
                            var tooltip = chart.interactiveLayer.tooltip;
                            tooltip.contentGenerator(function (d) {
                                var day = _.find(vm.chartData[0].values, function(num) {
                                    return num['x'] === d.value;
                                });
                                return vm.tooltipContent(d3.time.format('%b %Y')(new Date(d.value)), day);
                            });
                            return chart;
                        },
                    },
                    caption: {
                        enable: true,
                        html: '' + options['captionContent'],
                        css: {
                            'text-align': 'center',
                            'margin': '0 auto',
                            'width': '900px',
                        },
                    },
                };
            };
            vm.createTooltipContent = function(header, lines) {
                var template = "
" + header + "

";
                for (var i = 0; i < lines.length; i++) {
                    template += '
' + lines[i]['indicator_name'] + '' + lines[i]['indicator_value'] + '
';
                }
                return template;
            };

            // mobile dashboard
            // subsection navigation support
            vm.goToRoute = function (route) {
                $location.path(route);
            };
            // month filter support
            vm.selectedMonthDisplay = dateHelperService.getSelectedMonthDisplay();

            // popup support on rankings pages
            vm.displayMobilePopup = function (location) {
                // data is stored in .data for the first three location levels, but then moves to tooltips_data
                var dataSource = vm.data.mapData.data || vm.data.mapData.tooltips_data;
                var locationData = dataSource[location.loc_name];
                var data = vm.getPopupData(locationData);
                vm.mobilePopupLocation = location;
                vm.mobilePopupData = data;
            };
            vm.drilldownToLocationWithName = function (locationName) {
                // todo: this is heavily copied from map-or-sector-view's handling of the map click event
                // but there's not a great place to share the code since they're managed by separate controllers
                var currentLocationId = $location.search().location_id;
                locationsService.getLocationByNameAndParent(locationName, currentLocationId).then(function (locations) {
                    var location = locations[0];
                    $location.search('location_name', location.name);
                    $location.search('location_id', location.location_id);

                    storageService.setKey('search', $location.search());
                    if (location.location_type_name === 'awc') {
                        $location.path(navigationService.getAWCTabFromPagePath($location.path()));
                    }
                });
            };
        },
        BaseFilterController: BaseFilterController,
    };
}]);
<|endoftext|>


<commit_before>'use strict';

exports.init = function(req, res){
  res.render('account/timeline/index');
};
<commit_msg>added model<commit_after>/*'use strict';

exports.init = function(req, res){
  res.render('account/timeline/index');
};
*/

'use strict';

var getReturnUrl = function(req) {
  var returnUrl = req.user.defaultReturnUrl();
  if (req.session.returnUrl) {
    returnUrl = req.session.returnUrl;
    delete req.session.returnUrl;
  }
  return returnUrl;
};

exports.init = function(req, res){
  if (req.isAuthenticated()) {
    res.redirect(getReturnUrl(req));
  }
  else {
    res.render('account/timeline/index', {
      oauthMessage: '',
      oauthTwitter: !!req.app.get('twitter-oauth-key'),
      oauthGitHub: !!req.app.get('github-oauth-key'),
      oauthFacebook: !!req.app.get('facebook-oauth-key')
    });
  }
};<|endoftext|>


<commit_before>// @flow
/**
 * All About Olaf
 * Bus Line list helper
 */

import React from 'react'
import {
  StyleSheet,
  View,
  ListView,
} from 'react-native'
import BusStopView from './busStop'

let styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'stretch',
    flexDirection: 'row',
  },
})

export default class BusLineView extends React.Component {
  state = {
    dataSource: new ListView.DataSource({
      rowHasChanged: this._rowHasChanged,
    }).cloneWithRows(this.props.schedule)
  }

  _rowHasChanged(r1: busStopType, r2: busStopType) {
    return r1.location !== r2.location
  }

  _renderRow(data: busStopType) {
    return (
      <BusStopView location={data.location} times={data.times} />
    )
  }

  render() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this._renderRow.bind(this)} />
      </View>
    )
  }
}
<commit_msg>add basic proptypes to busLine (#132)<commit_after>// @flow
/**
 * All About Olaf
 * Bus Line list helper
 */

import React from 'react'
import {
  StyleSheet,
  View,
  ListView,
} from 'react-native'
import BusStopView from './busStop'

let styles = StyleSheet.create({
  container: {
    flex: 1,
    alignItems: 'stretch',
    flexDirection: 'row',
  },
})

export default class BusLineView extends React.Component {
  static propTypes = {
    schedule: React.PropTypes.any,
  }

  state = {
    dataSource: new ListView.DataSource({
      rowHasChanged: this._rowHasChanged,
    }).cloneWithRows(this.props.schedule),
  }

  _rowHasChanged(r1: busStopType, r2: busStopType) {
    return r1.location !== r2.location
  }

  _renderRow(data: busStopType) {
    return (
      <BusStopView location={data.location} times={data.times} />
    )
  }

  render() {
    return (
      <View style={styles.container}>
        <ListView
          dataSource={this.state.dataSource}
          renderRow={this._renderRow.bind(this)} />
      </View>
    )
  }
}
<|endoftext|>


<commit_before>var testCase = require('nodeunit').testCase,
    tags = require('../lib/tags'),
    parser = require('../lib/parser');

exports.Tags = testCase({
    'undefined tag throws error': function (test) {
        test.throws(function () {
            parser.parse('{% foobar %}', {});
        }, Error);
        test.done();
    },

    'basic tag': function (test) {
        var output = parser.parse('{% blah %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: {}, parent: [] }], output);

        output = parser.parse('{% blah "foobar" %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: ['"foobar"'], compile: {}, parent: [] }], output, 'args appended');

        output = parser.parse('{% blah "foobar" barfoo %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: ['"foobar"', 'barfoo'], compile: {}, parent: [] }], output, 'multiple args appended');

        test.done();
    },

    'basic tag with ends': function (test) {
        var output = parser.parse('{% blah %}{% endblah %}', { blah: { ends: true } });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: { ends: true }, tokens: [], parent: [] }], output);
        test.done();
    },

    'line number included in token': function (test) {
        var output = parser.parse('hi!\n\n\n{% blah %}{% endblah %}', { blah: { ends: true } });
        test.deepEqual({ type: parser.TOKEN_TYPES.LOGIC, line: 4, name: 'blah', args: [], compile: { ends: true }, tokens: [], parent: [] }, output[1]);
        test.done();
    },

    'throws if requires end but no end found': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}', { blah: { ends: true }});
        }, Error);
        test.done();
    },

    'throws if not end but end found': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}{% endblah %}', { blah: {}});
        }, Error);
        test.done();
    },

    'throws on unbalanced end tag': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}{% endfoo %}', { blah: { ends: true }, foo: { ends: true }});
        }, Error);
        test.done();
    },

    'tag with contents': function (test) {
        var output = parser.parse('{% blah %}hello{% endblah %}', { blah: { ends: true } });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: { ends: true }, tokens: ['hello'], parent: [] }], output);
        test.done();
    }
});

exports.Comments = testCase({
    'empty strings are ignored': function (test) {
        var output = parser.parse('');
        test.deepEqual([], output);

        output = parser.parse(' ');
        test.deepEqual([], output);

        output = parser.parse(' \n');
        test.deepEqual([], output);

        test.done();
    },

    'comments are ignored': function (test) {
        var output = parser.parse('{# foobar #}');
        test.deepEqual([], output);
        test.done();
    }
});

exports.Variable = testCase({
    'basic variable': function (test) {
        var output = parser.parse('{{ foobar }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }], output, 'with spaces');

        output = parser.parse('{{foobar}}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }], output, 'without spaces');

        test.done();
    },

    'dot-notation variable': function (test) {
        var output = parser.parse('{{ foo.bar }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foo.bar', filters: [], escape: false, args: null }], output);
        test.done();
    },

    'variable with filter': function (test) {
        var output = parser.parse('{{ foobar|awesome }}');
        test.deepEqual(output, [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [{ name: 'awesome', args: [] }] }], 'filter by name');

        output = parser.parse('{{ foobar|awesome("param", ctxvar, 2) }}');
        test.deepEqual(output, [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [{ name: 'awesome', args: ['"param"', 'ctxvar', '2'] }] }], 'filter with params');

        test.done();
    },

    'multiple filters': function (test) {
        var output = parser.parse('{{ foobar|baz(1)|rad|awesome("param", 2) }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [
            { name: 'baz', args: [1] },
            { name: 'rad', args: [] },
            { name: 'awesome', args: ['"param"', '2'] }
        ] }], output);

        test.done();
    },

    'filters do not carry over': function (test) {
        var output = parser.parse('{{ foo|baz }}{{ bar }}');
        test.deepEqual([
            { type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'baz', args: [] }], escape: false, args: null },
            { type: parser.TOKEN_TYPES.VAR, name: 'bar', filters: [], escape: false, args: null }
        ], output);
        test.done();
    },

    'filters with all kinds of characters in params': function (test) {
        var output = parser.parse("{{ foo|blah('01a|\"\\',;?./¨œ∑´®†][{}]') }}");
        test.deepEqual(output, [
            { type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'blah', args: ['01a|\"\\\',;?./¨œ∑´®†][{}]'] }], escape: false, args: null }
        ]);
        test.done();
    },

    'escapements carry over in filter args': function (test) {
        var output = parser.parse('{{ foo|blah("\\s") }}');
        test.deepEqual([
            { type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'blah', args: ['"\\s"'] }], escape: false, args: null }
        ], output);
        test.done();
    }
});

exports.Compiling = testCase({
    basic: function (test) {
        var template = { tokens: [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }] };
        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'broken compilation will throw (from template object)');

        template = { tokens: parser.parse('{% if foo %}blah{% endif %}', tags) };

        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'broken compilation will throw (fromt parsed string)');

        test.done();
    },

    'throws if bad extends': function (test) {
        var template = { tokens: parser.parse('{% extends foobar %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if given variable argument');

        template = { tokens: parser.parse('{% extends "foo" "bar" %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if given multiple arguments');

        test.done();
    },

    'throws if extends is not first token': function (test) {
        var template = { tokens: parser.parse('blah!{% extends foobar %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if it is not first token');
        test.done();
    },

    'block name requirements': function (test) {
        var template = { tokens: parser.parse('{% block foobar %}{% endblock %}', tags), type: parser.TEMPLATE };
        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block name must be alpha');

        template = { tokens: parser.parse('{% block 123 %}{% endblock %}', tags), type: parser.TEMPLATE };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block must throw if its name is not alpha');
        test.done();
    },

    'block nesting': function (test) {
        var template = { tokens: parser.parse('{% block foobar %}{% block barfoo %}{% endblock %}{% endblock %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block should throw if nested');
        test.done();
    }
});
<commit_msg>cut up filter parsing test. found edge cases<commit_after>var testCase = require('nodeunit').testCase,
    tags = require('../lib/tags'),
    parser = require('../lib/parser');

exports.Tags = testCase({
    'undefined tag throws error': function (test) {
        test.throws(function () {
            parser.parse('{% foobar %}', {});
        }, Error);
        test.done();
    },

    'basic tag': function (test) {
        var output = parser.parse('{% blah %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: {}, parent: [] }], output);

        output = parser.parse('{% blah "foobar" %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: ['"foobar"'], compile: {}, parent: [] }], output, 'args appended');

        output = parser.parse('{% blah "foobar" barfoo %}', { blah: {} });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: ['"foobar"', 'barfoo'], compile: {}, parent: [] }], output, 'multiple args appended');

        test.done();
    },

    'basic tag with ends': function (test) {
        var output = parser.parse('{% blah %}{% endblah %}', { blah: { ends: true } });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: { ends: true }, tokens: [], parent: [] }], output);
        test.done();
    },

    'line number included in token': function (test) {
        var output = parser.parse('hi!\n\n\n{% blah %}{% endblah %}', { blah: { ends: true } });
        test.deepEqual({ type: parser.TOKEN_TYPES.LOGIC, line: 4, name: 'blah', args: [], compile: { ends: true }, tokens: [], parent: [] }, output[1]);
        test.done();
    },

    'throws if requires end but no end found': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}', { blah: { ends: true }});
        }, Error);
        test.done();
    },

    'throws if not end but end found': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}{% endblah %}', { blah: {}});
        }, Error);
        test.done();
    },

    'throws on unbalanced end tag': function (test) {
        test.throws(function () {
            parser.parse('{% blah %}{% endfoo %}', { blah: { ends: true }, foo: { ends: true }});
        }, Error);
        test.done();
    },

    'tag with contents': function (test) {
        var output = parser.parse('{% blah %}hello{% endblah %}', { blah: { ends: true } });
        test.deepEqual([{ type: parser.TOKEN_TYPES.LOGIC, line: 1, name: 'blah', args: [], compile: { ends: true }, tokens: ['hello'], parent: [] }], output);
        test.done();
    }
});

exports.Comments = testCase({
    'empty strings are ignored': function (test) {
        var output = parser.parse('');
        test.deepEqual([], output);

        output = parser.parse(' ');
        test.deepEqual([], output);

        output = parser.parse(' \n');
        test.deepEqual([], output);

        test.done();
    },

    'comments are ignored': function (test) {
        var output = parser.parse('{# foobar #}');
        test.deepEqual([], output);
        test.done();
    }
});

exports.Variable = testCase({
    'basic variable': function (test) {
        var output = parser.parse('{{ foobar }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }], output, 'with spaces');

        output = parser.parse('{{foobar}}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }], output, 'without spaces');

        test.done();
    },

    'dot-notation variable': function (test) {
        var output = parser.parse('{{ foo.bar }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foo.bar', filters: [], escape: false, args: null }], output);
        test.done();
    },

    'variable with filter': function (test) {
        var output = parser.parse('{{ foobar|awesome }}');
        test.deepEqual(output, [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [{ name: 'awesome', args: [] }] }], 'filter by name');

        output = parser.parse('{{ foobar|awesome("param", ctxvar, 2) }}');
        test.deepEqual(output, [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [{ name: 'awesome', args: ['"param"', 'ctxvar', '2'] }] }], 'filter with params');

        test.done();
    },

    'multiple filters': function (test) {
        var output = parser.parse('{{ foobar|baz(1)|rad|awesome("param", 2) }}');
        test.deepEqual([{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', escape: false, args: null, filters: [
            { name: 'baz', args: [1] },
            { name: 'rad', args: [] },
            { name: 'awesome', args: ['"param"', '2'] }
        ] }], output);

        test.done();
    },

    'filters do not carry over': function (test) {
        var output = parser.parse('{{ foo|baz }}{{ bar }}');
        test.deepEqual([
            { type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'baz', args: [] }], escape: false, args: null },
            { type: parser.TOKEN_TYPES.VAR, name: 'bar', filters: [], escape: false, args: null }
        ], output);
        test.done();
    },

    'filters with all kinds of characters in params': function (test) {
        var output = parser.parse("{{ foo|blah('01a|\"\\',;?./¨œ∑´®†][{}]') }}");
        test.deepEqual(output[0].filters, [{ name: 'blah', args: ['01a|\"\\\',;?./¨œ∑´®†][{}]'] }]);

        test.deepEqual(parser.parse("{{ foo|blah('01a') }}")[0].filters, [{ name: 'blah', args: ['01a'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('|') }}")[0].filters, [{ name: 'blah', args: ['|'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('\"') }}")[0].filters, [{ name: 'blah', args: ['\"'] }]);
        test.deepEqual(parser.parse('{{ foo|blah("\'") }}')[0].filters, [{ name: 'blah', args: ['"\'"'] }]); // FIXME: WHAT?
        test.deepEqual(parser.parse("{{ foo|blah(',') }}")[0].filters, [{ name: 'blah', args: [','] }]);
        test.deepEqual(parser.parse("{{ foo|blah(';') }}")[0].filters, [{ name: 'blah', args: [';'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('?./¨œ∑´®†') }}")[0].filters, [{ name: 'blah', args: ['?./¨œ∑´®†'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('[]') }}")[0].filters, [{ name: 'blah', args: ['[]'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('[') }}")[0].filters, [{ name: 'blah', args: ['['] }]);
        test.deepEqual(parser.parse("{{ foo|blah(']') }}")[0].filters, [{ name: 'blah', args: [']'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('{}') }}")[0].filters, [{ name: 'blah', args: ['{}'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('{') }}")[0].filters, [{ name: 'blah', args: ['{'] }]);
        test.deepEqual(parser.parse("{{ foo|blah('}') }}")[0].filters, [{ name: 'blah', args: ['}'] }]);

        // FIXME: filter argument edge cases
        // test.deepEqual(parser.parse("{{ foo|blah('a\", \"b') }}")[0].filters, [{ name: 'blah', args: ['a\", \"b'] }]);
        // test.deepEqual(parser.parse('{{ foo|blah("a\', \'b") }}')[0].filters, [{ name: 'blah', args: ['a\',\'b'] }]);

        test.done();
    },

    'escapements carry over in filter args': function (test) {
        var output = parser.parse('{{ foo|blah("\\s") }}');
        test.deepEqual([
            { type: parser.TOKEN_TYPES.VAR, name: 'foo', filters: [{ name: 'blah', args: ['"\\s"'] }], escape: false, args: null }
        ], output);
        test.done();
    }
});

exports.Compiling = testCase({
    basic: function (test) {
        var template = { tokens: [{ type: parser.TOKEN_TYPES.VAR, name: 'foobar', filters: [], escape: false, args: null }] };
        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'broken compilation will throw (from template object)');

        template = { tokens: parser.parse('{% if foo %}blah{% endif %}', tags) };

        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'broken compilation will throw (fromt parsed string)');

        test.done();
    },

    'throws if bad extends': function (test) {
        var template = { tokens: parser.parse('{% extends foobar %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if given variable argument');

        template = { tokens: parser.parse('{% extends "foo" "bar" %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if given multiple arguments');

        test.done();
    },

    'throws if extends is not first token': function (test) {
        var template = { tokens: parser.parse('blah!{% extends foobar %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'extend must throw if it is not first token');
        test.done();
    },

    'block name requirements': function (test) {
        var template = { tokens: parser.parse('{% block foobar %}{% endblock %}', tags), type: parser.TEMPLATE };
        test.doesNotThrow(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block name must be alpha');

        template = { tokens: parser.parse('{% block 123 %}{% endblock %}', tags), type: parser.TEMPLATE };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block must throw if its name is not alpha');
        test.done();
    },

    'block nesting': function (test) {
        var template = { tokens: parser.parse('{% block foobar %}{% block barfoo %}{% endblock %}{% endblock %}', tags) };
        test.throws(function () {
            eval('var __output = "";' + parser.compile.call(template, ''));
        }, 'block should throw if nested');
        test.done();
    }
});
<|endoftext|>


<commit_before>import React from 'react'
import { PropTypes } from 'react'
// import Text from 'views/components/Form/Common/Text'

// NOTE: importing the non-wrapped provide() version
import FormRelatedAudioItem from 'views/components/Form/FormRelatedAudioItem'
import { getIndexOfElementById, removeItem, moveItemDown, moveItemUp } from 'views/components/Form/FormInteractions'
import ProviderHelpers from 'common/ProviderHelpers'
import provide from 'react-redux-provide'
import selectn from 'selectn'

const { string, array, object, func, number } = PropTypes
let SelectMediaComponent = null

export class FormRelatedAudio extends React.Component {
  static propTypes = {
    name: string.isRequired,
    className: string,
    textInfo: string,
    items: array,
    idDescribedbyItemBrowse: string,
    idDescribedByItemMove: string,
    textDescribedbyItemBrowse: string,
    textDescribedByItemMove: string,
    textLegendItems: string,
    textBtnAddItem: string,
    textLegendItem: string,
    textBtnRemoveItem: string,
    textBtnMoveItemUp: string,
    textBtnMoveItemDown: string,
    textBtnCreateItem: string,
    textBtnSelectExistingItems: string,
    textLabelItemSearch: string,
    DEFAULT_PAGE: number,
    DEFAULT_PAGE_SIZE: number,
    DEFAULT_LANGUAGE: string,
    DEFAULT_SORT_COL: string,
    DEFAULT_SORT_TYPE: string,
    handleChange: func,
    // REDUX/PROVIDE
    computeContributor: object.isRequired,
    computeContributors: object.isRequired,
    computeCreateContributor: object,
    computeDialect: object.isRequired,
    computeDialect2: object.isRequired,
    createContributor: func.isRequired,
    fetchContributors: func.isRequired,
    fetchDialect: func.isRequired,
    fetchResources: func.isRequired,
    splitWindowPath: array.isRequired,
  }
  static defaultProps = {
    className: 'FormRelatedAudio',
    idDescribedbyItemBrowse: 'describedbyRelatedAudioBrowse',
    idDescribedByItemMove: 'describedByRelatedAudioMove',
    name: 'FormRelatedAudio',
    textDescribedbyItemBrowse: 'Select an audio sample from previously uploaded items',
    textDescribedByItemMove: `If you are adding multiple Related Audio Items, you can change the position of the item with
    the 'Move Related Audio Item left' and 'Move Related Audio Item right' buttons`,
    textLegendItems: 'Related Audio',
    textBtnAddItem: 'Add Related Audio Item',
    textLegendItem: 'Related Audio Item',
    textBtnRemoveItem: 'Remove Related Audio Item',
    textBtnMoveItemUp: 'Move Related Audio Item left',
    textBtnMoveItemDown: 'Move Related Audio Item right',
    textBtnCreateItem: 'Create new audio item',
    textBtnSelectExistingItems: 'Select from existing audio',
    textLabelItemSearch: 'Search existing audio',
    DEFAULT_PAGE: 1,
    DEFAULT_PAGE_SIZE: 100,
    DEFAULT_LANGUAGE: 'english',
    DEFAULT_SORT_COL: 'dc:title',
    DEFAULT_SORT_TYPE: 'asc',
    handleChange: () => {},
  }

  state = {
    items: [],
    loading: true,
  }

  // Fetch data on initial render
  async componentDidMount() {
    const { computeDialect, fetchContributors, fetchDialect, fetchResources, splitWindowPath } = this.props

    // USING this.DIALECT_PATH instead of setting state
    // this.setState({ dialectPath: dialectPath })
    this.DIALECT_PATH = ProviderHelpers.getDialectPathFromURLArray(splitWindowPath)
    this.CONTRIBUTOR_PATH = `${this.DIALECT_PATH}/Contributors`
    // Get data for computeDialect
    if (!computeDialect.success) {
      await fetchDialect('/' + this.DIALECT_PATH)
    }

    const { DEFAULT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_SORT_TYPE, DEFAULT_SORT_COL } = this.props
    let currentAppliedFilter = '' // eslint-disable-line
    // if (filter.has('currentAppliedFilter')) {
    //   currentAppliedFilter = Object.values(filter.get('currentAppliedFilter').toJS()).join('')
    // }
    // Get contrinbutors
    await fetchContributors(
      this.CONTRIBUTOR_PATH,
      `${currentAppliedFilter}&currentPageIndex=${DEFAULT_PAGE -
        1}&pageSize=${DEFAULT_PAGE_SIZE}&sortOrder=${DEFAULT_SORT_TYPE}&sortBy=${DEFAULT_SORT_COL}`
    )

    // Get existing audio files
    // TODO: hardcoded current page and page size!
    await fetchResources(
      '/FV/Workspaces/',
      `AND ecm:primaryType LIKE 'FVAudio' AND ecm:isCheckedInVersion = 0 AND ecm:isTrashed = 0 AND ecm:currentLifeCycleState != 'Disabled' AND (ecm:path STARTSWITH '${
        this.DIALECT_PATH
      }/Resources/')&currentPageIndex=${0}&pageSize=${1000}`
    )

    const _SelectMediaComponent = await import('views/components/Editor/SelectMediaComponent')
    SelectMediaComponent = _SelectMediaComponent.default

    this.setState({ loading: false })
  }

  render() {
    const {
      className,
      idDescribedbyItemBrowse,
      idDescribedByItemMove,
      textDescribedbyItemBrowse,
      textDescribedByItemMove,
      textLegendItems,
      textBtnAddItem,
    } = this.props

    const items = this.state.items
    return (
      <fieldset className={className}>
        <legend>{textLegendItems}</legend>

        <button
          type="button"
          disabled={this.state.loading}
          onClick={() => {
            this.handleClickAddItem()
          }}
        >
          {textBtnAddItem}
        </button>

        {items}

        {this._generateHiddenInput()}

        {/* SCREEN READER DESCRIPTIONS --------------- */}

          {textDescribedbyItemBrowse}

          {textDescribedByItemMove}

      </fieldset>
    )
  }
  _getSelectedItems = () => {
    const { items } = this.state
    const selectedItems = items.map((element) => {
      return element.props.id
    })
    return selectedItems
  }
  _generateHiddenInput = () => {
    return <input type="hidden" name={this.props.name} value={JSON.stringify(this._getSelectedItems())} />
  }

  handleClickAddItem = () => {
    const _props = {
      name: this.props.name,
      className: this.props.className,
      idDescribedbyItemBrowse: this.props.idDescribedbyItemBrowse,
      idDescribedByItemMove: this.props.idDescribedByItemMove,
      textLegendItem: this.props.textLegendItem,
      textBtnRemoveItem: this.props.textBtnRemoveItem,
      textBtnMoveItemUp: this.props.textBtnMoveItemUp,
      textBtnMoveItemDown: this.props.textBtnMoveItemDown,
      textBtnCreateItem: this.props.textBtnCreateItem,
      textBtnSelectExistingItems: this.props.textBtnSelectExistingItems,
      textLabelItemSearch: this.props.textLabelItemSearch,
      handleClickSelectItem: this.handleClickSelectItem,
      handleClickRemoveItem: this.handleClickRemoveItem,
      handleClickMoveItemUp: this.handleClickMoveItemUp,
      handleClickMoveItemDown: this.handleClickMoveItemDown,
      handleItemSelected: this.handleItemSelected,
      computeDialectFromParent: this.props.computeDialect,
      DIALECT_PATH: this.DIALECT_PATH,
    }
    const items = this.state.items
    const id = `${_props.className}_${items.length}_${Date.now()}`
    items.push(<FormRelatedAudioItem key={id} id={id} {..._props} selectMediaComponent={SelectMediaComponent} />)
    this.setState({
      items,
    })
  }
  handleItemSelected = (selected, callback) => {
    const uid = selectn('uid', selected)

    let { items } = this.state
    const arg = { id: uid, items }

    if (getIndexOfElementById(arg) !== -1) {
      items = removeItem(arg)
    }

    const _props = {
      name: this.props.name,
      className: this.props.className,
      idDescribedbyItemBrowse: this.props.idDescribedbyItemBrowse,
      idDescribedByItemMove: this.props.idDescribedByItemMove,
      textLegendItem: this.props.textLegendItem,
      textBtnRemoveItem: this.props.textBtnRemoveItem,
      textBtnMoveItemUp: this.props.textBtnMoveItemUp,
      textBtnMoveItemDown: this.props.textBtnMoveItemDown,
      textBtnCreateItem: this.props.textBtnCreateItem,
      textBtnSelectExistingItems: this.props.textBtnSelectExistingItems,
      textLabelItemSearch: this.props.textLabelItemSearch,
      handleClickSelectItem: this.handleClickSelectItem,
      handleClickRemoveItem: this.handleClickRemoveItem,
      handleClickMoveItemUp: this.handleClickMoveItemUp,
      handleClickMoveItemDown: this.handleClickMoveItemDown,
      handleItemSelected: this.handleItemSelected,
      computeDialectFromParent: this.props.computeDialect,
      DIALECT_PATH: this.DIALECT_PATH,
    }

    this.setState(
      {
        items: [
          ...items,
          <FormRelatedAudioItem
            componentState={3}
            key={uid}
            id={uid}
            {..._props}
            selectMediaComponent={SelectMediaComponent}
          />,
        ],
      },
      () => {
        if (callback) {
          this._handleItemUpdate()
          callback()
        }
      }
    )
  }
  _handleItemUpdate = () => {
    this.props.handleChange(this._getSelectedItems())
  }
  handleClickRemoveItem = (id) => {
    this.setState(
      {
        items: removeItem({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
  handleClickMoveItemDown = (id) => {
    this.setState(
      {
        items: moveItemDown({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
  handleClickMoveItemUp = (id) => {
    this.setState(
      {
        items: moveItemUp({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
}

export default provide(FormRelatedAudio)
<commit_msg>Reduxed: FormRelatedAudio<commit_after>import React from 'react'
import { PropTypes } from 'react'
// import Text from 'views/components/Form/Common/Text'

// NOTE: importing the non-wrapped provide() version
import FormRelatedAudioItem from 'views/components/Form/FormRelatedAudioItem'
import { getIndexOfElementById, removeItem, moveItemDown, moveItemUp } from 'views/components/Form/FormInteractions'
import ProviderHelpers from 'common/ProviderHelpers'

// REDUX
import { connect } from 'react-redux'
// REDUX: actions/dispatch/func
import { createContributor, fetchContributors } from 'providers/redux/reducers/fvContributor'
import { fetchDialect } from 'providers/redux/reducers/fvResources'
import { fetchResources } from 'providers/redux/reducers/fvDialect'

import selectn from 'selectn'

const { string, array, object, func, number } = PropTypes
let SelectMediaComponent = null

export class FormRelatedAudio extends React.Component {
  static propTypes = {
    name: string.isRequired,
    className: string,
    textInfo: string,
    items: array,
    idDescribedbyItemBrowse: string,
    idDescribedByItemMove: string,
    textDescribedbyItemBrowse: string,
    textDescribedByItemMove: string,
    textLegendItems: string,
    textBtnAddItem: string,
    textLegendItem: string,
    textBtnRemoveItem: string,
    textBtnMoveItemUp: string,
    textBtnMoveItemDown: string,
    textBtnCreateItem: string,
    textBtnSelectExistingItems: string,
    textLabelItemSearch: string,
    DEFAULT_PAGE: number,
    DEFAULT_PAGE_SIZE: number,
    DEFAULT_LANGUAGE: string,
    DEFAULT_SORT_COL: string,
    DEFAULT_SORT_TYPE: string,
    handleChange: func,
    // REDUX: reducers/state
    computeContributor: object.isRequired,
    computeContributors: object.isRequired,
    computeCreateContributor: object,
    computeDialect: object.isRequired,
    computeDialect2: object.isRequired,
    splitWindowPath: array.isRequired,
    // REDUX: actions/dispatch/func
    createContributor: func.isRequired,
    fetchContributors: func.isRequired,
    fetchDialect: func.isRequired,
    fetchResources: func.isRequired,
  }
  static defaultProps = {
    className: 'FormRelatedAudio',
    idDescribedbyItemBrowse: 'describedbyRelatedAudioBrowse',
    idDescribedByItemMove: 'describedByRelatedAudioMove',
    name: 'FormRelatedAudio',
    textDescribedbyItemBrowse: 'Select an audio sample from previously uploaded items',
    textDescribedByItemMove: `If you are adding multiple Related Audio Items, you can change the position of the item with
    the 'Move Related Audio Item left' and 'Move Related Audio Item right' buttons`,
    textLegendItems: 'Related Audio',
    textBtnAddItem: 'Add Related Audio Item',
    textLegendItem: 'Related Audio Item',
    textBtnRemoveItem: 'Remove Related Audio Item',
    textBtnMoveItemUp: 'Move Related Audio Item left',
    textBtnMoveItemDown: 'Move Related Audio Item right',
    textBtnCreateItem: 'Create new audio item',
    textBtnSelectExistingItems: 'Select from existing audio',
    textLabelItemSearch: 'Search existing audio',
    DEFAULT_PAGE: 1,
    DEFAULT_PAGE_SIZE: 100,
    DEFAULT_LANGUAGE: 'english',
    DEFAULT_SORT_COL: 'dc:title',
    DEFAULT_SORT_TYPE: 'asc',
    handleChange: () => {},
  }

  state = {
    items: [],
    loading: true,
  }

  // Fetch data on initial render
  async componentDidMount() {
    const { computeDialect, splitWindowPath } = this.props

    // USING this.DIALECT_PATH instead of setting state
    // this.setState({ dialectPath: dialectPath })
    this.DIALECT_PATH = ProviderHelpers.getDialectPathFromURLArray(splitWindowPath)
    this.CONTRIBUTOR_PATH = `${this.DIALECT_PATH}/Contributors`
    // Get data for computeDialect
    if (!computeDialect.success) {
      await this.props.fetchDialect('/' + this.DIALECT_PATH)
    }

    const { DEFAULT_PAGE, DEFAULT_PAGE_SIZE, DEFAULT_SORT_TYPE, DEFAULT_SORT_COL } = this.props
    let currentAppliedFilter = '' // eslint-disable-line
    // if (filter.has('currentAppliedFilter')) {
    //   currentAppliedFilter = Object.values(filter.get('currentAppliedFilter').toJS()).join('')
    // }
    // Get contrinbutors
    await this.props.fetchContributors(
      this.CONTRIBUTOR_PATH,
      `${currentAppliedFilter}&currentPageIndex=${DEFAULT_PAGE -
        1}&pageSize=${DEFAULT_PAGE_SIZE}&sortOrder=${DEFAULT_SORT_TYPE}&sortBy=${DEFAULT_SORT_COL}`
    )

    // Get existing audio files
    // TODO: hardcoded current page and page size!
    await this.props.fetchResources(
      '/FV/Workspaces/',
      `AND ecm:primaryType LIKE 'FVAudio' AND ecm:isCheckedInVersion = 0 AND ecm:isTrashed = 0 AND ecm:currentLifeCycleState != 'Disabled' AND (ecm:path STARTSWITH '${
        this.DIALECT_PATH
      }/Resources/')&currentPageIndex=${0}&pageSize=${1000}`
    )

    const _SelectMediaComponent = await import('views/components/Editor/SelectMediaComponent')
    SelectMediaComponent = _SelectMediaComponent.default

    this.setState({ loading: false })
  }

  render() {
    const {
      className,
      idDescribedbyItemBrowse,
      idDescribedByItemMove,
      textDescribedbyItemBrowse,
      textDescribedByItemMove,
      textLegendItems,
      textBtnAddItem,
    } = this.props

    const items = this.state.items
    return (
      <fieldset className={className}>
        <legend>{textLegendItems}</legend>

        <button
          type="button"
          disabled={this.state.loading}
          onClick={() => {
            this.handleClickAddItem()
          }}
        >
          {textBtnAddItem}
        </button>

        {items}

        {this._generateHiddenInput()}

        {/* SCREEN READER DESCRIPTIONS --------------- */}

          {textDescribedbyItemBrowse}

          {textDescribedByItemMove}

      </fieldset>
    )
  }
  _getSelectedItems = () => {
    const { items } = this.state
    const selectedItems = items.map((element) => {
      return element.props.id
    })
    return selectedItems
  }
  _generateHiddenInput = () => {
    return <input type="hidden" name={this.props.name} value={JSON.stringify(this._getSelectedItems())} />
  }

  handleClickAddItem = () => {
    const _props = {
      name: this.props.name,
      className: this.props.className,
      idDescribedbyItemBrowse: this.props.idDescribedbyItemBrowse,
      idDescribedByItemMove: this.props.idDescribedByItemMove,
      textLegendItem: this.props.textLegendItem,
      textBtnRemoveItem: this.props.textBtnRemoveItem,
      textBtnMoveItemUp: this.props.textBtnMoveItemUp,
      textBtnMoveItemDown: this.props.textBtnMoveItemDown,
      textBtnCreateItem: this.props.textBtnCreateItem,
      textBtnSelectExistingItems: this.props.textBtnSelectExistingItems,
      textLabelItemSearch: this.props.textLabelItemSearch,
      handleClickSelectItem: this.handleClickSelectItem,
      handleClickRemoveItem: this.handleClickRemoveItem,
      handleClickMoveItemUp: this.handleClickMoveItemUp,
      handleClickMoveItemDown: this.handleClickMoveItemDown,
      handleItemSelected: this.handleItemSelected,
      computeDialectFromParent: this.props.computeDialect,
      DIALECT_PATH: this.DIALECT_PATH,
    }
    const items = this.state.items
    const id = `${_props.className}_${items.length}_${Date.now()}`
    items.push(<FormRelatedAudioItem key={id} id={id} {..._props} selectMediaComponent={SelectMediaComponent} />)
    this.setState({
      items,
    })
  }
  handleItemSelected = (selected, callback) => {
    const uid = selectn('uid', selected)

    let { items } = this.state
    const arg = { id: uid, items }

    if (getIndexOfElementById(arg) !== -1) {
      items = removeItem(arg)
    }

    const _props = {
      name: this.props.name,
      className: this.props.className,
      idDescribedbyItemBrowse: this.props.idDescribedbyItemBrowse,
      idDescribedByItemMove: this.props.idDescribedByItemMove,
      textLegendItem: this.props.textLegendItem,
      textBtnRemoveItem: this.props.textBtnRemoveItem,
      textBtnMoveItemUp: this.props.textBtnMoveItemUp,
      textBtnMoveItemDown: this.props.textBtnMoveItemDown,
      textBtnCreateItem: this.props.textBtnCreateItem,
      textBtnSelectExistingItems: this.props.textBtnSelectExistingItems,
      textLabelItemSearch: this.props.textLabelItemSearch,
      handleClickSelectItem: this.handleClickSelectItem,
      handleClickRemoveItem: this.handleClickRemoveItem,
      handleClickMoveItemUp: this.handleClickMoveItemUp,
      handleClickMoveItemDown: this.handleClickMoveItemDown,
      handleItemSelected: this.handleItemSelected,
      computeDialectFromParent: this.props.computeDialect,
      DIALECT_PATH: this.DIALECT_PATH,
    }

    this.setState(
      {
        items: [
          ...items,
          <FormRelatedAudioItem
            componentState={3}
            key={uid}
            id={uid}
            {..._props}
            selectMediaComponent={SelectMediaComponent}
          />,
        ],
      },
      () => {
        if (callback) {
          this._handleItemUpdate()
          callback()
        }
      }
    )
  }
  _handleItemUpdate = () => {
    this.props.handleChange(this._getSelectedItems())
  }
  handleClickRemoveItem = (id) => {
    this.setState(
      {
        items: removeItem({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
  handleClickMoveItemDown = (id) => {
    this.setState(
      {
        items: moveItemDown({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
  handleClickMoveItemUp = (id) => {
    this.setState(
      {
        items: moveItemUp({ id, items: this.state.items }),
      },
      () => {
        this._handleItemUpdate()
      }
    )
  }
}

// REDUX: reducers/state
const mapStateToProps = (state /*, ownProps*/) => {
  const { fvContributor, fvDialect, windowPath } = state

  const { computeContributor, computeContributors, computeCreateContributor } = fvContributor
  const { computeDialect, computeDialect2 } = fvDialect
  const { splitWindowPath } = windowPath

  return {
    computeContributor,
    computeContributors,
    computeCreateContributor,
    computeDialect,
    computeDialect2,
    splitWindowPath,
  }
}

// REDUX: actions/dispatch/func
const mapDispatchToProps = {
  createContributor,
  fetchContributors,
  fetchDialect,
  fetchResources,
}

export default connect(
  mapStateToProps,
  mapDispatchToProps
)(FormRelatedAudio)
<|endoftext|>


<commit_before>/* global describe, it, xit */
/* jslint node: true, esnext: true */

'use strict';

const chai = require('chai'),
  fs = require('fs'),
  path = require('path'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should();

const expand = require('../dist/expander').expand;

describe('expander', () => {

  describe('null expansion', () => {
    it('simple', () => expand({
      name: 'a1'
    }).then(r => assert.deepEqual({
      name: 'a1'
    }, r)));
  });

  describe('constants', () => {
    it('external', () => expand('${constA}', {
      constants: {
        constA: 'constAValue'
      }
    }).then(r => assert.equal(r, 'constAValue')));

    it('internal', () => expand({
      constants: {
        A: 1
      },
      name: '${A}'
    }).then(r => assert.deepEqual(r, {
      constants: {
        A: 1
      },
      name: 1
    })));

    it('double def', () => expand({
      constants: {
        A: 2
      },
      name: '${A}'
    }, {
      constants: {
        A: 7
      }
    }).then(r => assert.deepEqual(r, {
      constants: {
        A: 2
      },
      name: 2
    })));
  });

  describe('os', () => {
    it('os.arch', () => expand('${os.arch}').then(r => assert.equal(r, 'x64')));
    it('os.platform', () => expand('${os.platform}').then(r => assert.include(['aix', 'darwin', 'freebsd',
      'linux', 'win32'
    ], r)));
  });

  describe('expression', () => {
    it('string concat', () => expand("${'x' + 'y'}").then(r => assert.equal(r, 'xy')));
    it('addition', () => expand("${1 + 2}").then(r => assert.equal(r, 3)));
    it('substraction', () => expand("${3 - 2}").then(r => assert.equal(r, 1)));
    it('multiplication', () => expand("${3*2)}").then(r => assert.equal(r, 6)));
    it('division', () => expand("${8/2)}").then(r => assert.equal(r, 4)));
    it('number', () => expand("${number('77')}").then(r => assert.equal(r, 77)));
  });

  describe('boolean expression', () => {
    it('greater than false', () => expand("${1 > 2}").then(r => assert.equal(r, false)));
    it('greater than true', () => expand("${2 > 1}").then(r => assert.equal(r, true)));
    it('greater equal than false', () => expand("${1 >= 2}").then(r => assert.equal(r, false)));
    it('greater equal than true', () => expand("${2 >= 1}").then(r => assert.equal(r, true)));
    it('less than false', () => expand("${2 < 1}").then(r => assert.equal(r, false)));
    it('less than true', () => expand("${1 < 2}").then(r => assert.equal(r, true)));
    it('less equal than false', () => expand("${2 <= 1}").then(r => assert.equal(r, false)));
    it('less equal than true', () => expand("${1 <= 2}").then(r => assert.equal(r, true)));
    it('equal true', () => expand("${1 == 1}").then(r => assert.equal(r, true)));
    it('equal false', () => expand("${1 == 2}").then(r => assert.equal(r, false)));
    it('not equal true', () => expand("${2 != 1}").then(r => assert.equal(r, true)));
    it('not equal false', () => expand("${2 != 2}").then(r => assert.equal(r, false)));
    it('or false', () => expand("${0 || 0}").then(r => assert.equal(r, false)));
    it('or true', () => expand("${1 || 0}").then(r => assert.equal(r, true)));

    it('and false', () => expand("${1 && 0}").then(r => assert.equal(r, false)));
    it('and true', () => expand("${1 && 1}").then(r => assert.equal(r, true)));

    describe('combined', () => {
      it('or true', () => expand("${1 > 2 || 1 > 0}").then(r => assert.equal(r, true)));
      it('or false', () => expand("${1 > 2 || 1 < 0}").then(r => assert.equal(r, false)));

      it('and false', () => expand("${1>0 && 0>1}").then(r => assert.equal(r, false)));
      it('and true', () => expand("${1>0 && 2>0}").then(r => assert.equal(r, true)));
    });
  });

  describe('tenery expression', () => {
    it('true 1st.', () => expand("${2 > 1 ? 22 : 11}").then(r => assert.equal(r, 22)));
    it('false 2nd.', () => expand("${2 < 1 ? 22 : 11}").then(r => assert.equal(r, 11)));

    describe('combined', () => {
      it('false 2nd.', () => expand("${2 < 1 ? 22+1 : 11+1}").then(r => assert.equal(r, 12)));
      it('true 2nd.', () => expand("${2*0 < 1 ? 22+1 : 11+1}").then(r => assert.equal(r, 23)));
      it('true 2nd. with function call', () => expand("${'a'=='b' ? 22+1 : substring('abc',1,2)}").then(r =>
        assert.equal(r, 'b')));

      it('true with property access', () => expand(
        "${os.platform=='darwin' || os.platform=='linux' ? 1 : 0}").then(r =>
        assert.equal(r, 1)));
    });
  });

  describe('functions', () => {
    it('unknown function', () => expand("${  thisFunctionIsUnknown()}")
      .then(e => assert.equal(e, {}))
      .catch(e => assert.equal(e.message, '1,2: Unknown function "thisFunctionIsUnknown"')));
    it('toUpperCase', () => expand("${toUpperCase('lower')}").then(r => assert.equal(r, 'LOWER')));
    it('toLowerCase', () => expand("${toLowerCase('UPPER')}").then(r => assert.equal(r, 'upper')));
    it('substring', () => expand("${substring('lower',1,3)}").then(r => assert.equal(r, 'ow')));
    it('replace', () => expand("${replace('lower','ow','12')}").then(r => assert.equal(r, 'l12er')));

    it('length (string)', () => expand("${length('abc')}").then(r => assert.equal(r, 3)));
    it('length (array)', () => expand("${length([1,2,3])}").then(r => assert.equal(r, 3)));

    it('split', () => expand("${split('1,2,3,4',',')}").then(r => assert.deepEqual(r, ['1', '2', '3', '4'])));

    it('substring with expressions', () => expand("${substring('lower',1,1+2*1)}").then(r => assert.equal(r,
      'ow')));

    it('substring with expressions', () => expand("${substring('lower',1,number('2')+1)}").then(r => assert.equal(
      r, 'ow')));

    it('encrypt/decrypt', () => expand("${decrypt('key',encrypt('key','secret'))}").then(r => assert.equal(
      r, 'secret')));
  });

  describe('promise function args', () => {
    it('one promise arg', () => expand(
      "${substring(string(document('fixtures/short.txt')),0,4)}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, 'line')));
  });

  describe('promise expressions', () => {
    it('two promises binop', () => expand(
      "${document('fixtures/short.txt') + document('fixtures/short2.txt')}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('line 1\nline 2\n'))));

    it('left only promise binop', () => expand(
      "${document('fixtures/short.txt') + 'XX'}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('line 1\nXX'))));

    it('right only promise binop', () => expand(
      "${'XX' + document('fixtures/short.txt')}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('XXline 1\n'))));
  });

  describe('files', () => {
    it('has file content', () => expand({
      name: "${document('short.txt')}",
      name2: "${document('short.txt')}"
    }, {
      constants: {
        basedir: path.join(__dirname, 'fixtures')
      }
    }).then(r => assert.deepEqual(r, {
      name: new Buffer('line 1\n'),
      name2: new Buffer('line 1\n')
    })));

    it('resolve file names', () => expand({
      name: "${resolve('fixtures')}"
    }, {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      name: path.join(__dirname, 'fixtures')
    })));

    it('can include', () => expand("${include('fixtures/other.json')}", {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      key: 'value from other'
    })));

    it('can nest includes', () => expand("${include('fixtures/first.json')}", {
      constants: {
        nameOfTheOther: 'other.json',
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      first_key: {
        key: 'value from other'
      }
    })));

    it('include missing', () => expand("${include('fixtures/missing.json')}").catch(e => assert.equal(e.message,
      'ENOENT: no such file or directory, open \'/fixtures/missing.json\'')));

    xit('optional include', () => expand("${first(include('fixtures/missing.json'))}").then(r => assert.equal(
      r,
      undefined)));
  });

  describe('arrays', () => {
    it('access', () => expand("${myArray[2-1]}", {
      constants: {
        myArray: ['a', 'b', 'c'],
      }
    }).then(r => assert.equal(r, 'b')));

    it('access cascade', () => expand("${myArray[1][2]}", {
      constants: {
        myArray: ['a', [0, 0, 4711], 'c'],
      }
    }).then(r => assert.equal(r, 4711)));
  });

  describe('object paths', () => {
    it('access one level', () => expand("${myObject.att1}", {
      constants: {
        myObject: {
          att1: 'val1'
        },
      }
    }).then(r => assert.equal(r, 'val1')));

    it('access with promise', () => expand("${include('fixtures/with_sub.json').sub}", {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      "key": "value in other sub"
    })));

    it('access several levels', () => expand("${myObject.level1.level2}", {
      constants: {
        myObject: {
          level1: {
            level2: 'val2'
          }
        },
      }
    }).then(r => assert.equal(r, 'val2')));
  });

  describe('combined paths', () => {
    it('access objects first than array', () => expand("${myObject.level1.level2[1]}", {
      constants: {
        myObject: {
          level1: {
            level2: [1, 'val2']
          }
        },
      }
    }).then(r => assert.equal(r, 'val2')));

    it('access several levels', () => expand("${myObject.level1[1].level2}", {
      constants: {
        myObject: {
          level1: [{}, {
            level2: 'val2'
          }]
        },
      }
    }).then(r => assert.equal(r, 'val2')));
  });

  describe('array literals', () => {
    it('simple', () => expand("${[1,2,3]}").then(r => assert.deepEqual(r, [1, 2, 3])));
    it('nested', () => expand("${[1,['a'],3]}").then(r => assert.deepEqual(r, [1, ['a'], 3])));
  });
});
<commit_msg>fix: dummy first()<commit_after>/* global describe, it, xit */
/* jslint node: true, esnext: true */

'use strict';

const chai = require('chai'),
  fs = require('fs'),
  path = require('path'),
  assert = chai.assert,
  expect = chai.expect,
  should = chai.should();

const expand = require('../dist/expander').expand;

describe('expander', () => {

  describe('null expansion', () => {
    it('simple', () => expand({
      name: 'a1'
    }).then(r => assert.deepEqual({
      name: 'a1'
    }, r)));
  });

  describe('constants', () => {
    it('external', () => expand('${constA}', {
      constants: {
        constA: 'constAValue'
      }
    }).then(r => assert.equal(r, 'constAValue')));

    it('internal', () => expand({
      constants: {
        A: 1
      },
      name: '${A}'
    }).then(r => assert.deepEqual(r, {
      constants: {
        A: 1
      },
      name: 1
    })));

    it('double def', () => expand({
      constants: {
        A: 2
      },
      name: '${A}'
    }, {
      constants: {
        A: 7
      }
    }).then(r => assert.deepEqual(r, {
      constants: {
        A: 2
      },
      name: 2
    })));
  });

  describe('os', () => {
    it('os.arch', () => expand('${os.arch}').then(r => assert.equal(r, 'x64')));
    it('os.platform', () => expand('${os.platform}').then(r => assert.include(['aix', 'darwin', 'freebsd',
      'linux', 'win32'
    ], r)));
  });

  describe('expression', () => {
    it('string concat', () => expand("${'x' + 'y'}").then(r => assert.equal(r, 'xy')));
    it('addition', () => expand("${1 + 2}").then(r => assert.equal(r, 3)));
    it('substraction', () => expand("${3 - 2}").then(r => assert.equal(r, 1)));
    it('multiplication', () => expand("${3*2)}").then(r => assert.equal(r, 6)));
    it('division', () => expand("${8/2)}").then(r => assert.equal(r, 4)));
    it('number', () => expand("${number('77')}").then(r => assert.equal(r, 77)));
  });

  describe('boolean expression', () => {
    it('greater than false', () => expand("${1 > 2}").then(r => assert.equal(r, false)));
    it('greater than true', () => expand("${2 > 1}").then(r => assert.equal(r, true)));
    it('greater equal than false', () => expand("${1 >= 2}").then(r => assert.equal(r, false)));
    it('greater equal than true', () => expand("${2 >= 1}").then(r => assert.equal(r, true)));
    it('less than false', () => expand("${2 < 1}").then(r => assert.equal(r, false)));
    it('less than true', () => expand("${1 < 2}").then(r => assert.equal(r, true)));
    it('less equal than false', () => expand("${2 <= 1}").then(r => assert.equal(r, false)));
    it('less equal than true', () => expand("${1 <= 2}").then(r => assert.equal(r, true)));
    it('equal true', () => expand("${1 == 1}").then(r => assert.equal(r, true)));
    it('equal false', () => expand("${1 == 2}").then(r => assert.equal(r, false)));
    it('not equal true', () => expand("${2 != 1}").then(r => assert.equal(r, true)));
    it('not equal false', () => expand("${2 != 2}").then(r => assert.equal(r, false)));
    it('or false', () => expand("${0 || 0}").then(r => assert.equal(r, false)));
    it('or true', () => expand("${1 || 0}").then(r => assert.equal(r, true)));

    it('and false', () => expand("${1 && 0}").then(r => assert.equal(r, false)));
    it('and true', () => expand("${1 && 1}").then(r => assert.equal(r, true)));

    describe('combined', () => {
      it('or true', () => expand("${1 > 2 || 1 > 0}").then(r => assert.equal(r, true)));
      it('or false', () => expand("${1 > 2 || 1 < 0}").then(r => assert.equal(r, false)));

      it('and false', () => expand("${1>0 && 0>1}").then(r => assert.equal(r, false)));
      it('and true', () => expand("${1>0 && 2>0}").then(r => assert.equal(r, true)));
    });
  });

  describe('tenery expression', () => {
    it('true 1st.', () => expand("${2 > 1 ? 22 : 11}").then(r => assert.equal(r, 22)));
    it('false 2nd.', () => expand("${2 < 1 ? 22 : 11}").then(r => assert.equal(r, 11)));

    describe('combined', () => {
      it('false 2nd.', () => expand("${2 < 1 ? 22+1 : 11+1}").then(r => assert.equal(r, 12)));
      it('true 2nd.', () => expand("${2*0 < 1 ? 22+1 : 11+1}").then(r => assert.equal(r, 23)));
      it('true 2nd. with function call', () => expand("${'a'=='b' ? 22+1 : substring('abc',1,2)}").then(r =>
        assert.equal(r, 'b')));

      it('true with property access', () => expand(
        "${os.platform=='darwin' || os.platform=='linux' ? 1 : 0}").then(r =>
        assert.equal(r, 1)));
    });
  });

  describe('functions', () => {
    it('unknown function', () => expand("${  thisFunctionIsUnknown()}")
      .then(e => assert.equal(e, {}))
      .catch(e => assert.equal(e.message, '1,2: Unknown function "thisFunctionIsUnknown"')));
    it('toUpperCase', () => expand("${toUpperCase('lower')}").then(r => assert.equal(r, 'LOWER')));
    it('toLowerCase', () => expand("${toLowerCase('UPPER')}").then(r => assert.equal(r, 'upper')));
    it('substring', () => expand("${substring('lower',1,3)}").then(r => assert.equal(r, 'ow')));
    it('replace', () => expand("${replace('lower','ow','12')}").then(r => assert.equal(r, 'l12er')));

    it('length (string)', () => expand("${length('abc')}").then(r => assert.equal(r, 3)));
    it('length (array)', () => expand("${length([1,2,3])}").then(r => assert.equal(r, 3)));

    it('split', () => expand("${split('1,2,3,4',',')}").then(r => assert.deepEqual(r, ['1', '2', '3', '4'])));

    it('first', () => expand("${first(1,2,3)}").then(r => assert.equal(r, 1)));

    it('substring with expressions', () => expand("${substring('lower',1,1+2*1)}").then(r => assert.equal(r,
      'ow')));

    it('substring with expressions', () => expand("${substring('lower',1,number('2')+1)}").then(r => assert.equal(
      r, 'ow')));

    it('encrypt/decrypt', () => expand("${decrypt('key',encrypt('key','secret'))}").then(r => assert.equal(
      r, 'secret')));
  });

  describe('promise function args', () => {
    it('one promise arg', () => expand(
      "${substring(string(document('fixtures/short.txt')),0,4)}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, 'line')));
  });

  describe('promise expressions', () => {
    it('two promises binop', () => expand(
      "${document('fixtures/short.txt') + document('fixtures/short2.txt')}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('line 1\nline 2\n'))));

    it('left only promise binop', () => expand(
      "${document('fixtures/short.txt') + 'XX'}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('line 1\nXX'))));

    it('right only promise binop', () => expand(
      "${'XX' + document('fixtures/short.txt')}", {
        constants: {
          basedir: __dirname
        }
      }).then(r => assert.equal(r, new Buffer('XXline 1\n'))));
  });

  describe('files', () => {
    it('has file content', () => expand({
      name: "${document('short.txt')}",
      name2: "${document('short.txt')}"
    }, {
      constants: {
        basedir: path.join(__dirname, 'fixtures')
      }
    }).then(r => assert.deepEqual(r, {
      name: new Buffer('line 1\n'),
      name2: new Buffer('line 1\n')
    })));

    it('resolve file names', () => expand({
      name: "${resolve('fixtures')}"
    }, {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      name: path.join(__dirname, 'fixtures')
    })));

    it('can include', () => expand("${include('fixtures/other.json')}", {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      key: 'value from other'
    })));

    it('can nest includes', () => expand("${include('fixtures/first.json')}", {
      constants: {
        nameOfTheOther: 'other.json',
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      first_key: {
        key: 'value from other'
      }
    })));

    it('include missing', () => expand("${include('fixtures/missing.json')}").catch(e => assert.equal(e.message,
      'ENOENT: no such file or directory, open \'/fixtures/missing.json\'')));

    xit('optional include', () => expand("${first(include('fixtures/missing.json'))}").then(r => assert.equal(
      r,
      undefined)));
  });

  describe('arrays', () => {
    it('access', () => expand("${myArray[2-1]}", {
      constants: {
        myArray: ['a', 'b', 'c'],
      }
    }).then(r => assert.equal(r, 'b')));

    it('access cascade', () => expand("${myArray[1][2]}", {
      constants: {
        myArray: ['a', [0, 0, 4711], 'c'],
      }
    }).then(r => assert.equal(r, 4711)));
  });

  describe('object paths', () => {
    it('access one level', () => expand("${myObject.att1}", {
      constants: {
        myObject: {
          att1: 'val1'
        },
      }
    }).then(r => assert.equal(r, 'val1')));

    it('access with promise', () => expand("${include('fixtures/with_sub.json').sub}", {
      constants: {
        basedir: __dirname
      }
    }).then(r => assert.deepEqual(r, {
      "key": "value in other sub"
    })));

    it('access several levels', () => expand("${myObject.level1.level2}", {
      constants: {
        myObject: {
          level1: {
            level2: 'val2'
          }
        },
      }
    }).then(r => assert.equal(r, 'val2')));
  });

  describe('combined paths', () => {
    it('access objects first than array', () => expand("${myObject.level1.level2[1]}", {
      constants: {
        myObject: {
          level1: {
            level2: [1, 'val2']
          }
        },
      }
    }).then(r => assert.equal(r, 'val2')));

    it('access several levels', () => expand("${myObject.level1[1].level2}", {
      constants: {
        myObject: {
          level1: [{}, {
            level2: 'val2'
          }]
        },
      }
    }).then(r => assert.equal(r, 'val2')));
  });

  describe('array literals', () => {
    it('simple', () => expand("${[1,2,3]}").then(r => assert.deepEqual(r, [1, 2, 3])));
    it('nested', () => expand("${[1,['a'],3]}").then(r => assert.deepEqual(r, [1, ['a'], 3])));
  });
});
<|endoftext|>