Skip to content
This repository has been archived by the owner on Apr 12, 2024. It is now read-only.

Commit

Permalink
fix(ngScenario): Allow ngScenario to handle lazy-loaded and manually …
Browse files Browse the repository at this point in the history
…bootstrapped applications

I know protractor is preferred, and ngScenario is only in maintenance mode. But, we are limited to
ngScenario based on the devices/browsers we are targeting (no web-driver available). So, we need
to address the bug where ngScenario does not work with manual bootstrap and also has issues if
angular.resumeBootstrap is not yet defined (race condition when lazy-loading).

Closes #10723
  • Loading branch information
marcfallows authored and petebacondarwin committed Jan 29, 2015
1 parent 8c46919 commit c69caa7
Show file tree
Hide file tree
Showing 4 changed files with 152 additions and 11 deletions.
6 changes: 5 additions & 1 deletion src/Angular.js
Original file line number Diff line number Diff line change
Expand Up @@ -1403,8 +1403,12 @@ function bootstrap(element, modules, config) {
forEach(extraModules, function(module) {
modules.push(module);
});
doBootstrap();
return doBootstrap();
};

if (isFunction(angular.resumeDeferredBootstrap)) {
angular.resumeDeferredBootstrap();
}
}

/**
Expand Down
40 changes: 30 additions & 10 deletions src/ngScenario/Application.js
Original file line number Diff line number Diff line change
Expand Up @@ -68,19 +68,31 @@ angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorF
try {
var $window = self.getWindow_();

if ($window.angular) {
// Disable animations
$window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
if (!$window.angular) {
self.executeAction(loadFn);
return;
}

if (!$window.angular.resumeBootstrap) {
$window.angular.resumeDeferredBootstrap = resumeDeferredBootstrap;
} else {
resumeDeferredBootstrap();
}

self.executeAction(loadFn);
} catch (e) {
errorFn(e);
}

function resumeDeferredBootstrap() {
// Disable animations
var $injector = $window.angular.resumeBootstrap([['$provide', function($provide) {
return ['$animate', function($animate) {
$animate.enabled(false);
}];
}]]);
self.rootElement = $injector.get('$rootElement')[0];
self.executeAction(loadFn);
}
}).attr('src', url);

// for IE compatibility set the name *after* setting the frame url
Expand All @@ -105,7 +117,15 @@ angular.scenario.Application.prototype.executeAction = function(action) {
if (!$window.angular) {
return action.call(this, $window, _jQuery($window.document));
}
angularInit($window.document, function(element) {

if (!!this.rootElement) {
executeWithElement(this.rootElement);
}
else {
angularInit($window.document, angular.bind(this, executeWithElement));
}

function executeWithElement(element) {
var $injector = $window.angular.element(element).injector();
var $element = _jQuery(element);

Expand All @@ -118,5 +138,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
action.call(self, $window, $element);
});
});
});
}
};
20 changes: 20 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -1074,6 +1074,26 @@ describe('angular', function() {
window.name = originalName;
});

it('should provide injector for deferred bootstrap', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';

injector = angular.bootstrap(element);
expect(injector).toBeUndefined();

injector = angular.resumeBootstrap();
expect(injector).toBeDefined();
});

it('should resume deferred bootstrap, if defined', function() {
var injector;
window.name = 'NG_DEFER_BOOTSTRAP!';

angular.resumeDeferredBootstrap = noop;
var spy = spyOn(angular, "resumeDeferredBootstrap");
injector = angular.bootstrap(element);
expect(spy).toHaveBeenCalled();
});

it('should wait for extra modules', function() {
window.name = 'NG_DEFER_BOOTSTRAP!';
Expand Down
97 changes: 97 additions & 0 deletions test/ngScenario/ApplicationSpec.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,75 @@ describe('angular.scenario.Application', function() {
expect(called).toBeTruthy();
});

it('should set rootElement when navigateTo instigates bootstrap', inject(function($injector, $browser) {
var called;
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {},
resumeBootstrap: noop
}
};
jqLite(testWindow.document).data('$injector', $injector);
var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);

var injectorGet = $injector.get;
spyOn($injector, 'get').andCallFake(function(name) {
switch (name) {
case "$rootElement": return jqLite(testWindow.document);
default: return injectorGet(name);
}
});

app.getWindow_ = function() {
return testWindow;
};
app.navigateTo('http://localhost/', noop);
callLoadHandlers(app);
expect(app.rootElement).toBe(testWindow.document);
expect(resumeBootstrapSpy).toHaveBeenCalled();
dealoc(testWindow.document);
}));

it('should set setup resumeDeferredBootstrap if resumeBootstrap is not yet defined', inject(function($injector, $browser) {
var called;
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {},
resumeBootstrap: null
}
};
jqLite(testWindow.document).data('$injector', $injector);

var injectorGet = $injector.get;
var injectorSpy = spyOn($injector, 'get').andCallFake(function(name) {
switch (name) {
case "$rootElement": return jqLite(testWindow.document);
default: return injectorGet(name);
}
});

app.getWindow_ = function() {
return testWindow;
};
app.navigateTo('http://localhost/', noop);
expect(testWindow.angular.resumeDeferredBootstrap).toBeUndefined();
callLoadHandlers(app);
expect(testWindow.angular.resumeDeferredBootstrap).toBeDefined();
expect(app.rootElement).toBeUndefined;
expect(injectorSpy).not.toHaveBeenCalled();

var resumeBootstrapSpy = spyOn(testWindow.angular, 'resumeBootstrap').andReturn($injector);
testWindow.angular.resumeDeferredBootstrap();
expect(app.rootElement).toBe(testWindow.document);
expect(resumeBootstrapSpy).toHaveBeenCalled();
expect(injectorSpy).toHaveBeenCalledWith("$rootElement");
dealoc(testWindow.document);
}));

it('should wait for pending requests in executeAction', inject(function($injector, $browser) {
var called, polled;
var handlers = [];
Expand All @@ -144,4 +213,32 @@ describe('angular.scenario.Application', function() {
handlers[0]();
dealoc(testWindow.document);
}));

it('should allow explicit rootElement', inject(function($injector, $browser) {
var called, polled;
var handlers = [];
var testWindow = {
document: jqLite('<div class="test-foo"></div>')[0],
angular: {
element: jqLite,
service: {}
}
};
$browser.notifyWhenNoOutstandingRequests = function(fn) {
handlers.push(fn);
};
app.rootElement = testWindow.document;
jqLite(testWindow.document).data('$injector', $injector);
app.getWindow_ = function() {
return testWindow;
};
app.executeAction(function($window, $document) {
expect($window).toEqual(testWindow);
expect($document).toBeDefined();
expect($document[0].className).toEqual('test-foo');
});
expect(handlers.length).toEqual(1);
handlers[0]();
dealoc(testWindow.document);
}));
});

0 comments on commit c69caa7

Please sign in to comment.