Compare commits

...

27 Commits

Author SHA1 Message Date
Chakib Benziane a2f6528268 inject $injector to window 9 years ago
Richard Littauer db89db9431 Added the icon for chrome://extensions 9 years ago
Brian Ford 154772b7f4 style(json-tree.spec): add missing semicolon 10 years ago
Brian Ford f83c7f5ca6 style(inspectedApp): remove todo comment 10 years ago
Brian Ford a219e1cdd3 feat(pageAction): show page action when instrumentation is active
Closes #199
10 years ago
Brian Ford 68b72bfa6f fix(background): remove data for closed tabs 10 years ago
Brian Ford 0cf05a7588 fix(background): hydrate inspected app data on devtools connect 10 years ago
Brian Ford f2a9044e3b fix(panel): handle refresh better 10 years ago
Brian Ford ca0dc695e5 style(scopeTree): fix deep indentation 10 years ago
Brian Ford fce0052322 fix(inspectedApp): handle refresh better 10 years ago
Brian Ford 287074b73e fix(background): handle refresh better 10 years ago
Brian Ford bd32b00172 style(scopeTree): fix wrapping 10 years ago
Brian Ford e902ecae96 docs(readme): add travis badge 10 years ago
Brian Ford 8c67a3e77e test(travis): run unit tests on travis + saucelabs 10 years ago
Brian Ford d7008a1368 test(inspectedApp): add tests for messaging 10 years ago
Brian Ford d3e7bd6ec4 v0.7.4 10 years ago
Brian Ford 2d0e8abff0 docs(readme): link to instructions on installing from src 10 years ago
Brian Ford 60bdec9949 docs(contributing): update build and release docs 10 years ago
Brian Ford bd2ebbfd39 fix(sidebarPane): shorten title
Closes #75
10 years ago
Brian Ford bec405c8c7 docs(readme): explain how to install previous versions
Closes #191
10 years ago
Brian Ford 010d8b573b v0.7.3 10 years ago
Brian Ford 738ba58e77 fix(enableInstrumentation): sync instrumentation status 10 years ago
Brian Ford f745b92053 fix(panel/hints): fix category name 10 years ago
Brian Ford 5cee2720b7 v0.7.2 10 years ago
Brian Ford 5fd469035a fix(background): flush buffer on devtools connect 10 years ago
Brian Ford a521bb9ea3 fix(inspectedApp): ignore events with missing scope info 10 years ago
Brian Ford aa1c792d1a fix(batScopeTree): unfocus model editing on enter/exit keydown 10 years ago

@ -0,0 +1,21 @@
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

@ -6,14 +6,26 @@ This document describes how to contribute to Batarang
1. Clone the repository: `git clone git://github.com/angular/angularjs-batarang`
2. Install the npm dependencies `npm install`
3. Install the bower dependencies: `bower install`
4. Build the inject script: `npm run build`
5. Navigate to `chrome://chrome/extensions/` and enable Developer Mode.
6. Choose "Load unpacked extension"
7. In the dialog, open the directory you just cloned.
3. Build the inject script: `npm run build`
4. Navigate to `chrome://chrome/extensions/` and enable Developer Mode.
5. Choose "Load unpacked extension"
6. In the dialog, open the directory you just cloned.
## Running the tests
## Packaging a release
I (@btford) will do this periodically, but I'm adding these instructions here
for posterity.
1. Edit the version number in `manifest.json` and `package.json` with the new version.
2. Run `gulp zip`
3. `git add package.json manifest.json dist/`
4. `git commit -m "v1.2.3"`
5. `git tag v1.2.3`
6. `git push upstream master && git push upstream --tags`
7. Upload `batarang-v1.2.3.zip` to Web Store via the [Web Store Dashboard](https://chrome.google.com/webstore/developer/dashboard)
## Layout
The `panel` directory contains...

@ -1,8 +1,23 @@
# AngularJS Batarang
[![Build Status](https://travis-ci.org/angular/angularjs-batarang.svg)](https://travis-ci.org/angular/angularjs-batarang)
## Installing from the Web Store
https://chrome.google.com/webstore/detail/ighdmehidhipcmcojjgiloacoafjmpfk
## Installing Previous Versions
1. Download and extract one of the files from the [Batarang releases page on GitHub](https://github.com/angular/angularjs-batarang/releases)
1. Navigate to `chrome://chrome/extensions/` in Chrome
1. If you've installed Batarang from the web store, disable or remove that version
1. On the top right, check the checkbox for "Developer mode"
1. Click "Load unpacked extension..."
1. Select the directory where you extracted the extension
1. Close and re-open any inspected tabs
## Installing from Source
See the [instructions in the contributing guide](https://github.com/angular/angularjs-batarang/blob/master/CONTRIBUTING.md#installing-from-source)
## License
MIT

@ -1,23 +1,111 @@
var buffer = [];
function addToBuffer(message) {
buffer.push(message);
// 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);
}
}
chrome.runtime.onMessage.addListener(addToBuffer);
chrome.runtime.onConnect.addListener(function(devToolsPort) {
chrome.runtime.onMessage.removeListener(addToBuffer);
buffer.forEach(function(msg) {
devToolsPort.postMessage(msg);
});
devToolsPort.onMessage.addListener(function(inspectedTabId) {
chrome.tabs.onUpdated.addListener(function(tabId, changeInfo) {
if(tabId === inspectedTabId && changeInfo.status === 'loading') {
devToolsPort.postMessage('refresh');
function resetState(tabId) {
data[tabId] = {
hints: [],
scopes: {}
};
}
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.onConnect.addListener(function(devToolsPort) {
devToolsPort.onMessage.addListener(registerInspectedTabId);
function registerInspectedTabId(inspectedTabId) {
inspectedTabs[inspectedTabId] = devToolsPort;
if (!data[inspectedTabId]) {
resetState(inspectedTabId);
}
devToolsPort.postMessage({
event: 'hydrate',
data: data[inspectedTabId]
});
});
// context script > background
chrome.runtime.onMessage.addListener(function(msg, sender, sendResponse) {
devToolsPort.postMessage(msg);
});
devToolsPort.onDisconnect.addListener(function () {
delete inspectedTabs[inspectedTabId];
});
//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'
});
}

@ -0,0 +1,21 @@
/*
* 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'];
};

@ -0,0 +1,24 @@
/*
* 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,8 +6,11 @@ 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__: {}
@ -30,7 +33,7 @@ var getPanelContents = function () {
};
panels.elements.createSidebarPane(
"AngularJS Properties",
"$scope",
function (sidebar) {
panels.elements.onSelectionChanged.addListener(function updateElementProperties() {
sidebar.setExpression("(" + getPanelContents.toString() + ")()");

2259
dist/hint.js vendored

File diff suppressed because it is too large Load Diff

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.0 KiB

@ -3,6 +3,8 @@ 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,8 +2,11 @@
* 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) {
config.set({
var options = {
frameworks: ['browserify', 'jasmine'],
files: [
'node_modules/angular/angular.js',
@ -17,5 +20,12 @@ module.exports = function(config) {
'hint.js': [ 'browserify' ]
},
browsers: ['Chrome'],
});
};
if (process.argv.indexOf('--sauce') > -1) {
sauceConfig(options);
travisConfig(options);
}
config.set(options);
};

@ -1,6 +1,6 @@
{
"name": "AngularJS Batarang",
"version": "0.7.1",
"version": "0.7.4",
"description": "Extends the Developer Tools, adding tools for debugging and profiling AngularJS applications.",
"devtools_page": "devtoolsBackground.html",
"manifest_version": 2,
@ -8,6 +8,12 @@
"tabs",
"<all_urls>"
],
"icons": {
"16": "img/webstore-icon.png",
"48": "img/webstore-icon.png",
"128": "img/webstore-icon.png"
},
"content_scripts": [
{
"matches": ["<all_urls>"],
@ -15,11 +21,22 @@
"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"
],

@ -1,6 +1,6 @@
{
"name": "angularjs-batarang",
"version": "0.7.1",
"version": "0.7.4",
"description": "chrome extension for inspecting angular apps",
"main": "hint.js",
"devDependencies": {

@ -1,11 +1,10 @@
'use strict';
angular.module('batarang.inspected-app', []).
service('inspectedApp', ['$rootScope', inspectedAppService]);
service('inspectedApp', ['$rootScope', '$q', inspectedAppService]);
function inspectedAppService($rootScope) {
function inspectedAppService($rootScope, $q) {
// TODO: maybe state should live elsewhere
var scopes = this.scopes = {},
hints = this.hints = [];
@ -24,11 +23,23 @@ function inspectedAppService($rootScope) {
this.enableInstrumentation = function (setting) {
setting = !!setting;
chrome.devtools.inspectedWindow.eval(
"window.document.cookie = '__ngDebug=" + setting + ";';" +
"window.document.location.reload();"
"(function () {" +
"var prev = document.cookie.indexOf('__ngDebug=true') !== -1;" +
"if (prev !== " + setting + ") {" +
"window.document.cookie = '__ngDebug=" + setting + ";';" +
"window.document.location.reload();" +
"}" +
"}())"
);
};
this.getInstrumentationStatus = function () {
return $q(function(resolve, reject) {
chrome.devtools.inspectedWindow.eval(
"document.cookie.indexOf('__ngDebug=true') !== -1", resolve);
});
};
/*
* sets window.$scope to the scope of the given id
*/
@ -51,9 +62,12 @@ function inspectedAppService($rootScope) {
$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);
}
});
});
@ -64,22 +78,36 @@ function inspectedAppService($rootScope) {
function onHintMessage(hint) {
if (hint.message) {
hints.push(hint);
} else 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:new') {
addNewScope(hint);
} else if (hint.event === 'scope:link') {
scopes[hint.id].descriptor = hint.descriptor;
}
if (hint.event) {
} 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);
}
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);
}
}
function onRefreshMessage() {
clear(scopes);
hints.length = 0;
}
@ -94,4 +122,10 @@ function inspectedAppService($rootScope) {
}
}
function clear (obj) {
Object.keys(obj).forEach(function (key) {
delete obj[key];
});
}
}

@ -1,13 +1,45 @@
'use strict';
describe('inspectedApp', function() {
var inspectedApp;
var inspectedApp, port;
beforeEach(module('batarang.inspected-app'));
beforeEach(function() {
module('batarang.inspected-app')
window.chrome = createMockChrome();
inject(function(_inspectedApp_) {
inspectedApp = _inspectedApp_;
});
});
beforeEach(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');
}));
})
describe('watch', function () {
it('should call chrome devtools APIs', function() {
@ -23,26 +55,51 @@ 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 createMockChrome() {
return {
extension: {
connect: createMockSocket
function createListenerSpy(name) {
var symbol = '_' + name;
var listener = {
addListener: function (fn) {
listener[symbol].push(fn);
},
devtools: {
inspectedWindow: {
tabId: 1,
eval: jasmine.createSpy('inspectedWindowEval')
}
removeListener: function (fn) {
listener[symbol].splice(fn, 1);
},
trigger: function () {
var args = arguments;
listener[symbol].forEach(function (fn) {
fn.apply(listener, args);
});
}
};
}
function createListenerSpy(name) {
return {
addListener: jasmine.createSpy(name)
};
listener[symbol] = [];
return listener;
}
function createMockSocket() {
@ -51,4 +108,4 @@ function createMockSocket() {
postMessage: jasmine.createSpy('postMessageFunction'),
onDisconnect: createListenerSpy('onDisconnect')
};
}
}

@ -3,6 +3,9 @@ angular.module('batarang.json-tree', []).
var BAT_JSON_TREE_TEMPLATE = '<div class="properties-tree"></div>';
var ENTER_KEY = 13,
EXIT_KEY = 27;
var BAT_JSON_TREE_UNEDITABLE = [
'$id',
@ -39,6 +42,13 @@ 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.
@ -120,12 +130,23 @@ function batJsonTreeDirective() {
'</span>');
// TODO: test this
childElt.on('blur', function () {
childElt.on('keydown', function (ev) {
if (ev.keyCode === ENTER_KEY || ev.keyCode === EXIT_KEY) {
ev.preventDefault();
childElt[0].blur();
doAssign();
}
});
// TODO: test this
childElt.on('blur', doAssign);
function doAssign() {
scope.batAssign({
path: fullPath,
value: childElt.text()
});
});
}
}
}

@ -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,6 +117,9 @@
}
/* */
bat-scope-tree {
white-space: nowrap;
}
/*
bat-scope-tree .selected {

@ -9,76 +9,82 @@ function batScopeTreeDirective($compile) {
scope: {
batModel: '='
},
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);
});
}
link: batScopeTreeLink
};
scope.$on('scope:new', function (ev, data) {
renderScopeElement(data.child, data.parent);
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);
});
}
// 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('scope:new', function (ev, data) {
renderScopeElement(data.child, data.parent);
});
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');
});
});
// when a scope is linked, we can apply the descriptor info
scope.$on('scope:link', function (ev, data) {
renderScopeDescriptorElement(data.id, data.descriptor);
});
parentElt.append(elt);
}
scope.$on('refresh', function () {
map = {};
element.html('');
});
function renderScopeDescriptorElement (id, descriptor) {
var elt = map[id];
if (!elt) {
return;
}
elt.children().eq(1).children().eq(1).html(descriptor);
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');
// 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];
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;
}
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();
}
delete map[id];
});
}
}
@ -94,4 +100,3 @@ function newBranchElement(descriptor) {
'</span>',
'</ol>'].join(''));
}

@ -15,10 +15,9 @@ directive('batTabs', function ($compile, $templateCache, $http, inspectedApp) {
panes.push(pane);
};
$scope.$watch('enabled', function (newVal) {
if (newVal === !!newVal) {
inspectedApp.enableInstrumentation(newVal);
}
inspectedApp.getInstrumentationStatus().then(function (status) {
$scope.enabled = status;
$scope.$watch('enabled', inspectedApp.enableInstrumentation);
});
},
link: function (scope, element, attr) {

@ -11,7 +11,7 @@ function HintController($scope, inspectedApp) {
$scope.groupedHints = {};
newHints.forEach(function (hint) {
var moduleName = hint.module || 'Hints';
var category = hint.category || (moduleName + ' Stuff');
var category = hint.category || moduleName;
if (!$scope.groupedHints[moduleName]) {
$scope.groupedHints[moduleName] = {};
}

@ -16,13 +16,18 @@ 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);

@ -0,0 +1,29 @@
#!/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

@ -0,0 +1,15 @@
#!/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

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

@ -0,0 +1,8 @@
#!/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);

@ -0,0 +1,50 @@
#!/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 &

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

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