<commit_before>'use strict'
const slapp = require('../slackSetup.js').slapp
const getRadius = require('../radius.js').getRadius
const tinyurl = require('tinyurl');
const request = require('request')
const where = require('node-where')

// route functions
const snapsByGeo = require('./routes/snapsByGeoRoute.js').snapsByGeo
const ipGeo = require('./routes/ipGeoRoute.js')
const routeFuncs = require('./routes/routesIndex.js')
const yes = require('./routes/yes.js').yes
const addressToGeo = require('./routes/addressToGeo.js').addressToGeo

//db imports
const firebase    = require('../firebaseSetup.js'),
      db = firebase.db,
      snaps = firebase.snaps,
      tags = firebase.tags,
      users = firebase.users,
      slackDb = firebase.slackDb

let test = function() {
  let state = { requested: Date.now() }
  let teamId = ''
  let teamInfo = {}
  let answer
  /*
  ifs to determine desktop v mobile
  &&
  browser v slack app
  body.
    token: '', // same as verify_token
    team_id: '',
    team_domain: '',
    channel_id: '',
    channel_name: '',
    user_id: '',
    user_name: ''
  .meta:
    { app_token: '',
      app_user_id: '',
      app_bot_id: '',
      bot_token: '',
      bot_user_id: '',
      bot_user_name: '',
      team_name: '',
      team_domain: '',
      team_resource_id: '',
      error: undefined,
      config: {} },
  */
  slapp.command('/test', (msg, text) => {
    teamInfo = slackDb
      .child(msg.body.team_id)
      .once("value")
      .then(function(obj){
        if(obj.val().lat){
          return teamInfo = obj.val()
        } else{
          // might make condition to change buttons displayed?
          return teamInfo.team_id = msg.body.team_id
        }
      })
    // console.log(msg._slapp.client.channel.team)
    // console.log(msg.meta)
    msg
      .say({
        text: "",
        attachments: [{
          text: 'Where do you want to Jaywalk to?',
          fallback: 'Where to today?',
          callback_id: 'test_callback',
          color: 'good',
          actions: [
            {
              name: 'answer',
              text: 'address',
              type: 'button',
              value: 'address'
            },
            {
              name: 'answer',
              text: 'WeWork',
              type: 'button',
              value: 'wework'
            },
            {
              name: 'answer',
              text: 'Boomtown',
              type: 'button',
              value: 'boomtown'
            },
            {
              name: 'answer',
              text: 'Download app',
              type: 'button',
              value: 'app'
            }
          ]
        }]
      })
      .route('requestToDatabase', state, 60)
  })
// slapp.action('test_callback', 'answer', (msg, value) => {
//   msg.respond(msg.body.response_url, `${value} is a good choice!`)
// })

  .route('requestToDatabase', (msg, state) => {
    console.log(msg.type)
    console.log(msg.body.token)
    console.log(msg.body.message_ts)
    // console.log(msg.body.channel.id)
    answer = msg.body.actions[0].value
    if(answer == 'boomtown'){
      snapsByGeo(teamInfo.lat,teamInfo.lng, msg, state)
    }else if(answer == 'wework'){
      msg
        .say('Not threaded')
        .say('Not threaded')
        .thread()
        .say('is threaded')
        .say('is threaded')
        .say('is threaded')

    }else if(answer == 'app'){
      return msg.say({
        text: "",
        attachments: [{
          text: '<itms-apps://itunes.apple.com/us/app/jaywalk-walk-get-deals/id1171719157?mt=8|iPhone>',
          color: 'good'
        },{
          text: '<market://play.google.com/store/apps/details?id=com.kinetise.appb3e241f4c2ebeba41965ba16c05b2eba&hl=en_GB|Android>',
          color: 'good'
        }]
      })
    }else if(answer == 'address'){
      yes(teamInfo,msg,state)
      //https://jaywalk-geo.herokuapp.com/geoloc.htm
      // .route('handleGeoLoc', state, 60)
    }else{ //handle error
      return msg
        .say("Whoops, you just have to pick a button...")
        .say('Click a button!')
        .route('requestToDatabase', state, 60)
    }
  }) //end .route('requestToDatabase')
  .route('handleGeoLoc',(msg,state) =>{
  }) //end .route( handleGeoLoc
  .route('relaventAsk', (msg,state) => {
    routeFuncs.relaventAsk(msg,state)
  })//end .route(relaventAsk)
  .route('address_geo', (msg,state) => {
    let text = (msg.body.event && msg.body.event.text) || ''
    where.is(text,function(err,result){
      /*
        result.get('')=>
        address,streetNumber, street,streetAddress,city,region,
        regionCode, postalCode, country, countryCode, lat, lng
      */
      if (result) {
        let lat = result.get('lat')
        let lng = result.get('lng')
        snapsByGeo(lat,lng,msg,state)
      }
    })
  })
}
module.exports = {
  test:test()
}

<commit_msg>threaded test<commit_after>'use strict'
const slapp = require('../slackSetup.js').slapp
const getRadius = require('../radius.js').getRadius
const tinyurl = require('tinyurl');
const request = require('request')
const where = require('node-where')

// route functions
const snapsByGeo = require('./routes/snapsByGeoRoute.js').snapsByGeo
const ipGeo = require('./routes/ipGeoRoute.js')
const routeFuncs = require('./routes/routesIndex.js')
const yes = require('./routes/yes.js').yes
const addressToGeo = require('./routes/addressToGeo.js').addressToGeo

//db imports
const firebase    = require('../firebaseSetup.js'),
      db = firebase.db,
      snaps = firebase.snaps,
      tags = firebase.tags,
      users = firebase.users,
      slackDb = firebase.slackDb

let test = function() {
  let state = { requested: Date.now() }
  let teamId = ''
  let teamInfo = {}
  let answer
  /*
  ifs to determine desktop v mobile
  &&
  browser v slack app
  body.
    token: '', // same as verify_token
    team_id: '',
    team_domain: '',
    channel_id: '',
    channel_name: '',
    user_id: '',
    user_name: ''
  .meta:
    { app_token: '',
      app_user_id: '',
      app_bot_id: '',
      bot_token: '',
      bot_user_id: '',
      bot_user_name: '',
      team_name: '',
      team_domain: '',
      team_resource_id: '',
      error: undefined,
      config: {} },
  */
  slapp.command('/test', (msg, text) => {
    teamInfo = slackDb
      .child(msg.body.team_id)
      .once("value")
      .then(function(obj){
        if(obj.val().lat){
          return teamInfo = obj.val()
        } else{
          // might make condition to change buttons displayed?
          return teamInfo.team_id = msg.body.team_id
        }
      })
    // console.log(msg._slapp.client.channel.team)
    // console.log(msg.meta)
    msg
      .say({
        text: "",
        attachments: [{
          text: 'Where do you want to Jaywalk to?',
          fallback: 'Where to today?',
          callback_id: 'test_callback',
          color: 'good',
          actions: [
            {
              name: 'answer',
              text: 'address',
              type: 'button',
              value: 'address'
            },
            {
              name: 'answer',
              text: 'WeWork',
              type: 'button',
              value: 'wework'
            },
            {
              name: 'answer',
              text: 'Boomtown',
              type: 'button',
              value: 'boomtown'
            },
            {
              name: 'answer',
              text: 'Download app',
              type: 'button',
              value: 'app'
            }
          ]
        }]
      })
      .route('requestToDatabase', state, 60)
  })
// slapp.action('test_callback', 'answer', (msg, value) => {
//   msg.respond(msg.body.response_url, `${value} is a good choice!`)
// })

  .route('requestToDatabase', (msg, state) => {
    console.log(msg.type)
    console.log(msg.body.token)
    console.log(msg.body.message_ts)
    // console.log(msg.body.channel.id)
    answer = msg.body.actions[0].value
    if(answer == 'boomtown'){
      snapsByGeo(teamInfo.lat,teamInfo.lng, msg, state)
    }else if(answer == 'wework'){
      msg
        .say('Not threaded')
        .thread()
        .say('is threaded')
        .say('is threaded')
        .say('is threaded')

    }else if(answer == 'app'){
      return msg.say({
        text: "",
        attachments: [{
          text: '<itms-apps://itunes.apple.com/us/app/jaywalk-walk-get-deals/id1171719157?mt=8|iPhone>',
          color: 'good'
        },{
          text: '<market://play.google.com/store/apps/details?id=com.kinetise.appb3e241f4c2ebeba41965ba16c05b2eba&hl=en_GB|Android>',
          color: 'good'
        }]
      })
    }else if(answer == 'address'){
      yes(teamInfo,msg,state)
      //https://jaywalk-geo.herokuapp.com/geoloc.htm
      // .route('handleGeoLoc', state, 60)
    }else{ //handle error
      return msg
        .say("Whoops, you just have to pick a button...")
        .say('Click a button!')
        .route('requestToDatabase', state, 60)
    }
  }) //end .route('requestToDatabase')
  .route('handleGeoLoc',(msg,state) =>{
  }) //end .route( handleGeoLoc
  .route('relaventAsk', (msg,state) => {
    routeFuncs.relaventAsk(msg,state)
  })//end .route(relaventAsk)
  .route('address_geo', (msg,state) => {
    let text = (msg.body.event && msg.body.event.text) || ''
    where.is(text,function(err,result){
      /*
        result.get('')=>
        address,streetNumber, street,streetAddress,city,region,
        regionCode, postalCode, country, countryCode, lat, lng
      */
      if (result) {
        let lat = result.get('lat')
        let lng = result.get('lng')
        snapsByGeo(lat,lng,msg,state)
      }
    })
  })
}
module.exports = {
  test:test()
}

<|endoftext|>


<commit_before>import { osmNode } from '../osm/node';

// Disconect the ways at the given node.
//
// Optionally, disconnect only the given ways.
//
// For testing convenience, accepts an ID to assign to the (first) new node.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// This is the inverse of `iD.actionConnect`.
//
// Reference:
//   https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
//   https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
//
export function actionDisconnect(nodeId, newNodeId) {
    var wayIds;

    var action = function(graph) {
        var node = graph.entity(nodeId);
        var connections = action.connections(graph);

        connections.forEach(function(connection) {
            var way = graph.entity(connection.wayID);
            var newNode = osmNode({id: newNodeId, loc: node.loc, tags: node.tags});

            graph = graph.replace(newNode);
            if (connection.index === 0 && way.isArea()) {
                // replace shared node with shared node..
                graph = graph.replace(way.replaceNode(way.nodes[0], newNode.id));
            } else if (way.isClosed() && connection.index === way.nodes.length - 1) {
                // replace closing node with new new node..
                graph = graph.replace(way.unclose().addNode(newNode.id));
            } else {
                // replace shared node with multiple new nodes..
                graph = graph.replace(way.updateNode(newNode.id, connection.index));
            }
        });

        return graph;
    };

    action.connections = function(graph) {
        var candidates = [];
        var keeping = false;
        var parentWays = graph.parentWays(graph.entity(nodeId));

        parentWays.forEach(function(way) {
            if (wayIds && wayIds.indexOf(way.id) === -1) {
                keeping = true;
                return;
            }
            if (way.isArea() && (way.nodes[0] === nodeId)) {
                candidates.push({ wayID: way.id, index: 0 });
            } else {
                way.nodes.forEach(function(waynode, index) {
                    if (waynode === nodeId) {
                        if (way.isClosed() && parentWays.length > 1 && wayIds && wayIds.includes(way.id) && index === way.nodes.length-1) {
                            return;
                        }
                        candidates.push({ wayID: way.id, index: index });
                    }
                });
            }
        });

        return keeping ? candidates : candidates.slice(1);
    };

    action.disabled = function(graph) {
        var connections = action.connections(graph);
        if (connections.length === 0)
            return 'not_connected';

        var parentWays = graph.parentWays(graph.entity(nodeId));
        var seenRelationIds = {};
        var sharedRelation;

        parentWays.forEach(function(way) {
            if (wayIds && wayIds.indexOf(way.id) === -1)
                return;

            var relations = graph.parentRelations(way);
            relations.forEach(function(relation) {
                if (relation.id in seenRelationIds) {
                    sharedRelation = relation;
                } else {
                    seenRelationIds[relation.id] = true;
                }
            });
        });

        if (sharedRelation)
            return 'relation';
    };

    action.limitWays = function(_) {
        if (!arguments.length) return wayIds;
        wayIds = _;
        return action;
    };

    return action;
}
<commit_msg>Remove excess whitespace<commit_after>import { osmNode } from '../osm/node';

// Disconect the ways at the given node.
//
// Optionally, disconnect only the given ways.
//
// For testing convenience, accepts an ID to assign to the (first) new node.
// Normally, this will be undefined and the way will automatically
// be assigned a new ID.
//
// This is the inverse of `iD.actionConnect`.
//
// Reference:
//   https://github.com/openstreetmap/potlatch2/blob/master/net/systemeD/halcyon/connection/actions/UnjoinNodeAction.as
//   https://github.com/openstreetmap/josm/blob/mirror/src/org/openstreetmap/josm/actions/UnGlueAction.java
//
export function actionDisconnect(nodeId, newNodeId) {
    var wayIds;

    var action = function(graph) {
        var node = graph.entity(nodeId);
        var connections = action.connections(graph);

        connections.forEach(function(connection) {
            var way = graph.entity(connection.wayID);
            var newNode = osmNode({id: newNodeId, loc: node.loc, tags: node.tags});

            graph = graph.replace(newNode);
            if (connection.index === 0 && way.isArea()) {
                // replace shared node with shared node..
                graph = graph.replace(way.replaceNode(way.nodes[0], newNode.id));
            } else if (way.isClosed() && connection.index === way.nodes.length - 1) {
                // replace closing node with new new node..
                graph = graph.replace(way.unclose().addNode(newNode.id));
            } else {
                // replace shared node with multiple new nodes..
                graph = graph.replace(way.updateNode(newNode.id, connection.index));
            }
        });

        return graph;
    };

    action.connections = function(graph) {
        var candidates = [];
        var keeping = false;
        var parentWays = graph.parentWays(graph.entity(nodeId));

        parentWays.forEach(function(way) {
            if (wayIds && wayIds.indexOf(way.id) === -1) {
                keeping = true;
                return;
            }
            if (way.isArea() && (way.nodes[0] === nodeId)) {
                candidates.push({ wayID: way.id, index: 0 });
            } else {
                way.nodes.forEach(function(waynode, index) {
                    if (waynode === nodeId) {
                        if (way.isClosed() && parentWays.length > 1 && wayIds && wayIds.includes(way.id) && index === way.nodes.length-1) {
                            return;
                        }
                        candidates.push({ wayID: way.id, index: index });
                    }
                });
            }
        });

        return keeping ? candidates : candidates.slice(1);
    };

    action.disabled = function(graph) {
        var connections = action.connections(graph);
        if (connections.length === 0)
            return 'not_connected';

        var parentWays = graph.parentWays(graph.entity(nodeId));
        var seenRelationIds = {};
        var sharedRelation;

        parentWays.forEach(function(way) {
            if (wayIds && wayIds.indexOf(way.id) === -1)
                return;

            var relations = graph.parentRelations(way);
            relations.forEach(function(relation) {
                if (relation.id in seenRelationIds) {
                    sharedRelation = relation;
                } else {
                    seenRelationIds[relation.id] = true;
                }
            });
        });

        if (sharedRelation)
            return 'relation';
    };

    action.limitWays = function(_) {
        if (!arguments.length) return wayIds;
        wayIds = _;
        return action;
    };

    return action;
}
<|endoftext|>


<commit_before>/*global require, module:false */
module.exports = function (grunt) {
    'use strict';

    var distJs = 'dist/js/',
        distCss = 'dist/css/';

    // Project configuration.
    grunt.initConfig({
        // Metadata.
        pkg: grunt.file.readJSON('package.json'),
        banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
            '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
            '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
            '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
            ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',

        // Task configuration.
        concat: {
            options: {
                banner: '<%= banner %>',
                stripBanners: true
            },
            js: {
                src: ['src/js/*.js'],
                dest: distJs + '<%= pkg.name %>.js'
            },
            css: {
                src: ['src/css/*.css'],
                dest: distCss + '<%= pkg.name %>.css'
            }
        },

        uglify: {
            options: {
                banner: '<%= banner %>'
            },
            dist: {
                src: '<%= concat.js.dest %>',
                dest: distJs + '<%= pkg.name %>.min.js'
            }
        },

        cssmin: {
            minify: {
                src: 'dist/css/share.bar.css',
                dest: 'dist/css/share.bar.min.css',
                banner: '<%= banner %>'
            }
        },

        jslint: {
            client: {
                src: ['<%= concat.js.src %>', 'tests/*.spec.js', 'Gruntfile.js'],
                directives: {
                    browser: true,
                    unparam: true,
                    regexp: true,
                    nomen: true,
                    plusplus: true
                }
            }
        },

        compass: {
            dist: {
                options: {
                    sassDir: 'src/sass',
                    cssDir: 'src/css',
                    outputStyle: 'compressed',
                    noLineComments: true,
                    relativeAssets: true

                }
            }
        },

        svgstore: {
            options: {
                prefix : 'icon-'
            },
            default: {
                files: {
                    'dist/img/icons.svg': ['src/img/*.svg']
                }
            }
        },

        svgmin: {
            options: {
                plugins: [
                    { removeViewBox: false },
                    { cleanupIDs: false }
                ]
            },
            dist: {
                files: {
                    'dist/img/icons.svg': 'dist/img/icons.svg'
                }
            }
        },

        'string-replace': {
            dist: {
                files: {
                    'dist/js/share.bar.js': 'dist/js/share.bar.js'
                },
                options: {
                    replacements: [{
                        pattern: '[[X_SVG_X]]',
                        replacement: grunt.file.read('dist/img/icons.svg')
                    }]
                }
            }
        },

        jasmine: {
            share: {
                src: 'src/js/share.js',
                options: {
                    specs: 'tests/*.spec.js',
                    vendor: [],
                    junit: {
                        path: 'tests/reports/',
                        consolidate: true
                    },
                    keepRunner: true,
                    template: require('grunt-template-jasmine-istanbul'),
                    templateOptions: {
                        coverage: 'tests/reports/conbertura.json',
                        report: [{
                            type: 'lcov',
                            options: {
                                dir: 'tests/reports/'
                            }
                        }]
                    },
                    // phantom options
                    '--web-security' : false,
                    '--local-to-remote-url-access' : true,
                    '--ignore-ssl-errors' : true
                }
            }
        },

        coveralls: {

            options: {
              force: true
            },
            main_target: {
                src: "tests/reports/lcov.info"
            }
        },

        watch: {
            gruntfile: {
                files: 'Gruntfile.js',
                tasks: ['jslint']
            },
            test: {
                files: 'tests/*.spec.js',
                tasks: ['jstest']
            },
            buildJS: {
                files: ['<%= concat.js.src %>'],
                tasks: ['jstest', 'js']
            },
            buildCSS: {
                files: ['src/sass/*.scss'],
                tasks: ['css']
            },
            buildIcons: {
                files: ['src/img/*.svg'],
                tasks: ['icon']
            }
        },

        connect: {
            server: {
                options: {
                    port: 9002,
                    base: '.',
                    livereload: true
                }
            }
        },

        webfont: {
            icons: {
                src: 'src/img/*.svg',
                dest: 'src/fonts',
                destCss: 'src/sass',
                options: {
                    font: 'share-icon',
                    hashes: false,
                    htmlDemo: false,
                    stylesheet: 'scss',
                    embed: ['woff', 'ttf'],
                    templateOptions: {
                        baseClass: 'share-font',
                        classPrefix: 'ico-share-',
                        mixinPrefix: 'ico-share-'
                    },
                    types: 'woff,ttf'
                }
            }
        },

        buddyjs: {
            src: ['<%= concat.js.src %>'],
            options: {}
        },

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: [],
                commit: true,
                commitMessage: 'Release version: %VERSION%',
                commitFiles: ['package.json'],
                createTag: true,
                tagName: '%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin master',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        }
    });

    // These plugins provide necessary tasks.
    grunt.loadNpmTasks('grunt-contrib-compass');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-jslint');
    grunt.loadNpmTasks('grunt-webfont');
    grunt.loadNpmTasks('grunt-svgstore');
    grunt.loadNpmTasks('grunt-string-replace');
    grunt.loadNpmTasks('grunt-svgmin');
    grunt.loadNpmTasks('grunt-buddyjs');
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-coveralls');

    // Custom tasks
    grunt.registerTask('icon', ['svgstore', 'webfont', 'svgmin', 'string-replace']);
    grunt.registerTask('jstest', ['jslint', 'buddyjs', 'jasmine']);
    grunt.registerTask('js', ['concat:js', 'string-replace', 'uglify']);
    grunt.registerTask('css', ['compass', 'concat:css', 'cssmin']);
    grunt.registerTask('server', ['connect:server', 'watch']);
    grunt.registerTask('ci', ['jasmine', 'coveralls']);

    // Default task.
    grunt.registerTask('default', ['icon', 'jstest', 'js', 'css']);
};
<commit_msg>Fixed lint error ocurred by bad identantion<commit_after>/*global require, module:false */
module.exports = function (grunt) {
    'use strict';

    var distJs = 'dist/js/',
        distCss = 'dist/css/';

    // Project configuration.
    grunt.initConfig({
        // Metadata.
        pkg: grunt.file.readJSON('package.json'),
        banner: '/*! <%= pkg.title || pkg.name %> - v<%= pkg.version %> - ' +
            '<%= grunt.template.today("yyyy-mm-dd") %>\n' +
            '<%= pkg.homepage ? "* " + pkg.homepage + "\\n" : "" %>' +
            '* Copyright (c) <%= grunt.template.today("yyyy") %> <%= pkg.author.name %>;' +
            ' Licensed <%= _.pluck(pkg.licenses, "type").join(", ") %> */\n',

        // Task configuration.
        concat: {
            options: {
                banner: '<%= banner %>',
                stripBanners: true
            },
            js: {
                src: ['src/js/*.js'],
                dest: distJs + '<%= pkg.name %>.js'
            },
            css: {
                src: ['src/css/*.css'],
                dest: distCss + '<%= pkg.name %>.css'
            }
        },

        uglify: {
            options: {
                banner: '<%= banner %>'
            },
            dist: {
                src: '<%= concat.js.dest %>',
                dest: distJs + '<%= pkg.name %>.min.js'
            }
        },

        cssmin: {
            minify: {
                src: 'dist/css/share.bar.css',
                dest: 'dist/css/share.bar.min.css',
                banner: '<%= banner %>'
            }
        },

        jslint: {
            client: {
                src: ['<%= concat.js.src %>', 'tests/*.spec.js', 'Gruntfile.js'],
                directives: {
                    browser: true,
                    unparam: true,
                    regexp: true,
                    nomen: true,
                    plusplus: true
                }
            }
        },

        compass: {
            dist: {
                options: {
                    sassDir: 'src/sass',
                    cssDir: 'src/css',
                    outputStyle: 'compressed',
                    noLineComments: true,
                    relativeAssets: true

                }
            }
        },

        svgstore: {
            options: {
                prefix : 'icon-'
            },
            default: {
                files: {
                    'dist/img/icons.svg': ['src/img/*.svg']
                }
            }
        },

        svgmin: {
            options: {
                plugins: [
                    { removeViewBox: false },
                    { cleanupIDs: false }
                ]
            },
            dist: {
                files: {
                    'dist/img/icons.svg': 'dist/img/icons.svg'
                }
            }
        },

        'string-replace': {
            dist: {
                files: {
                    'dist/js/share.bar.js': 'dist/js/share.bar.js'
                },
                options: {
                    replacements: [{
                        pattern: '[[X_SVG_X]]',
                        replacement: grunt.file.read('dist/img/icons.svg')
                    }]
                }
            }
        },

        jasmine: {
            share: {
                src: 'src/js/share.js',
                options: {
                    specs: 'tests/*.spec.js',
                    vendor: [],
                    junit: {
                        path: 'tests/reports/',
                        consolidate: true
                    },
                    keepRunner: true,
                    template: require('grunt-template-jasmine-istanbul'),
                    templateOptions: {
                        coverage: 'tests/reports/conbertura.json',
                        report: [{
                            type: 'lcov',
                            options: {
                                dir: 'tests/reports/'
                            }
                        }]
                    },
                    // phantom options
                    '--web-security' : false,
                    '--local-to-remote-url-access' : true,
                    '--ignore-ssl-errors' : true
                }
            }
        },

        coveralls: {

            options: {
                force: true
            },
            main_target: {
                src: "tests/reports/lcov.info"
            }
        },

        watch: {
            gruntfile: {
                files: 'Gruntfile.js',
                tasks: ['jslint']
            },
            test: {
                files: 'tests/*.spec.js',
                tasks: ['jstest']
            },
            buildJS: {
                files: ['<%= concat.js.src %>'],
                tasks: ['jstest', 'js']
            },
            buildCSS: {
                files: ['src/sass/*.scss'],
                tasks: ['css']
            },
            buildIcons: {
                files: ['src/img/*.svg'],
                tasks: ['icon']
            }
        },

        connect: {
            server: {
                options: {
                    port: 9002,
                    base: '.',
                    livereload: true
                }
            }
        },

        webfont: {
            icons: {
                src: 'src/img/*.svg',
                dest: 'src/fonts',
                destCss: 'src/sass',
                options: {
                    font: 'share-icon',
                    hashes: false,
                    htmlDemo: false,
                    stylesheet: 'scss',
                    embed: ['woff', 'ttf'],
                    templateOptions: {
                        baseClass: 'share-font',
                        classPrefix: 'ico-share-',
                        mixinPrefix: 'ico-share-'
                    },
                    types: 'woff,ttf'
                }
            }
        },

        buddyjs: {
            src: ['<%= concat.js.src %>'],
            options: {}
        },

        bump: {
            options: {
                files: ['package.json'],
                updateConfigs: [],
                commit: true,
                commitMessage: 'Release version: %VERSION%',
                commitFiles: ['package.json'],
                createTag: true,
                tagName: '%VERSION%',
                tagMessage: 'Version %VERSION%',
                push: true,
                pushTo: 'origin master',
                gitDescribeOptions: '--tags --always --abbrev=1 --dirty=-d'
            }
        }
    });

    // These plugins provide necessary tasks.
    grunt.loadNpmTasks('grunt-contrib-compass');
    grunt.loadNpmTasks('grunt-contrib-jasmine');
    grunt.loadNpmTasks('grunt-contrib-concat');
    grunt.loadNpmTasks('grunt-contrib-connect');
    grunt.loadNpmTasks('grunt-contrib-uglify');
    grunt.loadNpmTasks('grunt-contrib-cssmin');
    grunt.loadNpmTasks('grunt-contrib-watch');
    grunt.loadNpmTasks('grunt-jslint');
    grunt.loadNpmTasks('grunt-webfont');
    grunt.loadNpmTasks('grunt-svgstore');
    grunt.loadNpmTasks('grunt-string-replace');
    grunt.loadNpmTasks('grunt-svgmin');
    grunt.loadNpmTasks('grunt-buddyjs');
    grunt.loadNpmTasks('grunt-bump');
    grunt.loadNpmTasks('grunt-coveralls');

    // Custom tasks
    grunt.registerTask('icon', ['svgstore', 'webfont', 'svgmin', 'string-replace']);
    grunt.registerTask('jstest', ['jslint', 'buddyjs', 'jasmine']);
    grunt.registerTask('js', ['concat:js', 'string-replace', 'uglify']);
    grunt.registerTask('css', ['compass', 'concat:css', 'cssmin']);
    grunt.registerTask('server', ['connect:server', 'watch']);
    grunt.registerTask('ci', ['jasmine', 'coveralls']);

    // Default task.
    grunt.registerTask('default', ['icon', 'jstest', 'js', 'css']);
};
<|endoftext|>


<commit_before>import _map from 'lodash-es/map';
import _remove from 'lodash-es/remove';
import _uniq from 'lodash-es/uniq';

import { dispatch as d3_dispatch } from 'd3-dispatch';

import {
    event as d3_event,
    select as d3_select
} from 'd3-selection';

import { t } from '../../util/locale';
import { services } from '../../services';
import { uiCombobox } from '../index';
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';

export {
    uiFieldCombo as uiFieldMultiCombo,
    uiFieldCombo as uiFieldNetworkCombo,
    uiFieldCombo as uiFieldSemiCombo,
    uiFieldCombo as uiFieldTypeCombo
};

export function uiFieldCombo(field, context) {
    var dispatch = d3_dispatch('change');
    var nominatim = services.geocoder;
    var taginfo = services.taginfo;
    var isMulti = (field.type === 'multiCombo');
    var isNetwork = (field.type === 'networkCombo');
    var isSemi = (field.type === 'semiCombo');
    var optstrings = field.strings && field.strings.options;
    var optarray = field.options;
    var snake_case = (field.snake_case || (field.snake_case === undefined));
    var caseSensitive = field.caseSensitive;
    var combobox = uiCombobox(context, 'combo-' + field.safeid)
        .caseSensitive(caseSensitive)
        .minItems(isMulti || isSemi ? 1 : 2);
    var container = d3_select(null);
    var inputWrap = d3_select(null);
    var input = d3_select(null);
    var _comboData = [];
    var _multiData = [];
    var _entity;
    var _country;

    // ensure multiCombo field.key ends with a ':'
    if (isMulti && /[^:]$/.test(field.key)) {
        field.key += ':';
    }

    function snake(s) {
        return s.replace(/\s+/g, '_');
    }

    function unsnake(s) {
        return s.replace(/_+/g, ' ');
    }

    function clean(s) {
        return s.split(';')
            .map(function(s) { return s.trim(); })
            .join(';');
    }

    // returns the tag value for a display value
    // (for multiCombo, dval should be the key suffix, not the entire key)
    function tagValue(dval) {
        dval = clean(dval || '');

        if (optstrings) {
            var found = _comboData.find(function(o) {
                return o.key && clean(o.value) === dval;
            });
            if (found) {
                return found.key;
            }
        }

        if (field.type === 'typeCombo' && !dval) {
            return 'yes';
        }

        return (snake_case ? snake(dval) : dval) || undefined;
    }

    // returns the display value for a tag value
    // (for multiCombo, tval should be the key suffix, not the entire key)
    function displayValue(tval) {
        tval = tval || '';

        if (optstrings) {
            var found = _comboData.find(function(o) {
                return o.key === tval && o.value;
            });
            if (found) {
                return found.value;
            }
        }

        if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
            return '';
        }

        return snake_case ? unsnake(tval) : tval;
    }

    // Compute the difference between arrays of objects by `value` property
    //
    // objectDifference([{value:1}, {value:2}, {value:3}], [{value:2}])
    // > [{value:1}, {value:3}]
    //
    function objectDifference(a, b) {
        return a.filter(function(d1) {
            return !b.some(function(d2) { return d1.value === d2.value; });
        });
    }

    function initCombo(selection, attachTo) {
        if (optstrings) {
            selection.attr('readonly', 'readonly');
            selection.call(combobox, attachTo);
            setStaticValues(setPlaceholder);

        } else if (optarray) {
            selection.call(combobox, attachTo);
            setStaticValues(setPlaceholder);

        } else if (taginfo) {
            selection.call(combobox.fetcher(setTaginfoValues), attachTo);
            setTaginfoValues('', setPlaceholder);
        }
    }

    function setStaticValues(callback) {
        if (!(optstrings || optarray)) return;

        if (optstrings) {
            _comboData = Object.keys(optstrings).map(function(k) {
                var v = field.t('options.' + k, { 'default': optstrings[k] });
                return {
                    key: k,
                    value: v,
                    title: v
                };
            });

        } else if (optarray) {
            _comboData = optarray.map(function(k) {
                var v = snake_case ? unsnake(k) : k;
                return {
                    key: k,
                    value: v,
                    title: v
                };
            });
        }

        combobox.data(objectDifference(_comboData, _multiData));
        if (callback) callback(_comboData);
    }

    function setTaginfoValues(q, callback) {
        var fn = isMulti ? 'multikeys' : 'values';
        var query = (isMulti ? field.key : '') + q;
        var hasCountryPrefix = isNetwork && _country && _country.indexOf(q.toLowerCase()) === 0;
        if (hasCountryPrefix) {
            query = _country + ':';
        }

        var params = {
            debounce: (q !== ''),
            key: field.key,
            query: query
        };

        if (_entity) {
            params.geometry = context.geometry(_entity.id);
        }

        taginfo[fn](params, function(err, data) {
            if (err) return;
            if (hasCountryPrefix) {
                data = data.filter(function(d) {
                    return d.value.toLowerCase().indexOf(_country + ':') === 0;
                });
            }

            // hide the caret if there are no suggestions
            container.classed('empty-combobox', data.length === 0);

            _comboData = _map(data, function(d) {
                var k = d.value;
                if (isMulti) k = k.replace(field.key, '');
                var v = snake_case ? unsnake(k) : k;
                return {
                    key: k,
                    value: v,
                    title: isMulti ? v : d.title
                };
            });

            _comboData = objectDifference(_comboData, _multiData);
            if (callback) callback(_comboData);
        });
    }

    function setPlaceholder(d) {
        var ph;

        if (isMulti || isSemi) {
            ph = field.placeholder() || t('inspector.add');
        } else {
            var vals = _map(d, 'value').filter(function(s) { return s.length < 20; }),
                placeholders = vals.length > 1 ? vals : _map(d, 'key');
            ph = field.placeholder() || placeholders.slice(0, 3).join(', ');
        }

        if (!/(…|\.\.\.)$/.test(ph)) {
            ph += '…';
        }

        container.selectAll('input')
            .attr('placeholder', ph);
    }

    function change() {
        var t = {};
        var val;

        if (isMulti || isSemi) {
            val = tagValue(utilGetSetValue(input).replace(/,/g, ';')) || '';
            container.classed('active', false);
            utilGetSetValue(input, '');

            var vals = val.split(';').filter(Boolean);
            if (!vals.length) return;

            if (isMulti) {
                _uniq(vals).forEach(function(v) {
                    var key = field.key + v;
                    if (_entity) {
                        // don't set a multicombo value to 'yes' if it already has a non-'no' value
                        // e.g. `language:de=main`
                        var old = _entity.tags[key] || '';
                        if (old && old.toLowerCase() !== 'no') return;
                    }
                    field.keys.push(key);
                    t[key] = 'yes';
                });

            } else if (isSemi) {
                var arr = _multiData.map(function(d) { return d.key; });
                arr = arr.concat(vals);
                t[field.key] = _uniq(arr).filter(Boolean).join(';');
            }

            window.setTimeout(function() { input.node().focus(); }, 10);

        } else {
            val = tagValue(utilGetSetValue(input));
            t[field.key] = val;
        }

        dispatch.call('change', this, t);
    }

    function removeMultikey(d) {
        d3_event.stopPropagation();
        var t = {};
        if (isMulti) {
            t[d.key] = undefined;
        } else if (isSemi) {
            _remove(_multiData, function(md) { return md.key === d.key; });
            var arr = _multiData.map(function(md) { return md.key; });
            arr = _uniq(arr).filter(Boolean);
            t[field.key] = arr.length ? arr.join(';') : undefined;
        }
        dispatch.call('change', this, t);
    }

    function combo(selection) {
        container = selection.selectAll('.form-field-input-wrap')
            .data([0]);

        var type = (isMulti || isSemi) ? 'multicombo': 'combo';
        container = container.enter()
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-' + type)
            .merge(container);

        if (isMulti || isSemi) {
            container = container.selectAll('.chiplist')
                .data([0]);

            var listClass = 'chiplist';

            // Use a separate line for each value in the Destinations field
            // to mimic highway exit signs
            if (field.id === 'destination_oneway') {
                listClass += ' full-line-chips';
            }

            container = container.enter()
                .append('ul')
                .attr('class', listClass)
                .on('click', function() {
                    window.setTimeout(function() { input.node().focus(); }, 10);
                })
                .merge(container);

            inputWrap = container.selectAll('.input-wrap')
                .data([0]);

            inputWrap = inputWrap.enter()
                .append('li')
                .attr('class', 'input-wrap')
                .merge(inputWrap);

            input = inputWrap.selectAll('input')
                .data([0]);
        } else {
            input = container.selectAll('input')
                .data([0]);
        }

        input = input.enter()
            .append('input')
            .attr('type', 'text')
            .attr('id', 'preset-input-' + field.safeid)
            .call(utilNoAuto)
            .call(initCombo, selection)
            .merge(input);

        if (isNetwork && nominatim && _entity) {
            var center = _entity.extent(context.graph()).center();
            nominatim.countryCode(center, function (err, code) {
                _country = code;
            });
        }

        input
            .on('change', change)
            .on('blur', change);

        input
            .on('keydown.field', function() {
                switch (d3_event.keyCode) {
                    case 13: // ↩ Return
                        input.node().blur(); // blurring also enters the value
                        d3_event.stopPropagation();
                        break;
                }
            });

        if (isMulti || isSemi) {
            combobox
                .on('accept', function() {
                    input.node().blur();
                    input.node().focus();
                });

            input
                .on('focus', function() { container.classed('active', true); });
        }
    }

    combo.tags = function(tags) {
        if (isMulti || isSemi) {
            _multiData = [];

            if (isMulti) {
                // Build _multiData array containing keys already set..
                for (var k in tags) {
                    if (k.indexOf(field.key) !== 0) continue;
                    var v = (tags[k] || '').toLowerCase();
                    if (v === '' || v === 'no') continue;

                    var suffix = k.substring(field.key.length);
                    _multiData.push({
                        key: k,
                        value: displayValue(suffix)
                    });
                }

                // Set keys for form-field modified (needed for undo and reset buttons)..
                field.keys = _map(_multiData, 'key');

            } else if (isSemi) {
                var arr = _uniq((tags[field.key] || '').split(';')).filter(Boolean);
                _multiData = arr.map(function(k) {
                    return {
                        key: k,
                        value: displayValue(k)
                    };
                });
            }

            // Exclude existing multikeys from combo options..
            var available = objectDifference(_comboData, _multiData);
            combobox.data(available);

            // Hide 'Add' button if this field uses fixed set of
            // translateable optstrings and they're all currently used..
            container.selectAll('.combobox-input, .combobox-caret')
                .classed('hide', optstrings && !available.length);

            // Render chips
            var chips = container.selectAll('.chips')
                .data(_multiData);

            chips.exit()
                .remove();

            var enter = chips.enter()
                .insert('li', '.input-wrap')
                .attr('class', 'chips');

            enter.append('span');
            enter.append('a');

            chips = chips.merge(enter);

            chips.select('span')
                .text(function(d) { return d.value; });

            chips.select('a')
                .on('click', removeMultikey)
                .attr('class', 'remove')
                .text('×');

        } else {
            utilGetSetValue(input, displayValue(tags[field.key]));
        }
    };

    combo.focus = function() {
        input.node().focus();
    };

    combo.entity = function(val) {
        if (!arguments.length) return _entity;
        _entity = val;
        return combo;
    };

    return utilRebind(combo, dispatch, 'on');
}
<commit_msg>Remove lodash remove (re: #6087)<commit_after>import _map from 'lodash-es/map';
import _uniq from 'lodash-es/uniq';

import { dispatch as d3_dispatch } from 'd3-dispatch';

import {
    event as d3_event,
    select as d3_select
} from 'd3-selection';

import { t } from '../../util/locale';
import { services } from '../../services';
import { uiCombobox } from '../index';
import { utilGetSetValue, utilNoAuto, utilRebind } from '../../util';

export {
    uiFieldCombo as uiFieldMultiCombo,
    uiFieldCombo as uiFieldNetworkCombo,
    uiFieldCombo as uiFieldSemiCombo,
    uiFieldCombo as uiFieldTypeCombo
};

export function uiFieldCombo(field, context) {
    var dispatch = d3_dispatch('change');
    var nominatim = services.geocoder;
    var taginfo = services.taginfo;
    var isMulti = (field.type === 'multiCombo');
    var isNetwork = (field.type === 'networkCombo');
    var isSemi = (field.type === 'semiCombo');
    var optstrings = field.strings && field.strings.options;
    var optarray = field.options;
    var snake_case = (field.snake_case || (field.snake_case === undefined));
    var caseSensitive = field.caseSensitive;
    var combobox = uiCombobox(context, 'combo-' + field.safeid)
        .caseSensitive(caseSensitive)
        .minItems(isMulti || isSemi ? 1 : 2);
    var container = d3_select(null);
    var inputWrap = d3_select(null);
    var input = d3_select(null);
    var _comboData = [];
    var _multiData = [];
    var _entity;
    var _country;

    // ensure multiCombo field.key ends with a ':'
    if (isMulti && /[^:]$/.test(field.key)) {
        field.key += ':';
    }

    function snake(s) {
        return s.replace(/\s+/g, '_');
    }

    function unsnake(s) {
        return s.replace(/_+/g, ' ');
    }

    function clean(s) {
        return s.split(';')
            .map(function(s) { return s.trim(); })
            .join(';');
    }

    // returns the tag value for a display value
    // (for multiCombo, dval should be the key suffix, not the entire key)
    function tagValue(dval) {
        dval = clean(dval || '');

        if (optstrings) {
            var found = _comboData.find(function(o) {
                return o.key && clean(o.value) === dval;
            });
            if (found) {
                return found.key;
            }
        }

        if (field.type === 'typeCombo' && !dval) {
            return 'yes';
        }

        return (snake_case ? snake(dval) : dval) || undefined;
    }

    // returns the display value for a tag value
    // (for multiCombo, tval should be the key suffix, not the entire key)
    function displayValue(tval) {
        tval = tval || '';

        if (optstrings) {
            var found = _comboData.find(function(o) {
                return o.key === tval && o.value;
            });
            if (found) {
                return found.value;
            }
        }

        if (field.type === 'typeCombo' && tval.toLowerCase() === 'yes') {
            return '';
        }

        return snake_case ? unsnake(tval) : tval;
    }

    // Compute the difference between arrays of objects by `value` property
    //
    // objectDifference([{value:1}, {value:2}, {value:3}], [{value:2}])
    // > [{value:1}, {value:3}]
    //
    function objectDifference(a, b) {
        return a.filter(function(d1) {
            return !b.some(function(d2) { return d1.value === d2.value; });
        });
    }

    function initCombo(selection, attachTo) {
        if (optstrings) {
            selection.attr('readonly', 'readonly');
            selection.call(combobox, attachTo);
            setStaticValues(setPlaceholder);

        } else if (optarray) {
            selection.call(combobox, attachTo);
            setStaticValues(setPlaceholder);

        } else if (taginfo) {
            selection.call(combobox.fetcher(setTaginfoValues), attachTo);
            setTaginfoValues('', setPlaceholder);
        }
    }

    function setStaticValues(callback) {
        if (!(optstrings || optarray)) return;

        if (optstrings) {
            _comboData = Object.keys(optstrings).map(function(k) {
                var v = field.t('options.' + k, { 'default': optstrings[k] });
                return {
                    key: k,
                    value: v,
                    title: v
                };
            });

        } else if (optarray) {
            _comboData = optarray.map(function(k) {
                var v = snake_case ? unsnake(k) : k;
                return {
                    key: k,
                    value: v,
                    title: v
                };
            });
        }

        combobox.data(objectDifference(_comboData, _multiData));
        if (callback) callback(_comboData);
    }

    function setTaginfoValues(q, callback) {
        var fn = isMulti ? 'multikeys' : 'values';
        var query = (isMulti ? field.key : '') + q;
        var hasCountryPrefix = isNetwork && _country && _country.indexOf(q.toLowerCase()) === 0;
        if (hasCountryPrefix) {
            query = _country + ':';
        }

        var params = {
            debounce: (q !== ''),
            key: field.key,
            query: query
        };

        if (_entity) {
            params.geometry = context.geometry(_entity.id);
        }

        taginfo[fn](params, function(err, data) {
            if (err) return;
            if (hasCountryPrefix) {
                data = data.filter(function(d) {
                    return d.value.toLowerCase().indexOf(_country + ':') === 0;
                });
            }

            // hide the caret if there are no suggestions
            container.classed('empty-combobox', data.length === 0);

            _comboData = _map(data, function(d) {
                var k = d.value;
                if (isMulti) k = k.replace(field.key, '');
                var v = snake_case ? unsnake(k) : k;
                return {
                    key: k,
                    value: v,
                    title: isMulti ? v : d.title
                };
            });

            _comboData = objectDifference(_comboData, _multiData);
            if (callback) callback(_comboData);
        });
    }

    function setPlaceholder(d) {
        var ph;

        if (isMulti || isSemi) {
            ph = field.placeholder() || t('inspector.add');
        } else {
            var vals = _map(d, 'value').filter(function(s) { return s.length < 20; }),
                placeholders = vals.length > 1 ? vals : _map(d, 'key');
            ph = field.placeholder() || placeholders.slice(0, 3).join(', ');
        }

        if (!/(…|\.\.\.)$/.test(ph)) {
            ph += '…';
        }

        container.selectAll('input')
            .attr('placeholder', ph);
    }

    function change() {
        var t = {};
        var val;

        if (isMulti || isSemi) {
            val = tagValue(utilGetSetValue(input).replace(/,/g, ';')) || '';
            container.classed('active', false);
            utilGetSetValue(input, '');

            var vals = val.split(';').filter(Boolean);
            if (!vals.length) return;

            if (isMulti) {
                _uniq(vals).forEach(function(v) {
                    var key = field.key + v;
                    if (_entity) {
                        // don't set a multicombo value to 'yes' if it already has a non-'no' value
                        // e.g. `language:de=main`
                        var old = _entity.tags[key] || '';
                        if (old && old.toLowerCase() !== 'no') return;
                    }
                    field.keys.push(key);
                    t[key] = 'yes';
                });

            } else if (isSemi) {
                var arr = _multiData.map(function(d) { return d.key; });
                arr = arr.concat(vals);
                t[field.key] = _uniq(arr).filter(Boolean).join(';');
            }

            window.setTimeout(function() { input.node().focus(); }, 10);

        } else {
            val = tagValue(utilGetSetValue(input));
            t[field.key] = val;
        }

        dispatch.call('change', this, t);
    }

    function removeMultikey(d) {
        d3_event.stopPropagation();
        var t = {};
        if (isMulti) {
            t[d.key] = undefined;
        } else if (isSemi) {
            var arr = _multiData.map(function(md) {
                return md.key === d.key ? null : md.key;
            });
            arr = _uniq(arr).filter(Boolean);
            t[field.key] = arr.length ? arr.join(';') : undefined;
        }
        dispatch.call('change', this, t);
    }

    function combo(selection) {
        container = selection.selectAll('.form-field-input-wrap')
            .data([0]);

        var type = (isMulti || isSemi) ? 'multicombo': 'combo';
        container = container.enter()
            .append('div')
            .attr('class', 'form-field-input-wrap form-field-input-' + type)
            .merge(container);

        if (isMulti || isSemi) {
            container = container.selectAll('.chiplist')
                .data([0]);

            var listClass = 'chiplist';

            // Use a separate line for each value in the Destinations field
            // to mimic highway exit signs
            if (field.id === 'destination_oneway') {
                listClass += ' full-line-chips';
            }

            container = container.enter()
                .append('ul')
                .attr('class', listClass)
                .on('click', function() {
                    window.setTimeout(function() { input.node().focus(); }, 10);
                })
                .merge(container);

            inputWrap = container.selectAll('.input-wrap')
                .data([0]);

            inputWrap = inputWrap.enter()
                .append('li')
                .attr('class', 'input-wrap')
                .merge(inputWrap);

            input = inputWrap.selectAll('input')
                .data([0]);
        } else {
            input = container.selectAll('input')
                .data([0]);
        }

        input = input.enter()
            .append('input')
            .attr('type', 'text')
            .attr('id', 'preset-input-' + field.safeid)
            .call(utilNoAuto)
            .call(initCombo, selection)
            .merge(input);

        if (isNetwork && nominatim && _entity) {
            var center = _entity.extent(context.graph()).center();
            nominatim.countryCode(center, function (err, code) {
                _country = code;
            });
        }

        input
            .on('change', change)
            .on('blur', change);

        input
            .on('keydown.field', function() {
                switch (d3_event.keyCode) {
                    case 13: // ↩ Return
                        input.node().blur(); // blurring also enters the value
                        d3_event.stopPropagation();
                        break;
                }
            });

        if (isMulti || isSemi) {
            combobox
                .on('accept', function() {
                    input.node().blur();
                    input.node().focus();
                });

            input
                .on('focus', function() { container.classed('active', true); });
        }
    }

    combo.tags = function(tags) {
        if (isMulti || isSemi) {
            _multiData = [];

            if (isMulti) {
                // Build _multiData array containing keys already set..
                for (var k in tags) {
                    if (k.indexOf(field.key) !== 0) continue;
                    var v = (tags[k] || '').toLowerCase();
                    if (v === '' || v === 'no') continue;

                    var suffix = k.substring(field.key.length);
                    _multiData.push({
                        key: k,
                        value: displayValue(suffix)
                    });
                }

                // Set keys for form-field modified (needed for undo and reset buttons)..
                field.keys = _map(_multiData, 'key');

            } else if (isSemi) {
                var arr = _uniq((tags[field.key] || '').split(';')).filter(Boolean);
                _multiData = arr.map(function(k) {
                    return {
                        key: k,
                        value: displayValue(k)
                    };
                });
            }

            // Exclude existing multikeys from combo options..
            var available = objectDifference(_comboData, _multiData);
            combobox.data(available);

            // Hide 'Add' button if this field uses fixed set of
            // translateable optstrings and they're all currently used..
            container.selectAll('.combobox-input, .combobox-caret')
                .classed('hide', optstrings && !available.length);

            // Render chips
            var chips = container.selectAll('.chips')
                .data(_multiData);

            chips.exit()
                .remove();

            var enter = chips.enter()
                .insert('li', '.input-wrap')
                .attr('class', 'chips');

            enter.append('span');
            enter.append('a');

            chips = chips.merge(enter);

            chips.select('span')
                .text(function(d) { return d.value; });

            chips.select('a')
                .on('click', removeMultikey)
                .attr('class', 'remove')
                .text('×');

        } else {
            utilGetSetValue(input, displayValue(tags[field.key]));
        }
    };

    combo.focus = function() {
        input.node().focus();
    };

    combo.entity = function(val) {
        if (!arguments.length) return _entity;
        _entity = val;
        return combo;
    };

    return utilRebind(combo, dispatch, 'on');
}
<|endoftext|>


<commit_before>/*
 * This file is part of the squeakyJS project.
 *
 * Copyright (C) 2010, bp2010h1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

// setup

A = Class({
	instanceVariables: ['foo'],
	instanceMethods: {
		initialize: function() {

		},
		testMethod: function() {
		},
		setTest: function(num) {
			this.test = num;
		}
	},
	classVariables: ['age'],
	classMethods: {
		myAge: function() {
			return this.age;
		},
		firstName: function() {
			return 'Chuck';
		},
		lastName: function() {
			return 'Norris';
		},
		married: function() {
			// for non-local return testing
//			b = block(function() { nonLocalReturn(true); });
//			b.value();
			return false;
		}
	}
});

B = Class({
	superclass: A,
	instanceMethods: {
		testMethod2: function(num) {
			return num;
		},
		setTest: function(num) {
			return this._super.setTest(num);
		},
		lastName: function() {
			return this._class.lastName();
		}
	},
	classVariables: ['size'],
	classMethods: {
		myAge: function() {
			return this._super.myAge();
		},
		lastName: function() {
			return 'Skywalker';
		},
		married: function() {
			// for non-local return testing
			return this._super.married();
		}
	}
});

a = A._newInstance();
b = B._newInstance();

// testing
Assert.isTrue(typeof a.initialize == 'function');
Assert.isFalse(typeof a.foo == 'undefined');
Assert.isFalse(typeof b.foo == 'undefined');
Assert.isTrue(b.foo == null);
Assert.isTrue(typeof a.bar == 'undefined');
Assert.isTrue(typeof a.initialize == 'function');
Assert.isTrue(typeof b.testMethod == 'function');
Assert.isTrue(typeof b.testMethod2 == 'function');
Assert.isTrue(b.testMethod2(1337) == 1337);
Assert.isTrue(typeof A._newInstance == 'function');
Assert.isTrue(typeof A.firstName == 'function');
Assert.isTrue(A.firstName() == 'Chuck');
Assert.isTrue(A.lastName() == 'Norris');
Assert.isTrue(typeof B.firstName == 'function');
Assert.isTrue(B.firstName() == 'Chuck');
Assert.isTrue(B.lastName() == 'Skywalker');

a.setTest(3);
Assert.isTrue(a.test == 3);
Assert.isFalse(b.test == 3);

b.setTest(4);
Assert.isTrue(a.test == 3);
Assert.isTrue(b.test == 4);

B.age = 100;
Assert.isTrue(B.myAge() == 100);
Assert.isTrue(b.lastName() == B.lastName());

// test non-local return
//Assert.isTrue(A.married() == true);
//Assert.isTrue(B.married() == true);<commit_msg>Finished merging from master... (?)<commit_after>/*
 * This file is part of the squeakyJS project.
 *
 * Copyright (C) 2010, bp2010h1
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA  02110-1301 USA
 */

// setup

A = Class({
	instanceVariables: ['foo'],
	instanceMethods: {
		initialize: function() {

		},
		testMethod: function() {
		},
		setTest: function(num) {
			this.test = num;
		}
	},
	classVariables: ['age'],
	classMethods: {
		myAge: function() {
			return this.age;
		},
		firstName: function() {
			return 'Chuck';
		},
		lastName: function() {
			return 'Norris';
		},
		married: function() {
			// for non-local return testing
//			b = block(function() { nonLocalReturn(true); });
//			b.value();
			return false;
		}
	}
});

B = Class({
	superclass: A,
	instanceMethods: {
		testMethod2: function(num) {
			return num;
		},
		setTest: function(num) {
			return this._super.setTest(num);
		},
		lastName: function() {
			return this._class.lastName();
		}
	},
	classVariables: ['size'],
	classMethods: {
		myAge: function() {
			return this._super.myAge();
		},
		lastName: function() {
			return 'Skywalker';
		},
		married: function() {
			// for non-local return testing
			return this._super.married();
		}
	}
});

a = A.basicNew();
b = B.basicNew();

// testing
Assert.isTrue(typeof a.initialize == 'function');
Assert.isFalse(typeof a.foo == 'undefined');
Assert.isFalse(typeof b.foo == 'undefined');
Assert.isTrue(b.foo == null);
Assert.isTrue(typeof a.bar == 'undefined');
Assert.isTrue(typeof a.initialize == 'function');
Assert.isTrue(typeof b.testMethod == 'function');
Assert.isTrue(typeof b.testMethod2 == 'function');
Assert.isTrue(b.testMethod2(1337) == 1337);
Assert.isTrue(typeof A.basicNew == 'function');
Assert.isTrue(typeof A.firstName == 'function');
Assert.isTrue(A.firstName() == 'Chuck');
Assert.isTrue(A.lastName() == 'Norris');
Assert.isTrue(typeof B.firstName == 'function');
Assert.isTrue(B.firstName() == 'Chuck');
Assert.isTrue(B.lastName() == 'Skywalker');

a.setTest(3);
Assert.isTrue(a.test == 3);
Assert.isFalse(b.test == 3);

b.setTest(4);
Assert.isTrue(a.test == 3);
Assert.isTrue(b.test == 4);

B.age = 100;
Assert.isTrue(B.myAge() == 100);
Assert.isTrue(b.lastName() == B.lastName());

// test non-local return
//Assert.isTrue(A.married() == true);
//Assert.isTrue(B.married() == true);<|endoftext|>


<commit_before>'use strict';

const Query = require('../connection/commands').Query;
const Msg = require('../connection/msg').Msg;
const retrieveBSON = require('../connection/utils').retrieveBSON;
const MongoError = require('../error').MongoError;
const getReadPreference = require('./shared').getReadPreference;
const BSON = retrieveBSON();
const ReadPreference = require('../topologies/read_preference');
const TxnState = require('../transactions').TxnState;
const isSharded = require('./shared').isSharded;
const databaseNamespace = require('./shared').databaseNamespace;

function command(server, ns, cmd, options, callback) {
  if (typeof options === 'function') (callback = options), (options = {});
  options = options || {};

  if (cmd == null) {
    return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
  }

  const bson = server.s.bson;
  const pool = server.s.pool;
  const readPreference = getReadPreference(cmd, options);

  let finalCmd = Object.assign({}, cmd);
  if (isSharded(server) && readPreference && readPreference.preference !== 'primary') {
    finalCmd = {
      $query: finalCmd,
      $readPreference: readPreference.toJSON()
    };
  }

  const err = decorateWithSessionsData(finalCmd, options.session, options);
  if (err) {
    return callback(err);
  }

  const commandOptions = Object.assign(
    {
      command: true,
      numberToSkip: 0,
      numberToReturn: -1,
      checkKeys: false
    },
    options
  );

  // This value is not overridable
  commandOptions.slaveOk = readPreference.slaveOk();

  const cmdNs = `${databaseNamespace(ns)}.$cmd`;
  const message = supportsOpMsg(server)
    ? new Msg(bson, cmdNs, finalCmd, commandOptions)
    : new Query(bson, cmdNs, finalCmd, commandOptions);

  try {
    pool.write(message, commandOptions, callback);
  } catch (err) {
    callback(err);
  }
}

function supportsOpMsg(topologyOrServer) {
  const description = topologyOrServer.ismaster
    ? topologyOrServer.ismaster
    : topologyOrServer.description;

  if (description == null) {
    return false;
  }

  return description.maxWireVersion >= 6 && description.__nodejs_mock_server__ == null;
}

function isTransactionCommand(command) {
  return !!(command.commitTransaction || command.abortTransaction);
}

/**
 * Optionally decorate a command with sessions specific keys
 *
 * @param {Object} command the command to decorate
 * @param {ClientSession} session the session tracking transaction state
 * @param {Object} [options] Optional settings passed to calling operation
 * @return {MongoError|null} An error, if some error condition was met
 */
function decorateWithSessionsData(command, session, options) {
  if (!session) {
    return;
  }

  // first apply non-transaction-specific sessions data
  const serverSession = session.serverSession;
  const inTransaction = session.inTransaction() || isTransactionCommand(command);
  const isRetryableWrite = options.willRetryWrite;

  if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
    command.txnNumber = BSON.Long.fromNumber(serverSession.txnNumber);
  }

  // now attempt to apply transaction-specific sessions data
  if (!inTransaction) {
    if (session.transaction.state !== TxnState.NO_TRANSACTION) {
      session.transaction.transition(TxnState.NO_TRANSACTION);
    }

    // for causal consistency
    if (session.supports.causalConsistency && session.operationTime) {
      command.readConcern = command.readConcern || {};
      Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
    }

    return;
  }

  if (options.readPreference && !options.readPreference.equals(ReadPreference.primary)) {
    return new MongoError(
      `Read preference in a transaction must be primary, not: ${options.readPreference.mode}`
    );
  }

  // `autocommit` must always be false to differentiate from retryable writes
  command.autocommit = false;

  if (session.transaction.state === TxnState.STARTING_TRANSACTION) {
    session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
    command.startTransaction = true;

    const readConcern =
      session.transaction.options.readConcern || session.clientOptions.readConcern;
    if (readConcern) {
      command.readConcern = readConcern;
    }

    if (session.supports.causalConsistency && session.operationTime) {
      command.readConcern = command.readConcern || {};
      Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
    }
  }
}

module.exports = command;
<commit_msg>fix(command): only add `$query` to command in OP_QUERY<commit_after>'use strict';

const Query = require('../connection/commands').Query;
const Msg = require('../connection/msg').Msg;
const retrieveBSON = require('../connection/utils').retrieveBSON;
const MongoError = require('../error').MongoError;
const getReadPreference = require('./shared').getReadPreference;
const BSON = retrieveBSON();
const ReadPreference = require('../topologies/read_preference');
const TxnState = require('../transactions').TxnState;
const isSharded = require('./shared').isSharded;
const databaseNamespace = require('./shared').databaseNamespace;

function command(server, ns, cmd, options, callback) {
  if (typeof options === 'function') (callback = options), (options = {});
  options = options || {};

  if (cmd == null) {
    return callback(new MongoError(`command ${JSON.stringify(cmd)} does not return a cursor`));
  }

  const bson = server.s.bson;
  const pool = server.s.pool;
  const readPreference = getReadPreference(cmd, options);
  const shouldUseOpMsg = supportsOpMsg(server);

  let finalCmd = Object.assign({}, cmd);
  if (
    isSharded(server) &&
    shouldUseOpMsg &&
    readPreference &&
    readPreference.preference !== 'primary'
  ) {
    finalCmd = {
      $query: finalCmd,
      $readPreference: readPreference.toJSON()
    };
  }

  const err = decorateWithSessionsData(finalCmd, options.session, options);
  if (err) {
    return callback(err);
  }

  const commandOptions = Object.assign(
    {
      command: true,
      numberToSkip: 0,
      numberToReturn: -1,
      checkKeys: false
    },
    options
  );

  // This value is not overridable
  commandOptions.slaveOk = readPreference.slaveOk();

  const cmdNs = `${databaseNamespace(ns)}.$cmd`;
  const message = shouldUseOpMsg
    ? new Msg(bson, cmdNs, finalCmd, commandOptions)
    : new Query(bson, cmdNs, finalCmd, commandOptions);

  try {
    pool.write(message, commandOptions, callback);
  } catch (err) {
    callback(err);
  }
}

function supportsOpMsg(topologyOrServer) {
  const description = topologyOrServer.ismaster
    ? topologyOrServer.ismaster
    : topologyOrServer.description;

  if (description == null) {
    return false;
  }

  return description.maxWireVersion >= 6 && description.__nodejs_mock_server__ == null;
}

function isTransactionCommand(command) {
  return !!(command.commitTransaction || command.abortTransaction);
}

/**
 * Optionally decorate a command with sessions specific keys
 *
 * @param {Object} command the command to decorate
 * @param {ClientSession} session the session tracking transaction state
 * @param {Object} [options] Optional settings passed to calling operation
 * @return {MongoError|null} An error, if some error condition was met
 */
function decorateWithSessionsData(command, session, options) {
  if (!session) {
    return;
  }

  // first apply non-transaction-specific sessions data
  const serverSession = session.serverSession;
  const inTransaction = session.inTransaction() || isTransactionCommand(command);
  const isRetryableWrite = options.willRetryWrite;

  if (serverSession.txnNumber && (isRetryableWrite || inTransaction)) {
    command.txnNumber = BSON.Long.fromNumber(serverSession.txnNumber);
  }

  // now attempt to apply transaction-specific sessions data
  if (!inTransaction) {
    if (session.transaction.state !== TxnState.NO_TRANSACTION) {
      session.transaction.transition(TxnState.NO_TRANSACTION);
    }

    // for causal consistency
    if (session.supports.causalConsistency && session.operationTime) {
      command.readConcern = command.readConcern || {};
      Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
    }

    return;
  }

  if (options.readPreference && !options.readPreference.equals(ReadPreference.primary)) {
    return new MongoError(
      `Read preference in a transaction must be primary, not: ${options.readPreference.mode}`
    );
  }

  // `autocommit` must always be false to differentiate from retryable writes
  command.autocommit = false;

  if (session.transaction.state === TxnState.STARTING_TRANSACTION) {
    session.transaction.transition(TxnState.TRANSACTION_IN_PROGRESS);
    command.startTransaction = true;

    const readConcern =
      session.transaction.options.readConcern || session.clientOptions.readConcern;
    if (readConcern) {
      command.readConcern = readConcern;
    }

    if (session.supports.causalConsistency && session.operationTime) {
      command.readConcern = command.readConcern || {};
      Object.assign(command.readConcern, { afterClusterTime: session.operationTime });
    }
  }
}

module.exports = command;
<|endoftext|>


<commit_before>var test = require('tape')
var isValidDomain = require('../')

test('is valid domain', function(t) {
  t.plan(91)

  // tld and subdomains
  t.equal(isValidDomain('example.com'), true)
  t.equal(isValidDomain('foo.example.com'), true)
  t.equal(isValidDomain('bar.foo.example.com'), true)
  t.equal(isValidDomain('exa-mple.co.uk'), true)
  t.equal(isValidDomain('a.com'), true)
  t.equal(isValidDomain('a.b'), true)
  t.equal(isValidDomain('foo.bar.baz'), true)
  t.equal(isValidDomain('foo-bar.ba-z.qux'), true)
  t.equal(isValidDomain('hello.world'), true)
  t.equal(isValidDomain('ex-am-ple.com'), true)
  t.equal(isValidDomain('xn--80ak6aa92e.com'), true)
  t.equal(isValidDomain('example.a9'), true)
  t.equal(isValidDomain('example.9a'), true)
  t.equal(isValidDomain('example.99'), false)

  // punycode
  t.equal(isValidDomain('xn--6qq79v.xn--fiqz9s'), true)
  t.equal(isValidDomain('xn--ber-goa.com'), true)
  t.equal(isValidDomain('xn--a--ber-goa.com'), false)

  // invalid tld and subdomains
  t.equal(isValidDomain('localhost'), false)
  t.equal(isValidDomain('127.0.0.1'), false)
  t.equal(isValidDomain('bar.q-ux'), false)
  t.equal(isValidDomain('exa_mple.com'), false)
  t.equal(isValidDomain('example'), false)
  t.equal(isValidDomain('ex*mple.com'), false)
  t.equal(isValidDomain('@#$@#$%fd'), false)
  t.equal(isValidDomain('_example.com'), false)
  t.equal(isValidDomain('-example.com'), false)
  t.equal(isValidDomain('xn–pple-43d.com'), false)
  t.equal(isValidDomain('foo._example.com'), false)
  t.equal(isValidDomain('foo.-example.com'), false)
  t.equal(isValidDomain('foo.example-.co.uk'), false)
  t.equal(isValidDomain('example-.com'), false)
  t.equal(isValidDomain('example_.com'), false)
  t.equal(isValidDomain('foo.example-.com'), false)
  t.equal(isValidDomain('foo.example_.com'), false)
  t.equal(isValidDomain('example.com-'), false)
  t.equal(isValidDomain('example.com_'), false)
  t.equal(isValidDomain('-foo.example.com_'), false)
  t.equal(isValidDomain('_foo.example.com_'), false)
  t.equal(isValidDomain('*.com_'), false)
  t.equal(isValidDomain('*.*.com_'), false)

  // subdomain
  t.equal(isValidDomain('example.com'), true)
  t.equal(isValidDomain('foo.example.com'), true)
  t.equal(isValidDomain('foo.example.com', {subdomain: true}), true)
  t.equal(isValidDomain('foo.example.com', {subdomain: false}), false)
  t.equal(isValidDomain('-foo.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('foo-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo.example.com'), false)
  t.equal(isValidDomain('foo-.example.com'), false)
  t.equal(isValidDomain('-foo-.example.com'), false)
  t.equal(isValidDomain('foo-.bar.example.com'), false)
  t.equal(isValidDomain('-foo.bar.example.com'), false)
  t.equal(isValidDomain('-foo-.bar.example.com'), false)
  t.equal(isValidDomain('-foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.-bar-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('example.com', {subdomain: false}), true)
  t.equal(isValidDomain('*.example.com', {subdomain: true}), false)

  // subomdain underscores
  t.equal(isValidDomain('_dnslink.ipfs.io'), true)
  t.equal(isValidDomain('_foo.example.com'), true)
  t.equal(isValidDomain('xn--_eamop.donata.com'), true)
  t.equal(isValidDomain('__foo.example.com'), true)

  // second level domain
  t.equal(isValidDomain('example.co.uk'), true)
  t.equal(isValidDomain('exampl1.co.uk', {subdomain: false}), true)
  t.equal(isValidDomain('abc.example.co.uk', {subdomain: false}), false)
  t.equal(isValidDomain('*.example.co.uk', {subdomain: true}), false)
  t.equal(isValidDomain('*.example.co.uk', {subdomain: true, wildcard: true}), true)

  // wildcard
  t.equal(isValidDomain('*.example.com'), false)
  t.equal(isValidDomain('*.example.com', {wildcard:false}), false)
  t.equal(isValidDomain('*.example.com', {wildcard:true}), true)
  t.equal(isValidDomain('*.*.com', {wildcard:true}), false)
  t.equal(isValidDomain('*.com', {wildcard:true}), false)
  t.equal(isValidDomain('*.example.com', {subdomain: true, wildcard: true}), true)
  t.equal(isValidDomain('*.example.com', {subdomain: false, wildcard: true}), false)

  // valid length
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(61)}`), true)
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(61)}.`), true)
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(62)}`), false)

  // invalid types
  t.equal(isValidDomain(3434), false)
  t.equal(isValidDomain(''), false)
  t.equal(isValidDomain({}), false)
  t.equal(isValidDomain(function(){}), false)

  // junk
  t.equal(isValidDomain('foo.example.com*'), false)
  t.equal(isValidDomain('foo.example.com*', {wildcard: true}), false)
  t.equal(isValidDomain(`google.com"\'\"\""\\"\\'test test`), false)
  t.equal(isValidDomain(`google.com.au'"\'\"\""\\"\\'test`), false)
  t.equal(isValidDomain('...'), false)
  t.equal(isValidDomain('example..com'), false)
  t.equal(isValidDomain('.example.'), false)
  t.equal(isValidDomain('.example.com'), false)
  t.equal(isValidDomain('"example.com"'), false)
})
<commit_msg>Add more test cases<commit_after>var test = require('tape')
var isValidDomain = require('../')

test('is valid domain', function(t) {
  t.plan(95)

  // tld and subdomains
  t.equal(isValidDomain('example.com'), true)
  t.equal(isValidDomain('foo.example.com'), true)
  t.equal(isValidDomain('bar.foo.example.com'), true)
  t.equal(isValidDomain('exa-mple.co.uk'), true)
  t.equal(isValidDomain('a.com'), true)
  t.equal(isValidDomain('a.b'), true)
  t.equal(isValidDomain('foo.bar.baz'), true)
  t.equal(isValidDomain('foo-bar.ba-z.qux'), true)
  t.equal(isValidDomain('hello.world'), true)
  t.equal(isValidDomain('ex-am-ple.com'), true)
  t.equal(isValidDomain('xn--80ak6aa92e.com'), true)
  t.equal(isValidDomain('example.a9'), true)
  t.equal(isValidDomain('example.9a'), true)
  t.equal(isValidDomain('example.99'), false)

  // punycode
  t.equal(isValidDomain('xn--6qq79v.xn--fiqz9s'), true)
  t.equal(isValidDomain('xn--ber-goa.com'), true)
  t.equal(isValidDomain('xn--a--ber-goa.com'), false)
  t.equal(isValidDomain('xn--c1yn36f.example.com'), true)
  t.equal(isValidDomain('xn--addas-o4a.de'), true)

  // invalid tld and subdomains
  t.equal(isValidDomain('localhost'), false)
  t.equal(isValidDomain('127.0.0.1'), false)
  t.equal(isValidDomain('bar.q-ux'), false)
  t.equal(isValidDomain('exa_mple.com'), false)
  t.equal(isValidDomain('example'), false)
  t.equal(isValidDomain('ex*mple.com'), false)
  t.equal(isValidDomain('@#$@#$%fd'), false)
  t.equal(isValidDomain('_example.com'), false)
  t.equal(isValidDomain('-example.com'), false)
  t.equal(isValidDomain('xn–pple-43d.com'), false)
  t.equal(isValidDomain('foo._example.com'), false)
  t.equal(isValidDomain('foo.-example.com'), false)
  t.equal(isValidDomain('foo.example-.co.uk'), false)
  t.equal(isValidDomain('example-.com'), false)
  t.equal(isValidDomain('example_.com'), false)
  t.equal(isValidDomain('foo.example-.com'), false)
  t.equal(isValidDomain('foo.example_.com'), false)
  t.equal(isValidDomain('example.com-'), false)
  t.equal(isValidDomain('example.com_'), false)
  t.equal(isValidDomain('-foo.example.com_'), false)
  t.equal(isValidDomain('_foo.example.com_'), false)
  t.equal(isValidDomain('*.com_'), false)
  t.equal(isValidDomain('*.*.com_'), false)

  // subdomain
  t.equal(isValidDomain('example.com'), true)
  t.equal(isValidDomain('foo.example.com'), true)
  t.equal(isValidDomain('foo.example.com', {subdomain: true}), true)
  t.equal(isValidDomain('foo.example.com', {subdomain: false}), false)
  t.equal(isValidDomain('-foo.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('foo-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo.example.com'), false)
  t.equal(isValidDomain('foo-.example.com'), false)
  t.equal(isValidDomain('-foo-.example.com'), false)
  t.equal(isValidDomain('foo-.bar.example.com'), false)
  t.equal(isValidDomain('-foo.bar.example.com'), false)
  t.equal(isValidDomain('-foo-.bar.example.com'), false)
  t.equal(isValidDomain('-foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.bar.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('-foo-.-bar-.example.com', {subdomain: true}), false)
  t.equal(isValidDomain('example.com', {subdomain: false}), true)
  t.equal(isValidDomain('*.example.com', {subdomain: true}), false)

  // subomdain underscores
  t.equal(isValidDomain('_dnslink.ipfs.io'), true)
  t.equal(isValidDomain('_dnslink.ip_fs.io'), false)
  t.equal(isValidDomain('_foo.example.com'), true)
  t.equal(isValidDomain('xn--_eamop.donata.com'), true)
  t.equal(isValidDomain('__foo.example.com'), true)

  // second level domain
  t.equal(isValidDomain('example.co.uk'), true)
  t.equal(isValidDomain('exampl1.co.uk', {subdomain: false}), true)
  t.equal(isValidDomain('abc.example.co.uk', {subdomain: false}), false)
  t.equal(isValidDomain('*.example.co.uk', {subdomain: true}), false)
  t.equal(isValidDomain('*.example.co.uk', {subdomain: true, wildcard: true}), true)

  // wildcard
  t.equal(isValidDomain('*.example.com'), false)
  t.equal(isValidDomain('*.example.com', {wildcard:false}), false)
  t.equal(isValidDomain('*.example.com', {wildcard:true}), true)
  t.equal(isValidDomain('*.*.com', {wildcard:true}), false)
  t.equal(isValidDomain('*.com', {wildcard:true}), false)
  t.equal(isValidDomain('*.example.com', {subdomain: true, wildcard: true}), true)
  t.equal(isValidDomain('*.example.com', {subdomain: false, wildcard: true}), false)

  // valid length
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(61)}`), true)
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(61)}.`), true)
  t.equal(isValidDomain(`${'a'.repeat(63)}.${'b'.repeat(63)}.${'c'.repeat(63)}.${'c'.repeat(62)}`), false)

  // invalid types
  t.equal(isValidDomain(3434), false)
  t.equal(isValidDomain(''), false)
  t.equal(isValidDomain({}), false)
  t.equal(isValidDomain(function(){}), false)

  // invalid values
  t.equal(isValidDomain('foo.example.com*'), false)
  t.equal(isValidDomain('foo.example.com*', {wildcard: true}), false)
  t.equal(isValidDomain(`google.com"\'\"\""\\"\\'test test`), false)
  t.equal(isValidDomain(`google.com.au'"\'\"\""\\"\\'test`), false)
  t.equal(isValidDomain('...'), false)
  t.equal(isValidDomain('example..com'), false)
  t.equal(isValidDomain('.example.'), false)
  t.equal(isValidDomain('.example.com'), false)
  t.equal(isValidDomain('"example.com"'), false)
  t.equal(isValidDomain('http://xn--addas-o4a.de'), false)
})
<|endoftext|>