<commit_before>var _ = require('lodash');
var socketio = require('socket.io');
var utils = require('./utils.js');

module.exports = function (app, options) {
  'use strict';
  var
    io = socketio(app),
    config = options.config,
    emitter = options.emitter,
    nsApp = io.of('/devicewallapp'),
    nsCtrl = io.of('/devicewall'),
    devices = require('./devices'),
    instances = require('./instances'),
    bsInterval;

  // Device
  nsApp.use(function(socket, next) {
    var label = socket.handshake.query.label;
    if (label) {
      utils.updateLastSeen(label, devices);
      updateDevicesToControlPanel();
    }
    next();
  });
  nsApp.on('connection', function (socket) {
    console.log('Test device connected!');
    nsApp.emit("version", utils.getVersion());

    socket.on('ping', pingApp);
    socket.on('rename', rename);
    socket.on('update', update);
    socket.on('started', started);
    socket.on('idling', idling);
    socket.on('disconnect', disconnect);
    socket.on('check-platform', checkPlatform);

    function pingApp(data) {
      if (data.label) {
        utils.updateLastSeen(data.label, devices);
        //console.log(new Date().toISOString() + " Client <<<< ping " + data.label);
      }
    }

    function rename(data) {
      console.log("Client <<<< rename ", data);
      var device = devices.find(data.oldLabel);
      if (device) {
        if (devices.remove(data.oldLabel)) {
          console.log("Removed label " + data.oldLabel);
        } else {
          console.log("Unable to remove label " + data.oldLabel);
        }
        device.set('label', data.newLabel);
        devices.update(device.toJSON());
        console.log("Control >>>> rename ", data);
        nsCtrl.emit('rename', data);
      } else {
        console.log("Device label " + data.oldLabel + " does not exists");
      }
    }

    function update(data) {
      console.log("Client <<< update", data);
      var device;
      if (_.has(data, 'label') === false || data.label === '') {
        console.error("Client sent an empty label", data);
        return;
      }
      if (_.has(data, 'appVersion')) {
        if (data.appVersion !== utils.getVersion()) {
          nsApp.emit("version", utils.getVersion());
        }
      }
      device = devices.find(data.label);
      if (device) {
        // Set version and platform if not defined
        _.each(['version', 'platform'], function(infoItem) {
          if (_.has(data, infoItem) && device.has(infoItem) === false) {
            device.set(infoItem, data[infoItem]);
          }
        });
        data = device.toJSON();
      }

      data.lastSeen = new Date().getTime();
      device = devices.update(data);

      if (device.get('userId')) {
        var instance = instances.find(device.get('userId'));
        if (instance) {
          if (instance.get('status') === 'running') {
            device.set('status', 'starting');
            console.log("Client >>> start", data);
            var startData = {
              labels: instance.get('labels'),
              url: instance.get('startUrl')
            };
            socket.emit('start', startData);
            instance.syncClientLocations();
          }
        }
      }
      console.log("Control >>>> update", devices.toJSON());
      nsCtrl.emit('update', devices.toJSON());
    }

    function started(options) {
      console.log("Client <<< started: ", options.label, options.socketId);
      var device = devices.find(options.label);
      if (device) {
        device.set('status', 'running');
        device.set('devicewall', options.socketId);
        nsCtrl.emit('update', devices.toJSON());
      }
    }

    function idling(label) {
      console.log("Client <<< idling", label);
      var device = devices.find(label);
      if (device) {
        device.set('status', 'idle');
        device.set('updated', +new Date());
        devices.update(device.toJSON());
        app.emit('update-devices');
        console.log("Control >>>> update", devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
      }
    }

    function disconnect() {
      console.log("Client <<< disconnect");
    }

    function checkPlatform(data, fn) {
      console.log("Client <<< check-platform", data);
      var appPlatform = '';
      var device = devices.find(data.label);
      if (device) {
        appPlatform = device.appPlatform;
      }
      fn({appPlatform: appPlatform});
    }
  });

  // Control panel
  nsCtrl.on('connection', function (socket) {
    console.log('Control <<<< connection');
    // Start
    socket.on('start', start);
    socket.on('stop', stop);
    socket.on('stopall', stopAll);
    socket.on('disconnect', disconnect);
    socket.on('list', list);
    socket.on('save', save);
    socket.on('remove', removeDevices);
    socket.on('reload-devices', reloadDevices);
    if (process.env.NODE_ENV === "test") {
      socket.on('reset', resetAppData);
    }

    function stop(data) {
      console.log("Control <<< stop", data);
      instances.stop(data.url).then(function() {
        console.log('Control >> update', devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
        console.log('Control >> stop', data);
        nsCtrl.emit('stop', data);
      });
    }

    function stopAll() {
      console.log("Control <<< stopAll");
      instances.stopAll().done(function() {
        console.log('Control >> update', devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
        console.log('Control >> stopall');
        nsCtrl.emit('stopall');
      });
    }

    function disconnect () {
      console.log("Control <<< disconnect");
    }

    function list(data, fn) {
      console.log("Control <<< list");
      devices.sort();
      if (typeof(fn) === typeof(Function)) {
        fn(devices.toJSON());
      } else {
        console.log("not a function: ", fn);
      }
    }

    function save(data) {
      console.log("Control <<< save", data);
      var device = devices.find(data.label);
      if (device) {
        ['model', 'version', 'platform'].forEach(function(val) {
          if (data[val]) {
            device.set(val, data[val]);
          }
        });
        devices.update(device.toJSON());
      }
      app.emit('update-devices');
      socket.broadcast.emit('update', devices.toJSON());
    }

    function removeDevices(data) {
      console.log("Control <<< remove", data);
      data.labels.forEach(function(label) {
        devices.remove(label);
      });
      app.emit('update-devices');
      socket.broadcast.emit('remove', data.labels);
    }

    function reloadDevices() {
      console.log("Control <<< reload-devices");
      devices.read();
    }

    // only used with e2e tests
    function resetAppData() {
      console.info("Control <<< reset");
      instances.forceStopAll();
      //instances.stopAll().then(function() {
        console.info(">>> resetted");
        socket.emit("resetted");
      //});
      devices.removeAll();
    }
  });

  function start(data) {
    console.log("Control <<< start", data);
    var uri = utils.guessURI(data.url);
    if (uri === false) {
      console.log("Invalid URL: " + data.url);
      var emitData = {user: data.user, reason: "Invalid URL"};
      console.log('Control >> server-stop', emitData);
      nsCtrl.emit('server-stop', emitData);
      return;
    }
    data.url = uri;

    instances.start(data).then(
      function(startData) {
        var appData = _.clone(data);
        appData.url = startData.startUrl;

        console.log('Control >> socket "update"');
        // Add site and proxy URIs to control panel 'update' event
        var devicesClone = devices.toJSON();
        _.each(devicesClone, function(device) {
          if (data.labels.indexOf(device.label) > -1) {
            device.site = data.url;
            device.proxy = startData.startUrl;
          }
        });
        nsCtrl.emit('update', devicesClone);

        console.log('Control >> start', data);
        nsCtrl.emit('start', data);
        console.log('Client >> start', appData);
        nsApp.emit('start', appData);
        checkDevicesSentToBrowserSync(data, appData);
        instances.waitForClientConnections(data.labels.length).then(function() {
          console.log('Control >> running', data);
          nsCtrl.emit('running', data);
        });
      },
      function(reason) {
        var emitData = {user: data.user, reason: reason};
        console.log('Control >> server-stop', emitData);
        nsCtrl.emit('server-stop', emitData);
      }
    );
  };

  function checkDevicesSentToBrowserSync(data, appData) {
    if (bsInterval) { return; }

    var count = 0;
    bsInterval = setInterval(function() {
      count++;
      var check = false;
      update();

      if (count > 5) {
        console.log('Problem with BrowserSync, recalling all devices');
        instances.stopAll().done(function() {
          console.log('Control >> update', devices.toJSON());
          update({ stopall: true });
          console.log('Control >> stopall');
          nsCtrl.emit('stopall');
        });
        clear();
      } else if (check) {
        console.log('Not all requested devices in browsersync context');
        console.log('Client >> re-start');
      } else {
        console.log('All requested devices in browsersync context');
        clear();
      }

      function clear() {
        clearInterval(bsInterval);
        bsInterval = undefined;
      }

      function update(options) {
        options = options || {};
        devices.devices.forEach(function(device) {
          if (options.stopall) {
            device.set('browsersyncConnecting', false);
          } else {
            if (data.labels.indexOf(device.get('label')) > -1 && device.get('browsersyncStatus') !== 'Connected') {
              device.set('browsersyncConnecting', true);
              check = true;
            } else {
              device.set('browsersyncConnecting', false);
            }
          }
        });
        nsCtrl.emit('update', devices.toJSON());
      }
      nsApp.emit('start', appData);
    }, 5000);
  };

  function removeGhostDevices() {
    var labels = devices.getGhostDevices();
    if (labels.length) {
      console.log("Control <<< removeGhostDevices", labels);
      labels.forEach(function(label) {
        devices.remove(label);
      });
      app.emit('update-devices');
      nsCtrl.emit('remove', labels);
    }
  }

  function updateDevicesToControlPanel() {
    nsCtrl.emit('update', devices.toJSON());
  }

  // Events
  emitter.on('click:externalurl', function(data) {
    start(data);
  });

  emitter.on('client:connected', function() {
    nsCtrl.emit('update', devices.toJSON());
  });

  devices.init({config: config});
  devices.read();
  removeGhostDevices();
  instances.init({
    config: config,
    devices: devices,
    emitter: emitter
  });
  setInterval(function() {
    devices.write();
  }, 10000);
  setInterval(function() {
    updateDevicesToControlPanel();
  }, 10000);
  setInterval(function() {
    removeGhostDevices();
  }, 86400000);
};
<commit_msg>Disable same browsersync context check<commit_after>var _ = require('lodash');
var socketio = require('socket.io');
var utils = require('./utils.js');

module.exports = function (app, options) {
  'use strict';
  var
    io = socketio(app),
    config = options.config,
    emitter = options.emitter,
    nsApp = io.of('/devicewallapp'),
    nsCtrl = io.of('/devicewall'),
    devices = require('./devices'),
    instances = require('./instances'),
    bsInterval;

  // Device
  nsApp.use(function(socket, next) {
    var label = socket.handshake.query.label;
    if (label) {
      utils.updateLastSeen(label, devices);
      updateDevicesToControlPanel();
    }
    next();
  });
  nsApp.on('connection', function (socket) {
    console.log('Test device connected!');
    nsApp.emit("version", utils.getVersion());

    socket.on('ping', pingApp);
    socket.on('rename', rename);
    socket.on('update', update);
    socket.on('started', started);
    socket.on('idling', idling);
    socket.on('disconnect', disconnect);
    socket.on('check-platform', checkPlatform);

    function pingApp(data) {
      if (data.label) {
        utils.updateLastSeen(data.label, devices);
        //console.log(new Date().toISOString() + " Client <<<< ping " + data.label);
      }
    }

    function rename(data) {
      console.log("Client <<<< rename ", data);
      var device = devices.find(data.oldLabel);
      if (device) {
        if (devices.remove(data.oldLabel)) {
          console.log("Removed label " + data.oldLabel);
        } else {
          console.log("Unable to remove label " + data.oldLabel);
        }
        device.set('label', data.newLabel);
        devices.update(device.toJSON());
        console.log("Control >>>> rename ", data);
        nsCtrl.emit('rename', data);
      } else {
        console.log("Device label " + data.oldLabel + " does not exists");
      }
    }

    function update(data) {
      console.log("Client <<< update", data);
      var device;
      if (_.has(data, 'label') === false || data.label === '') {
        console.error("Client sent an empty label", data);
        return;
      }
      if (_.has(data, 'appVersion')) {
        if (data.appVersion !== utils.getVersion()) {
          nsApp.emit("version", utils.getVersion());
        }
      }
      device = devices.find(data.label);
      if (device) {
        // Set version and platform if not defined
        _.each(['version', 'platform'], function(infoItem) {
          if (_.has(data, infoItem) && device.has(infoItem) === false) {
            device.set(infoItem, data[infoItem]);
          }
        });
        data = device.toJSON();
      }

      data.lastSeen = new Date().getTime();
      device = devices.update(data);

      if (device.get('userId')) {
        var instance = instances.find(device.get('userId'));
        if (instance) {
          if (instance.get('status') === 'running') {
            device.set('status', 'starting');
            console.log("Client >>> start", data);
            var startData = {
              labels: instance.get('labels'),
              url: instance.get('startUrl')
            };
            socket.emit('start', startData);
            instance.syncClientLocations();
          }
        }
      }
      console.log("Control >>>> update", devices.toJSON());
      nsCtrl.emit('update', devices.toJSON());
    }

    function started(options) {
      console.log("Client <<< started: ", options.label, options.socketId);
      var device = devices.find(options.label);
      if (device) {
        device.set('status', 'running');
        device.set('devicewall', options.socketId);
        nsCtrl.emit('update', devices.toJSON());
      }
    }

    function idling(label) {
      console.log("Client <<< idling", label);
      var device = devices.find(label);
      if (device) {
        device.set('status', 'idle');
        device.set('updated', +new Date());
        devices.update(device.toJSON());
        app.emit('update-devices');
        console.log("Control >>>> update", devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
      }
    }

    function disconnect() {
      console.log("Client <<< disconnect");
    }

    function checkPlatform(data, fn) {
      console.log("Client <<< check-platform", data);
      var appPlatform = '';
      var device = devices.find(data.label);
      if (device) {
        appPlatform = device.appPlatform;
      }
      fn({appPlatform: appPlatform});
    }
  });

  // Control panel
  nsCtrl.on('connection', function (socket) {
    console.log('Control <<<< connection');
    // Start
    socket.on('start', start);
    socket.on('stop', stop);
    socket.on('stopall', stopAll);
    socket.on('disconnect', disconnect);
    socket.on('list', list);
    socket.on('save', save);
    socket.on('remove', removeDevices);
    socket.on('reload-devices', reloadDevices);
    if (process.env.NODE_ENV === "test") {
      socket.on('reset', resetAppData);
    }

    function stop(data) {
      console.log("Control <<< stop", data);
      instances.stop(data.url).then(function() {
        console.log('Control >> update', devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
        console.log('Control >> stop', data);
        nsCtrl.emit('stop', data);
      });
    }

    function stopAll() {
      console.log("Control <<< stopAll");
      instances.stopAll().done(function() {
        console.log('Control >> update', devices.toJSON());
        nsCtrl.emit('update', devices.toJSON());
        console.log('Control >> stopall');
        nsCtrl.emit('stopall');
      });
    }

    function disconnect () {
      console.log("Control <<< disconnect");
    }

    function list(data, fn) {
      console.log("Control <<< list");
      devices.sort();
      if (typeof(fn) === typeof(Function)) {
        fn(devices.toJSON());
      } else {
        console.log("not a function: ", fn);
      }
    }

    function save(data) {
      console.log("Control <<< save", data);
      var device = devices.find(data.label);
      if (device) {
        ['model', 'version', 'platform'].forEach(function(val) {
          if (data[val]) {
            device.set(val, data[val]);
          }
        });
        devices.update(device.toJSON());
      }
      app.emit('update-devices');
      socket.broadcast.emit('update', devices.toJSON());
    }

    function removeDevices(data) {
      console.log("Control <<< remove", data);
      data.labels.forEach(function(label) {
        devices.remove(label);
      });
      app.emit('update-devices');
      socket.broadcast.emit('remove', data.labels);
    }

    function reloadDevices() {
      console.log("Control <<< reload-devices");
      devices.read();
    }

    // only used with e2e tests
    function resetAppData() {
      console.info("Control <<< reset");
      instances.forceStopAll();
      //instances.stopAll().then(function() {
        console.info(">>> resetted");
        socket.emit("resetted");
      //});
      devices.removeAll();
    }
  });

  function start(data) {
    console.log("Control <<< start", data);
    var uri = utils.guessURI(data.url);
    if (uri === false) {
      console.log("Invalid URL: " + data.url);
      var emitData = {user: data.user, reason: "Invalid URL"};
      console.log('Control >> server-stop', emitData);
      nsCtrl.emit('server-stop', emitData);
      return;
    }
    data.url = uri;

    instances.start(data).then(
      function(startData) {
        var appData = _.clone(data);
        appData.url = startData.startUrl;

        console.log('Control >> socket "update"');
        // Add site and proxy URIs to control panel 'update' event
        var devicesClone = devices.toJSON();
        _.each(devicesClone, function(device) {
          if (data.labels.indexOf(device.label) > -1) {
            device.site = data.url;
            device.proxy = startData.startUrl;
          }
        });
        nsCtrl.emit('update', devicesClone);

        console.log('Control >> start', data);
        nsCtrl.emit('start', data);
        console.log('Client >> start', appData);
        nsApp.emit('start', appData);
        checkDevicesSentToBrowserSync(data, appData);
        instances.waitForClientConnections(data.labels.length).then(function() {
          console.log('Control >> running', data);
          nsCtrl.emit('running', data);
        });
      },
      function(reason) {
        var emitData = {user: data.user, reason: reason};
        console.log('Control >> server-stop', emitData);
        nsCtrl.emit('server-stop', emitData);
      }
    );
  };

  function checkDevicesSentToBrowserSync(data, appData) {
    if (bsInterval) { return; }

    var count = 0;
    bsInterval = setInterval(function() {
      count++;
      var check = false;
      update();

      if (count > 5) {
        /*
        console.log('Problem with BrowserSync, recalling all devices');
        instances.stopAll().done(function() {
          console.log('Control >> update', devices.toJSON());
          update({ stopall: true });
          console.log('Control >> stopall');
          nsCtrl.emit('stopall');
        });
        */
        console.log('Problem with moving all devices to same browsersync context.');
        clear();
      } else if (check) {
        console.log('Not all requested devices in browsersync context');
        console.log('Client >> re-start');
      } else {
        console.log('All requested devices in browsersync context');
        clear();
      }

      function clear() {
        clearInterval(bsInterval);
        bsInterval = undefined;
      }

      function update(options) {
        options = options || {};
        devices.devices.forEach(function(device) {
          if (options.stopall) {
            device.set('browsersyncConnecting', false);
          } else {
            if (data.labels.indexOf(device.get('label')) > -1 && device.get('browsersyncStatus') !== 'Connected') {
              device.set('browsersyncConnecting', true);
              check = true;
            } else {
              device.set('browsersyncConnecting', false);
            }
          }
        });
        nsCtrl.emit('update', devices.toJSON());
      }
      nsApp.emit('start', appData);
    }, 5000);
  };

  function removeGhostDevices() {
    var labels = devices.getGhostDevices();
    if (labels.length) {
      console.log("Control <<< removeGhostDevices", labels);
      labels.forEach(function(label) {
        devices.remove(label);
      });
      app.emit('update-devices');
      nsCtrl.emit('remove', labels);
    }
  }

  function updateDevicesToControlPanel() {
    nsCtrl.emit('update', devices.toJSON());
  }

  // Events
  emitter.on('click:externalurl', function(data) {
    start(data);
  });

  emitter.on('client:connected', function() {
    nsCtrl.emit('update', devices.toJSON());
  });

  devices.init({config: config});
  devices.read();
  removeGhostDevices();
  instances.init({
    config: config,
    devices: devices,
    emitter: emitter
  });
  setInterval(function() {
    devices.write();
  }, 10000);
  setInterval(function() {
    updateDevicesToControlPanel();
  }, 10000);
  setInterval(function() {
    removeGhostDevices();
  }, 86400000);
};
<|endoftext|>


<commit_before>$(function() {

function formatValues($elements) {
    var values = $elements.map(function() {
        return $(this).text();
    });
    return values.toArray().join(';');
}

function getRowValues($table) {
    return formatValues(
        $table.find('tbody td')
    );
}

module('utils');

test('slugify', function() {
    slugify = SimpleDataGrid.slugify;

    equal(slugify(''), '');
    equal(slugify('abc'), 'abc');
    equal(slugify('Abc'), 'abc');
    equal(slugify('abc def'), 'abc-def');
    equal(slugify('123'), '123');
    equal(slugify('abc#def'), 'abcdef');
});

test('buildUrl', function() {
    buildUrl = SimpleDataGrid.buildUrl;

    equal(buildUrl('abc'), 'abc');
    equal(buildUrl('abc', {x: '1', y: '2'}), 'abc?x=1&y=2');
});

module('simple-data-grid', {
    setup: function() {
        $('body').append(
            '
'+
            '
'+
            '    Name	'+
            '    Latin name	'+
            '
'+
            '
'
        );
    },

    teardown: function() {
        var $table1 = $('#table1');
        $table1.simple_datagrid('destroy');
        $table1.remove();
    }
});

test('get column data from  elements', function() {
    var $table1 = $('#table1');

    // Change the key of the first column to 'name1' by setting the 'data-key' property
    var $name_th = $table1.find('th:first');
    $name_th.attr('data-key', 'name1');

    // init widget
    $table1.simple_datagrid();

    // check column data
    var columns = $('#table1').simple_datagrid('getColumns');
    equal(columns.length, 2);
    equal(columns[0].title, 'Name');
    equal(columns[0].key, 'name1');  // from data-key
    equal(columns[1].title, 'Latin name');
    equal(columns[1].key, 'latin-name');  // slug of 'Latin name'
});

test('get column data from options', function() {
    var $table1 = $('#table1');

    // init widget
    $table1.simple_datagrid({
        columns: [
            'Column1',
            {
                title: 'Column2',
                key: 'c2'
            },
            {
                title: 'Column3'
            }
        ]
    });

    // check column data
    var columns = $('#table1').simple_datagrid('getColumns');
    equal(columns.length, 3);
    equal(columns[0].title, 'Column1');
    equal(columns[0].key, 'column1');  // slug of name
    equal(columns[1].title, 'Column2');
    equal(columns[1].key, 'c2');   // defined in options
    equal(columns[2].key, 'column3');
    equal(columns[2].title, 'Column3');
});

test('get data from array', function() {
    var $table1 = $('#table1');

    // 1. row is an array
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });
    equal(getRowValues($table1), 'Avocado;Persea americana');

    // 2. make empty
    $table1.simple_datagrid('loadData', []);
    equal(getRowValues($table1), '');

    // 3. row is an object
    $table1.simple_datagrid(
        'loadData',
        [
            {
                name: "Bell pepper",
                'latin-name': "Capsicum annuum"
            },
            {
                name: 'Tomatillo'  // no latin-name
            }
        ]
    );
    equal(getRowValues($table1), 'Bell pepper;Capsicum annuum;Tomatillo;');
});

test('get data from callback', function() {
    // setup
    var $table1 = $('#table1');

    function get_data(parameters, on_success) {
        on_success(
            [
                ['Avocado', 'Persea americana']
            ]
        );
    }

    // 1. get data from callback
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    equal(
        formatValues($table1.find('tbody td')),
        'Avocado;Persea americana'
    );
});

test('getSelectedRow', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        data: [
            {
                name: 'Avocado',
                'latin-name': 'Persea americana',
                id: 200
            },
            {
                name: 'Bell pepper',
                'latin-name': 'Capsicum annuum',
                id: 201
            }
        ]
    });

    // 1. no selection
    equal($table1.simple_datagrid('getSelectedRow'), null);

    // 2. select second row
    $table1.find('tbody tr:eq(1) td:first').click();
    ok($('tbody tr:eq(1)').hasClass('selected'));
    equal($table1.simple_datagrid('getSelectedRow').id, 201);
});

test('header html', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid();

    // 1. check html
    equal(
        formatValues($table1.find('thead th')),
        'Name;Latin name'
    );

    var keys = $table1.find('thead th').map(function() {
        return $(this).data('key');
    });
    equal(keys.toArray().join(' '), 'name latin-name');
});

test('pagination', function() {
    // setup
    function getData(parameters, on_success) {
        var total_pages = 3;
        var rows_per_page = 5;

        var rows = [];
        var index = (parameters.page - 1) * rows_per_page + 1;
        for (var i=0; i<5; i++) {
            rows.push({
                name: 'n' + index,
                'latin-name': 'l' + index
            });
            index += 1;
        }

        on_success({
            total_pages: total_pages,
            rows: rows
        });
    }

    // 1. init table
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: getData
    });

    equal(
        formatValues($table1.find('tbody td')),
        'n1;l1;n2;l2;n3;l3;n4;l4;n5;l5'
    );

    // 2. next page
    $table1.find('.next').click();

    equal(
        formatValues($table1.find('tbody td')),
        'n6;l6;n7;l7;n8;l8;n9;l9;n10;l10'
    );
});

test('sorting', function() {
    function get_data(parameters, on_success) {
        var data = [];

        if (parameters.order_by == 'name') {
            if (parameters.sortorder == 'asc') {
                data = [
                    ['Avocado', 'Persea americana'],
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Eggplant', 'Solanum melongena']
                ];
            }
            else if (parameters.sortorder == 'desc') {
                data = [
                    ['Eggplant', 'Solanum melongena'],
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Avocado', 'Persea americana']
                ];
            }
        }
        else if (parameters.order_by == 'latin-name') {
            if (parameters.sortorder == 'asc') {
                data = [
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Avocado', 'Persea americana'],
                    ['Eggplant', 'Solanum melongena']
                ];
            }
            else if (parameters.sortorder == 'desc') {
                data = [
                    ['Eggplant', 'Solanum melongena'],
                    ['Avocado', 'Persea americana'],
                    ['Bell pepper', 'Capsicum annuum']
                ];
            }
        }

        on_success(data);
    }

    var $table1 = $('#table1');

    function format_first_columns() {
        var values = $table1.find('tbody tr').map(
            function() {
                return $(this).find('td:eq(0)').text();
            }
        );

        return values.toArray().join(';');
    }

    // 1. init tree; order by name
    $table1.simple_datagrid({
        on_get_data: get_data,
        order_by: 'name'
    });
    equal(format_first_columns(), 'Avocado;Bell pepper;Eggplant');

    // 2. click on 'name' -> sort descending
    $table1.find('th:eq(0) a').click();
    equal(format_first_columns(), 'Eggplant;Bell pepper;Avocado');

    // 3. click on 'latin-name; -> sort ascending
    $table1.find('th:eq(1) a').click();
    equal(format_first_columns(), 'Bell pepper;Avocado;Eggplant');

    // 4. click on 'latin-name; -> sort descending
    $table1.find('th:eq(1) a').click();
    equal(format_first_columns(), 'Eggplant;Avocado;Bell pepper');
});

test('reload', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });

    equal(getRowValues($table1), 'Avocado;Persea americana');

    // 1. empty html
    $table1.find('tbody tr').detach();
    equal(getRowValues($table1), '');

    // 2. reload
    $table1.simple_datagrid('reload');
    equal(getRowValues($table1), 'Avocado;Persea americana');
});

test('setParameter', function() {
    // setup
    stop();

    var step = 1;

    function get_data(parameters, on_success) {
        if (step == 2) {
            equal(parameters.my_param, 'abc');
        }
        step += 1;

        on_success([
            ['Avocado', 'Persea americana']
        ]);
        start();
    }

    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    // 1. set parameter and reload
    $table1.simple_datagrid('setParameter', 'my_param', 'abc');
    $table1.simple_datagrid('reload');
});

test('setCurrentPage', function() {
    // setup
    stop();
    var step = 1;

    function get_data(parameters, on_success) {
        if (step == 2) {
            equal(parameters.page, 2);
        }
        step += 1;

        on_success([
            ['Avocado', 'Persea americana']
        ]);
        start();
    }

    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    // 1. set current page an reload
    $table1.simple_datagrid('setCurrentPage', 2);
    $table1.simple_datagrid('reload');
});

test('table with existing elements', function() {
    // Test with a table that already has rows and a footer.
    // The existing elements must be overwritten.

    // setup
    var $table1 = $('#table1');
    $table1.append('

abc
</tbody');
    $table1.append('

my footer
');

    // 1. init table
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });

    equal(getRowValues($table1), 'Avocado;Persea americana');
    equal($table1.find('tfoot').children().length, 0);
});

test('table with empty head', function() {
    // setup
    var $table1 = $('#table1');
    $table1.find('thead').remove();

    // 1. init table
    $table1.simple_datagrid({
        columns: ['Column1'],
        data: [['abc']]
    });
});

test('on_generate', function() {
    // Test the 'on_generate' option of a column
    var $table1 = $('#table1');

    // 1. init table
    $table1.simple_datagrid({
        columns: [
            {
                title: 'Fruit',
                on_generate: function(value, row) {
                    return '_' + value + '_';
                }
            }
        ],
        data: [
            ['Avocado']
        ]
    });

    equal(getRowValues($table1), '_Avocado_');

    // 2. load data in object
    $table1.simple_datagrid(
        'loadData',
        [
            {fruit: 'Tomato'}
        ]
    );

    equal(getRowValues($table1), '_Tomato_');
});

});
<commit_msg>Updated sorting and pagination tests<commit_after>$(function() {

function formatValues($elements) {
    var values = $elements.map(function() {
        return $(this).text();
    });
    return values.toArray().join(';');
}

function getRowValues($table) {
    return formatValues(
        $table.find('tbody td')
    );
}

module('utils');

test('slugify', function() {
    slugify = SimpleDataGrid.slugify;

    equal(slugify(''), '');
    equal(slugify('abc'), 'abc');
    equal(slugify('Abc'), 'abc');
    equal(slugify('abc def'), 'abc-def');
    equal(slugify('123'), '123');
    equal(slugify('abc#def'), 'abcdef');
});

test('buildUrl', function() {
    buildUrl = SimpleDataGrid.buildUrl;

    equal(buildUrl('abc'), 'abc');
    equal(buildUrl('abc', {x: '1', y: '2'}), 'abc?x=1&y=2');
});

module('simple-data-grid', {
    setup: function() {
        $('body').append(
            '
'+
            '
'+
            '    Name	'+
            '    Latin name	'+
            '
'+
            '
'
        );
    },

    teardown: function() {
        var $table1 = $('#table1');
        $table1.simple_datagrid('destroy');
        $table1.remove();
    }
});

test('get column data from  elements', function() {
    var $table1 = $('#table1');

    // Change the key of the first column to 'name1' by setting the 'data-key' property
    var $name_th = $table1.find('th:first');
    $name_th.attr('data-key', 'name1');

    // init widget
    $table1.simple_datagrid();

    // check column data
    var columns = $('#table1').simple_datagrid('getColumns');
    equal(columns.length, 2);
    equal(columns[0].title, 'Name');
    equal(columns[0].key, 'name1');  // from data-key
    equal(columns[1].title, 'Latin name');
    equal(columns[1].key, 'latin-name');  // slug of 'Latin name'
});

test('get column data from options', function() {
    var $table1 = $('#table1');

    // init widget
    $table1.simple_datagrid({
        columns: [
            'Column1',
            {
                title: 'Column2',
                key: 'c2'
            },
            {
                title: 'Column3'
            }
        ]
    });

    // check column data
    var columns = $('#table1').simple_datagrid('getColumns');
    equal(columns.length, 3);
    equal(columns[0].title, 'Column1');
    equal(columns[0].key, 'column1');  // slug of name
    equal(columns[1].title, 'Column2');
    equal(columns[1].key, 'c2');   // defined in options
    equal(columns[2].key, 'column3');
    equal(columns[2].title, 'Column3');
});

test('get data from array', function() {
    var $table1 = $('#table1');

    // 1. row is an array
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });
    equal(getRowValues($table1), 'Avocado;Persea americana');

    // 2. make empty
    $table1.simple_datagrid('loadData', []);
    equal(getRowValues($table1), '');

    // 3. row is an object
    $table1.simple_datagrid(
        'loadData',
        [
            {
                name: "Bell pepper",
                'latin-name': "Capsicum annuum"
            },
            {
                name: 'Tomatillo'  // no latin-name
            }
        ]
    );
    equal(getRowValues($table1), 'Bell pepper;Capsicum annuum;Tomatillo;');
});

test('get data from callback', function() {
    // setup
    var $table1 = $('#table1');

    function get_data(parameters, on_success) {
        on_success(
            [
                ['Avocado', 'Persea americana']
            ]
        );
    }

    // 1. get data from callback
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    equal(
        formatValues($table1.find('tbody td')),
        'Avocado;Persea americana'
    );
});

test('getSelectedRow', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        data: [
            {
                name: 'Avocado',
                'latin-name': 'Persea americana',
                id: 200
            },
            {
                name: 'Bell pepper',
                'latin-name': 'Capsicum annuum',
                id: 201
            }
        ]
    });

    // 1. no selection
    equal($table1.simple_datagrid('getSelectedRow'), null);

    // 2. select second row
    $table1.find('tbody tr:eq(1) td:first').click();
    ok($('tbody tr:eq(1)').hasClass('selected'));
    equal($table1.simple_datagrid('getSelectedRow').id, 201);
});

test('header html', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid();

    // 1. check html
    equal(
        formatValues($table1.find('thead th')),
        'Name;Latin name'
    );

    var keys = $table1.find('thead th').map(function() {
        return $(this).data('key');
    });
    equal(keys.toArray().join(' '), 'name latin-name');
});

test('pagination', function() {
    // setup
    function getData(parameters, on_success) {
        var total_pages = 3;
        var rows_per_page = 5;

        var rows = [];
        var index = (parameters.page - 1) * rows_per_page + 1;
        for (var i=0; i<5; i++) {
            rows.push({
                name: 'n' + index,
                'latin-name': 'l' + index
            });
            index += 1;
        }

        on_success({
            total_pages: total_pages,
            rows: rows
        });
    }

    // 1. init table
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: getData
    });

    equal(
        formatValues($table1.find('tbody td')),
        'n1;l1;n2;l2;n3;l3;n4;l4;n5;l5'
    );

    // 2. next page
    $table1.find('.next').click();

    equal(
        getRowValues($table1),
        'n6;l6;n7;l7;n8;l8;n9;l9;n10;l10'
    );

    // 3. last page
    $table1.find('.last').click();

    equal(
        getRowValues($table1),
        'n11;l11;n12;l12;n13;l13;n14;l14;n15;l15'
    );

    // 4. first page
    $table1.find('.first').click();

    // 5. previous page
    $table1.find('.next').click();
    $table1.find('.previous').click();
});

test('sorting', function() {
    function get_data(parameters, on_success) {
        var data = [];

        if (parameters.order_by == 'name') {
            if (parameters.sortorder == 'asc') {
                data = [
                    ['Avocado', 'Persea americana'],
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Eggplant', 'Solanum melongena']
                ];
            }
            else if (parameters.sortorder == 'desc') {
                data = [
                    ['Eggplant', 'Solanum melongena'],
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Avocado', 'Persea americana']
                ];
            }
        }
        else if (parameters.order_by == 'latin-name') {
            if (parameters.sortorder == 'asc') {
                data = [
                    ['Bell pepper', 'Capsicum annuum'],
                    ['Avocado', 'Persea americana'],
                    ['Eggplant', 'Solanum melongena']
                ];
            }
            else if (parameters.sortorder == 'desc') {
                data = [
                    ['Eggplant', 'Solanum melongena'],
                    ['Avocado', 'Persea americana'],
                    ['Bell pepper', 'Capsicum annuum']
                ];
            }
        }

        on_success(data);
    }

    var $table1 = $('#table1');

    function format_first_columns() {
        var values = $table1.find('tbody tr').map(
            function() {
                return $(this).find('td:eq(0)').text();
            }
        );

        return values.toArray().join(';');
    }

    // 1. init tree; order by name
    $table1.simple_datagrid({
        on_get_data: get_data,
        order_by: 'name'
    });
    equal(format_first_columns(), 'Avocado;Bell pepper;Eggplant');

    // 2. click on 'name' -> sort descending
    $table1.find('th:eq(0) a').click();
    equal(format_first_columns(), 'Eggplant;Bell pepper;Avocado');

    // 3. click on 'latin-name' -> sort ascending
    $table1.find('th:eq(1) a').click();
    equal(format_first_columns(), 'Bell pepper;Avocado;Eggplant');

    // 4. click on 'latin-name' -> sort descending
    $table1.find('th:eq(1) a').click();
    equal(format_first_columns(), 'Eggplant;Avocado;Bell pepper');

    // 5. click on 'latin-name' -> sort ascending
    $table1.find('th:eq(1) a').click();
});

test('reload', function() {
    // setup
    var $table1 = $('#table1');
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });

    equal(getRowValues($table1), 'Avocado;Persea americana');

    // 1. empty html
    $table1.find('tbody tr').detach();
    equal(getRowValues($table1), '');

    // 2. reload
    $table1.simple_datagrid('reload');
    equal(getRowValues($table1), 'Avocado;Persea americana');
});

test('setParameter', function() {
    // setup
    stop();

    var step = 1;

    function get_data(parameters, on_success) {
        if (step == 2) {
            equal(parameters.my_param, 'abc');
        }
        step += 1;

        on_success([
            ['Avocado', 'Persea americana']
        ]);
        start();
    }

    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    // 1. set parameter and reload
    $table1.simple_datagrid('setParameter', 'my_param', 'abc');
    $table1.simple_datagrid('reload');
});

test('setCurrentPage', function() {
    // setup
    stop();
    var step = 1;

    function get_data(parameters, on_success) {
        if (step == 2) {
            equal(parameters.page, 2);
        }
        step += 1;

        on_success([
            ['Avocado', 'Persea americana']
        ]);
        start();
    }

    var $table1 = $('#table1');
    $table1.simple_datagrid({
        on_get_data: get_data
    });

    // 1. set current page an reload
    $table1.simple_datagrid('setCurrentPage', 2);
    $table1.simple_datagrid('reload');
});

test('table with existing elements', function() {
    // Test with a table that already has rows and a footer.
    // The existing elements must be overwritten.

    // setup
    var $table1 = $('#table1');
    $table1.append('

abc
</tbody');
    $table1.append('

my footer
');

    // 1. init table
    $table1.simple_datagrid({
        data: [
            ['Avocado', 'Persea americana']
        ]
    });

    equal(getRowValues($table1), 'Avocado;Persea americana');
    equal($table1.find('tfoot').children().length, 0);
});

test('table with empty head', function() {
    // setup
    var $table1 = $('#table1');
    $table1.find('thead').remove();

    // 1. init table
    $table1.simple_datagrid({
        columns: ['Column1'],
        data: [['abc']]
    });
});

test('on_generate', function() {
    // Test the 'on_generate' option of a column
    var $table1 = $('#table1');

    // 1. init table
    $table1.simple_datagrid({
        columns: [
            {
                title: 'Fruit',
                on_generate: function(value, row) {
                    return '_' + value + '_';
                }
            }
        ],
        data: [
            ['Avocado']
        ]
    });

    equal(getRowValues($table1), '_Avocado_');

    // 2. load data in object
    $table1.simple_datagrid(
        'loadData',
        [
            {fruit: 'Tomato'}
        ]
    );

    equal(getRowValues($table1), '_Tomato_');
});

});
<|endoftext|>


<commit_before>/* @flow */
/* global describe, it */

import { clone } from 'ramda'
import assert from 'assert'
import { allIsomorphismsForDigraphs, allIsomorphismsForWeightedDigraphs } from '../lib/index'

declare class describe {
  static (description: string, spec: () => void): void;
}

declare class it {
  static (description: string, spec: () => void): void;
}

// TODO test for every error type

describe('Weighted', function () {

  it('Three-cycle with one two-weighted edge has one isomorpism to a certain directed Hajos graph', function () {
    assert.deepEqual(
      allIsomorphismsForWeightedDigraphs(
        [
          [[1,200]],
          [[2,1]],
          [[0,1]]
        ],
        [
          [[1,800]],
          [[2,1],[3,1]],
          [[0,1],[4,1]],
          [[4,1]],
          [[1,1],[5,1]],
          [[2,1]]
        ],
        null
      ),
      [
        [0,1,2]
      ]
    )
  })

})

describe('Unweighted', function () {

  it('a -> b has only one isomorphism to a -> b', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          []
        ],
        [
          [1],
          []
        ],
        null
      ),
      [
        [0,1]
      ]
    )
  })

  it('Three-cycle isomorphic to every cycle', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [0]
        ],
        [
          [1],
          [2],
          [0]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,0],
        [2,0,1]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-chain', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          []
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,9]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-cycle', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          [0]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,9],
        [8,9,0],
        [9,0,1]
      ]
    )
  })

	// TODO firecracker graph and Y?

  it('Pattern larger than graph produces no isomorphisms', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          [0]
        ],
        [
          [1],
          [2],
          []
        ],
        null
      ),
      []
    )
  })

	// http://mathworld.wolfram.com/HajosGraph.html
  it('Three-cycle has twelve isomorpisms to a certain directed Hajos graph', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [0]
        ],
        [
          [1],
          [2,3],
          [0,4],
          [4],
          [1,5],
          [2]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,0],
        [1,2,4],
        [1,3,4],
        [2,0,1],
        [2,4,1],
        [2,4,5],
        [3,4,1],
        [4,1,2],
        [4,1,3],
        [4,5,2],
        [5,2,4]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-cycle with orphans', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [10],
          [],
          [0],
          [],
          [],
          [],
          [],
          []
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,10],
        [8,10,0],
        [10,0,1]
      ]
    )
  })

})
<commit_msg>better orphan test<commit_after>/* @flow */
/* global describe, it */

import { clone } from 'ramda'
import assert from 'assert'
import { allIsomorphismsForDigraphs, allIsomorphismsForWeightedDigraphs } from '../lib/index'

declare class describe {
  static (description: string, spec: () => void): void;
}

declare class it {
  static (description: string, spec: () => void): void;
}

// TODO test for every error type

describe('Weighted', function () {

  it('Three-cycle with one two-weighted edge has one isomorpism to a certain directed Hajos graph', function () {
    assert.deepEqual(
      allIsomorphismsForWeightedDigraphs(
        [
          [[1,200]],
          [[2,1]],
          [[0,1]]
        ],
        [
          [[1,800]],
          [[2,1],[3,1]],
          [[0,1],[4,1]],
          [[4,1]],
          [[1,1],[5,1]],
          [[2,1]]
        ],
        null
      ),
      [
        [0,1,2]
      ]
    )
  })

})

describe('Unweighted', function () {

  it('a -> b has only one isomorphism to a -> b', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          []
        ],
        [
          [1],
          []
        ],
        null
      ),
      [
        [0,1]
      ]
    )
  })

  it('Three-cycle isomorphic to every cycle', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [0]
        ],
        [
          [1],
          [2],
          [0]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,0],
        [2,0,1]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-chain', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          []
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,9]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-cycle', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          [0]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,9],
        [8,9,0],
        [9,0,1]
      ]
    )
  })

	// TODO firecracker graph and Y?

  it('Pattern larger than graph produces no isomorphisms', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          [0]
        ],
        [
          [1],
          [2],
          []
        ],
        null
      ),
      []
    )
  })

	// http://mathworld.wolfram.com/HajosGraph.html
  it('Three-cycle has twelve isomorpisms to a certain directed Hajos graph', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          [0]
        ],
        [
          [1],
          [2,3],
          [0,4],
          [4],
          [1,5],
          [2]
        ],
        null
      ),
      [
        [0,1,2],
        [1,2,0],
        [1,2,4],
        [1,3,4],
        [2,0,1],
        [2,4,1],
        [2,4,5],
        [3,4,1],
        [4,1,2],
        [4,1,3],
        [4,5,2],
        [5,2,4]
      ]
    )
  })

  it('Isomorphisms for three-chain pattern on ten-cycle with orphans', function () {
    assert.deepEqual(
      allIsomorphismsForDigraphs(
        [
          [1],
          [2],
          []
        ],
        [
          [],
          [2],
          [3],
          [4],
          [5],
          [6],
          [7],
          [8],
          [9],
          [11],
          [],
          [1],
          [],
          [],
          [],
          [],
          []
        ],
        null
      ),
      [
        [1,2,3],
        [2,3,4],
        [3,4,5],
        [4,5,6],
        [5,6,7],
        [6,7,8],
        [7,8,9],
        [8,9,11],
        [9,11,1],
        [11,1,2]
      ]
    )
  })

})
<|endoftext|>


<commit_before>/**
 * This module receives the 'io' object from socket.io and
 * defines all the events that a server can receive and process.
 * @param io
 */
module.exports = function (io) {

  /**
   * Server interface. List of the events that the server can process.
   */
  io.sockets.on('connection', function (socket) {
    console.log('Client connected');

    // LOGIN FUNCTIONS
    socket.on('login', function(nickname, callback) {
      if (login(nickname, callback)) {
        socket.nickname = nickname;
        sendMessage('user_add', 'SERVER', nickname);
      }
    });

    socket.on('log_me_out', function () {
      log_user_out(socket.nickname);
      sendMessage('user_left', 'SERVER', nickname);
    });

    // CHAT FUNCTIONS
    socket.on('request_chat', function (source, target, callback) {
      request_chat(source, target, callback);
    });

    socket.on('send_message_to', function (nickname, message, callback) {
      callback(sendMessageToOther(nickname, message));
    });

    socket.on('leave_chat', function (nickname) {
      leave_chat(nickname);
    });
  });

  // COMMUNICATION FUNCTIONS
  /**
   * Broadcasts a message.
   */
  var sendMessage = function (subject, nickname, message) {
    io.sockets.emit(subject, nickname, message);
  };

  /**
   * Send a message from one user to another.
   * @returns false if the message is empty or the user is not chatting.
   */
  var sendMessageToOther = function (nickname, message) {
    if (message !== "") {
      console.log("Sending message: " + message + " to " + people[nickname]);
      var other_socket = findSocket(people[nickname]);
      if (other_socket !== undefined) {
        console.log("other_socket" + other_socket);
        other_socket.emit('new_message', message);
        return true;
      } else {
        return false;
      }
    }
    return false;
  };

  // SERVER LOGIC

  /**
   * Maps users with other users.
   */
  var people = {};

  /**
   * If the nickname is not used, it logs the user in.
   */
  login = function (nickname, callback) {
    console.log('Trying to set nickname ' + nickname);
    var nickAvailable = isFreeNickname(nickname);

    if (nickAvailable) {
      people[nickname] = "";
      callback(undefined, true, getClients());
    } else {
      callback('username_not_available', false, getClients());
    }

    return nickAvailable;
  };

  /**
   * Removes the user from the users list.
   */
  log_user_out = function (nickname) {
    console.log("Logging " + nickname + " out");
    people[nickname] = undefined;
  };

  /**
   * Redirects a chat request to its target. It redirects the answer as well.
   */
  request_chat = function (source, target, callback) {
    var status = check_chat_request(source, target);
    var target_socket;
    if (!status.valid) {
      callback(status.error, false);
    } else {
      target_socket = findSocket(target);
      target_socket.emit('request_from', source, function (ok) {
        if (ok) {
          console.log("Successful connection: " + source + " - " + target);
          set_chat(source, target);
          callback(undefined, true);
        } else {
          callback('Request rejected', false);
        }
      });
    }
  };

  /**
   * Leaves the conversation.
   */
  leave_chat = function (nickname) {
    var other_nickname = people[nickname];
    if (other_nickname && other_nickname !== "") {
      var other_socket = findSocket(other_nickname);
      other_socket.emit('user_left_chat');
      console.log("Disconnection between " + nickname + " and " + other_nickname);
      remove_chat(nickname);
    }
  };

  var check_chat_request = function (source, target) {
    if (isFreeNickname(source)) {
      return { valid: false, error: "We don't recognize your nick" };
    }

    if (isFreeNickname(target)) {
      return { valid: false, error: "We don't recognize the target's nick" };
    }

    if (source === target) {
      return { valid: false, error: "You can\'t speak to yourself!" };
    }
    return { valid: true };
  };

  var set_chat = function (source, target) {
    people[source] = target;
    people[target] = source;
  };

  var remove_chat = function (source) {
    if (people[source]) {
      people[people[source]] = "";
      people[source] = "";
    }
  };

  var isFreeNickname = function (nickname) {
    return people[nickname] === undefined;
  };

  var findSocket = function (nickname) {
    var sockets = io.sockets.clients();
    for (var s in sockets) {
      if (sockets.hasOwnProperty(s) && sockets[s].nickname === nickname) {
        return sockets[s];
      }
    }
    return undefined;
  };

  var getClients = function () {
    console.log("clients? --> " + people);
    result = [];
    for (var person in people) {
      if (people.hasOwnProperty(person) && people[person] === "") {
        result.push(person);
      }
    }
    return result;
  };
};
<commit_msg>Changed message_to protocol<commit_after>/**
 * This module receives the 'io' object from socket.io and
 * defines all the events that a server can receive and process.
 * @param io
 */
module.exports = function (io) {

  /**
   * Server interface. List of the events that the server can process.
   */
  io.sockets.on('connection', function (socket) {
    console.log('Client connected');

    // LOGIN FUNCTIONS
    socket.on('login', function(nickname, callback) {
      if (login(nickname, callback)) {
        socket.nickname = nickname;
        sendMessage('user_add', 'SERVER', nickname);
      }
    });

    socket.on('log_me_out', function () {
      log_user_out(socket.nickname);
      sendMessage('user_left', 'SERVER', nickname);
    });

    // CHAT FUNCTIONS
    socket.on('request_chat', function (source, target, callback) {
      request_chat(source, target, callback);
    });

    socket.on('send_message', function (nickname, message, callback) {
      callback(sendMessageToOther(nickname, message));
    });

    socket.on('leave_chat', function (nickname) {
      leave_chat(nickname);
    });
  });

  // COMMUNICATION FUNCTIONS
  /**
   * Broadcasts a message.
   */
  var sendMessage = function (subject, nickname, message) {
    io.sockets.emit(subject, nickname, message);
  };

  /**
   * Send a message from one user to another.
   * @returns false if the message is empty or the user is not chatting.
   */
  var sendMessageToOther = function (nickname, message) {
    if (message !== "") {
      console.log("Sending message: " + message + " to " + people[nickname]);
      var other_socket = findSocket(people[nickname]);
      if (other_socket !== undefined) {
        console.log("other_socket" + other_socket);
        other_socket.emit('new_message', message);
        return true;
      } else {
        return false;
      }
    }
    return false;
  };

  // SERVER LOGIC

  /**
   * Maps users with other users.
   */
  var people = {};

  /**
   * If the nickname is not used, it logs the user in.
   */
  login = function (nickname, callback) {
    console.log('Trying to set nickname ' + nickname);
    var nickAvailable = isFreeNickname(nickname);

    if (nickAvailable) {
      people[nickname] = "";
      callback(undefined, true, getClients());
    } else {
      callback('username_not_available', false, getClients());
    }

    return nickAvailable;
  };

  /**
   * Removes the user from the users list.
   */
  log_user_out = function (nickname) {
    console.log("Logging " + nickname + " out");
    people[nickname] = undefined;
  };

  /**
   * Redirects a chat request to its target. It redirects the answer as well.
   */
  request_chat = function (source, target, callback) {
    var status = check_chat_request(source, target);
    var target_socket;
    if (!status.valid) {
      callback(status.error, false);
    } else {
      target_socket = findSocket(target);
      target_socket.emit('request_from', source, function (ok) {
        if (ok) {
          console.log("Successful connection: " + source + " - " + target);
          set_chat(source, target);
          callback(undefined, true);
        } else {
          callback('Request rejected', false);
        }
      });
    }
  };

  /**
   * Leaves the conversation.
   */
  leave_chat = function (nickname) {
    var other_nickname = people[nickname];
    if (other_nickname && other_nickname !== "") {
      var other_socket = findSocket(other_nickname);
      other_socket.emit('user_left_chat');
      console.log("Disconnection between " + nickname + " and " + other_nickname);
      remove_chat(nickname);
    }
  };

  var check_chat_request = function (source, target) {
    if (isFreeNickname(source)) {
      return { valid: false, error: "We don't recognize your nick" };
    }

    if (isFreeNickname(target)) {
      return { valid: false, error: "We don't recognize the target's nick" };
    }

    if (source === target) {
      return { valid: false, error: "You can\'t speak to yourself!" };
    }
    return { valid: true };
  };

  var set_chat = function (source, target) {
    people[source] = target;
    people[target] = source;
  };

  var remove_chat = function (source) {
    if (people[source]) {
      people[people[source]] = "";
      people[source] = "";
    }
  };

  var isFreeNickname = function (nickname) {
    return people[nickname] === undefined;
  };

  var findSocket = function (nickname) {
    var sockets = io.sockets.clients();
    for (var s in sockets) {
      if (sockets.hasOwnProperty(s) && sockets[s].nickname === nickname) {
        return sockets[s];
      }
    }
    return undefined;
  };

  var getClients = function () {
    console.log("clients? --> " + people);
    result = [];
    for (var person in people) {
      if (people.hasOwnProperty(person) && people[person] === "") {
        result.push(person);
      }
    }
    return result;
  };
};
<|endoftext|>


<commit_before>require('../lib/promise-7.0.4.min.js');
var Excp = require('excp.js');
var Session = require('session.js');

function table( option, table_name ) {

	option = option || {};

	this.host = option['https'] || option['host'];
	this.prefix= option['table.prefix'] || '';
	this.api = 'https://' +  this.host + '/baas/table';
	this.table_name = table_name || null;
	this.queryBuilder = {
			where:[],
			limit:{},
			order:[],
			paginate:{},
			join:[],
			leftjoin:[],
			rightjoin:[],
			group:{},
			having:[]
	};
	this.sync = false;
	this.ss = new Session( option );
	this.ss.start();

	/**
	 * 返回数据表全名
	 * @return string table_fullname
	 */
	this.table = function() {
		return '_baas_' +  this.prefix + '_' +  this.table_name;
	}

	/**
	 * 创建一条记录
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.create = function( data ) {
		return this.__r('/create', {data:data} );
	}

	/**
	 * 根据数据表ID, 更新一条记录
	 * @param  int id 数据表ID
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.update = function( id, data ) {
		return this.__r('/update', {_id:id, data:data} );
	}

	/**
	 * 使用数据表唯一键，创建一条记录
	 * @param  string uni_key 唯一键字段名称
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.updateBy = function( uni_key, data ) {
		return this.__r('/updateby', {uni_key:uni_key, data:data} );
	}

	/**
	 * 根据数据表唯一键数值，删除一条记录
	 * @param  string data_key 唯一键数值
	 * @param  string uni_key  唯一键字段名称
	 * @return Promise
	 */
	this.remove = function( data_key, uni_key ){
		uni_key = uni_key || '_id';
		return this.__r('/remove', {data_key:data_key, uni_key:uni_key});
	}

	/**
	 * 查询数据表， 返回一组记录和总数 {data:[], total:100}
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.select = function( where, data, fields ) {
		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/select', {query:where, data:data, fields:fields});
	}

	/**
	 * 查询数据表，返回一组记录
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getData = function ( where, data, fields ) {

		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/getdata', {query:where, data:data, fields:fields});

	}

	/**
	 * 查询数据表, 返回一行数据
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getLine = function ( where, data, fields ) {

		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/getline', {query:where, data:data, fields:fields});

	}

	/**
	 * 查询数据表, 返回一个字段数值
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getVar = function( field,  where, data ) {

		field = field || '_id';
		data = data || [];
		where = where || "";
		return this.__r('/getvar', {query:where, data:data, field:field});
	}

	/**
	 * 下一个自增ID 数值
	 * @return Promise
	 */
	this.nextid = function() {
		return this.__r('/nextid', {});
	}

	/**
	 * 根据数据表 ID, 查询一条数值
	 * @param int id 数据表ID
	 * @return Promise
	 */
	this.get = function( id ) {
		return this.__r('/get', {_id:id} );
	}

	/**
	 * 查询语句构造器
	 *
	 * 	tb.query()
     *      .where('name', '=', '冯小刚')
     *      .orderby('name', 'asc')
     *      .limit(2)  // 仅查询 2条
     *      .fetch('name','company');  // 读取字段
     *
	 *  tb.query()
     *   .where('name', '=', '张艺谋')
     *   .orwhere('name', '=', '冯小刚')
     *   .orderby('name', 'desc')
     *   .paginate(3, 2)  // 分3页，当前显示第 2页
     *   .fetch('name','company');    // 读取字段
     *
	 * @return this
	 */
	this.query = function () {
		this.queryBuilder = {
			where:[],
			limit:{},
			order:[],
			paginate:{},
			join:[],
			leftjoin:[],
			rightjoin:[],
			group:{},
			having:[]
		};
		return this;
	}

		/**
		 * 构造查询条件
		 * @param  string  field 字段名称
		 * @param  string  exp   关系操作符 ( =, <> , > , <, like ... )
		 * @param  string  value 字段数值
		 * @return this
		 */
		this.where = function( field, exp, value ) {
			this.queryBuilder['where'].push({op:'and', field:field, exp:exp, value:value});
			return this;
		}

		/**
		 * 构造查询条件 ( Or 关系 )
		 * @param  string  field 字段名称
		 * @param  string  exp   关系操作符 ( =, <> , > , <, like ... )
		 * @param  string  value 字段数值
		 * @return this
		 */
		this.orWhere = function( field, exp, value ) {

			this.queryBuilder['where'].push({op:'or', field:field, exp:exp, value:value});
			return this;
		}

		this.orwhere = this.orWhere;

		/**
		 * 构造 Order 查询
		 * @param  string field 字段名称
		 * @param  string  order 排序呢方法 ( asc / desc )
		 * @return this
		 */
		this.orderBy = function(field, order ) {
			this.queryBuilder['order'].push({ field:field, order:order});
			return this;
		}

		this.orderby = this.orderBy;

		this.groupBy = function( field ) {
			this.queryBuilder['group'] = {field:field};
			return this;
		}

		this.groupby = this.groupBy;

		this.having = function( field, exp, value ) {
			this.queryBuilder['having'].push(
				{ field:field, exp:exp, value:value}
			);
			return this;
		}

		/**
		 * 构造 Limit 查询
		 *
		 * td.limit(10)      //最多返回10条记录
		 * td.limit(2, 10);  // 从第2条记录开始，最多返回10条记录
		 *
		 * @param  int from 记录偏移量 （  如 limit 为空, from 为记录最大值  )
		 * @param  int limit 记录最大值
		 * @return this
		 */
		this.limit = function( from, limit ) {

			if ( arguments.length == 1 ) {
				limit = from;
				from = null;
			}
			this.queryBuilder['limit'] = { limit:limit, from:from};
			return this;
		}

		/**
		 * 构造分页查询
		 * @param  int perpage 每页显示记录数量, 默认 20
		 * @param  int page 当前页， 默认为 1
		 * @param  string link 分页链接，默认为 null
		 * @param  array fields 计算记录总数时查询字段, 默认为 ['_id'] （ 一般无需修改 )
		 * @return this
		 */
		this.paginate = function(  perpage, page, link, fields ) {
			fields = fields || ['_id'];
			link = link || null;
			page = page || 1;
			perpage = perpage || 20;
			this.queryBuilder['paginate'] = { perpage:perpage, fields:fields, link:link, page:page};
			return this;
		}

		this.whereIn = function() {

		}

		this.wherein = this.whereIn;

		this.join = function( table, field, exp, value) {
			this.queryBuilder['join'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.leftJoin = function( table, field, exp, value) {
			this.queryBuilder['leftjoin'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.leftjoin = this.leftJoin;

		this.rightJoin = function( table, field, exp, value) {
			this.queryBuilder['rightjoin'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.rightjoin = this.rightJoin;

		/**
		 * 执行数据查询
		 * @return Promise
		 */
		this.fetch = function() {
			return this.__r('/query', {fields:arguments, query:this.queryBuilder});
		}

	/**
	 * 运行 SQL ( 需管理员权限 )
	 * @param  string sql 完整的 SQL  ( eg: "UPDATE tab_name SET name=? WHERE id=? " )
	 * @param  array  data 动态数据 ( eg: where = "UPDATE tab_name SET name=? WHERE id=? ", data= ["张艺谋", 1] 拼接后的 SQL: UPDATE tab_name SET name='张艺谋' WHERE id=1 )
	 * @param  bool ret 是否返回查询结果，默认 false , 仅返回是否查询成功
	 * @return Promise
	 */
	this._run = function ( sql, data, ret  ) {
		sql = sql || "";
		ret = ret || false;
		data = data || [];
		return this.__r('/runsql', {sql:sql, data:data, return:ret});
	}

	/**
	 * 创建数据表结构  ( 需管理员权限 )
	 * @param  object schema        数据表结构体
	 * @param  object acl  数据表权限声明
	 * @param  bool dropIfExist     如果存在则删除数据表 默认为 false
	 * @return Promise
	 */
	this._schema = function( schema, acl,  dropIfExist ) {
		dropIfExist = dropIfExist || false;
		acl = acl || {};
		return this.__r('/schema', {schema:schema, acl:acl, dropIfExist:dropIfExist} );
	}

	/**
	 * 删除数据表 ( 需管理员权限 )
	 * @param bool checkExist 检查数据表是否存在,  默认 false 不存储则忽略
	 * @return Promise
	 */
	this._clear = function( checkExist ) {
		checkExist = checkExist || false;
		return this.__r('/clear', {} );
	}

	/**
	 * 设定/查询数据表鉴权 ( 需要管理员权限 )
	 * @param  object acl 鉴权, 为空返回当前鉴权信息
	 * @return Promise
	 */
	this._acl = function(  acl, name ) {

		name = name || '{record}';  // {table} / {record} / field_name
		return this.__r('/acl', {name:name, acl:acl} );
	}

	/**
	 * 转成同步函数 ( 暂未实现  )
	 *
	 * @计划实现如下写法:
	 *
	 * 	 var resp = tb.sync().create();
	 * 	 var resp = tb.sync().query()->where("name=?",[100])->fetch('name');
	 * 	 ...
	 *
	 * @return this.syncMethods
	 */

	this.sync = function() {
		this.sync = true;
		return this;
	}

	/**
	 * 请求远程 API
	 * @param  string api    api 地址
	 * @param  object data   post 数据
	 * @param  object header 请求 Header
	 * @return Promise
	 */
	this.__r = function( api, data, header ) {

		var that = this;
		header = header || {'content-type': 'application/json' };
		data = data || {}
		api = api || 'index';
		data['_sid'] = that.ss.id();
		data['_table'] = that.table_name;
		data['_prefix'] = that.prefix;

		// data['_input'] = JSON.stringify( data );

		return new Promise(function (resolve, reject) {
			wx.request({
				url: that.api + api,
				data: data, //
				header: header,
				method: 'POST',
				success: function (res){

					var resp  = res.data || {};
					if ( typeof resp['code'] == 'undefined' ) {
						res['__api'] = api;
						reject( new Excp( api + ' API Error', res.statusCode, res));
						return;
					} else if (  resp['code'] != 0 ) {
						resp['message'] = resp['message'] || 'Undefined Error';
						resp['extra'] = resp['extra'] || resp;
						resp['extra']['__api'] = api;
						reject( new Excp( resp['message'], resp['code'], resp['extra'] ) );
						return;
					}

					// 返回结果成功
					resolve( resp['result'] );
				},
				fail:function( res ) {
					reject( new Excp(api + ' API Error', 500, res));
				}
			});
		});

	}

}

module.exports = table;<commit_msg>+ inWhere 查询方法<commit_after>require('../lib/promise-7.0.4.min.js');
var Excp = require('excp.js');
var Session = require('session.js');

function table( option, table_name ) {

	option = option || {};

	this.host = option['https'] || option['host'];
	this.prefix= option['table.prefix'] || '';
	this.api = 'https://' +  this.host + '/baas/table';
	this.table_name = table_name || null;
	this.queryBuilder = {
			where:[],
			limit:{},
			order:[],
			paginate:{},
			join:[],
			leftjoin:[],
			rightjoin:[],
			group:{},
			having:[],
			inwhere:{}
	};
	this.sync = false;
	this.ss = new Session( option );
	this.ss.start();

	/**
	 * 返回数据表全名
	 * @return string table_fullname
	 */
	this.table = function() {
		return '_baas_' +  this.prefix + '_' +  this.table_name;
	}

	/**
	 * 创建一条记录
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.create = function( data ) {
		return this.__r('/create', {data:data} );
	}

	/**
	 * 根据数据表ID, 更新一条记录
	 * @param  int id 数据表ID
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.update = function( id, data ) {
		return this.__r('/update', {_id:id, data:data} );
	}

	/**
	 * 使用数据表唯一键，创建一条记录
	 * @param  string uni_key 唯一键字段名称
	 * @param  object data 记录数据 ( Key-Value 结构)
	 * @return Promise
	 */
	this.updateBy = function( uni_key, data ) {
		return this.__r('/updateby', {uni_key:uni_key, data:data} );
	}

	/**
	 * 根据数据表唯一键数值，删除一条记录
	 * @param  string data_key 唯一键数值
	 * @param  string uni_key  唯一键字段名称
	 * @return Promise
	 */
	this.remove = function( data_key, uni_key ){
		uni_key = uni_key || '_id';
		return this.__r('/remove', {data_key:data_key, uni_key:uni_key});
	}

	/**
	 * 查询数据表， 返回一组记录和总数 {data:[], total:100}
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.select = function( where, data, fields ) {
		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/select', {query:where, data:data, fields:fields});
	}

	/**
	 * 查询数据表，返回一组记录
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getData = function ( where, data, fields ) {

		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/getdata', {query:where, data:data, fields:fields});

	}

	/**
	 * 查询数据表, 返回一行数据
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getLine = function ( where, data, fields ) {

		fields = fields || [];
		data = data || [];
		where = where || "";
		return this.__r('/getline', {query:where, data:data, fields:fields});

	}

	/**
	 * 查询数据表, 返回一个字段数值
	 * @param  string where 查询条件, 从 where 开始 ( eg: "where name=? " )
	 * @param  array  data 动态数据 ( eg: where = "where name=? or name=? ", data= ["张艺谋", "冯小刚"] 拼接后的 SQL: where name='张艺谋' or name='冯小刚' )
	 * @param  array fields 返回字段清单,默认为 [] 返回所有字段
	 * @return Promise
	 */
	this.getVar = function( field,  where, data ) {

		field = field || '_id';
		data = data || [];
		where = where || "";
		return this.__r('/getvar', {query:where, data:data, field:field});
	}

	/**
	 * 下一个自增ID 数值
	 * @return Promise
	 */
	this.nextid = function() {
		return this.__r('/nextid', {});
	}

	/**
	 * 根据数据表 ID, 查询一条数值
	 * @param int id 数据表ID
	 * @return Promise
	 */
	this.get = function( id ) {
		return this.__r('/get', {_id:id} );
	}

	/**
	 * 查询语句构造器
	 *
	 * 	tb.query()
     *      .where('name', '=', '冯小刚')
     *      .orderby('name', 'asc')
     *      .limit(2)  // 仅查询 2条
     *      .fetch('name','company');  // 读取字段
     *
	 *  tb.query()
     *   .where('name', '=', '张艺谋')
     *   .orwhere('name', '=', '冯小刚')
     *   .orderby('name', 'desc')
     *   .paginate(3, 2)  // 分3页，当前显示第 2页
     *   .fetch('name','company');    // 读取字段
     *
	 * @return this
	 */
	this.query = function () {
		this.queryBuilder = {
			where:[],
			limit:{},
			order:[],
			paginate:{},
			join:[],
			leftjoin:[],
			rightjoin:[],
			group:{},
			having:[],
			inwhere:{}
		};
		return this;
	}

		/**
		 * 构造查询条件
		 * @param  string  field 字段名称
		 * @param  string  exp   关系操作符 ( =, <> , > , <, like ... )
		 * @param  string  value 字段数值
		 * @return this
		 */
		this.where = function( field, exp, value ) {
			this.queryBuilder['where'].push({op:'and', field:field, exp:exp, value:value});
			return this;
		}

		/**
		 * 构造查询条件 ( Or 关系 )
		 * @param  string  field 字段名称
		 * @param  string  exp   关系操作符 ( =, <> , > , <, like ... )
		 * @param  string  value 字段数值
		 * @return this
		 */
		this.orWhere = function( field, exp, value ) {

			this.queryBuilder['where'].push({op:'or', field:field, exp:exp, value:value});
			return this;
		}

		this.orwhere = this.orWhere;

		/**
		 * 构造 Order 查询
		 * @param  string field 字段名称
		 * @param  string  order 排序呢方法 ( asc / desc )
		 * @return this
		 */
		this.orderBy = function(field, order ) {
			this.queryBuilder['order'].push({ field:field, order:order});
			return this;
		}

		this.orderby = this.orderBy;

		this.groupBy = function( field ) {
			this.queryBuilder['group'] = {field:field};
			return this;
		}

		this.groupby = this.groupBy;

		this.having = function( field, exp, value ) {
			this.queryBuilder['having'].push(
				{ field:field, exp:exp, value:value}
			);
			return this;
		}

		/**
		 * 构造 Limit 查询
		 *
		 * td.limit(10)      //最多返回10条记录
		 * td.limit(2, 10);  // 从第2条记录开始，最多返回10条记录
		 *
		 * @param  int from 记录偏移量 （  如 limit 为空, from 为记录最大值  )
		 * @param  int limit 记录最大值
		 * @return this
		 */
		this.limit = function( from, limit ) {

			if ( arguments.length == 1 ) {
				limit = from;
				from = null;
			}
			this.queryBuilder['limit'] = { limit:limit, from:from};
			return this;
		}

		/**
		 * 构造分页查询
		 * @param  int perpage 每页显示记录数量, 默认 20
		 * @param  int page 当前页， 默认为 1
		 * @param  string link 分页链接，默认为 null
		 * @param  array fields 计算记录总数时查询字段, 默认为 ['_id'] （ 一般无需修改 )
		 * @return this
		 */
		this.paginate = function(  perpage, page, link, fields ) {
			fields = fields || ['_id'];
			link = link || null;
			page = page || 1;
			perpage = perpage || 20;
			this.queryBuilder['paginate'] = { perpage:perpage, fields:fields, link:link, page:page};
			return this;
		}

		this.inWhere = function( field,  whereTable, whereField, getFields ) {

			getFields = getFields || ['*'];
			whereField = whereField || '_id';

			this.queryBuilder['inwhere'][field] = {
				field:field, table:whereTable,  where:whereField, fields:getFields
			};

			return this;

		}

		this.inwhere = this.inWhere;

		this.join = function( table, field, exp, value) {
			this.queryBuilder['join'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.leftJoin = function( table, field, exp, value) {
			this.queryBuilder['leftjoin'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.leftjoin = this.leftJoin;

		this.rightJoin = function( table, field, exp, value) {
			this.queryBuilder['rightjoin'].push(
				{table:table, field:field, exp:exp, value:value}
			);
			return this;
		}

		this.rightjoin = this.rightJoin;

		/**
		 * 执行数据查询
		 * @return Promise
		 */
		this.fetch = function() {
			return this.__r('/query', {fields:arguments, query:this.queryBuilder});
		}

	/**
	 * 运行 SQL ( 需管理员权限 )
	 * @param  string sql 完整的 SQL  ( eg: "UPDATE tab_name SET name=? WHERE id=? " )
	 * @param  array  data 动态数据 ( eg: where = "UPDATE tab_name SET name=? WHERE id=? ", data= ["张艺谋", 1] 拼接后的 SQL: UPDATE tab_name SET name='张艺谋' WHERE id=1 )
	 * @param  bool ret 是否返回查询结果，默认 false , 仅返回是否查询成功
	 * @return Promise
	 */
	this._run = function ( sql, data, ret  ) {
		sql = sql || "";
		ret = ret || false;
		data = data || [];
		return this.__r('/runsql', {sql:sql, data:data, return:ret});
	}

	/**
	 * 创建数据表结构  ( 需管理员权限 )
	 * @param  object schema        数据表结构体
	 * @param  object acl  数据表权限声明
	 * @param  bool dropIfExist     如果存在则删除数据表 默认为 false
	 * @return Promise
	 */
	this._schema = function( schema, acl,  dropIfExist ) {
		dropIfExist = dropIfExist || false;
		acl = acl || {};
		return this.__r('/schema', {schema:schema, acl:acl, dropIfExist:dropIfExist} );
	}

	/**
	 * 删除数据表 ( 需管理员权限 )
	 * @param bool checkExist 检查数据表是否存在,  默认 false 不存储则忽略
	 * @return Promise
	 */
	this._clear = function( checkExist ) {
		checkExist = checkExist || false;
		return this.__r('/clear', {} );
	}

	/**
	 * 设定/查询数据表鉴权 ( 需要管理员权限 )
	 * @param  object acl 鉴权, 为空返回当前鉴权信息
	 * @return Promise
	 */
	this._acl = function(  acl, name ) {

		name = name || '{record}';  // {table} / {record} / field_name
		return this.__r('/acl', {name:name, acl:acl} );
	}

	/**
	 * 转成同步函数 ( 暂未实现  )
	 *
	 * @计划实现如下写法:
	 *
	 * 	 var resp = tb.sync().create();
	 * 	 var resp = tb.sync().query()->where("name=?",[100])->fetch('name');
	 * 	 ...
	 *
	 * @return this.syncMethods
	 */

	this.sync = function() {
		this.sync = true;
		return this;
	}

	/**
	 * 请求远程 API
	 * @param  string api    api 地址
	 * @param  object data   post 数据
	 * @param  object header 请求 Header
	 * @return Promise
	 */
	this.__r = function( api, data, header ) {

		var that = this;
		header = header || {'content-type': 'application/json' };
		data = data || {}
		api = api || 'index';
		data['_sid'] = that.ss.id();
		data['_table'] = that.table_name;
		data['_prefix'] = that.prefix;

		// data['_input'] = JSON.stringify( data );

		return new Promise(function (resolve, reject) {
			wx.request({
				url: that.api + api,
				data: data, //
				header: header,
				method: 'POST',
				success: function (res){

					var resp  = res.data || {};
					if ( typeof resp['code'] == 'undefined' ) {
						res['__api'] = api;
						reject( new Excp( api + ' API Error', res.statusCode, res));
						return;
					} else if (  resp['code'] != 0 ) {
						resp['message'] = resp['message'] || 'Undefined Error';
						resp['extra'] = resp['extra'] || resp;
						resp['extra']['__api'] = api;
						reject( new Excp( resp['message'], resp['code'], resp['extra'] ) );
						return;
					}

					// 返回结果成功
					resolve( resp['result'] );
				},
				fail:function( res ) {
					reject( new Excp(api + ' API Error', 500, res));
				}
			});
		});

	}

}

module.exports = table;<|endoftext|>


<commit_before>var assert = require("assert");
var Parser = require("../Parser.js");
var parser = new Parser();

describe('HyperDown.js', function() {
    describe('heading type1', function() {
        it('#heading#', function() {
            assert.equal('
heading
', parser.makeHtml('#heading#'));
            assert.equal('
heading
', parser.makeHtml('######heading######'));
        });
    });

    describe('heading type2', function() {
        it('heading ======', function() {
            assert.equal('
heading
', parser.makeHtml('heading\n======'));
        });

        it('heading ------', function() {
            assert.equal('
heading
', parser.makeHtml('heading\n------'));
        });
    });

    describe('bold', function() {
        it('**bold**', function() {
            assert.equal('
123bold123
', parser.makeHtml('123**bold**123'));
        });
    });

    describe('italy', function() {
        it('*italy*', function() {
            assert.equal('
123 italy 123
', parser.makeHtml('123 *italy* 123'));
        });
    });

    describe('list', function() {
        it('ul', function() {
            assert.equal('

list

', parser.makeHtml('\n\n - list'));
        });
        it('ol', function() {
            assert.equal('

list

', parser.makeHtml('1. list'));
        });
    });

    describe('link', function() {
        it('type1', function() {
            assert.equal('
sf.gg123
', parser.makeHtml('[sf.gg](http://sf.gg)123'));
        });
        it('type2', function() {
            assert.equal('
123http://sf.gg(tt) 123 http://sf.gg
', parser.makeHtml('123http://sf.gg(tt) 123 http://sf.gg'));
        });
        it('type3', function() {
            assert.equal('
sf.gg
', parser.makeHtml('[sf.gg][1]\n [1]: http://sf.gg'));
        });
        it('type4', function() {
            assert.equal('
http://sf.gg
', parser.makeHtml('<http://sf.gg>'));
        });
    });

    describe('image', function() {
        it('type1', function() {
            assert.equal('

', parser.makeHtml('![integ](http://sfault-avatar.b0.upaiyun.com/388/398/3883982934-55d3f0dc8f1e1_huge256)'));
        });
        it('type2', function() {
            assert.equal('

', parser.makeHtml('![integ][1]\n [1]: http://sfault-avatar.b0.upaiyun.com/388/398/3883982934-55d3f0dc8f1e1_huge256'));
        });
    });

    describe('code', function() {
        it('type1', function() {
            assert.equal('
var s="123";
', parser.makeHtml('```javascript\nvar s="123";\n```'));
        });
        it('type2', function() {
            assert.equal('
Objective-C使用了消息机制代替调javascript用方法。
', parser.makeHtml('`Objective-C`使用了消息机制代替调`javascript`用方法。'));
        });
        it('type3', function() {
            assert.equal('
var s="123";
', parser.makeHtml('```javascript:example.js\nvar s="123";\n```'));
        });
    });

    describe('quote', function() {
        it('> ', function() {
            assert.equal('
test
', parser.makeHtml('> test'));
        });
    });

    describe('table', function() {
        it('type1 html ', function() {
            assert.equal('

test	test

', parser.makeHtml('

test	test

'));
        });
        it('type2 |', function() {
            assert.equal('

Item	Value	Qty

Computer	1600 USD	5

Phone	12 USD	12

Pipe	1 USD	234

',
                parser.makeHtml('| Item      |    Value | Qty  |\n| :-------- | --------:| :--: |\n| Computer  | 1600 USD |  5   |\n| Phone     |   12 USD |  12  |\n| Pipe      |    1 USD | 234  |'));
        });
        it('table align', function() {
            assert.equal('
\

\

\
Left-Aligned	\
Center Aligned	\
Right Aligned	\

\

\

\

\
col 3 is	\
some wordy text	\
$1600	\

\

\
col 2 is	\
centered	\
$12	\

\

\
zebra stripes	\
are neat	\
$1	\

\

\

', parser.makeHtml('| Left-Aligned  | Center Aligned  | Right Aligned |\n\
| :------------ |:---------------:| -----:|\n\
| col 3 is      | some wordy text | $1600 |\n\
| col 2 is      | centered        |   $12 |\n\
| zebra stripes | are neat        |    $1 |'));
        });
    });

    describe('footnote', function() {
        it('脚注 ', function() {
            assert.equal('
Never write "[click here]1".

2 ↩

', parser.makeHtml('Never write "[click here][^2]".\n [^2]: http://www.w3.org/QA/Tips/noClickHere'));
        });
    });

    describe('complex', function() {
        it('specialhtml', function() {
            assert.equal('
<li>asdf
', parser.makeHtml('
asdf'));
        });
        it('specialHR', function() {
            assert.equal('
a

', parser.makeHtml('```\na\n```\n---'));
        });
        it('list + code', function() {
            var codeInList = "1. 1111\n 1111\n\n 1111\`operator new\` 在分配内存失败的情况下会调用 \`new_handler\` 尝试让系统释放点内存，然后再次尝试申请内存。如果这时系统中内存确实紧张，即使调用。";
            assert.equal('

1111
1111

1111operator new 在分配内存失败的情况下会调用 new_handler 尝试让系统释放点内存，然后再次尝试申请内存。如果这时系统中内存确实紧张，即使调用。

', parser.makeHtml(codeInList));
        });
    });

    describe('bugfix', function() {
        it('escape', function () {
            assert.equal('
[系统盘]:\\Documents and Settings\\[用户名]\\Cookies
', parser.makeHtml('\\[系统盘]:\\\\Documents and Settings\\\\[用户名]\\\\Cookies'));
        });

        it('hr', function () {
            assert.equal('
', parser.makeHtml('* * *'));
        });

        it('backslash', function () {
            assert.equal('
\\\\
', parser.makeHtml('\\\\\\\\'));
            assert.equal('
\\123
', parser.makeHtml('\\123'));
            assert.equal('
|123
', parser.makeHtml('\\|123'));
        });

        it('table', function () {
            assert.equal('

Variable_name	Value

sql_mode	ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION

', parser.makeHtml('|---------------|-------|\n| Variable_name | Value |\n| ------------- | ----- |\n| sql_mode      | ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION |\n|---------------|-------|'));
        });
    });

});
<commit_msg>fix test<commit_after>var assert = require("assert");
var Parser = require("../Parser.js");
var parser = new Parser();

describe('HyperDown.js', function() {
    describe('heading type1', function() {
        it('#heading#', function() {
            assert.equal('
heading
', parser.makeHtml('#heading#'));
            assert.equal('
heading
', parser.makeHtml('######heading######'));
        });
    });

    describe('heading type2', function() {
        it('heading ======', function() {
            assert.equal('
heading
', parser.makeHtml('heading\n======'));
        });

        it('heading ------', function() {
            assert.equal('
heading
', parser.makeHtml('heading\n------'));
        });
    });

    describe('bold', function() {
        it('**bold**', function() {
            assert.equal('
123bold123
', parser.makeHtml('123**bold**123'));
        });
    });

    describe('italy', function() {
        it('*italy*', function() {
            assert.equal('
123 italy 123
', parser.makeHtml('123 *italy* 123'));
        });
    });

    describe('list', function() {
        it('ul', function() {
            assert.equal('

list

', parser.makeHtml('\n\n - list'));
        });
        it('ol', function() {
            assert.equal('

list

', parser.makeHtml('1. list'));
        });
    });

    describe('link', function() {
        it('type1', function() {
            assert.equal('
sf.gg123
', parser.makeHtml('[sf.gg](http://sf.gg)123'));
        });
        it('type2', function() {
            assert.equal('
123http://sf.gg(tt) 123 http://sf.gg
', parser.makeHtml('123http://sf.gg(tt) 123 http://sf.gg'));
        });
        it('type3', function() {
            assert.equal('
sf.gg
', parser.makeHtml('[sf.gg][1]\n [1]: http://sf.gg'));
        });
        it('type4', function() {
            assert.equal('
http://sf.gg
', parser.makeHtml('<http://sf.gg>'));
        });
    });

    describe('image', function() {
        it('type1', function() {
            assert.equal('

', parser.makeHtml('![integ](http://sfault-avatar.b0.upaiyun.com/388/398/3883982934-55d3f0dc8f1e1_huge256)'));
        });
        it('type2', function() {
            assert.equal('

', parser.makeHtml('![integ][1]\n [1]: http://sfault-avatar.b0.upaiyun.com/388/398/3883982934-55d3f0dc8f1e1_huge256'));
        });
    });

    describe('code', function() {
        it('type1', function() {
            assert.equal('
var s="123";
', parser.makeHtml('```javascript\nvar s="123";\n```'));
        });
        it('type2', function() {
            assert.equal('
Objective-C使用了消息机制代替调javascript用方法。
', parser.makeHtml('`Objective-C`使用了消息机制代替调`javascript`用方法。'));
        });
        it('type3', function() {
            assert.equal('
var s="123";
', parser.makeHtml('```javascript:example.js\nvar s="123";\n```'));
        });
    });

    describe('quote', function() {
        it('> ', function() {
            assert.equal('
test
', parser.makeHtml('> test'));
        });
    });

    describe('table', function() {
        it('type1 html ', function() {
            assert.equal('

test	test

', parser.makeHtml('

test	test

'));
        });
        it('type2 |', function() {
            assert.equal('

Item	Value	Qty

Computer	1600 USD	5

Phone	12 USD	12

Pipe	1 USD	234

',
                parser.makeHtml('| Item      |    Value | Qty  |\n| :-------- | --------:| :--: |\n| Computer  | 1600 USD |  5   |\n| Phone     |   12 USD |  12  |\n| Pipe      |    1 USD | 234  |'));
        });
        it('table align', function() {
            assert.equal('
\

\

\
Left-Aligned	\
Center Aligned	\
Right Aligned	\

\

\

\

\
col 3 is	\
some wordy text	\
$1600	\

\

\
col 2 is	\
centered	\
$12	\

\

\
zebra stripes	\
are neat	\
$1	\

\

\

', parser.makeHtml('| Left-Aligned  | Center Aligned  | Right Aligned |\n\
| :------------ |:---------------:| -----:|\n\
| col 3 is      | some wordy text | $1600 |\n\
| col 2 is      | centered        |   $12 |\n\
| zebra stripes | are neat        |    $1 |'));
        });
    });

    describe('footnote', function() {
        it('脚注 ', function() {
            assert.equal('
Never write "[click here]1".

2 ↩

', parser.makeHtml('Never write "[click here][^2]".\n [^2]: http://www.w3.org/QA/Tips/noClickHere'));
        });
    });

    describe('complex', function() {
        it('specialhtml', function() {
            assert.equal('
<li>asdf
', parser.makeHtml('
asdf'));
        });
        it('specialHR', function() {
            assert.equal('
a

', parser.makeHtml('```\na\n```\n---'));
        });
        it('list + code', function() {
            var codeInList = "1. 1111\n   1111\n\n 1111\`operator new\` 在分配内存失败的情况下会调用 \`new_handler\` 尝试让系统释放点内存，然后再次尝试申请内存。如果这时系统中内存确实紧张，即使调用。";
            assert.equal('

1111
1111

1111operator new 在分配内存失败的情况下会调用 new_handler 尝试让系统释放点内存，然后再次尝试申请内存。如果这时系统中内存确实紧张，即使调用。

', parser.makeHtml(codeInList));
        });
    });

    describe('bugfix', function() {
        it('escape', function () {
            assert.equal('
[系统盘]:\\Documents and Settings\\[用户名]\\Cookies
', parser.makeHtml('\\[系统盘]:\\\\Documents and Settings\\\\[用户名]\\\\Cookies'));
        });

        it('hr', function () {
            assert.equal('
', parser.makeHtml('* * *'));
        });

        it('backslash', function () {
            assert.equal('
\\\\
', parser.makeHtml('\\\\\\\\'));
            assert.equal('
\\123
', parser.makeHtml('\\123'));
            assert.equal('
|123
', parser.makeHtml('\\|123'));
        });

        it('table', function () {
            assert.equal('

Variable_name	Value

sql_mode	ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION

', parser.makeHtml('|---------------|-------|\n| Variable_name | Value |\n| ------------- | ----- |\n| sql_mode      | ONLY_FULL_GROUP_BY, STRICT_TRANS_TABLES, NO_ZERO_IN_DATE, NO_ZERO_DATE, ERROR_FOR_DIVISION_BY_ZERO, NO_AUTO_CREATE_USER, NO_ENGINE_SUBSTITUTION |\n|---------------|-------|'));
        });
    });

});
<|endoftext|>