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

fix(ngScenario): Allow ngScenario to handle lazy-loaded and manually bootstrapped applications #10723

Closed
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 5 additions & 1 deletion src/Angular.js
Original file line number Diff line number Diff line change
@@ -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();
}
}

/**
40 changes: 30 additions & 10 deletions src/ngScenario/Application.js
Original file line number Diff line number Diff line change
@@ -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);
});
});
});
}
};
20 changes: 20 additions & 0 deletions test/AngularSpec.js
Original file line number Diff line number Diff line change
@@ -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!';
97 changes: 97 additions & 0 deletions test/ngScenario/ApplicationSpec.js
Original file line number Diff line number Diff line change
@@ -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);
}));
});