<commit_before>import postcss from 'postcss';
import test    from 'ava';
import plugin  from './';

function run (t, input, expected, opts = { }) {
  return postcss([ plugin(opts) ]).process(input)
    .then(result => {
      t.deepEqual(result.css, expected);
      t.deepEqual(result.warnings().length, 0);
    });
}

test('Should have no effect when there are no identical styles', t => {
  return run(t,
    '.foo { width:0; } .bar { height:0; }',
    '.foo { width:0; } .bar { height:0; }',
    {}
  );
});

test('Should merge adjacent selectors that have identical styles', t => {
  return run(t,
    '.foo { width:0; height:0 } .bar { height:0; width:0 }',
    '.foo, .bar { width:0; height:0 }',
    { }
  );
});

test('Should not merge selectors that have different styles', t => {
  return run(t,
    '.foo { width:0; height:0 } A.foobar { top: 0 } .bar { height:0; width:0 }',
    '.foo, .bar { width:0; height:0 } A.foobar { top: 0 }',
    {}
  );
});

test('Should merge selectors and dedupe those with same name', t => {
  return run(t,
    '.foo { top:0 } .bar { top: 0 } .foo { top:0 } .baz { left: 10px }',
    '.foo, .bar { top:0 } .baz { left: 10px }',
    {}
  );
});

test('Should merge selectors that match config matcher', t => {
  return run(t,
    '.foo1 { top:0 } .fooDummy { top:0 } .foo2 { top:0 } .fooDummy { top:0 }',
    '.foo1, .foo2 { top:0 } .fooDummy { top:0 } .fooDummy { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /\.foo\d+/ }
    }}
  );
});

test('Should not merge selectors that don\'t match config matcher', t => {
  return run(t,
    '.foo { top:0 } .bar { top:0 } .baz { top:0 }',
    '.foo { top:0 } .bar, .baz { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /bar|baz/ }
    }}
  );
});

test('Should merge selectors that are more complex than the over-simplified examples above', t => {
  return run(t,
    'div#foo-bar, .foo:before { margin:1rem; padding-top:1vw } [whatever] { top:10px } [data-foo="test"].bar { padding-top:1vw; margin:1rem }',
    'div#foo-bar, .foo:before, [data-foo="test"].bar { margin:1rem; padding-top:1vw } [whatever] { top:10px }',
    { matchers : {
      whatever : { selectorFilter : /foo/ }
    }}
  );
});

test('Should merge selectors at the position of the last occurrence (when "promote" flag set)', t => {
  return run(t,
    '.foo1 { top:0 } .bar { top:0 } .foo2 { top:0 } .bar { top:0 }',
    '.bar { top:0 } .foo1, .foo2 { top:0 } .bar { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /\.foo/, promote : true  }
    }}
  );
});

test('Should merge each matcher group separately', t => {
  return run(t,
    '.foo1 { top:0 } .bar1 { top:0 } .foo2 { top:0 } .bar2 { top:0 }',
    '.bar1, .bar2 { top:0 } .foo1, .foo2 { top:0 }',
    { matchers : {
      whatever1 : { selectorFilter : /\.foo/, promote : true  },
      whatever2 : { selectorFilter : /\.bar/, promote : false }
    }}
  );
});

test('Should merge identical selectors without being affected by comment nodes', t => {
  return run(t,
    '.foo1 {  /* comment1 */ top:0 } /* comment2 */ .bar1 { top:0 } .foo2 { top:0 /* comment3 */ } .bar2 { left:0 /* comment4 */ }',
    '.foo1, .bar1, .foo2 {  /* comment1 */ top:0 } /* comment2 */ .bar2 { left:0 /* comment4 */ }',
  );
});
<commit_msg>Added test for keyframe-selector bug<commit_after>import postcss from 'postcss';
import test    from 'ava';
import plugin  from './';

function run (t, input, expected, opts = { }) {
  return postcss([ plugin(opts) ]).process(input)
    .then(result => {
      t.deepEqual(result.css, expected);
      t.deepEqual(result.warnings().length, 0);
    });
}

test('Should have no effect when there are no identical styles', t => {
  return run(t,
    '.foo { width:0; } .bar { height:0; }',
    '.foo { width:0; } .bar { height:0; }',
    {}
  );
});

test('Should merge adjacent selectors that have identical styles', t => {
  return run(t,
    '.foo { width:0; height:0 } .bar { height:0; width:0 }',
    '.foo, .bar { width:0; height:0 }',
    { }
  );
});

test('Should not merge selectors that have different styles', t => {
  return run(t,
    '.foo { width:0; height:0 } A.foobar { top: 0 } .bar { height:0; width:0 }',
    '.foo, .bar { width:0; height:0 } A.foobar { top: 0 }',
    {}
  );
});

test('Should merge selectors and dedupe those with same name', t => {
  return run(t,
    '.foo { top:0 } .bar { top: 0 } .foo { top:0 } .baz { left: 10px }',
    '.foo, .bar { top:0 } .baz { left: 10px }',
    {}
  );
});

test('Should merge selectors that match config matcher', t => {
  return run(t,
    '.foo1 { top:0 } .fooDummy { top:0 } .foo2 { top:0 } .fooDummy { top:0 }',
    '.foo1, .foo2 { top:0 } .fooDummy { top:0 } .fooDummy { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /\.foo\d+/ }
    }}
  );
});

test('Should not merge selectors that don\'t match config matcher', t => {
  return run(t,
    '.foo { top:0 } .bar { top:0 } .baz { top:0 }',
    '.foo { top:0 } .bar, .baz { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /bar|baz/ }
    }}
  );
});

test('Should merge selectors that are more complex than the over-simplified examples above', t => {
  return run(t,
    'div#foo-bar, .foo:before { margin:1rem; padding-top:1vw } [whatever] { top:10px } [data-foo="test"].bar { padding-top:1vw; margin:1rem }',
    'div#foo-bar, .foo:before, [data-foo="test"].bar { margin:1rem; padding-top:1vw } [whatever] { top:10px }',
    { matchers : {
      whatever : { selectorFilter : /foo/ }
    }}
  );
});

test('Should merge selectors at the position of the last occurrence (when "promote" flag set)', t => {
  return run(t,
    '.foo1 { top:0 } .bar { top:0 } .foo2 { top:0 } .bar { top:0 }',
    '.bar { top:0 } .foo1, .foo2 { top:0 } .bar { top:0 }',
    { matchers : {
      whatever : { selectorFilter : /\.foo/, promote : true  }
    }}
  );
});

test('Should merge each matcher group separately', t => {
  return run(t,
    '.foo1 { top:0 } .bar1 { top:0 } .foo2 { top:0 } .bar2 { top:0 }',
    '.bar1, .bar2 { top:0 } .foo1, .foo2 { top:0 }',
    { matchers : {
      whatever1 : { selectorFilter : /\.foo/, promote : true  },
      whatever2 : { selectorFilter : /\.bar/, promote : false }
    }}
  );
});

test('Should merge identical selectors without being affected by comment nodes', t => {
  return run(t,
    '.foo1 {  /* comment1 */ top:0 } /* comment2 */ .bar1 { top:0 } .foo2 { top:0 /* comment3 */ } .bar2 { left:0 /* comment4 */ }',
    '.foo1, .bar1, .foo2 {  /* comment1 */ top:0 } /* comment2 */ .bar2 { left:0 /* comment4 */ }',
  );
});

test('Should not merge keyframe-selectors', t => {
  return run(t,
    '.visible { opacity: 1; } @keyframes flash { 50%, from, to { opacity: 1; } 25%, 75% { opacity: 0; } }',
    '.visible { opacity: 1; } @keyframes flash { 50%, from, to { opacity: 1; } 25%, 75% { opacity: 0; } }',
  );
});
<|endoftext|>


<commit_before>/*global angular*/
(function () {
    'use strict';
    angular.module('core').directive('slidePart', ['ScriptInjector',
        function (ScriptInjector) {
            return {
                link: function postLink(scope, element, attrs) {
                    var oldContent;

                    var injectScript = function (templatePath) {
                        ScriptInjector.inject(templatePath + element.attr("url"), element.attr("tag") || element.attr("url"));
                    };

                    var update = function (content) {
                        if (element.attr("type") === "script" && element.attr("url") && !scope.isEdit) {
                            scope.$emit("getTemplatePath", injectScript);

                            return;
                        }

                        if (content === oldContent) {
                            return;
                        }

                        oldContent = content;

                        switch (element.prop("tagName").toUpperCase()) {
                            case "IMG":
                                element.attr("src", content || 'modules/slideshows/css/img/default.jpg');

                                break;

                            case "A":
                                if (content) {
                                    var href = content;
                                    var httpRegex = /^https?:\/\//;

                                    // Checking for @ is fine for our purposes; there is no point in validating the email address.
                                    if (content.indexOf('@') !== -1) {
                                        href = "mailto:" + content;
                                    } else if (!httpRegex.test(content)) {
                                        href = 'http://' + content;
                                    }

                                    element.attr("href", href);
                                    element.text(content.replace(httpRegex, ""));
                                }

                                break;

                            default:
                                if (content) {
                                    var text = content;
                                    if (element.attr("encoded") === "true") {
                                        eval("text='" + content + "'");
                                    }
                                    element.html(text);
                                }

                                break;
                        }

                        scope.$broadcast("slidePartUpdated", content, attrs.member);
                    };

                    if (scope.slide
                            && scope.slide.fireSetTemplateElementEvent
                            && element.attr("type") !== "script"
                            && element.attr("hiddenPart") !== "true") {
                        scope.$emit("setTemplateElement", attrs.member, {
                            type: attrs.type || 'text',
                            label: attrs.label || attrs.member
                        });
                    }

                    scope.$watch("slide.content." + attrs.member, update);
                }
            };
        }
    ]);
}());
<commit_msg>Show a grey placeholder image for slide pictures.<commit_after>/*global angular*/
(function () {
    'use strict';
    angular.module('core').directive('slidePart', ['ScriptInjector',
        function (ScriptInjector) {
            return {
                link: function postLink(scope, element, attrs) {
                    var oldContent;

                    var injectScript = function (templatePath) {
                        ScriptInjector.inject(templatePath + element.attr("url"), element.attr("tag") || element.attr("url"));
                    };

                    var update = function (content) {
                        if (element.attr("type") === "script" && element.attr("url") && !scope.isEdit) {
                            scope.$emit("getTemplatePath", injectScript);

                            return;
                        }

                        if (!content || /^\s+$/.test(content)) {
                            content = "";
                        }

                        if (content === oldContent) {
                            return;
                        }

                        oldContent = content;

                        switch (element.prop("tagName").toUpperCase()) {
                            case "IMG":
                                element.attr("src", content || 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAEAAAABAQMAAAAl21bKAAAAA1BMVEXMzMzKUkQnAAAACklEQVR4XmNgAAAAAgAB3p6PvwAAAABJRU5ErkJggg==');

                                break;

                            case "A":
                                if (content) {
                                    var href = content;
                                    var httpRegex = /^https?:\/\//;

                                    // Checking for @ is fine for our purposes; there is no point in validating the email address.
                                    if (content.indexOf('@') !== -1) {
                                        href = "mailto:" + content;
                                    } else if (!httpRegex.test(content)) {
                                        href = 'http://' + content;
                                    }

                                    element.attr("href", href);
                                    element.text(content.replace(httpRegex, ""));
                                }

                                break;

                            default:
                                if (content) {
                                    var text = content;
                                    if (element.attr("encoded") === "true") {
                                        eval("text='" + content + "'");
                                    }
                                    element.html(text);
                                }

                                break;
                        }

                        scope.$broadcast("slidePartUpdated", content, attrs.member);
                    };

                    if (scope.slide
                            && scope.slide.fireSetTemplateElementEvent
                            && element.attr("type") !== "script"
                            && element.attr("hiddenPart") !== "true") {
                        scope.$emit("setTemplateElement", attrs.member, {
                            type: attrs.type || 'text',
                            label: attrs.label || attrs.member
                        });
                    }

                    scope.$watch("slide.content." + attrs.member, update);
                }
            };
        }
    ]);
}());
<|endoftext|>


<commit_before>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length > 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<commit_msg>🤖(break): 2017-7-6 00:37:02<commit_after>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length < 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<|endoftext|>


<commit_before>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length > 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<commit_msg>🤖(fix): 2017-6-13 20:58:47<commit_after>const test = require('ava');
const random = require('./helpers/random');
const all = require('./products');

test('all()', t => {
  t.true(all.length < 0);
});

test('random()', t => {
  t.truthy(random(all));
  t.not(random(all), random(all));
});
<|endoftext|>


<commit_before>var cp = require('child_process')
  , spawn = cp.spawn
  , server  = spawn('./node_modules/serve/bin/serve', ['.'])
  , phantom = spawn('./vendor/phantomjs', ['./phantom.js'])

phantom.on('exit', function (code, signal) {
  var outcome = code == 0 ? 'passed' : 'failed'
  console.log('Qwery tests have %s', outcome)
  server.kill('SIGHUP')
})
<commit_msg>use process exit<commit_after>var cp = require('child_process')
  , spawn = cp.spawn
  , server  = spawn('./node_modules/serve/bin/serve', ['.'])
  , phantom = spawn('./vendor/phantomjs', ['./phantom.js'])

phantom.on('exit', function (code, signal) {
  var outcome = code == 0 ? 'passed' : 'failed'
  console.log('Qwery tests have %s', outcome)
  server.kill('SIGHUP')
  process.exit(code)
})
<|endoftext|>


<commit_before>/*
Name:         openkvk - test.js
Description:  Test script for openkvk.js
Source:       https://github.com/fvdm/nodejs-openkvk
Feedback:     https://github.com/fvdm/nodejs-openkvk/issues
License:      Public Domain / Unlicense (see LICENSE file)
*/

var util = require('util')

// Setup
var app = require('./')

// handle exits
var errors = 0
process.on( 'exit', function() {
  if( errors == 0 ) {
    console.log('\n\033[1mDONE, no errors.\033[0m\n')
    process.exit(0)
  } else {
    console.log('\n\033[1mFAIL, '+ errors +' error'+ (errors > 1 ? 's' : '') +' occurred!\033[0m\n')
    process.exit(1)
  }
})

// prevent errors from killing the process
process.on( 'uncaughtException', function( err ) {
  console.log()
  console.error( err.stack )
  console.trace()
  console.log()
  errors++
})

// Queue to prevent flooding
var queue = []
var next = 0

function doNext() {
  next++
  if( queue[next] ) {
    queue[next]()
  }
}

// doTest( passErr, 'methods', [
//   ['feeds', typeof feeds === 'object']
// ])
function doTest( err, label, tests ) {
  if( err instanceof Error ) {
    console.error( label +': \033[1m\033[31mERROR\033[0m\n' )
    console.error( util.inspect(err, false, 10, true) )
    console.log()
    console.error( err.stack )
    console.log()
    errors++
  } else {
    var testErrors = []
    tests.forEach( function( test ) {
      if( test[1] !== true ) {
        testErrors.push(test[0])
        errors++
      }
    })

    if( testErrors.length == 0 ) {
      console.log( label +': \033[1m\033[32mok\033[0m' )
    } else {
      console.error( label +': \033[1m\033[31mfailed\033[0m ('+ testErrors.join(', ') +')' )
    }
  }

  doNext()
}

queue.push( function() {
  app( null, function( err, data ) {
    doTest( null, 'Error: invalid params', [
      ['error', err && err.message === 'invalid params']
    ])
  })
})

queue.push( function() {
  app(
    {
      entity: 'software',
      country: 'NL',
      term: null,
      limit: 1,
      price: 0
    },
    function( err, data ) {
      doTest( null, 'Error: no results', [
        ['error', err && err.message === 'no results']
      ])
    }
  )
})

queue.push( function() {
  app(
    {
      entity: 'software',
      country: 'NL',
      term: 'github',
      limit: 1,
      price: 0
    },
    function( err, data ) {
      doTest( err, 'search', [
        ['data type', data && data instanceof Object],
        ['resultCount', data && data.resultCount && data.resultCount === 1],
        ['results type', data && data.results && data.results instanceof Array],
        ['results length', data && data.results && data.results instanceof Array && data.results.length === 1],
        ['item type', data && data.results && data.results[0] && data.results[0] instanceof Object],
        ['item kind', data && data.results && data.results[0] && data.results[0].kind === 'software']
      ])
    }
  )
})

// Start the tests
console.log('Running tests...\n')
queue[0]()
<commit_msg>Test: util.inspect soon requires object params<commit_after>/*
Name:         openkvk - test.js
Description:  Test script for openkvk.js
Source:       https://github.com/fvdm/nodejs-openkvk
Feedback:     https://github.com/fvdm/nodejs-openkvk/issues
License:      Public Domain / Unlicense (see LICENSE file)
*/

var util = require('util')

// Setup
var app = require('./')

// handle exits
var errors = 0
process.on( 'exit', function() {
  if( errors == 0 ) {
    console.log('\n\033[1mDONE, no errors.\033[0m\n')
    process.exit(0)
  } else {
    console.log('\n\033[1mFAIL, '+ errors +' error'+ (errors > 1 ? 's' : '') +' occurred!\033[0m\n')
    process.exit(1)
  }
})

// prevent errors from killing the process
process.on( 'uncaughtException', function( err ) {
  console.log()
  console.error( err.stack )
  console.trace()
  console.log()
  errors++
})

// Queue to prevent flooding
var queue = []
var next = 0

function doNext() {
  next++
  if( queue[next] ) {
    queue[next]()
  }
}

// doTest( passErr, 'methods', [
//   ['feeds', typeof feeds === 'object']
// ])
function doTest( err, label, tests ) {
  if( err instanceof Error ) {
    console.error( label +': \033[1m\033[31mERROR\033[0m\n' )
    console.error( util.inspect(err, {depth: 10, colors: true}) )
    console.log()
    console.error( err.stack )
    console.log()
    errors++
  } else {
    var testErrors = []
    tests.forEach( function( test ) {
      if( test[1] !== true ) {
        testErrors.push(test[0])
        errors++
      }
    })

    if( testErrors.length == 0 ) {
      console.log( label +': \033[1m\033[32mok\033[0m' )
    } else {
      console.error( label +': \033[1m\033[31mfailed\033[0m ('+ testErrors.join(', ') +')' )
    }
  }

  doNext()
}

queue.push( function() {
  app( null, function( err, data ) {
    doTest( null, 'Error: invalid params', [
      ['error', err && err.message === 'invalid params']
    ])
  })
})

queue.push( function() {
  app(
    {
      entity: 'software',
      country: 'NL',
      term: null,
      limit: 1,
      price: 0
    },
    function( err, data ) {
      doTest( null, 'Error: no results', [
        ['error', err && err.message === 'no results']
      ])
    }
  )
})

queue.push( function() {
  app(
    {
      entity: 'software',
      country: 'NL',
      term: 'github',
      limit: 1,
      price: 0
    },
    function( err, data ) {
      doTest( err, 'search', [
        ['data type', data && data instanceof Object],
        ['resultCount', data && data.resultCount && data.resultCount === 1],
        ['results type', data && data.results && data.results instanceof Array],
        ['results length', data && data.results && data.results instanceof Array && data.results.length === 1],
        ['item type', data && data.results && data.results[0] && data.results[0] instanceof Object],
        ['item kind', data && data.results && data.results[0] && data.results[0].kind === 'software']
      ])
    }
  )
})

// Start the tests
console.log('Running tests...\n')
queue[0]()
<|endoftext|>


<commit_before>(function() {
  'use strict';

  function QueryViewCtrl($scope, Events, $route, $location, notifications, growl, $modal, Query, DataSource) {
    var DEFAULT_TAB = 'table';

    var getQueryResult = function(maxAge) {
      if (maxAge === undefined) {
        maxAge = $location.search()['maxAge'];
      }

      if (maxAge === undefined) {
        maxAge = -1;
      }

      $scope.showLog = false;
      $scope.queryResult = $scope.query.getQueryResult(maxAge);
    };

    var getDataSourceId = function() {
      // Try to get the query's data source id
      var dataSourceId = $scope.query.data_source_id;

      // If there is no source yet, then parse what we have in localStorage
      //   e.g. `null` -> `NaN`, malformed data -> `NaN`, "1" -> 1
      if (dataSourceId === undefined) {
        dataSourceId = parseInt(localStorage.lastSelectedDataSourceId, 10);
      }

      // If we had an invalid value in localStorage (e.g. nothing, deleted source), then use the first data source
      var isValidDataSourceId = !isNaN(dataSourceId) && _.some($scope.dataSources, function(ds) {
        return ds.id == dataSourceId;
      });

      if (!isValidDataSourceId) {
        dataSourceId = $scope.dataSources[0].id;
      }

      // Return our data source id
      return dataSourceId;
    }

    var updateDataSources = function(dataSources) {
      // Filter out data sources the user can't query (or used by current query):
      $scope.dataSources = _.filter(dataSources, function(dataSource) {
        return !dataSource.view_only || dataSource.id === $scope.query.data_source_id;
      });

      if ($scope.dataSources.length == 0) {
        $scope.noDataSources = true;
        return;
      }

      if ($scope.query.isNew()) {
        $scope.query.data_source_id = getDataSourceId();
      }

      $scope.dataSource = _.find(dataSources, function(ds) { return ds.id == $scope.query.data_source_id; });

      //$scope.canExecuteQuery = $scope.canExecuteQuery && _.some(dataSources, function(ds) { return !ds.view_only });
      $scope.canCreateQuery = _.any(dataSources, function(ds) { return !ds.view_only });

      updateSchema();
    }

    $scope.dataSource = {};
    $scope.query = $route.current.locals.query;

    var updateSchema = function() {
      $scope.hasSchema = false;
      $scope.editorSize = "col-md-12";
      DataSource.getSchema({id: $scope.query.data_source_id}, function(data) {
        if (data && data.length > 0) {
          $scope.schema = data;
          _.each(data, function(table) {
            table.collapsed = true;
          });

          $scope.editorSize = "col-md-9";
          $scope.hasSchema = true;
        } else {
          $scope.hasSchema = false;
          $scope.editorSize = "col-md-12";
        }
      });
    }

    Events.record(currentUser, 'view', 'query', $scope.query.id);
    if ($scope.query.hasResult() || $scope.query.paramsRequired()) {
      getQueryResult();
    }
    $scope.queryExecuting = false;

    $scope.isQueryOwner = (currentUser.id === $scope.query.user.id) || currentUser.hasPermission('admin');
    $scope.canViewSource = currentUser.hasPermission('view_source');

    $scope.canExecuteQuery = function() {
      return currentUser.hasPermission('execute_query') && !$scope.dataSource.view_only;
    }

    $scope.canScheduleQuery = currentUser.hasPermission('schedule_query');

    if ($route.current.locals.dataSources) {
      $scope.dataSources = $route.current.locals.dataSources;
      updateDataSources($route.current.locals.dataSources);
    } else {
      $scope.dataSources = DataSource.query(updateDataSources);
    }

    // in view mode, latest dataset is always visible
    // source mode changes this behavior
    $scope.showDataset = true;
    $scope.showLog = false;

    $scope.lockButton = function(lock) {
      $scope.queryExecuting = lock;
    };

    $scope.showApiKey = function() {
      alert("API Key for this query:\n" + $scope.query.api_key);
    };

    $scope.saveQuery = function(options, data) {
      if (data) {
        // Don't save new query with partial data
        if ($scope.query.isNew()) {
          return;
        }
        data.id = $scope.query.id;
      } else {
        data = _.pick($scope.query, ["schedule", "query", "id", "description", "name", "data_source_id", "options"]);
        if ($scope.query.isNew()) {
          data['latest_query_data_id'] = $scope.query.latest_query_data_id;
        }
      }

      options = _.extend({}, {
        successMessage: 'Query saved',
        errorMessage: 'Query could not be saved'
      }, options);

      return Query.save(data, function() {
        growl.addSuccessMessage(options.successMessage);
      }, function(httpResponse) {
        growl.addErrorMessage(options.errorMessage);
      }).$promise;
    }

    $scope.saveDescription = function() {
      Events.record(currentUser, 'edit_description', 'query', $scope.query.id);
      $scope.saveQuery(undefined, {'description': $scope.query.description});
    };

    $scope.saveName = function() {
      Events.record(currentUser, 'edit_name', 'query', $scope.query.id);
      $scope.saveQuery(undefined, {'name': $scope.query.name});
    };

    $scope.executeQuery = function() {
      if (!$scope.canExecuteQuery()) {
        return;
      }

      if (!$scope.query.query) {
        return;
      }

      getQueryResult(0);
      $scope.lockButton(true);
      $scope.cancelling = false;
      Events.record(currentUser, 'execute', 'query', $scope.query.id);

      notifications.getPermissions();
    };

    $scope.cancelExecution = function() {
      $scope.cancelling = true;
      $scope.queryResult.cancelExecution();
      Events.record(currentUser, 'cancel_execute', 'query', $scope.query.id);
    };

    $scope.archiveQuery = function(options, data) {
      if (data) {
        data.id = $scope.query.id;
      } else {
        data = $scope.query;
      }

      $scope.isDirty = false;

      options = _.extend({}, {
        successMessage: 'Query archived',
        errorMessage: 'Query could not be archived'
      }, options);

      return Query.delete({id: data.id}, function() {
        $scope.query.is_archived = true;
        $scope.query.schedule = null;
        growl.addSuccessMessage(options.successMessage);
          // This feels dirty.
          $('#archive-confirmation-modal').modal('hide');
        }, function(httpResponse) {
          growl.addErrorMessage(options.errorMessage);
        }).$promise;
    }

    $scope.updateDataSource = function() {
      Events.record(currentUser, 'update_data_source', 'query', $scope.query.id);
      localStorage.lastSelectedDataSourceId = $scope.query.data_source_id;

      $scope.query.latest_query_data = null;
      $scope.query.latest_query_data_id = null;

      if ($scope.query.id) {
        Query.save({
          'id': $scope.query.id,
          'data_source_id': $scope.query.data_source_id,
          'latest_query_data_id': null
        });
      }

      updateSchema();
      $scope.dataSource = _.find($scope.dataSources, function(ds) { return ds.id == $scope.query.data_source_id; });
      $scope.executeQuery();
    };

    $scope.setVisualizationTab = function (visualization) {
      $scope.selectedTab = visualization.id;
      $location.hash(visualization.id);
    };

    $scope.$watch('query.name', function() {
      $scope.$parent.pageTitle = $scope.query.name;
    });

    $scope.$watch('queryResult && queryResult.getData()', function(data, oldData) {
      if (!data) {
        return;
      }

      $scope.filters = $scope.queryResult.getFilters();
    });

    $scope.$watch("queryResult && queryResult.getStatus()", function(status) {
      if (!status) {
        return;
      }

      if (status == 'done') {
        $scope.query.latest_query_data_id = $scope.queryResult.getId();
        $scope.query.queryResult = $scope.queryResult;

        notifications.showNotification("Re:dash", $scope.query.name + " updated.");
      } else if (status == 'failed') {
        notifications.showNotification("Re:dash", $scope.query.name + " failed to run: " + $scope.queryResult.getError());
      }

      if (status === 'done' || status === 'failed') {
        $scope.lockButton(false);
      }

      if ($scope.queryResult.getLog() != null) {
          $scope.showLog = true;
      }
    });

    $scope.openVisualizationEditor = function(visualization) {
      function openModal() {
        $modal.open({
          templateUrl: '/views/directives/visualization_editor.html',
          windowClass:'modal-xl',
          scope: $scope,
          controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
            $scope.modalInstance = $modalInstance;
            $scope.visualization = visualization;
            $scope.close = function() {
              $modalInstance.close();
            }
          }]
        });
      }

      if ($scope.query.isNew()) {
        $scope.saveQuery().then(function(query) {
          // Because we have a path change, we need to "signal" the next page to open the visualization editor.
          $location.path(query.getSourceLink()).hash('add');
        });
      } else {
        openModal();
      }
    };

    if ($location.hash() === 'add') {
      $location.hash(null);
      $scope.openVisualizationEditor();
    }

    $scope.openScheduleForm = function() {
      if (!$scope.isQueryOwner || !$scope.canScheduleQuery) {
        return;
      };

      $modal.open({
        templateUrl: '/views/schedule_form.html',
        size: 'sm',
        scope: $scope,
        controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
          $scope.close = function() {
            $modalInstance.close();
          }
          if ($scope.query.hasDailySchedule()) {
            $scope.refreshType = 'daily';
          } else {
            $scope.refreshType = 'periodic';
          }
        }]
      });
    };

    $scope.showEmbedDialog = function(query, visualization) {
      $modal.open({
        templateUrl: '/views/dialogs/embed_code.html',
        controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
          $scope.close = function() {
            $modalInstance.close();
          }
          $scope.embedUrl = basePath + 'embed/query/' + query.id + '/visualization/' + visualization.id + '?api_key=' + query.api_key;
        }]
      })
    }

    $scope.$watch(function() {
      return $location.hash()
    }, function(hash) {
      if (hash == 'pivot') {
        Events.record(currentUser, 'pivot', 'query', $scope.query && $scope.query.id);
      }
      $scope.selectedTab = hash || DEFAULT_TAB;
    });
  };

  angular.module('redash.controllers')
    .controller('QueryViewCtrl',
      ['$scope', 'Events', '$route', '$location', 'notifications', 'growl', '$modal', 'Query', 'DataSource', QueryViewCtrl]);
})();
<commit_msg>Fix: updated result not being saved when changing query text.<commit_after>(function() {
  'use strict';

  function QueryViewCtrl($scope, Events, $route, $location, notifications, growl, $modal, Query, DataSource) {
    var DEFAULT_TAB = 'table';

    var getQueryResult = function(maxAge) {
      if (maxAge === undefined) {
        maxAge = $location.search()['maxAge'];
      }

      if (maxAge === undefined) {
        maxAge = -1;
      }

      $scope.showLog = false;
      $scope.queryResult = $scope.query.getQueryResult(maxAge);
    };

    var getDataSourceId = function() {
      // Try to get the query's data source id
      var dataSourceId = $scope.query.data_source_id;

      // If there is no source yet, then parse what we have in localStorage
      //   e.g. `null` -> `NaN`, malformed data -> `NaN`, "1" -> 1
      if (dataSourceId === undefined) {
        dataSourceId = parseInt(localStorage.lastSelectedDataSourceId, 10);
      }

      // If we had an invalid value in localStorage (e.g. nothing, deleted source), then use the first data source
      var isValidDataSourceId = !isNaN(dataSourceId) && _.some($scope.dataSources, function(ds) {
        return ds.id == dataSourceId;
      });

      if (!isValidDataSourceId) {
        dataSourceId = $scope.dataSources[0].id;
      }

      // Return our data source id
      return dataSourceId;
    }

    var updateDataSources = function(dataSources) {
      // Filter out data sources the user can't query (or used by current query):
      $scope.dataSources = _.filter(dataSources, function(dataSource) {
        return !dataSource.view_only || dataSource.id === $scope.query.data_source_id;
      });

      if ($scope.dataSources.length == 0) {
        $scope.noDataSources = true;
        return;
      }

      if ($scope.query.isNew()) {
        $scope.query.data_source_id = getDataSourceId();
      }

      $scope.dataSource = _.find(dataSources, function(ds) { return ds.id == $scope.query.data_source_id; });

      //$scope.canExecuteQuery = $scope.canExecuteQuery && _.some(dataSources, function(ds) { return !ds.view_only });
      $scope.canCreateQuery = _.any(dataSources, function(ds) { return !ds.view_only });

      updateSchema();
    }

    $scope.dataSource = {};
    $scope.query = $route.current.locals.query;

    var updateSchema = function() {
      $scope.hasSchema = false;
      $scope.editorSize = "col-md-12";
      DataSource.getSchema({id: $scope.query.data_source_id}, function(data) {
        if (data && data.length > 0) {
          $scope.schema = data;
          _.each(data, function(table) {
            table.collapsed = true;
          });

          $scope.editorSize = "col-md-9";
          $scope.hasSchema = true;
        } else {
          $scope.hasSchema = false;
          $scope.editorSize = "col-md-12";
        }
      });
    }

    Events.record(currentUser, 'view', 'query', $scope.query.id);
    if ($scope.query.hasResult() || $scope.query.paramsRequired()) {
      getQueryResult();
    }
    $scope.queryExecuting = false;

    $scope.isQueryOwner = (currentUser.id === $scope.query.user.id) || currentUser.hasPermission('admin');
    $scope.canViewSource = currentUser.hasPermission('view_source');

    $scope.canExecuteQuery = function() {
      return currentUser.hasPermission('execute_query') && !$scope.dataSource.view_only;
    }

    $scope.canScheduleQuery = currentUser.hasPermission('schedule_query');

    if ($route.current.locals.dataSources) {
      $scope.dataSources = $route.current.locals.dataSources;
      updateDataSources($route.current.locals.dataSources);
    } else {
      $scope.dataSources = DataSource.query(updateDataSources);
    }

    // in view mode, latest dataset is always visible
    // source mode changes this behavior
    $scope.showDataset = true;
    $scope.showLog = false;

    $scope.lockButton = function(lock) {
      $scope.queryExecuting = lock;
    };

    $scope.showApiKey = function() {
      alert("API Key for this query:\n" + $scope.query.api_key);
    };

    $scope.saveQuery = function(options, data) {
      if (data) {
        // Don't save new query with partial data
        if ($scope.query.isNew()) {
          return;
        }
        data.id = $scope.query.id;
      } else {
        data = _.pick($scope.query, ["schedule", "query", "id", "description", "name", "data_source_id", "options"]);
        data['latest_query_data_id'] = $scope.query.latest_query_data_id;
      }

      options = _.extend({}, {
        successMessage: 'Query saved',
        errorMessage: 'Query could not be saved'
      }, options);

      return Query.save(data, function() {
        growl.addSuccessMessage(options.successMessage);
      }, function(httpResponse) {
        growl.addErrorMessage(options.errorMessage);
      }).$promise;
    }

    $scope.saveDescription = function() {
      Events.record(currentUser, 'edit_description', 'query', $scope.query.id);
      $scope.saveQuery(undefined, {'description': $scope.query.description});
    };

    $scope.saveName = function() {
      Events.record(currentUser, 'edit_name', 'query', $scope.query.id);
      $scope.saveQuery(undefined, {'name': $scope.query.name});
    };

    $scope.executeQuery = function() {
      if (!$scope.canExecuteQuery()) {
        return;
      }

      if (!$scope.query.query) {
        return;
      }

      getQueryResult(0);
      $scope.lockButton(true);
      $scope.cancelling = false;
      Events.record(currentUser, 'execute', 'query', $scope.query.id);

      notifications.getPermissions();
    };

    $scope.cancelExecution = function() {
      $scope.cancelling = true;
      $scope.queryResult.cancelExecution();
      Events.record(currentUser, 'cancel_execute', 'query', $scope.query.id);
    };

    $scope.archiveQuery = function(options, data) {
      if (data) {
        data.id = $scope.query.id;
      } else {
        data = $scope.query;
      }

      $scope.isDirty = false;

      options = _.extend({}, {
        successMessage: 'Query archived',
        errorMessage: 'Query could not be archived'
      }, options);

      return Query.delete({id: data.id}, function() {
        $scope.query.is_archived = true;
        $scope.query.schedule = null;
        growl.addSuccessMessage(options.successMessage);
          // This feels dirty.
          $('#archive-confirmation-modal').modal('hide');
        }, function(httpResponse) {
          growl.addErrorMessage(options.errorMessage);
        }).$promise;
    }

    $scope.updateDataSource = function() {
      Events.record(currentUser, 'update_data_source', 'query', $scope.query.id);
      localStorage.lastSelectedDataSourceId = $scope.query.data_source_id;

      $scope.query.latest_query_data = null;
      $scope.query.latest_query_data_id = null;

      if ($scope.query.id) {
        Query.save({
          'id': $scope.query.id,
          'data_source_id': $scope.query.data_source_id,
          'latest_query_data_id': null
        });
      }

      updateSchema();
      $scope.dataSource = _.find($scope.dataSources, function(ds) { return ds.id == $scope.query.data_source_id; });
      $scope.executeQuery();
    };

    $scope.setVisualizationTab = function (visualization) {
      $scope.selectedTab = visualization.id;
      $location.hash(visualization.id);
    };

    $scope.$watch('query.name', function() {
      $scope.$parent.pageTitle = $scope.query.name;
    });

    $scope.$watch('queryResult && queryResult.getData()', function(data, oldData) {
      if (!data) {
        return;
      }

      $scope.filters = $scope.queryResult.getFilters();
    });

    $scope.$watch("queryResult && queryResult.getStatus()", function(status) {
      if (!status) {
        return;
      }

      if (status == 'done') {
        $scope.query.latest_query_data_id = $scope.queryResult.getId();
        $scope.query.queryResult = $scope.queryResult;

        notifications.showNotification("Re:dash", $scope.query.name + " updated.");
      } else if (status == 'failed') {
        notifications.showNotification("Re:dash", $scope.query.name + " failed to run: " + $scope.queryResult.getError());
      }

      if (status === 'done' || status === 'failed') {
        $scope.lockButton(false);
      }

      if ($scope.queryResult.getLog() != null) {
          $scope.showLog = true;
      }
    });

    $scope.openVisualizationEditor = function(visualization) {
      function openModal() {
        $modal.open({
          templateUrl: '/views/directives/visualization_editor.html',
          windowClass:'modal-xl',
          scope: $scope,
          controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
            $scope.modalInstance = $modalInstance;
            $scope.visualization = visualization;
            $scope.close = function() {
              $modalInstance.close();
            }
          }]
        });
      }

      if ($scope.query.isNew()) {
        $scope.saveQuery().then(function(query) {
          // Because we have a path change, we need to "signal" the next page to open the visualization editor.
          $location.path(query.getSourceLink()).hash('add');
        });
      } else {
        openModal();
      }
    };

    if ($location.hash() === 'add') {
      $location.hash(null);
      $scope.openVisualizationEditor();
    }

    $scope.openScheduleForm = function() {
      if (!$scope.isQueryOwner || !$scope.canScheduleQuery) {
        return;
      };

      $modal.open({
        templateUrl: '/views/schedule_form.html',
        size: 'sm',
        scope: $scope,
        controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
          $scope.close = function() {
            $modalInstance.close();
          }
          if ($scope.query.hasDailySchedule()) {
            $scope.refreshType = 'daily';
          } else {
            $scope.refreshType = 'periodic';
          }
        }]
      });
    };

    $scope.showEmbedDialog = function(query, visualization) {
      $modal.open({
        templateUrl: '/views/dialogs/embed_code.html',
        controller: ['$scope', '$modalInstance', function($scope, $modalInstance) {
          $scope.close = function() {
            $modalInstance.close();
          }
          $scope.embedUrl = basePath + 'embed/query/' + query.id + '/visualization/' + visualization.id + '?api_key=' + query.api_key;
        }]
      })
    }

    $scope.$watch(function() {
      return $location.hash()
    }, function(hash) {
      if (hash == 'pivot') {
        Events.record(currentUser, 'pivot', 'query', $scope.query && $scope.query.id);
      }
      $scope.selectedTab = hash || DEFAULT_TAB;
    });
  };

  angular.module('redash.controllers')
    .controller('QueryViewCtrl',
      ['$scope', 'Events', '$route', '$location', 'notifications', 'growl', '$modal', 'Query', 'DataSource', QueryViewCtrl]);
})();
<|endoftext|>


<commit_before>"use strict";

var sinon = require('sinon');

var chai = require('chai'),
  expect = chai.expect,
  should = chai.should();

chai.use(require('sinon-chai'));

var Logger = require('./logarama.min');

var mocker = null,
  spy = {};

var test = module.exports = {
  beforeEach: function() {
    mocker = sinon.sandbox.create();

    // node doesn't have console.debug
    console.debug = function() {};

    spy = {
      trace: mocker.spy(console, 'trace'),
      debug: mocker.spy(console, 'debug'),
      info: mocker.spy(console, 'info'),
      warn: mocker.spy(console, 'warn'),
      error: mocker.spy(console, 'error'),
    };
  },
  afterEach: function() {
    mocker.restore();
  }
};

test['LEVELS'] = function() {
  Logger.LEVELS.should.eql({
    trace: 1,
    debug: 2,
    info: 3,
    warn: 4,
    error: 5,
  });
};

test['basic logger'] = function() {
  var logger = new Logger();

  logger.trace(1);
  spy.trace.should.not.have.been.called;

  logger.debug(2);
  spy.debug.should.have.been.calledOnce;
  spy.debug.should.have.been.calledWithExactly('[DEBUG]: 2');

  logger.info(3);
  spy.info.should.have.been.calledOnce;
  spy.info.should.have.been.calledWithExactly('[INFO]: 3');

  logger.warn(4);
  spy.warn.should.have.been.calledOnce;
  spy.warn.should.have.been.calledWithExactly('[WARN]: 4');

  logger.error(5);
  spy.error.should.have.been.calledOnce;
  spy.error.should.have.been.calledWithExactly('[ERROR]: 5');
};

test['set level'] = function() {
  var logger = new Logger({
    minLevel: 'warn'
  });

  logger.trace(1);
  spy.trace.should.not.have.been.called;

  logger.debug(2);
  spy.debug.should.not.have.been.called;

  logger.info(3);
  spy.info.should.not.have.been.called;

  logger.warn(4);
  spy.warn.should.have.been.calledOnce;

  logger.error(5);
  spy.error.should.have.been.calledOnce;
};

test['set level at runtime'] = function() {
  var logger = new Logger({
    minLevel: 'warn'
  });

  logger.trace(1);
  spy.debug.should.not.have.been.called;

  logger.setLevel('trace');

  logger.trace(1);
  spy.trace.should.have.been.calledWithExactly('[TRACE]: 1');
};

test['set tag'] = function() {
  var logger = new Logger({
    tag: 'app32'
  });

  logger.info(1);
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 1');
};

test['multiple arguments'] = function() {
  var logger = new Logger({
    tag: 'app32'
  });

  logger.info(1, 2, 3);
  spy.info.should.have.been.calledThrice;
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 1');
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 2');
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 3');
};

test['default formatting'] = {
  beforeEach: function() {
    this.logger = new Logger();
  },

  'simple': function() {
    this.logger.info(1, 1.2234234, false, true, null, undefined, 'str2');

    spy.info.should.have.been.calledWithExactly('[INFO]: 1');
    spy.info.should.have.been.calledWithExactly('[INFO]: 1.2234234');
    spy.info.should.have.been.calledWithExactly('[INFO]: false');
    spy.info.should.have.been.calledWithExactly('[INFO]: true');
    spy.info.should.have.been.calledWithExactly('[INFO]: null');
    spy.info.should.have.been.calledWithExactly('[INFO]: undefined');
    spy.info.should.have.been.calledWithExactly('[INFO]: str2');
  },

  'Error': function() {
    var error = new Error('test');

    this.logger.error(error);
    spy.error.should.have.been.calledWithExactly('[ERROR]: ' + error.stack);

    error.stack = [123, 456];
    this.logger.error(error);
    spy.error.should.have.been.calledWithExactly("[ERROR]: 123\n456");
  },

  'Array': function() {
    var arr = [1, 2, 3];

    this.logger.info(arr);
    spy.info.should.have.been.calledWithExactly('[INFO]: ' + arr.join("\n"));
  },

  'Object': {
    'JSON stringified': function() {
      var obj = {
        key1: 1.2,
        key2: {
          key2_1: 'abc',
          key2_2: false
        },
        key3: function() {},
        key4: [1,2,3,4],
        key5: [
          {
            key5_1: true
          }
        ]
      }

      this.logger.info(obj);
      spy.info.should.have.been.calledWithExactly('[INFO]: ' + JSON.stringify(obj, null, 2));
    },

    'Circular references': function() {
      var obj = {
        key1: 1.2,
      }

      obj.key2 = obj;

      this.logger.info(obj);
      spy.info.should.have.been.calledWithExactly('[INFO]: [Object with circular references]');
    }
  }
};

test['custom formatting'] = function() {
  var logger = new Logger({
    format: function(arg) {
      return '1' + arg;
    }
  });

  logger.info(1, 1.2234234, false, null, undefined, 'str2', {}, [2]);

  spy.info.should.have.been.calledWithExactly('[INFO]: 11');
  spy.info.should.have.been.calledWithExactly('[INFO]: 11.2234234');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1false');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1false');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1null');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1undefined');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1str2');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1[object Object]');
  spy.info.should.have.been.calledWithExactly('[INFO]: 12');
};

test['child logger'] = {
  'default': function() {
    var logger = new Logger(),
      childLogger = logger.create();

    childLogger.info(1);
    spy.info.should.have.been.calledWithExactly('[INFO]: 1');
  },

  'parent tag as prefix': function() {
    var logger = new Logger({
      tag: 'blah'
    }),
      childLogger = logger.create({
        tag: 'mah'
      });

    childLogger.info(1);
    spy.info.should.have.been.calledWithExactly('blah/mah[INFO]: 1');
  },

  'inherits parent level': function() {
    var logger = new Logger({
      tag: 'blah',
      minLevel: 'error',
    }),
      childLogger = logger.create();

    childLogger.warn(1);
    spy.warn.should.not.have.been.called;
  },

  'can override parent level': function() {
    var logger = new Logger({
      tag: 'blah',
      minLevel: 'error',
    }),
      childLogger = logger.create({
        minLevel: 'debug'
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledOnce;
  },

  'inherits parent formatter': function() {
    var logger = new Logger({
      format: function() {
        return 2;
      }
    }),
      childLogger = logger.create({
        tag: 'mah'
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledWithExactly('mah[WARN]: 2');
  },

  'can override parent formatter': function() {
    var logger = new Logger({
      format: function() {
        return 2;
      }
    }),
      childLogger = logger.create({
        tag: 'mah',
        format: function() {
          return 3;
        }
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledWithExactly('mah[WARN]: 3');
  },

};

<commit_msg>more tests for child loggers<commit_after>"use strict";

var sinon = require('sinon');

var chai = require('chai'),
  expect = chai.expect,
  should = chai.should();

chai.use(require('sinon-chai'));

var Logger = require('./logarama.min');

var mocker = null,
  spy = {};

var test = module.exports = {
  beforeEach: function() {
    mocker = sinon.sandbox.create();

    // node doesn't have console.debug
    console.debug = function() {};

    spy = {
      trace: mocker.spy(console, 'trace'),
      debug: mocker.spy(console, 'debug'),
      info: mocker.spy(console, 'info'),
      warn: mocker.spy(console, 'warn'),
      error: mocker.spy(console, 'error'),
    };
  },
  afterEach: function() {
    mocker.restore();
  }
};

test['LEVELS'] = function() {
  Logger.LEVELS.should.eql({
    trace: 1,
    debug: 2,
    info: 3,
    warn: 4,
    error: 5,
  });
};

test['basic logger'] = function() {
  var logger = new Logger();

  logger.trace(1);
  spy.trace.should.not.have.been.called;

  logger.debug(2);
  spy.debug.should.have.been.calledOnce;
  spy.debug.should.have.been.calledWithExactly('[DEBUG]: 2');

  logger.info(3);
  spy.info.should.have.been.calledOnce;
  spy.info.should.have.been.calledWithExactly('[INFO]: 3');

  logger.warn(4);
  spy.warn.should.have.been.calledOnce;
  spy.warn.should.have.been.calledWithExactly('[WARN]: 4');

  logger.error(5);
  spy.error.should.have.been.calledOnce;
  spy.error.should.have.been.calledWithExactly('[ERROR]: 5');
};

test['set level'] = function() {
  var logger = new Logger({
    minLevel: 'warn'
  });

  logger.trace(1);
  spy.trace.should.not.have.been.called;

  logger.debug(2);
  spy.debug.should.not.have.been.called;

  logger.info(3);
  spy.info.should.not.have.been.called;

  logger.warn(4);
  spy.warn.should.have.been.calledOnce;

  logger.error(5);
  spy.error.should.have.been.calledOnce;
};

test['set level at runtime'] = function() {
  var logger = new Logger({
    minLevel: 'warn'
  });

  logger.trace(1);
  spy.debug.should.not.have.been.called;

  logger.setLevel('trace');

  logger.trace(1);
  spy.trace.should.have.been.calledWithExactly('[TRACE]: 1');
};

test['set tag'] = function() {
  var logger = new Logger({
    tag: 'app32'
  });

  logger.info(1);
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 1');
};

test['multiple arguments'] = function() {
  var logger = new Logger({
    tag: 'app32'
  });

  logger.info(1, 2, 3);
  spy.info.should.have.been.calledThrice;
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 1');
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 2');
  spy.info.should.have.been.calledWithExactly('app32[INFO]: 3');
};

test['default formatting'] = {
  beforeEach: function() {
    this.logger = new Logger();
  },

  'simple': function() {
    this.logger.info(1, 1.2234234, false, true, null, undefined, 'str2');

    spy.info.should.have.been.calledWithExactly('[INFO]: 1');
    spy.info.should.have.been.calledWithExactly('[INFO]: 1.2234234');
    spy.info.should.have.been.calledWithExactly('[INFO]: false');
    spy.info.should.have.been.calledWithExactly('[INFO]: true');
    spy.info.should.have.been.calledWithExactly('[INFO]: null');
    spy.info.should.have.been.calledWithExactly('[INFO]: undefined');
    spy.info.should.have.been.calledWithExactly('[INFO]: str2');
  },

  'Error': function() {
    var error = new Error('test');

    this.logger.error(error);
    spy.error.should.have.been.calledWithExactly('[ERROR]: ' + error.stack);

    error.stack = [123, 456];
    this.logger.error(error);
    spy.error.should.have.been.calledWithExactly("[ERROR]: 123\n456");
  },

  'Array': function() {
    var arr = [1, 2, 3];

    this.logger.info(arr);
    spy.info.should.have.been.calledWithExactly('[INFO]: ' + arr.join("\n"));
  },

  'Object': {
    'JSON stringified': function() {
      var obj = {
        key1: 1.2,
        key2: {
          key2_1: 'abc',
          key2_2: false
        },
        key3: function() {},
        key4: [1,2,3,4],
        key5: [
          {
            key5_1: true
          }
        ]
      }

      this.logger.info(obj);
      spy.info.should.have.been.calledWithExactly('[INFO]: ' + JSON.stringify(obj, null, 2));
    },

    'Circular references': function() {
      var obj = {
        key1: 1.2,
      }

      obj.key2 = obj;

      this.logger.info(obj);
      spy.info.should.have.been.calledWithExactly('[INFO]: [Object with circular references]');
    }
  }
};

test['custom formatting'] = function() {
  var logger = new Logger({
    format: function(arg) {
      return '1' + arg;
    }
  });

  logger.info(1, 1.2234234, false, null, undefined, 'str2', {}, [2]);

  spy.info.should.have.been.calledWithExactly('[INFO]: 11');
  spy.info.should.have.been.calledWithExactly('[INFO]: 11.2234234');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1false');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1false');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1null');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1undefined');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1str2');
  spy.info.should.have.been.calledWithExactly('[INFO]: 1[object Object]');
  spy.info.should.have.been.calledWithExactly('[INFO]: 12');
};

test['child logger'] = {
  'default': function() {
    var logger = new Logger(),
      childLogger = logger.create();

    childLogger.info(1);
    spy.info.should.have.been.calledWithExactly('[INFO]: 1');
  },

  'parent tag as prefix': function() {
    var logger = new Logger({
      tag: 'blah'
    }),
      childLogger = logger.create({
        tag: 'mah'
      });

    childLogger.info(1);
    spy.info.should.have.been.calledWithExactly('blah/mah[INFO]: 1');
  },

  'inherits parent level': function() {
    var logger = new Logger({
      tag: 'blah',
      minLevel: 'error',
    }),
      childLogger = logger.create();

    childLogger.warn(1);
    spy.warn.should.not.have.been.called;
  },

  'can override parent level': function() {
    var logger = new Logger({
      tag: 'blah',
      minLevel: 'error',
    }),
      childLogger = logger.create({
        minLevel: 'debug'
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledOnce;
  },

  'inherits parent formatter': function() {
    var logger = new Logger({
      format: function() {
        return 2;
      }
    }),
      childLogger = logger.create({
        tag: 'mah'
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledWithExactly('mah[WARN]: 2');
  },

  'can override parent formatter': function() {
    var logger = new Logger({
      format: function() {
        return 2;
      }
    }),
      childLogger = logger.create({
        tag: 'mah',
        format: function() {
          return 3;
        }
      });

    childLogger.warn(1);
    spy.warn.should.have.been.calledWithExactly('mah[WARN]: 3');
  },

  'tracks parent level changes': function() {
    var logger = new Logger({
      minLevel: 'error'
    }),

    var childLogger = logger.create({
      minLevel: 'warn'
    });

    childLogger.info(1);
    spy.info.should.not.have.been.called;

    logger.setLevel('info');

    childLogger.info(1);
    spy.info.should.have.been.calledWithExactly('[INFO]: 1');
  },

};

<|endoftext|>


<commit_before>(function () {
    var notifications = function (Events) {
        var notificationService = {};
        var lastNotification = null;

        notificationService.isSupported = function () {
            if (window.webkitNotifications) {
                return true;
            } else {
                console.log("HTML5 notifications are not supported.");
                return false;
            }
        }

        notificationService.getPermissions = function () {
            if (!this.isSupported()) {
                return;
            }

            if (!window.webkitNotifications.checkPermission() == 0) { // 0 is PERMISSION_ALLOWED
                window.webkitNotifications.requestPermission();
            }
        }

        notificationService.showNotification = function (title, content) {
            if (!this.isSupported()) {
                return;
            }

            if (document.webkitVisibilityState && document.webkitVisibilityState == 'visible') {
                return;
            }

            if (lastNotification) {
                lastNotification.cancel();
            }

            var notification = window.webkitNotifications.createNotification('', title, content);
            lastNotification = notification;
            notification.onclick = function () {
                window.focus();
                this.cancel();
                Events.record(currentUser, 'click', 'notification');
            };

            notification.show()
        }

        return notificationService;
    }

    angular.module('redash.services')
        .factory('notifications', ['Events', notifications]);
})();
<commit_msg>Bug #307 fix. Notifications stopped working<commit_after>(function () {
    var notifications = function (Events) {
        var notificationService = {};

        notificationService.isSupported = function () {
            if ("Notification" in window) {
                return true;
            } else {
                console.log("HTML5 notifications are not supported.");
                return false;
            }
        }

        notificationService.getPermissions = function () {
            if (!this.isSupported()) {
                return;
            }

            if (Notification.permission !== "granted") {
	        Notification.requestPermission(function (status) {
	            if (Notification.permission !== status) {
                        Notification.permission = status;
                    }
                });
            }
        }

        notificationService.showNotification = function (title, content) {
            if (!this.isSupported()) {
                return;
            }

            //using the 'tag' to avoid showing duplicate notifications
            var notification = new Notification(title, {'tag': title+content, 'body': content});
            lastNotification = notification;
            notification.onclick = function () {
                window.focus();
                this.cancel();
                Events.record(currentUser, 'click', 'notification');
            };
        }

        return notificationService;
    }

    angular.module('redash.services')
        .factory('notifications', ['Events', notifications]);
})();
<|endoftext|>


<commit_before>/*
The files in this directory are part of node-ansible-test, a javascript-based library.
Copyright (C) 2016 Daniel Bush
This program is distributed under the terms of the BSD-2-Clause License.
*/
// File for using cucumber-mink.
// Cucumber-mink will run webdriverio and give you a bunch of pre-defined step definitions.
// You just need something like phantomjs --webdriver or similar.
// See 'npm run test:acceptance'.

var Mink = require('cucumber-mink');
var username  = process.env.SAUCE_USERNAME;
var accessKey = process.env.SAUCE_ACCESS_KEY;

// Phantomjs - GhostDriver
var parameters = {
  driver: {
    desiredCapabilities: {
      browserName: 'internet explorer',
      platform: 'Windows 10',
      version: '11.103',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      build: process.env.TRAVIS_BUILD_NUMBER,
      name: 'integration',
      username: username,
      accessKey: accessKey
    },
    //host: 'localhost',
    //host: 'ondemand.saucelabs.com',
    host: '127.0.0.1',
    //host: username + ':' + accessKey + '@ondemand.saucelabs.com',
    //host: username + ':' + accessKey + '@localhost',
    //host: username + ':' + accessKey + '@127.0.0.1',
    port: 4445,
    path: '/wd/hub',
    logLevel: 'verbose'
//"http://" + username + ":" + accessKey + "@ondemand.saucelabs.com:80/wd/hub"
  }
};
console.log('<<<< parameters', parameters);

module.exports = function () {
  Mink.init(this, parameters);
};
<commit_msg>Trying to add saucelabs.<commit_after>/*
The files in this directory are part of node-ansible-test, a javascript-based library.
Copyright (C) 2016 Daniel Bush
This program is distributed under the terms of the BSD-2-Clause License.
*/
// File for using cucumber-mink.
// Cucumber-mink will run webdriverio and give you a bunch of pre-defined step definitions.
// You just need something like phantomjs --webdriver or similar.
// See 'npm run test:acceptance'.

var Mink = require('cucumber-mink');
var username  = process.env.SAUCE_USERNAME;
var accessKey = process.env.SAUCE_ACCESS_KEY;

// Phantomjs - GhostDriver
var parameters = {
  driver: {
    desiredCapabilities: {
      browserName: 'internet explorer',
      platform: 'Windows 10',
      version: '11.103',
      'tunnel-identifier': process.env.TRAVIS_JOB_NUMBER,
      build: process.env.TRAVIS_BUILD_NUMBER,
      name: 'integration',
      username: username,
      accessKey: accessKey
    },
    //host: 'localhost',
    //host: 'ondemand.saucelabs.com',
    //host: '127.0.0.1',
    host: 'http://' + username + ':' + accessKey + '@ondemand.saucelabs.com',
    //host: username + ':' + accessKey + '@localhost',
    //host: username + ':' + accessKey + '@127.0.0.1',
    port: 4445,
    path: '/wd/hub',
    logLevel: 'verbose'
//"http://" + username + ":" + accessKey + "@ondemand.saucelabs.com:80/wd/hub"
  }
};
console.log('<<<< parameters', parameters);

module.exports = function () {
  Mink.init(this, parameters);
};
<|endoftext|>


<commit_before>(function() {
  'use strict';

  var app = angular.module( 'dolmen' );
  app.controller( 'PendingController', function( $http, $scope ){

    var display = this;
    display.details = [ ];

    $http.get( 'app/po_dash/request_2.json' )
      .then( function( response ){
        console.log( response );
        display.details = response.data;
      });

      app.run( function( editableOptions ){
        editableOptions.theme = 'bs3';
      });

      $scope.user = {
        date: 'detail.date',
        category: 'detail.category',
        address: 'detail.address',
        tenant: 'detail.tenant',
        phone: 'detail.phone',
        email: 'detail.email',
        description: 'detail.description',
        comments: 'detail.comments',
        status: 'detail.status'
      };

      // var firebase = new Firebase( 'https://dolmen.firebaseio.com');
      // self.data = $firebaseArray(firebase);
      // console.log(self.data);
      // self.submit = function() {
      //   self.data.$add({
      //     date: self.date,
      //     category: self.category,
      //     status: self.status
      //   }).then( function(){
      //     $location.path('/dashboard');
      //   });
      // };

  });
})();
<commit_msg>trying to communicate with firebase, info went away, changed key valuesgws<commit_after>(function() {
  'use strict';

  var app = angular.module( 'dolmen' );
  app.controller( 'PendingController', function( $http, $scope ){

    var display = this;
    display.detalles = [ ];

    $http.get( 'https://dolmen.firebaseio.com/' + 'json' )
      .then( function( responses ){
        console.log( responses );
        display.detalles = responses.data;
      });

      app.run( function( editableOptions ){
        editableOptions.theme = 'bs3';
      });

      $scope.user = {
          date: 'detalle.date',
          category: 'detalle.category',
          address: 'detalle.address',
          tenant: 'detalle.tenant',
          phone: 'detalle.phone',
          email: 'detalle.email',
          description: 'detalle.description',
          comments: 'detalle.comments',
          status: 'detalle.status'
      };

      // var firebase = new Firebase( 'https://dolmen.firebaseio.com');
      // self.data = $firebaseArray(firebase);
      // console.log(self.data);
      // self.submit = function() {
      //   self.data.$add({
      //     date: self.date,
      //     category: self.category,
      //     status: self.status
      //   }).then( function(){
      //     $location.path('/dashboard');
      //   });
      // };

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


<commit_before>/*
 * Copyright 2012, Mozilla Foundation and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var Promise = require('../util/promise').Promise;
var l10n = require('../util/l10n');

var Conversion = require('./types').Conversion;
var Type = require('./types').Type;
var Status = require('./types').Status;

/**
 * 'javascript' handles scripted input
 */
function JavascriptType(typeSpec) {
}

JavascriptType.prototype = Object.create(Type.prototype);

JavascriptType.prototype.getSpec = function() {
  return 'javascript';
};

JavascriptType.prototype.stringify = function(value, context) {
  if (value == null) {
    return '';
  }
  return value;
};

/**
 * When sorting out completions, there is no point in displaying millions of
 * matches - this the number of matches that we aim for
 */
JavascriptType.MAX_COMPLETION_MATCHES = 10;

JavascriptType.prototype.parse = function(arg, context) {
  var typed = arg.text;
  var scope = (context.environment.window == null) ?
              null : context.environment.window;

  // No input is undefined
  if (typed === '') {
    return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE));
  }
  // Just accept numbers
  if (!isNaN(parseFloat(typed)) && isFinite(typed)) {
    return Promise.resolve(new Conversion(typed, arg));
  }
  // Just accept constants like true/false/null/etc
  if (typed.trim().match(/(null|undefined|NaN|Infinity|true|false)/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // Analyze the input text and find the beginning of the last part that
  // should be completed.
  var beginning = this._findCompletionBeginning(typed);

  // There was an error analyzing the string.
  if (beginning.err) {
    return Promise.resolve(new Conversion(typed, arg, Status.ERROR, beginning.err));
  }

  // If the current state is ParseState.COMPLEX, then we can't do completion.
  // so bail out now
  if (beginning.state === ParseState.COMPLEX) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // If the current state is not ParseState.NORMAL, then we are inside of a
  // string which means that no completion is possible.
  if (beginning.state !== ParseState.NORMAL) {
    return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
  }

  var completionPart = typed.substring(beginning.startPos);
  var properties = completionPart.split('.');
  var matchProp;
  var prop;

  if (properties.length > 1) {
    matchProp = properties.pop().trimLeft();
    for (var i = 0; i < properties.length; i++) {
      prop = properties[i].trim();

      // We can't complete on null.foo, so bail out
      if (scope == null) {
        return Promise.resolve(new Conversion(typed, arg, Status.ERROR,
                                        l10n.lookup('jstypeParseScope')));
      }

      if (prop === '') {
        return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
      }

      // Check if prop is a getter function on 'scope'. Functions can change
      // other stuff so we can't execute them to get the next object. Stop here.
      if (this._isSafeProperty(scope, prop)) {
        return Promise.resolve(new Conversion(typed, arg));
      }

      try {
        scope = scope[prop];
      }
      catch (ex) {
        // It would be nice to be able to report this error in some way but
        // as it can happen just when someone types '{sessionStorage.', it
        // almost doesn't really count as an error, so we ignore it
        return Promise.resolve(new Conversion(typed, arg, Status.VALID, ''));
      }
    }
  }
  else {
    matchProp = properties[0].trimLeft();
  }

  // If the reason we just stopped adjusting the scope was a non-simple string,
  // then we're not sure if the input is valid or invalid, so accept it
  if (prop && !prop.match(/^[0-9A-Za-z]*$/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // However if the prop was a simple string, it is an error
  if (scope == null) {
    var msg = l10n.lookupFormat('jstypeParseMissing', [ prop ]);
    return Promise.resolve(new Conversion(typed, arg, Status.ERROR, msg));
  }

  // If the thing we're looking for isn't a simple string, then we're not going
  // to find it, but we're not sure if it's valid or invalid, so accept it
  if (!matchProp.match(/^[0-9A-Za-z]*$/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // Skip Iterators and Generators.
  if (this._isIteratorOrGenerator(scope)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  var matchLen = matchProp.length;
  var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen);
  var status = Status.INCOMPLETE;
  var message = '';

  // We really want an array of matches (for sorting) but it's easier to
  // detect existing members if we're using a map initially
  var matches = {};

  // We only display a maximum of MAX_COMPLETION_MATCHES, so there is no point
  // in digging up the prototype chain for matches that we're never going to
  // use. Initially look for matches directly on the object itself and then
  // look up the chain to find more
  var distUpPrototypeChain = 0;
  var root = scope;
  try {
    while (root != null &&
        Object.keys(matches).length < JavascriptType.MAX_COMPLETION_MATCHES) {

      /* jshint loopfunc:true */
      Object.keys(root).forEach(function(property) {
        // Only add matching properties. Also, as we're walking up the
        // prototype chain, properties on 'higher' prototypes don't override
        // similarly named properties lower down
        if (property.indexOf(matchProp) === 0 && !(property in matches)) {
          matches[property] = {
            prop: property,
            distUpPrototypeChain: distUpPrototypeChain
          };
        }
      });

      distUpPrototypeChain++;
      root = Object.getPrototypeOf(root);
    }
  }
  catch (ex) {
    return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
  }

  // Convert to an array for sorting, and while we're at it, note if we got
  // an exact match so we know that this input is valid
  matches = Object.keys(matches).map(function(property) {
    if (property === matchProp) {
      status = Status.VALID;
    }
    return matches[property];
  });

  // The sort keys are:
  // - Being on the object itself, not in the prototype chain
  // - The lack of existence of a vendor prefix
  // - The name
  matches.sort(function(m1, m2) {
    if (m1.distUpPrototypeChain !== m2.distUpPrototypeChain) {
      return m1.distUpPrototypeChain - m2.distUpPrototypeChain;
    }
    // Push all vendor prefixes to the bottom of the list
    return isVendorPrefixed(m1.prop) ?
      (isVendorPrefixed(m2.prop) ? m1.prop.localeCompare(m2.prop) : 1) :
      (isVendorPrefixed(m2.prop) ? -1 : m1.prop.localeCompare(m2.prop));
  });

  // Trim to size. There is a bug for doing a better job of finding matches
  // (bug 682694), but in the mean time there is a performance problem
  // associated with creating a large number of DOM nodes that few people will
  // ever read, so trim ...
  if (matches.length > JavascriptType.MAX_COMPLETION_MATCHES) {
    matches = matches.slice(0, JavascriptType.MAX_COMPLETION_MATCHES - 1);
  }

  // Decorate the matches with:
  // - a description
  // - a value (for the menu) and,
  // - an incomplete flag which reports if we should assume that the user isn't
  //   going to carry on the JS expression with this input so far
  var predictions = matches.map(function(match) {
    var description;
    var incomplete = true;

    if (this._isSafeProperty(scope, match.prop)) {
      description = '(property getter)';
    }
    else {
      try {
        var value = scope[match.prop];

        if (typeof value === 'function') {
          description = '(function)';
        }
        else if (typeof value === 'boolean' || typeof value === 'number') {
          description = '= ' + value;
          incomplete = false;
        }
        else if (typeof value === 'string') {
          if (value.length > 40) {
            value = value.substring(0, 37) + '…';
          }
          description = '= \'' + value + '\'';
          incomplete = false;
        }
        else {
          description = '(' + typeof value + ')';
        }
      }
      catch (ex) {
        description = '(' + l10n.lookup('jstypeParseError') + ')';
      }
    }

    return {
      name: prefix + match.prop,
      value: {
        name: prefix + match.prop,
        description: description
      },
      description: description,
      incomplete: incomplete
    };
  }, this);

  if (predictions.length === 0) {
    status = Status.ERROR;
    message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]);
  }

  // If the match is the only one possible, and its VALID, predict nothing
  if (predictions.length === 1 && status === Status.VALID) {
    predictions = [];
  }

  return Promise.resolve(new Conversion(typed, arg, status, message,
                                  Promise.resolve(predictions)));
};

/**
 * Does the given property have a prefix that indicates that it is vendor
 * specific?
 */
function isVendorPrefixed(name) {
  return name.indexOf('moz') === 0 ||
         name.indexOf('webkit') === 0 ||
         name.indexOf('ms') === 0;
}

/**
 * Constants used in return value of _findCompletionBeginning()
 */
var ParseState = {
  /**
   * We have simple input like window.foo, without any punctuation that makes
   * completion prediction be confusing or wrong
   */
  NORMAL: 0,

  /**
   * The cursor is in some Javascript that makes completion hard to predict,
   * like console.log(
   */
  COMPLEX: 1,

  /**
   * The cursor is inside single quotes (')
   */
  QUOTE: 2,

  /**
   * The cursor is inside single quotes (")
   */
  DQUOTE: 3
};

var OPEN_BODY = '{[('.split('');
var CLOSE_BODY = '}])'.split('');
var OPEN_CLOSE_BODY = {
  '{': '}',
  '[': ']',
  '(': ')'
};

/**
 * How we distinguish between simple and complex JS input. We attempt
 * completion against simple JS.
 */
var simpleChars = /[a-zA-Z0-9.]/;

/**
 * Analyzes a given string to find the last statement that is interesting for
 * later completion.
 * @param text A string to analyze
 * @return If there was an error in the string detected, then a object like
 *   { err: 'ErrorMesssage' }
 * is returned, otherwise a object like
 *   {
 *     state: ParseState.NORMAL|ParseState.QUOTE|ParseState.DQUOTE,
 *     startPos: index of where the last statement begins
 *   }
 */
JavascriptType.prototype._findCompletionBeginning = function(text) {
  var bodyStack = [];

  var state = ParseState.NORMAL;
  var start = 0;
  var c;
  var complex = false;

  for (var i = 0; i < text.length; i++) {
    c = text[i];
    if (!simpleChars.test(c)) {
      complex = true;
    }

    switch (state) {
      // Normal JS state.
      case ParseState.NORMAL:
        if (c === '"') {
          state = ParseState.DQUOTE;
        }
        else if (c === '\'') {
          state = ParseState.QUOTE;
        }
        else if (c === ';') {
          start = i + 1;
        }
        else if (c === ' ') {
          start = i + 1;
        }
        else if (OPEN_BODY.indexOf(c) != -1) {
          bodyStack.push({
            token: c,
            start: start
          });
          start = i + 1;
        }
        else if (CLOSE_BODY.indexOf(c) != -1) {
          var last = bodyStack.pop();
          if (!last || OPEN_CLOSE_BODY[last.token] != c) {
            return { err: l10n.lookup('jstypeBeginSyntax') };
          }
          if (c === '}') {
            start = i + 1;
          }
          else {
            start = last.start;
          }
        }
        break;

      // Double quote state > " <
      case ParseState.DQUOTE:
        if (c === '\\') {
          i ++;
        }
        else if (c === '\n') {
          return { err: l10n.lookup('jstypeBeginUnterm') };
        }
        else if (c === '"') {
          state = ParseState.NORMAL;
        }
        break;

      // Single quote state > ' <
      case ParseState.QUOTE:
        if (c === '\\') {
          i ++;
        }
        else if (c === '\n') {
          return { err: l10n.lookup('jstypeBeginUnterm') };
        }
        else if (c === '\'') {
          state = ParseState.NORMAL;
        }
        break;
    }
  }

  if (state === ParseState.NORMAL && complex) {
    state = ParseState.COMPLEX;
  }

  return {
    state: state,
    startPos: start
  };
};

/**
 * Return true if the passed object is either an iterator or a generator, and
 * false otherwise
 * @param obj The object to check
 */
JavascriptType.prototype._isIteratorOrGenerator = function(obj) {
  if (obj === null) {
    return false;
  }

  if (typeof aObject === 'object') {
    if (typeof obj.__iterator__ === 'function' ||
        obj.constructor && obj.constructor.name === 'Iterator') {
      return true;
    }

    try {
      var str = obj.toString();
      if (typeof obj.next === 'function' &&
          str.indexOf('[object Generator') === 0) {
        return true;
      }
    }
    catch (ex) {
      // window.history.next throws in the typeof check above.
      return false;
    }
  }

  return false;
};

/**
 * Would calling 'scope[prop]' cause the invocation of a non-native (i.e. user
 * defined) function property?
 * Since calling functions can have side effects, it's only safe to do that if
 * explicitly requested, rather than because we're trying things out for the
 * purposes of completion.
 */
JavascriptType.prototype._isSafeProperty = function(scope, prop) {
  if (typeof scope !== 'object') {
    return false;
  }

  // Walk up the prototype chain of 'scope' looking for a property descriptor
  // for 'prop'
  var propDesc;
  while (scope) {
    try {
      propDesc = Object.getOwnPropertyDescriptor(scope, prop);
      if (propDesc) {
        break;
      }
    }
    catch (ex) {
      // Native getters throw here. See bug 520882.
      if (ex.name === 'NS_ERROR_XPC_BAD_CONVERT_JS' ||
          ex.name === 'NS_ERROR_XPC_BAD_OP_ON_WN_PROTO') {
        return false;
      }
      return true;
    }
    scope = Object.getPrototypeOf(scope);
  }

  if (!propDesc) {
    return false;
  }

  if (!propDesc.get) {
    return false;
  }

  // The property is safe if 'get' isn't a function or if the function has a
  // prototype (in which case it's native)
  return typeof propDesc.get !== 'function' || 'prototype' in propDesc.get;
};

JavascriptType.prototype.name = 'javascript';

exports.items = [ JavascriptType ];
<commit_msg>runat-1128988: Remote the javascript type correctly.<commit_after>/*
 * Copyright 2012, Mozilla Foundation and contributors
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

'use strict';

var Promise = require('../util/promise').Promise;
var l10n = require('../util/l10n');

var Conversion = require('./types').Conversion;
var Type = require('./types').Type;
var Status = require('./types').Status;

/**
 * 'javascript' handles scripted input
 */
function JavascriptType(typeSpec) {
}

JavascriptType.prototype = Object.create(Type.prototype);

JavascriptType.prototype.getSpec = function(commandName, paramName) {
  return {
    name: 'remote',
    paramName: paramName
  };
};

JavascriptType.prototype.stringify = function(value, context) {
  if (value == null) {
    return '';
  }
  return value;
};

/**
 * When sorting out completions, there is no point in displaying millions of
 * matches - this the number of matches that we aim for
 */
JavascriptType.MAX_COMPLETION_MATCHES = 10;

JavascriptType.prototype.parse = function(arg, context) {
  var typed = arg.text;
  var scope = (context.environment.window == null) ?
              null : context.environment.window;

  // No input is undefined
  if (typed === '') {
    return Promise.resolve(new Conversion(undefined, arg, Status.INCOMPLETE));
  }
  // Just accept numbers
  if (!isNaN(parseFloat(typed)) && isFinite(typed)) {
    return Promise.resolve(new Conversion(typed, arg));
  }
  // Just accept constants like true/false/null/etc
  if (typed.trim().match(/(null|undefined|NaN|Infinity|true|false)/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // Analyze the input text and find the beginning of the last part that
  // should be completed.
  var beginning = this._findCompletionBeginning(typed);

  // There was an error analyzing the string.
  if (beginning.err) {
    return Promise.resolve(new Conversion(typed, arg, Status.ERROR, beginning.err));
  }

  // If the current state is ParseState.COMPLEX, then we can't do completion.
  // so bail out now
  if (beginning.state === ParseState.COMPLEX) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // If the current state is not ParseState.NORMAL, then we are inside of a
  // string which means that no completion is possible.
  if (beginning.state !== ParseState.NORMAL) {
    return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
  }

  var completionPart = typed.substring(beginning.startPos);
  var properties = completionPart.split('.');
  var matchProp;
  var prop;

  if (properties.length > 1) {
    matchProp = properties.pop().trimLeft();
    for (var i = 0; i < properties.length; i++) {
      prop = properties[i].trim();

      // We can't complete on null.foo, so bail out
      if (scope == null) {
        return Promise.resolve(new Conversion(typed, arg, Status.ERROR,
                                        l10n.lookup('jstypeParseScope')));
      }

      if (prop === '') {
        return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
      }

      // Check if prop is a getter function on 'scope'. Functions can change
      // other stuff so we can't execute them to get the next object. Stop here.
      if (this._isSafeProperty(scope, prop)) {
        return Promise.resolve(new Conversion(typed, arg));
      }

      try {
        scope = scope[prop];
      }
      catch (ex) {
        // It would be nice to be able to report this error in some way but
        // as it can happen just when someone types '{sessionStorage.', it
        // almost doesn't really count as an error, so we ignore it
        return Promise.resolve(new Conversion(typed, arg, Status.VALID, ''));
      }
    }
  }
  else {
    matchProp = properties[0].trimLeft();
  }

  // If the reason we just stopped adjusting the scope was a non-simple string,
  // then we're not sure if the input is valid or invalid, so accept it
  if (prop && !prop.match(/^[0-9A-Za-z]*$/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // However if the prop was a simple string, it is an error
  if (scope == null) {
    var msg = l10n.lookupFormat('jstypeParseMissing', [ prop ]);
    return Promise.resolve(new Conversion(typed, arg, Status.ERROR, msg));
  }

  // If the thing we're looking for isn't a simple string, then we're not going
  // to find it, but we're not sure if it's valid or invalid, so accept it
  if (!matchProp.match(/^[0-9A-Za-z]*$/)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  // Skip Iterators and Generators.
  if (this._isIteratorOrGenerator(scope)) {
    return Promise.resolve(new Conversion(typed, arg));
  }

  var matchLen = matchProp.length;
  var prefix = matchLen === 0 ? typed : typed.slice(0, -matchLen);
  var status = Status.INCOMPLETE;
  var message = '';

  // We really want an array of matches (for sorting) but it's easier to
  // detect existing members if we're using a map initially
  var matches = {};

  // We only display a maximum of MAX_COMPLETION_MATCHES, so there is no point
  // in digging up the prototype chain for matches that we're never going to
  // use. Initially look for matches directly on the object itself and then
  // look up the chain to find more
  var distUpPrototypeChain = 0;
  var root = scope;
  try {
    while (root != null &&
        Object.keys(matches).length < JavascriptType.MAX_COMPLETION_MATCHES) {

      /* jshint loopfunc:true */
      Object.keys(root).forEach(function(property) {
        // Only add matching properties. Also, as we're walking up the
        // prototype chain, properties on 'higher' prototypes don't override
        // similarly named properties lower down
        if (property.indexOf(matchProp) === 0 && !(property in matches)) {
          matches[property] = {
            prop: property,
            distUpPrototypeChain: distUpPrototypeChain
          };
        }
      });

      distUpPrototypeChain++;
      root = Object.getPrototypeOf(root);
    }
  }
  catch (ex) {
    return Promise.resolve(new Conversion(typed, arg, Status.INCOMPLETE, ''));
  }

  // Convert to an array for sorting, and while we're at it, note if we got
  // an exact match so we know that this input is valid
  matches = Object.keys(matches).map(function(property) {
    if (property === matchProp) {
      status = Status.VALID;
    }
    return matches[property];
  });

  // The sort keys are:
  // - Being on the object itself, not in the prototype chain
  // - The lack of existence of a vendor prefix
  // - The name
  matches.sort(function(m1, m2) {
    if (m1.distUpPrototypeChain !== m2.distUpPrototypeChain) {
      return m1.distUpPrototypeChain - m2.distUpPrototypeChain;
    }
    // Push all vendor prefixes to the bottom of the list
    return isVendorPrefixed(m1.prop) ?
      (isVendorPrefixed(m2.prop) ? m1.prop.localeCompare(m2.prop) : 1) :
      (isVendorPrefixed(m2.prop) ? -1 : m1.prop.localeCompare(m2.prop));
  });

  // Trim to size. There is a bug for doing a better job of finding matches
  // (bug 682694), but in the mean time there is a performance problem
  // associated with creating a large number of DOM nodes that few people will
  // ever read, so trim ...
  if (matches.length > JavascriptType.MAX_COMPLETION_MATCHES) {
    matches = matches.slice(0, JavascriptType.MAX_COMPLETION_MATCHES - 1);
  }

  // Decorate the matches with:
  // - a description
  // - a value (for the menu) and,
  // - an incomplete flag which reports if we should assume that the user isn't
  //   going to carry on the JS expression with this input so far
  var predictions = matches.map(function(match) {
    var description;
    var incomplete = true;

    if (this._isSafeProperty(scope, match.prop)) {
      description = '(property getter)';
    }
    else {
      try {
        var value = scope[match.prop];

        if (typeof value === 'function') {
          description = '(function)';
        }
        else if (typeof value === 'boolean' || typeof value === 'number') {
          description = '= ' + value;
          incomplete = false;
        }
        else if (typeof value === 'string') {
          if (value.length > 40) {
            value = value.substring(0, 37) + '…';
          }
          description = '= \'' + value + '\'';
          incomplete = false;
        }
        else {
          description = '(' + typeof value + ')';
        }
      }
      catch (ex) {
        description = '(' + l10n.lookup('jstypeParseError') + ')';
      }
    }

    return {
      name: prefix + match.prop,
      value: {
        name: prefix + match.prop,
        description: description
      },
      description: description,
      incomplete: incomplete
    };
  }, this);

  if (predictions.length === 0) {
    status = Status.ERROR;
    message = l10n.lookupFormat('jstypeParseMissing', [ matchProp ]);
  }

  // If the match is the only one possible, and its VALID, predict nothing
  if (predictions.length === 1 && status === Status.VALID) {
    predictions = [];
  }

  return Promise.resolve(new Conversion(typed, arg, status, message,
                                  Promise.resolve(predictions)));
};

/**
 * Does the given property have a prefix that indicates that it is vendor
 * specific?
 */
function isVendorPrefixed(name) {
  return name.indexOf('moz') === 0 ||
         name.indexOf('webkit') === 0 ||
         name.indexOf('ms') === 0;
}

/**
 * Constants used in return value of _findCompletionBeginning()
 */
var ParseState = {
  /**
   * We have simple input like window.foo, without any punctuation that makes
   * completion prediction be confusing or wrong
   */
  NORMAL: 0,

  /**
   * The cursor is in some Javascript that makes completion hard to predict,
   * like console.log(
   */
  COMPLEX: 1,

  /**
   * The cursor is inside single quotes (')
   */
  QUOTE: 2,

  /**
   * The cursor is inside single quotes (")
   */
  DQUOTE: 3
};

var OPEN_BODY = '{[('.split('');
var CLOSE_BODY = '}])'.split('');
var OPEN_CLOSE_BODY = {
  '{': '}',
  '[': ']',
  '(': ')'
};

/**
 * How we distinguish between simple and complex JS input. We attempt
 * completion against simple JS.
 */
var simpleChars = /[a-zA-Z0-9.]/;

/**
 * Analyzes a given string to find the last statement that is interesting for
 * later completion.
 * @param text A string to analyze
 * @return If there was an error in the string detected, then a object like
 *   { err: 'ErrorMesssage' }
 * is returned, otherwise a object like
 *   {
 *     state: ParseState.NORMAL|ParseState.QUOTE|ParseState.DQUOTE,
 *     startPos: index of where the last statement begins
 *   }
 */
JavascriptType.prototype._findCompletionBeginning = function(text) {
  var bodyStack = [];

  var state = ParseState.NORMAL;
  var start = 0;
  var c;
  var complex = false;

  for (var i = 0; i < text.length; i++) {
    c = text[i];
    if (!simpleChars.test(c)) {
      complex = true;
    }

    switch (state) {
      // Normal JS state.
      case ParseState.NORMAL:
        if (c === '"') {
          state = ParseState.DQUOTE;
        }
        else if (c === '\'') {
          state = ParseState.QUOTE;
        }
        else if (c === ';') {
          start = i + 1;
        }
        else if (c === ' ') {
          start = i + 1;
        }
        else if (OPEN_BODY.indexOf(c) != -1) {
          bodyStack.push({
            token: c,
            start: start
          });
          start = i + 1;
        }
        else if (CLOSE_BODY.indexOf(c) != -1) {
          var last = bodyStack.pop();
          if (!last || OPEN_CLOSE_BODY[last.token] != c) {
            return { err: l10n.lookup('jstypeBeginSyntax') };
          }
          if (c === '}') {
            start = i + 1;
          }
          else {
            start = last.start;
          }
        }
        break;

      // Double quote state > " <
      case ParseState.DQUOTE:
        if (c === '\\') {
          i ++;
        }
        else if (c === '\n') {
          return { err: l10n.lookup('jstypeBeginUnterm') };
        }
        else if (c === '"') {
          state = ParseState.NORMAL;
        }
        break;

      // Single quote state > ' <
      case ParseState.QUOTE:
        if (c === '\\') {
          i ++;
        }
        else if (c === '\n') {
          return { err: l10n.lookup('jstypeBeginUnterm') };
        }
        else if (c === '\'') {
          state = ParseState.NORMAL;
        }
        break;
    }
  }

  if (state === ParseState.NORMAL && complex) {
    state = ParseState.COMPLEX;
  }

  return {
    state: state,
    startPos: start
  };
};

/**
 * Return true if the passed object is either an iterator or a generator, and
 * false otherwise
 * @param obj The object to check
 */
JavascriptType.prototype._isIteratorOrGenerator = function(obj) {
  if (obj === null) {
    return false;
  }

  if (typeof aObject === 'object') {
    if (typeof obj.__iterator__ === 'function' ||
        obj.constructor && obj.constructor.name === 'Iterator') {
      return true;
    }

    try {
      var str = obj.toString();
      if (typeof obj.next === 'function' &&
          str.indexOf('[object Generator') === 0) {
        return true;
      }
    }
    catch (ex) {
      // window.history.next throws in the typeof check above.
      return false;
    }
  }

  return false;
};

/**
 * Would calling 'scope[prop]' cause the invocation of a non-native (i.e. user
 * defined) function property?
 * Since calling functions can have side effects, it's only safe to do that if
 * explicitly requested, rather than because we're trying things out for the
 * purposes of completion.
 */
JavascriptType.prototype._isSafeProperty = function(scope, prop) {
  if (typeof scope !== 'object') {
    return false;
  }

  // Walk up the prototype chain of 'scope' looking for a property descriptor
  // for 'prop'
  var propDesc;
  while (scope) {
    try {
      propDesc = Object.getOwnPropertyDescriptor(scope, prop);
      if (propDesc) {
        break;
      }
    }
    catch (ex) {
      // Native getters throw here. See bug 520882.
      if (ex.name === 'NS_ERROR_XPC_BAD_CONVERT_JS' ||
          ex.name === 'NS_ERROR_XPC_BAD_OP_ON_WN_PROTO') {
        return false;
      }
      return true;
    }
    scope = Object.getPrototypeOf(scope);
  }

  if (!propDesc) {
    return false;
  }

  if (!propDesc.get) {
    return false;
  }

  // The property is safe if 'get' isn't a function or if the function has a
  // prototype (in which case it's native)
  return typeof propDesc.get !== 'function' || 'prototype' in propDesc.get;
};

JavascriptType.prototype.name = 'javascript';

exports.items = [ JavascriptType ];
<|endoftext|>