fix(ngScenario): Allow ngScenario to handle lazy-loaded and manually 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
This commit is contained in:
+5
-1
@@ -1408,8 +1408,12 @@ function bootstrap(element, modules, config) {
|
||||
forEach(extraModules, function(module) {
|
||||
modules.push(module);
|
||||
});
|
||||
doBootstrap();
|
||||
return doBootstrap();
|
||||
};
|
||||
|
||||
if (isFunction(angular.resumeDeferredBootstrap)) {
|
||||
angular.resumeDeferredBootstrap();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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
|
||||
@@ -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);
|
||||
|
||||
@@ -118,5 +138,5 @@ angular.scenario.Application.prototype.executeAction = function(action) {
|
||||
action.call(self, $window, $element);
|
||||
});
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
@@ -1081,6 +1081,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!';
|
||||
|
||||
@@ -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 = [];
|
||||
@@ -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);
|
||||
}));
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user