Compare commits

..

No commits in common. 'master' and 'v0.7.4' have entirely different histories.

@ -1,21 +0,0 @@
language: node_js
node_js:
- "0.10"
env:
global:
- BROWSER_PROVIDER_READY_FILE=/tmp/sauce-connect-ready
- LOGS_DIR=/tmp/angular-hint-build/logs
- SAUCE_USERNAME=angular-ci
- SAUCE_ACCESS_KEY=9b988f434ff8-fbca-8aa4-4ae3-35442987
install:
- mkdir -p $LOGS_DIR
- ./scripts/sauce_connect_setup.sh
- npm install
- npm install -g gulp
- npm install -g karma-cli
- ./scripts/wait_for_browser_provider.sh
script:
- ./scripts/test_on_sauce.sh

@ -1,5 +1,4 @@
# AngularJS Batarang
[![Build Status](https://travis-ci.org/angular/angularjs-batarang.svg)](https://travis-ci.org/angular/angularjs-batarang)
## Installing from the Web Store

@ -1,111 +1,24 @@
// tabId -> devtool port
var inspectedTabs = {};
// tabId -> buffered data
var data = {};
function bufferOrForward(message, sender) {
var tabId = sender.tab.id,
devToolsPort = inspectedTabs[tabId];
if (!data[tabId] || message === 'refresh') {
resetState(tabId);
// TODO: this is kind of a hack-y spot to put this
showPageAction(tabId);
}
// TODO: not sure how I feel about special-casing `refresh`
if (message !== 'refresh') {
message = JSON.parse(message);
}
bufferData(tabId, message);
if (devToolsPort) {
devToolsPort.postMessage(message);
}
}
function resetState(tabId) {
data[tabId] = {
hints: [],
scopes: {}
};
var buffer = [];
function addToBuffer(message) {
buffer.push(message);
}
function bufferData(tabId, message) {
var tabData = data[tabId],
scope;
if (message.message) {
return tabData.hints.push(message);
}
if (message.event) {
if (message.event === 'scope:new') {
tabData.scopes[message.child] = {
parent: message.parent,
children: [],
models: {}
};
if (tabData.scopes[message.parent]) {
tabData.scopes[message.parent].children.push(message.child);
}
} else if (message.id && (scope = tabData.scopes[message.id])) {
if (message.event === 'scope:destroy') {
if (scope.parent) {
scope.parent.children.splice(scope.parent.children.indexOf(child), 1);
}
delete scopes[message.id];
} else if (message.event === 'model:change') {
scope.models[message.path] = (typeof message.value === 'undefined') ?
undefined : JSON.parse(message.value);
} else if (message.event === 'scope:link') {
scope.descriptor = message.descriptor;
}
}
}
}
// context script > background
chrome.runtime.onMessage.addListener(bufferOrForward);
chrome.runtime.onMessage.addListener(addToBuffer);
chrome.runtime.onConnect.addListener(function(devToolsPort) {
chrome.runtime.onMessage.removeListener(addToBuffer);
buffer.forEach(function(msg) {
devToolsPort.postMessage(msg);
});
buffer = [];
devToolsPort.onMessage.addListener(registerInspectedTabId);
function registerInspectedTabId(inspectedTabId) {
inspectedTabs[inspectedTabId] = devToolsPort;
if (!data[inspectedTabId]) {
resetState(inspectedTabId);
}
devToolsPort.postMessage({
event: 'hydrate',
data: data[inspectedTabId]
});
devToolsPort.onDisconnect.addListener(function () {
delete inspectedTabs[inspectedTabId];
devToolsPort.onMessage.addListener(function(inspectedTabId) {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if(tabId === inspectedTabId && changeInfo.status === 'loading') {
devToolsPort.postMessage('refresh');
}
});
//devToolsPort.onMessage.removeListener(registerInspectedTabId);
}
});
chrome.tabs.onRemoved.addListener(function (tabId) {
if (data[tabId]) {
delete data[tabId];
}
});
function showPageAction(tabId) {
chrome.pageAction.show(tabId);
chrome.pageAction.setTitle({
tabId: tabId,
title: 'Batarang Active'
});
}
// context script > background
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
devToolsPort.postMessage(msg);
});
});

@ -1,21 +0,0 @@
/*
* karma.conf.js and karma.es5.conf.js optionally load this
*/
var CUSTOM_LAUNCHERS = {
'SL_Chrome': {
base: 'SauceLabs',
browserName: 'chrome',
version: '35'
}
};
module.exports = function(options) {
options.sauceLabs = {
testName: 'AngularJS Batarang Unit Tests',
startConnect: true
};
options.customLaunchers = CUSTOM_LAUNCHERS;
options.browsers = Object.keys(CUSTOM_LAUNCHERS);
options.reporters = ['dots', 'saucelabs'];
};

@ -1,24 +0,0 @@
/*
* karma.conf.js optionally loads this
*/
module.exports = function(options) {
if (!isTravis()) {
return;
} else if (!options.sauceLabs) {
throw new Error('This should be loaded after karma.sauce config');
}
options.sauceLabs.build = 'TRAVIS #' + process.env.TRAVIS_BUILD_NUMBER + ' (' + process.env.TRAVIS_BUILD_ID + ')';
options.sauceLabs.tunnelIdentifier = process.env.TRAVIS_JOB_NUMBER;
options.sauceLabs.startConnect = false;
// TODO(vojta): remove once SauceLabs supports websockets.
// This speeds up the capturing a bit, as browsers don't even try to use websocket.
options.transports = ['xhr-polling'];
options.singleRun = true;
};
function isTravis() {
return !!process.env.TRAVIS;
}

@ -6,11 +6,8 @@ var getPanelContents = function () {
if (window.angular && $0) {
//TODO: can we move this scope export into updateElementProperties
var scope = window.angular.element($0).scope();
var injector = window.angular.element($0).injector();
// Export $scope to the console
window.$scope = scope;
// Export $injector to the console
window.$injector = injector;
return (function (scope) {
var panelContents = {
__private__: {}

2491
dist/hint.js vendored

File diff suppressed because it is too large Load Diff

Binary file not shown.

Before

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 6.0 KiB

@ -3,8 +3,6 @@ if (document.cookie.indexOf('__ngDebug=true') != -1) {
}
function bootstrapHint () {
chrome.extension.sendMessage('refresh');
var html = document.getElementsByTagName('html')[0];
var eventProxyElement = document.createElement('div');

@ -2,11 +2,8 @@
* This karma conf tests just the panel app
*/
var sauceConfig = require('./config/karma.sauce.conf');
var travisConfig = require('./config/karma.travis.conf');
module.exports = function(config) {
var options = {
config.set({
frameworks: ['browserify', 'jasmine'],
files: [
'node_modules/angular/angular.js',
@ -20,12 +17,5 @@ module.exports = function(config) {
'hint.js': [ 'browserify' ]
},
browsers: ['Chrome'],
};
if (process.argv.indexOf('--sauce') > -1) {
sauceConfig(options);
travisConfig(options);
}
config.set(options);
});
};

@ -8,12 +8,6 @@
"tabs",
"<all_urls>"
],
"icons": {
"16": "img/webstore-icon.png",
"48": "img/webstore-icon.png",
"128": "img/webstore-icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
@ -21,22 +15,11 @@
"run_at": "document_start"
}
],
"background": {
"scripts": [
"background.js"
]
},
"page_action": {
"default_icon": {
"19": "img/icon19.png",
"38": "img/icon38.png"
},
"default_title": "AngularJS Super-Powered"
},
"web_accessible_resources": [
"dist/hint.js"
],

@ -5,6 +5,7 @@ angular.module('batarang.inspected-app', []).
function inspectedAppService($rootScope, $q) {
// TODO: maybe state should live elsewhere
var scopes = this.scopes = {},
hints = this.hints = [];
@ -62,12 +63,9 @@ function inspectedAppService($rootScope, $q) {
$rootScope.$applyAsync(function () {
if (msg === 'refresh') {
onRefreshMessage();
$rootScope.$broadcast('refresh');
} else if (typeof msg === 'string') {
var hint = JSON.parse(msg);
onHintMessage(hint);
} else if (typeof msg === 'object') {
onHintMessage(msg);
}
});
});
@ -79,27 +77,16 @@ function inspectedAppService($rootScope, $q) {
if (hint.message) {
hints.push(hint);
} else if (hint.event) {
if (hint.event === 'hydrate') {
Object.keys(hint.data.scopes).forEach(function (scopeId) {
scopes[scopeId] = hint.data.scopes[scopeId];
});
hint.data.hints.forEach(function (hint) {
hints.push(hint);
});
} else if (hint.event === 'scope:new') {
addNewScope(hint);
} else if (hint.id && scopes[hint.id]) {
var scope = scopes[hint.id];
if (hint.event === 'scope:destroy') {
if (scope.parent) {
scope.parent.children.splice(scope.parent.children.indexOf(child), 1);
if (hint.id) {
if (hint.event === 'scope:new') {
addNewScope(hint);
} else if (scopes[hint.id]) {
if (hint.event === 'model:change') {
scopes[hint.id].models[hint.path] = (typeof hint.value === 'undefined') ?
undefined : JSON.parse(hint.value);
} else if (hint.event === 'scope:link') {
scopes[hint.id].descriptor = hint.descriptor;
}
delete scopes[hint.id];
} else if (hint.event === 'model:change') {
scope.models[hint.path] = (typeof hint.value === 'undefined') ?
undefined : JSON.parse(hint.value);
} else if (hint.event === 'scope:link') {
scope.descriptor = hint.descriptor;
}
}
$rootScope.$broadcast(hint.event, hint);
@ -107,7 +94,6 @@ function inspectedAppService($rootScope, $q) {
}
function onRefreshMessage() {
clear(scopes);
hints.length = 0;
}
@ -122,10 +108,4 @@ function inspectedAppService($rootScope, $q) {
}
}
function clear (obj) {
Object.keys(obj).forEach(function (key) {
delete obj[key];
});
}
}

@ -1,45 +1,13 @@
'use strict';
describe('inspectedApp', function() {
var inspectedApp, port;
var inspectedApp;
beforeEach(module('batarang.inspected-app'));
beforeEach(function() {
module('batarang.inspected-app')
window.chrome = createMockChrome();
inject(function(_inspectedApp_) {
inspectedApp = _inspectedApp_;
});
});
describe('when instantiated', function () {
it('should post a message with the inspected tabId', function () {
expect(port.postMessage).
toHaveBeenCalledWith(window.chrome.devtools.inspectedWindow.tabId);
});
});
describe('messaging', function () {
it('should track hints', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ message: 'hi' }));
$browser.defer.flush();
expect(inspectedApp.hints).toEqual([{message: 'hi'}]);
}));
it('should track new scopes', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ event: 'scope:new', child: 1 }));
$browser.defer.flush();
expect(inspectedApp.scopes).toEqual({ 1: { parent: undefined, children: [], models: {} } });
}));
it('should track updates to scope descriptors', inject(function ($browser) {
port.onMessage.trigger(JSON.stringify({ event: 'scope:new', child: 1 }));
port.onMessage.trigger(JSON.stringify({ event: 'scope:link', id: 1, descriptor: 'pasta' }));
$browser.defer.flush();
expect(inspectedApp.scopes[1].descriptor).toBe('pasta');
}));
})
beforeEach(inject(function(_inspectedApp_) {
inspectedApp = _inspectedApp_;
}));
describe('watch', function () {
it('should call chrome devtools APIs', function() {
@ -55,51 +23,26 @@ describe('inspectedApp', function() {
});
});
describe('inspectScope', function () {
it('should call chrome devtools APIs', function() {
inspectedApp.inspectScope(2);
expect(chrome.devtools.inspectedWindow.eval).toHaveBeenCalledWith('angular.hint.inspectScope(2,"")');
});
});
function createMockChrome() {
return {
extension: {
connect: function () {
return port = createMockSocket();
}
},
devtools: {
inspectedWindow: {
tabId: 1,
eval: jasmine.createSpy('inspectedWindowEval')
}
}
};
}
});
function createListenerSpy(name) {
var symbol = '_' + name;
var listener = {
addListener: function (fn) {
listener[symbol].push(fn);
},
removeListener: function (fn) {
listener[symbol].splice(fn, 1);
function createMockChrome() {
return {
extension: {
connect: createMockSocket
},
trigger: function () {
var args = arguments;
listener[symbol].forEach(function (fn) {
fn.apply(listener, args);
});
devtools: {
inspectedWindow: {
tabId: 1,
eval: jasmine.createSpy('inspectedWindowEval')
}
}
};
}
listener[symbol] = [];
return listener;
function createListenerSpy(name) {
return {
addListener: jasmine.createSpy(name)
};
}
function createMockSocket() {
@ -108,4 +51,4 @@ function createMockSocket() {
postMessage: jasmine.createSpy('postMessageFunction'),
onDisconnect: createListenerSpy('onDisconnect')
};
}
}

@ -42,13 +42,6 @@ function batJsonTreeDirective() {
scope.$watch('batModel', function (val) {
if (!val) {
root = angular.element(BAT_JSON_TREE_TEMPLATE);
element.html('');
element.append(root);
branches = {
'': root
};
return;
}
Object.

@ -17,7 +17,7 @@ describe('batJsonTree', function () {
it('should render a simple model', function () {
$rootScope.data = {
'': { '$id': 1 }
};
}
compileTree();
expect(element.text()).toBe('$id: 1');
});

@ -117,9 +117,6 @@
}
/* */
bat-scope-tree {
white-space: nowrap;
}
/*
bat-scope-tree .selected {

@ -9,82 +9,76 @@ function batScopeTreeDirective($compile) {
scope: {
batModel: '='
},
link: batScopeTreeLink
};
link: function (scope, element, attrs) {
// scope.$id > DOM node
var map = {};
var selectedElt = angular.element();
// init
var scopes = scope.batModel;
if (scopes) {
Object.keys(scopes).forEach(function (scopeId) {
var parentId = scopes[scopeId].parent;
renderScopeElement(scopeId, parentId);
renderScopeDescriptorElement(scopeId, scopes[scopeId].descriptor);
});
}
function batScopeTreeLink(scope, element, attrs) {
// scope.$id > DOM node
var map = {};
var selectedElt = angular.element();
// init
var scopes = scope.batModel;
if (scopes) {
Object.keys(scopes).forEach(function (scopeId) {
var parentId = scopes[scopeId].parent;
renderScopeElement(scopeId, parentId);
renderScopeDescriptorElement(scopeId, scopes[scopeId].descriptor);
scope.$on('scope:new', function (ev, data) {
renderScopeElement(data.child, data.parent);
});
}
scope.$on('scope:new', function (ev, data) {
renderScopeElement(data.child, data.parent);
});
// when a scope is linked, we can apply the descriptor info
scope.$on('scope:link', function (ev, data) {
renderScopeDescriptorElement(data.id, data.descriptor);
});
scope.$on('refresh', function () {
map = {};
element.html('');
});
function renderScopeElement (id, parentId) {
if (map[id]) {
return;
}
var elt = map[id] = newBranchElement(id);
var parentElt = map[parentId] || element;
// when a scope is linked, we can apply the descriptor info
scope.$on('scope:link', function (ev, data) {
renderScopeDescriptorElement(data.id, data.descriptor);
});
elt.children().eq(1).on('click', function () {
scope.$apply(function () {
scope.$emit('inspected-scope:change', {
id: id
function renderScopeElement (id, parentId) {
if (map[id]) {
return;
}
var elt = map[id] = newBranchElement(id);
var parentElt = map[parentId] || element;
elt.children().eq(1).on('click', function () {
scope.$apply(function () {
scope.$emit('inspected-scope:change', {
id: id
});
selectedElt.children().eq(0).removeClass('selected');
selectedElt.children().eq(1).removeClass('selected');
selectedElt = elt;
selectedElt.children().eq(0).addClass('selected');
selectedElt.children().eq(1).addClass('selected');
});
selectedElt.children().eq(0).removeClass('selected');
selectedElt.children().eq(1).removeClass('selected');
selectedElt = elt;
selectedElt.children().eq(0).addClass('selected');
selectedElt.children().eq(1).addClass('selected');
});
});
parentElt.append(elt);
}
function renderScopeDescriptorElement (id, descriptor) {
var elt = map[id];
if (!elt) {
return;
parentElt.append(elt);
}
elt.children().eq(1).children().eq(1).html(descriptor);
}
// TODO: also destroy elements corresponding to descendant scopes
scope.$on('scope:destroy', function (ev, data) {
var id = data.id;
var elt = map[id];
if (elt) {
elt.remove();
function renderScopeDescriptorElement (id, descriptor) {
var elt = map[id];
if (!elt) {
return;
}
elt.children().eq(1).children().eq(1).html(descriptor);
}
delete map[id];
});
}
// TODO: also destroy elements corresponding to descendant scopes
scope.$on('scope:destroy', function (ev, data) {
var id = data.id;
var elt = map[id];
if (elt) {
elt.remove();
}
delete map[id];
});
}
};
}
@ -100,3 +94,4 @@ function newBranchElement(descriptor) {
'</span>',
'</ol>'].join(''));
}

@ -16,18 +16,13 @@ function ScopesController($scope, inspectedApp) {
$scope.inspectedScope = null;
$scope.$on('refresh', function () {
$scope.inspectedScope = null;
});
// expand models the fist time we inspect a scope
var cancelWatch = $scope.$watch('inspectedScope', function (newScope) {
if (newScope) {
$scope.modelsExpanded = true;
cancelWatch();
}
});
})
$scope.$on('inspected-scope:change', function (ev, data) {
inspectScope(data.id);

@ -1,29 +0,0 @@
#!/bin/bash
#
# Switch a dependency to git repo.
# Remove the NPM package and link it to a repo in parent directory.
DEP_NAME=$1
SCRIPT_DIR=$(dirname $0)
cd $SCRIPT_DIR/..
if [ -L ./node_modules/$DEP_NAME ]; then
echo "$DEP_NAME is already a symlink"
else
PKG_INFO=($($SCRIPT_DIR/read-pkg-url.js ./node_modules/$DEP_NAME/package.json))
URL=${PKG_INFO[0]}
DIR_NAME=${PKG_INFO[1]}
echo "Switching $DEP_NAME"
rm -rf ./node_modules/$DEP_NAME
if [ -d ../$DIR_NAME ]; then
echo "Repo already cloned in ../$DIR_NAME"
else
cd ..
git clone $URL $DIR_NAME
cd -
fi
echo "Link ./node_modules/$DEP_NAME -> ../$DIR_NAME"
ln -s ../../$DIR_NAME ./node_modules/$DEP_NAME
fi

@ -1,15 +0,0 @@
#!/bin/bash
#
# Switch a dependency to NPM.
# Remove the symlink and install from NPM.
DEP_NAME=$1
SCRIPT_DIR=$(dirname $0)
cd $SCRIPT_DIR/..
if [ ! -L ./node_modules/$DEP_NAME ]; then
echo "$DEP_NAME is not a symlink"
else
rm ./node_modules/$DEP_NAME
npm install $DEP_NAME
fi

@ -1,11 +0,0 @@
#!/bin/bash
LOG_FILES=$LOGS_DIR/*
for FILE in $LOG_FILES; do
echo -e "\n\n\n"
echo "=================================================================="
echo " $FILE"
echo "=================================================================="
cat $FILE
done

@ -1,8 +0,0 @@
#!/usr/bin/env node
var fs = require('fs');
var pkg = JSON.parse(fs.readFileSync(process.argv[2]));
var url = pkg.repository.url;
var dirname = url.replace(/^.*\//, '').replace(/\.git$/, '');
console.log(url + ' ' + dirname);

@ -1,50 +0,0 @@
#!/bin/bash
set -e
# Setup and start Sauce Connect for your TravisCI build
# This script requires your .travis.yml to include the following two private env variables:
# SAUCE_USERNAME
# SAUCE_ACCESS_KEY
# Follow the steps at https://saucelabs.com/opensource/travis to set that up.
#
# Curl and run this script as part of your .travis.yml before_script section:
# before_script:
# - curl https://gist.github.com/santiycr/5139565/raw/sauce_connect_setup.sh | bash
CONNECT_URL="https://saucelabs.com/downloads/sc-4.3-linux.tar.gz"
CONNECT_DIR="/tmp/sauce-connect-$RANDOM"
CONNECT_DOWNLOAD="sc-latest-linux.tar.gz"
CONNECT_LOG="$LOGS_DIR/sauce-connect"
CONNECT_STDOUT="$LOGS_DIR/sauce-connect.stdout"
CONNECT_STDERR="$LOGS_DIR/sauce-connect.stderr"
# Get Connect and start it
mkdir -p $CONNECT_DIR
cd $CONNECT_DIR
curl $CONNECT_URL -o $CONNECT_DOWNLOAD 2> /dev/null 1> /dev/null
mkdir sauce-connect
tar --extract --file=$CONNECT_DOWNLOAD --strip-components=1 --directory=sauce-connect > /dev/null
rm $CONNECT_DOWNLOAD
SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
ARGS=""
# Set tunnel-id only on Travis, to make local testing easier.
if [ ! -z "$TRAVIS_JOB_NUMBER" ]; then
ARGS="$ARGS --tunnel-identifier $TRAVIS_JOB_NUMBER"
fi
if [ ! -z "$BROWSER_PROVIDER_READY_FILE" ]; then
ARGS="$ARGS --readyfile $BROWSER_PROVIDER_READY_FILE"
fi
echo "Starting Sauce Connect in the background, logging into:"
echo " $CONNECT_LOG"
echo " $CONNECT_STDOUT"
echo " $CONNECT_STDERR"
sauce-connect/bin/sc -u $SAUCE_USERNAME -k $SAUCE_ACCESS_KEY -v $ARGS \
--logfile $CONNECT_LOG 2> $CONNECT_STDERR 1> $CONNECT_STDOUT &

@ -1,8 +0,0 @@
#! /bin/bash
SCRIPT_DIR=$(dirname $0)
cd $SCRIPT_DIR/..
SAUCE_ACCESS_KEY=`echo $SAUCE_ACCESS_KEY | rev`
gulp
karma start --sauce

@ -1,7 +0,0 @@
#!/bin/bash
# Wait for Connect to be ready before exiting
while [ ! -f $BROWSER_PROVIDER_READY_FILE ]; do
sleep .5
done