From e5fb92978f310d690e394bac2c09beeae4a56e03 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Mon, 4 Dec 2017 22:12:53 -0800 Subject: [PATCH 1/8] revert: fix($rootScope): fix potential memory leak when removing scope listeners This reverts commit 817ac56719505680ac4c9997972e8f39eb40a6d0. --- docs/content/error/$rootScope/inevt.ngdoc | 22 ---- src/ng/rootScope.js | 79 ++++++++------- test/ng/rootScopeSpec.js | 118 +--------------------- 3 files changed, 47 insertions(+), 172 deletions(-) delete mode 100644 docs/content/error/$rootScope/inevt.ngdoc diff --git a/docs/content/error/$rootScope/inevt.ngdoc b/docs/content/error/$rootScope/inevt.ngdoc deleted file mode 100644 index a06eeba18627..000000000000 --- a/docs/content/error/$rootScope/inevt.ngdoc +++ /dev/null @@ -1,22 +0,0 @@ -@ngdoc error -@name $rootScope:inevt -@fullName Recursive $emit/$broadcast event -@description - -This error occurs when the an event is `$emit`ed or `$broadcast`ed recursively on a scope. - -For example, when an event listener fires the same event being listened to. - -``` -$scope.$on('foo', function() { - $scope.$emit('foo'); -}); -``` - -Or when a parent element causes indirect recursion. - -``` -$scope.$on('foo', function() { - $rootScope.$broadcast('foo'); -}); -``` \ No newline at end of file diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 964ba7bceaf5..07c692e9101b 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -1271,14 +1271,10 @@ function $RootScopeProvider() { var self = this; return function() { - var index = arrayRemove(namedListeners, listener); - if (index >= 0) { + var indexOfListener = namedListeners.indexOf(listener); + if (indexOfListener !== -1) { + namedListeners[indexOfListener] = null; decrementListenerCount(self, 1, name); - // We are removing a listener while iterating over the list of listeners. - // Update the current $$index if necessary to ensure no listener is skipped. - if (index <= namedListeners.$$index) { - namedListeners.$$index--; - } } }; }, @@ -1307,7 +1303,9 @@ function $RootScopeProvider() { * @return {Object} Event object (see {@link ng.$rootScope.Scope#$on}). */ $emit: function(name, args) { - var scope = this, + var empty = [], + namedListeners, + scope = this, stopPropagation = false, event = { name: name, @@ -1318,11 +1316,28 @@ function $RootScopeProvider() { }, defaultPrevented: false }, - listenerArgs = concat([event], arguments, 1); + listenerArgs = concat([event], arguments, 1), + i, length; do { - invokeListeners(scope, event, listenerArgs, name); - + namedListeners = scope.$$listeners[name] || empty; + event.currentScope = scope; + for (i = 0, length = namedListeners.length; i < length; i++) { + + // if listeners were deregistered, defragment the array + if (!namedListeners[i]) { + namedListeners.splice(i, 1); + i--; + length--; + continue; + } + try { + //allow all listeners attached to the current scope to run + namedListeners[i].apply(null, listenerArgs); + } catch (e) { + $exceptionHandler(e); + } + } //if any listener on the current scope stops propagation, prevent bubbling if (stopPropagation) { break; @@ -1373,11 +1388,28 @@ function $RootScopeProvider() { if (!target.$$listenerCount[name]) return event; - var listenerArgs = concat([event], arguments, 1); + var listenerArgs = concat([event], arguments, 1), + listeners, i, length; //down while you can, then up and next sibling or up and next sibling until back at root while ((current = next)) { - invokeListeners(current, event, listenerArgs, name); + event.currentScope = current; + listeners = current.$$listeners[name] || []; + for (i = 0, length = listeners.length; i < length; i++) { + // if listeners were deregistered, defragment the array + if (!listeners[i]) { + listeners.splice(i, 1); + i--; + length--; + continue; + } + + try { + listeners[i].apply(null, listenerArgs); + } catch (e) { + $exceptionHandler(e); + } + } // Insanity Warning: scope depth-first traversal // yes, this code is a bit crazy, but it works and we have tests to prove it! @@ -1408,27 +1440,6 @@ function $RootScopeProvider() { return $rootScope; - function invokeListeners(scope, event, listenerArgs, name) { - var listeners = scope.$$listeners[name]; - if (listeners) { - if (listeners.$$index !== undefined) { - throw $rootScopeMinErr('inevt', '{0} already $emit/$broadcast-ing on scope ({1})', name, scope.$id); - } - event.currentScope = scope; - try { - for (listeners.$$index = 0; listeners.$$index < listeners.length; listeners.$$index++) { - try { - //allow all listeners attached to the current scope to run - listeners[listeners.$$index].apply(null, listenerArgs); - } catch (e) { - $exceptionHandler(e); - } - } - } finally { - listeners.$$index = undefined; - } - } - } function beginPhase(phase) { if ($rootScope.$$phase) { diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 90a79625e62c..6f683221742e 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -2438,19 +2438,6 @@ describe('Scope', function() { })); - // See issue https://github.com/angular/angular.js/issues/16135 - it('should deallocate the listener array entry', inject(function($rootScope) { - var remove1 = $rootScope.$on('abc', noop); - $rootScope.$on('abc', noop); - - expect($rootScope.$$listeners['abc'].length).toBe(2); - - remove1(); - - expect($rootScope.$$listeners['abc'].length).toBe(1); - })); - - it('should call next listener after removing the current listener via its own handler', inject(function($rootScope) { var listener1 = jasmine.createSpy('listener1').and.callFake(function() { remove1(); }); var remove1 = $rootScope.$on('abc', listener1); @@ -2583,107 +2570,6 @@ describe('Scope', function() { expect($rootScope.$$listenerCount).toEqual({abc: 1}); expect(child.$$listenerCount).toEqual({abc: 1}); })); - - - it('should throw on recursive $broadcast', inject(function($rootScope) { - $rootScope.$on('e', function() { $rootScope.$broadcast('e'); }); - - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should throw on nested recursive $broadcast', inject(function($rootScope) { - $rootScope.$on('e2', function() { $rootScope.$broadcast('e'); }); - $rootScope.$on('e', function() { $rootScope.$broadcast('e2'); }); - - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should throw on recursive $emit', inject(function($rootScope) { - $rootScope.$on('e', function() { $rootScope.$emit('e'); }); - - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should throw on nested recursive $emit', inject(function($rootScope) { - $rootScope.$on('e2', function() { $rootScope.$emit('e'); }); - $rootScope.$on('e', function() { $rootScope.$emit('e2'); }); - - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should throw on recursive $broadcast on child listener', inject(function($rootScope) { - var child = $rootScope.$new(); - child.$on('e', function() { $rootScope.$broadcast('e'); }); - - expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)'); - expect(function() { child.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)'); - })); - - - it('should throw on nested recursive $broadcast on child listener', inject(function($rootScope) { - var child = $rootScope.$new(); - child.$on('e2', function() { $rootScope.$broadcast('e'); }); - child.$on('e', function() { $rootScope.$broadcast('e2'); }); - - expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)'); - expect(function() { child.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (2)'); - })); - - - it('should throw on recursive $emit parent listener', inject(function($rootScope) { - var child = $rootScope.$new(); - $rootScope.$on('e', function() { child.$emit('e'); }); - - expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should throw on nested recursive $emit parent listener', inject(function($rootScope) { - var child = $rootScope.$new(); - $rootScope.$on('e2', function() { child.$emit('e'); }); - $rootScope.$on('e', function() { child.$emit('e2'); }); - - expect(function() { child.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$emit('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - expect(function() { $rootScope.$broadcast('e', 5); }).toThrowMinErr('$rootScope', 'inevt', 'e already $emit/$broadcast-ing on scope (1)'); - })); - - - it('should clear recursive state of $broadcast if $exceptionHandler rethrows', function() { - module(function($exceptionHandlerProvider) { - $exceptionHandlerProvider.mode('rethrow'); - }); - inject(function($rootScope) { - var throwingListener = jasmine.createSpy('thrower').and.callFake(function() { - throw new Error('Listener Error!'); - }); - var secondListener = jasmine.createSpy('second'); - - $rootScope.$on('e', throwingListener); - $rootScope.$on('e', secondListener); - - expect(function() { $rootScope.$broadcast('e'); }).toThrowError('Listener Error!'); - expect(throwingListener).toHaveBeenCalled(); - expect(secondListener).not.toHaveBeenCalled(); - - throwingListener.calls.reset(); - secondListener.calls.reset(); - - expect(function() { $rootScope.$broadcast('e'); }).toThrowError('Listener Error!'); - expect(throwingListener).toHaveBeenCalled(); - expect(secondListener).not.toHaveBeenCalled(); - }); - }); }); }); @@ -2773,7 +2659,7 @@ describe('Scope', function() { expect(spy1).toHaveBeenCalledOnce(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).toHaveBeenCalledOnce(); - expect(child.$$listeners['evt'].length).toBe(2); + expect(child.$$listeners['evt'].length).toBe(3); // cleanup will happen on next $emit spy1.calls.reset(); spy2.calls.reset(); @@ -2807,7 +2693,7 @@ describe('Scope', function() { expect(spy1).toHaveBeenCalledOnce(); expect(spy2).toHaveBeenCalledOnce(); expect(spy3).toHaveBeenCalledOnce(); - expect(child.$$listeners['evt'].length).toBe(2); + expect(child.$$listeners['evt'].length).toBe(3); //cleanup will happen on next $broadcast spy1.calls.reset(); spy2.calls.reset(); From 97d0224ae60f614c8b8a07a00829e46ec4997382 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 8 Aug 2017 23:24:17 -0700 Subject: [PATCH 2/8] fix($rootScope): fix potential memory leak when removing scope listeners When removing listeners they are removed from the array but the array size is not changed until the event is fired again. If the event is never fired but listeners are added/removed then the array will continue growing. By changing the listener removal to `delete` the array entry instead of setting it to `null` browsers can potentially deallocate the memory for the entry. Fixes #16135 Closes #16161 --- src/ng/rootScope.js | 5 ++++- test/ng/rootScopeSpec.js | 15 +++++++++++++++ 2 files changed, 19 insertions(+), 1 deletion(-) diff --git a/src/ng/rootScope.js b/src/ng/rootScope.js index 07c692e9101b..7f37338e6636 100644 --- a/src/ng/rootScope.js +++ b/src/ng/rootScope.js @@ -1273,7 +1273,10 @@ function $RootScopeProvider() { return function() { var indexOfListener = namedListeners.indexOf(listener); if (indexOfListener !== -1) { - namedListeners[indexOfListener] = null; + // Use delete in the hope of the browser deallocating the memory for the array entry, + // while not shifting the array indexes of other listeners. + // See issue https://github.com/angular/angular.js/issues/16135 + delete namedListeners[indexOfListener]; decrementListenerCount(self, 1, name); } }; diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 6f683221742e..06a20f6963c5 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -2438,6 +2438,21 @@ describe('Scope', function() { })); + // See issue https://github.com/angular/angular.js/issues/16135 + it('should deallocate the listener array entry', inject(function($rootScope) { + var remove1 = $rootScope.$on('abc', noop); + $rootScope.$on('abc', noop); + + expect($rootScope.$$listeners['abc'].length).toBe(2); + expect(0 in $rootScope.$$listeners['abc']).toBe(true); + + remove1(); + + expect($rootScope.$$listeners['abc'].length).toBe(2); + expect(0 in $rootScope.$$listeners['abc']).toBe(false); + })); + + it('should call next listener after removing the current listener via its own handler', inject(function($rootScope) { var listener1 = jasmine.createSpy('listener1').and.callFake(function() { remove1(); }); var remove1 = $rootScope.$on('abc', listener1); From 5c38fb744eb9deaf602f2f1d2e02cefdb815a6c6 Mon Sep 17 00:00:00 2001 From: Jason Bedard Date: Tue, 5 Dec 2017 00:49:16 -0800 Subject: [PATCH 3/8] test($rootScope): test recursive event broadcast and emit --- test/ng/rootScopeSpec.js | 39 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/test/ng/rootScopeSpec.js b/test/ng/rootScopeSpec.js index 06a20f6963c5..eea9e824ae66 100644 --- a/test/ng/rootScopeSpec.js +++ b/test/ng/rootScopeSpec.js @@ -2925,6 +2925,45 @@ describe('Scope', function() { })); }); }); + + + it('should allow recursive $emit/$broadcast', inject(function($rootScope) { + var callCount = 0; + $rootScope.$on('evt', function($event, arg0) { + callCount++; + if (arg0 !== 1234) { + $rootScope.$emit('evt', 1234); + $rootScope.$broadcast('evt', 1234); + } + }); + + $rootScope.$emit('evt'); + $rootScope.$broadcast('evt'); + expect(callCount).toBe(6); + })); + + + it('should allow recursive $emit/$broadcast between parent/child', inject(function($rootScope) { + var child = $rootScope.$new(); + var calls = ''; + + $rootScope.$on('evt', function($event, arg0) { + calls += 'r'; // For "root". + if (arg0 === 'fromChild') { + $rootScope.$broadcast('evt', 'fromRoot2'); + } + }); + + child.$on('evt', function($event, arg0) { + calls += 'c'; // For "child". + if (arg0 === 'fromRoot1') { + child.$emit('evt', 'fromChild'); + } + }); + + $rootScope.$broadcast('evt', 'fromRoot1'); + expect(calls).toBe('rccrrc'); + })); }); describe('doc examples', function() { From 223de59e988dc0cc8b4ec3a045b7c0735eba1c77 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 12 Dec 2017 12:45:44 +0100 Subject: [PATCH 4/8] fix(form): set $submitted to true on child forms when parent is submitted Closes #10071 BREAKING CHANGE: Forms will now set $submitted on child forms when they are submitted. For example: ```
Submitting this form will set $submitted on "parentform" and "childform". Previously, it was only set on "parentform". This change was introduced because mixing form and ngForm does not create logically separate forms, but rather something like input groups. Therefore, child forms should inherit the submission state from their parent form. --- src/ng/directive/form.js | 20 ++++++- test/ng/directive/formSpec.js | 107 ++++++++++++++++++++++++++++++++++ 2 files changed, 124 insertions(+), 3 deletions(-) diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 1fc3b6f1cff1..2beb0837ecfd 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -9,7 +9,8 @@ var nullFormCtrl = { $setValidity: noop, $setDirty: noop, $setPristine: noop, - $setSubmitted: noop + $setSubmitted: noop, + $$setSubmitted: noop }, PENDING_CLASS = 'ng-pending', SUBMITTED_CLASS = 'ng-submitted'; @@ -274,12 +275,25 @@ FormController.prototype = { * @name form.FormController#$setSubmitted * * @description - * Sets the form to its submitted state. + * Sets the form to its `$submitted` state. This will also set `$submitted` on all child and + * parent forms of the form. */ $setSubmitted: function() { + var rootForm = this; + while (rootForm.$$parentForm && (rootForm.$$parentForm !== nullFormCtrl)) { + rootForm = rootForm.$$parentForm; + } + rootForm.$$setSubmitted(); + }, + + $$setSubmitted: function() { this.$$animate.addClass(this.$$element, SUBMITTED_CLASS); this.$submitted = true; - this.$$parentForm.$setSubmitted(); + forEach(this.$$controls, function(control) { + if (control.$$setSubmitted) { + control.$$setSubmitted(); + } + }); } }; diff --git a/test/ng/directive/formSpec.js b/test/ng/directive/formSpec.js index b48ff1084468..2d996bb359e1 100644 --- a/test/ng/directive/formSpec.js +++ b/test/ng/directive/formSpec.js @@ -539,6 +539,113 @@ describe('form', function() { expect(parent.$submitted).toBeTruthy(); }); + it('should set $submitted to true on child forms when parent is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child; + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + }); + + + it('should not propagate $submitted state on removed child forms when parent is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child, + grandchild = scope.grandchild, + ggchild = scope.greatgrandchild; + + parent.$removeControl(child); + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + parent.$addControl(child); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + parent.$setSubmitted(); + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + parent.$removeControl(child); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + parent.$setPristine(); // sets $submitted to false + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + grandchild.$setPristine(); + expect(grandchild.$submitted).not.toBeTruthy(); + + child.$setSubmitted(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + + child.$setPristine(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).not.toBeTruthy(); + expect(grandchild.$submitted).not.toBeTruthy(); + + // Test upwards submission setting + grandchild.$setSubmitted(); + expect(parent.$submitted).not.toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + }); + + + it('should set $submitted to true on child and parent forms when form is submitted', function() { + doc = jqLite( + '' + + '' + + '' + + '' + + '' + + '' + + '' + + ''); + $compile(doc)(scope); + + var parent = scope.parent, + child = scope.child, + grandchild = scope.grandchild; + + child.$setSubmitted(); + + expect(parent.$submitted).toBeTruthy(); + expect(child.$submitted).toBeTruthy(); + expect(grandchild.$submitted).toBeTruthy(); + }); it('should deregister a child form when its DOM is removed', function() { doc = jqLite( From f6e60c14c055960ce85c55d9c819d30eda0b9086 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Tue, 12 Dec 2017 13:23:08 +0100 Subject: [PATCH 5/8] docs(ngForm): clarify usage and limitations --- src/ng/directive/form.js | 25 +++++++++++++++---------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/src/ng/directive/form.js b/src/ng/directive/form.js index 2beb0837ecfd..2677921c0d74 100644 --- a/src/ng/directive/form.js +++ b/src/ng/directive/form.js @@ -352,16 +352,21 @@ addSetValidityMethod({ * @restrict EAC * * @description - * Nestable alias of {@link ng.directive:form `form`} directive. HTML - * does not allow nesting of form elements. It is useful to nest forms, for example if the validity of a - * sub-group of controls needs to be determined. - * - * Note: the purpose of `ngForm` is to group controls, - * but not to be a replacement for the `
` tag with all of its capabilities - * (e.g. posting to the server, ...). - * - * @param {string=} ngForm|name Name of the form. If specified, the form controller will be published into - * related scope, under this name. + * Helper directive that makes it possible to create control groups inside a + * {@link ng.directive:form `form`} directive. + * These "child forms" can be used, for example, to determine the validity of a sub-group of + * controls. + * + *
+ * **Note**: `ngForm` cannot be used as a replacement for ``, because it lacks its + * [built-in HTML functionality](https://html.spec.whatwg.org/#the-form-element). + * Specifically, you cannot submit `ngForm` like a `` tag. That means, + * you cannot send data to the server with `ngForm`, or integrate it with + * {@link ng.directive:ngSubmit `ngSubmit`}. + *
+ * + * @param {string=} ngForm|name Name of the form. If specified, the form controller will + * be published into the related scope, under this name. * */ From 0cd39217828b0ad53eaf731576af17d66c18ff60 Mon Sep 17 00:00:00 2001 From: Martin Staffa Date: Wed, 15 Feb 2017 14:20:02 +0100 Subject: [PATCH 6/8] fix(ngScenario): completely remove the angular scenario runner The runner has been deprecated and undocumented since 2014: See commit 8d6d126899d4b1927360599403a7592011243270 Closes #9405 BREAKING CHANGE: The angular scenario runner end-to-end test framework has been removed from the project and will no longer be available on npm or bower starting with 1.7.0. It was deprecated and removed from the documentation in 2014. Applications that still use it should migrate to [Protractor](http://www.protractortest.org). Technically, it should also be possible to continue using an older version of the scenario runner, as the underlying APIs have not changed. However, we do not guarantee future compatibility. --- Gruntfile.js | 12 - angularFiles.js | 27 - package.json | 1 - src/ngScenario/.eslintrc.json | 15 - src/ngScenario/Application.js | 142 ---- src/ngScenario/Describe.js | 155 ----- src/ngScenario/Future.js | 64 -- src/ngScenario/ObjectModel.js | 252 -------- src/ngScenario/Runner.js | 229 ------- src/ngScenario/Scenario.js | 329 ---------- src/ngScenario/SpecRunner.js | 149 ----- src/ngScenario/angular-bootstrap.js | 60 -- src/ngScenario/angular.prefix | 7 - src/ngScenario/angular.suffix | 22 - src/ngScenario/dsl.js | 484 -------------- src/ngScenario/matchers.js | 45 -- src/ngScenario/output/Html.js | 171 ----- src/ngScenario/output/Json.js | 10 - src/ngScenario/output/Object.js | 8 - src/ngScenario/output/Xml.js | 54 -- test/ng/directive/inputSpec.js | 3 +- test/ngScenario/ApplicationSpec.js | 244 ------- test/ngScenario/DescribeSpec.js | 123 ---- test/ngScenario/FutureSpec.js | 77 --- test/ngScenario/ObjectModelSpec.js | 333 ---------- test/ngScenario/RunnerSpec.js | 116 ---- test/ngScenario/ScenarioSpec.js | 44 -- test/ngScenario/SpecRunnerSpec.js | 177 ----- test/ngScenario/dslSpec.js | 789 ----------------------- test/ngScenario/e2e/.eslintrc.json | 9 - test/ngScenario/e2e/Runner-compiled.html | 9 - test/ngScenario/e2e/Runner.html | 10 - test/ngScenario/e2e/style.css | 11 - test/ngScenario/e2e/widgets-scenario.js | 69 -- test/ngScenario/e2e/widgets.html | 99 --- test/ngScenario/matchersSpec.js | 51 -- test/ngScenario/mocks.js | 33 - test/ngScenario/output/HtmlSpec.js | 127 ---- test/ngScenario/output/jsonSpec.js | 37 -- test/ngScenario/output/objectSpec.js | 40 -- test/ngScenario/output/xmlSpec.js | 49 -- yarn.lock | 4 - 42 files changed, 1 insertion(+), 4689 deletions(-) delete mode 100644 src/ngScenario/.eslintrc.json delete mode 100644 src/ngScenario/Application.js delete mode 100644 src/ngScenario/Describe.js delete mode 100644 src/ngScenario/Future.js delete mode 100644 src/ngScenario/ObjectModel.js delete mode 100644 src/ngScenario/Runner.js delete mode 100644 src/ngScenario/Scenario.js delete mode 100644 src/ngScenario/SpecRunner.js delete mode 100644 src/ngScenario/angular-bootstrap.js delete mode 100644 src/ngScenario/angular.prefix delete mode 100644 src/ngScenario/angular.suffix delete mode 100644 src/ngScenario/dsl.js delete mode 100644 src/ngScenario/matchers.js delete mode 100644 src/ngScenario/output/Html.js delete mode 100644 src/ngScenario/output/Json.js delete mode 100644 src/ngScenario/output/Object.js delete mode 100644 src/ngScenario/output/Xml.js delete mode 100644 test/ngScenario/ApplicationSpec.js delete mode 100644 test/ngScenario/DescribeSpec.js delete mode 100644 test/ngScenario/FutureSpec.js delete mode 100644 test/ngScenario/ObjectModelSpec.js delete mode 100644 test/ngScenario/RunnerSpec.js delete mode 100644 test/ngScenario/ScenarioSpec.js delete mode 100644 test/ngScenario/SpecRunnerSpec.js delete mode 100644 test/ngScenario/dslSpec.js delete mode 100644 test/ngScenario/e2e/.eslintrc.json delete mode 100644 test/ngScenario/e2e/Runner-compiled.html delete mode 100644 test/ngScenario/e2e/Runner.html delete mode 100644 test/ngScenario/e2e/style.css delete mode 100644 test/ngScenario/e2e/widgets-scenario.js delete mode 100644 test/ngScenario/e2e/widgets.html delete mode 100644 test/ngScenario/matchersSpec.js delete mode 100644 test/ngScenario/mocks.js delete mode 100644 test/ngScenario/output/HtmlSpec.js delete mode 100644 test/ngScenario/output/jsonSpec.js delete mode 100644 test/ngScenario/output/objectSpec.js delete mode 100644 test/ngScenario/output/xmlSpec.js diff --git a/Gruntfile.js b/Gruntfile.js index ff5eb26de2a0..c9c32ca8d71a 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -186,16 +186,6 @@ module.exports = function(grunt) { }, build: { - scenario: { - dest: 'build/angular-scenario.js', - src: [ - 'bower_components/jquery/dist/jquery.js', - util.wrap([files['angularSrc'], files['angularScenario']], 'ngScenario/angular') - ], - styles: { - css: ['css/angular.css', 'css/angular-scenario.css'] - } - }, angular: { dest: 'build/angular.js', src: util.wrap([files['angularSrc']], 'angular'), @@ -281,9 +271,7 @@ module.exports = function(grunt) { files: [ 'src/**/*.js', 'test/**/*.js', - '!test/ngScenario/DescribeSpec.js', '!src/ng/directive/attrs.js', // legitimate xit here - '!src/ngScenario/**/*.js', '!test/helpers/privateMocks*.js' ], options: { diff --git a/angularFiles.js b/angularFiles.js index 4c7b8cc361e4..7c4062d41ad4 100644 --- a/angularFiles.js +++ b/angularFiles.js @@ -153,26 +153,8 @@ var angularFiles = { ] }, - 'angularScenario': [ - 'src/ngScenario/Scenario.js', - 'src/ngScenario/Application.js', - 'src/ngScenario/Describe.js', - 'src/ngScenario/Future.js', - 'src/ngScenario/ObjectModel.js', - 'src/ngScenario/Runner.js', - 'src/ngScenario/SpecRunner.js', - 'src/ngScenario/dsl.js', - 'src/ngScenario/matchers.js', - 'src/ngScenario/output/Html.js', - 'src/ngScenario/output/Json.js', - 'src/ngScenario/output/Xml.js', - 'src/ngScenario/output/Object.js' - ], - 'angularTest': [ 'test/helpers/*.js', - 'test/ngScenario/*.js', - 'test/ngScenario/output/*.js', 'test/*.js', 'test/auto/*.js', 'test/ng/**/*.js', @@ -193,22 +175,15 @@ var angularFiles = { 'test/jquery_remove.js', '@angularSrc', '@angularSrcModules', - '@angularScenario', '@angularTest' ], 'karmaExclude': [ 'test/jquery_alias.js', 'src/angular-bootstrap.js', - 'src/ngScenario/angular-bootstrap.js', 'src/angular.bind.js' ], - 'karmaScenario': [ - 'build/angular-scenario.js', - 'build/docs/docs-scenario.js' - ], - 'karmaModules': [ 'build/angular.js', '@angularSrcModules', @@ -231,13 +206,11 @@ var angularFiles = { 'test/jquery_alias.js', '@angularSrc', '@angularSrcModules', - '@angularScenario', '@angularTest' ], 'karmaJqueryExclude': [ 'src/angular-bootstrap.js', - 'src/ngScenario/angular-bootstrap.js', 'test/jquery_remove.js', 'src/angular.bind.js' ] diff --git a/package.json b/package.json index 74335fb4705f..bb62da46e9c6 100644 --- a/package.json +++ b/package.json @@ -67,7 +67,6 @@ "karma-firefox-launcher": "^1.0.1", "karma-jasmine": "^1.1.0", "karma-junit-reporter": "^1.2.0", - "karma-ng-scenario": "^1.0.0", "karma-sauce-launcher": "^1.2.0", "karma-script-launcher": "^1.0.0", "karma-spec-reporter": "^0.0.31", diff --git a/src/ngScenario/.eslintrc.json b/src/ngScenario/.eslintrc.json deleted file mode 100644 index 454d02348435..000000000000 --- a/src/ngScenario/.eslintrc.json +++ /dev/null @@ -1,15 +0,0 @@ -{ - "globals": { - "includes": false, - "asyncForEach": false, - "msie": false, - "browserTrigger": false, - "console": false, - "alert": false, - "_jQuery": false, - "angularInit": false, - "formatException": false, - "$runner": false, - "callerFile": false - } -} diff --git a/src/ngScenario/Application.js b/src/ngScenario/Application.js deleted file mode 100644 index ec5d1626a6bd..000000000000 --- a/src/ngScenario/Application.js +++ /dev/null @@ -1,142 +0,0 @@ -'use strict'; - -/** - * Represents the application currently being tested and abstracts usage - * of iframes or separate windows. - * - * @param {Object} context jQuery wrapper around HTML context. - */ -angular.scenario.Application = function(context) { - this.context = context; - context.append( - '

Current URL: None

' + - '
' - ); -}; - -/** - * Gets the jQuery collection of frames. Don't use this directly because - * frames may go stale. - * - * @private - * @return {Object} jQuery collection - */ -angular.scenario.Application.prototype.getFrame_ = function() { - return this.context.find('#test-frames iframe:last'); -}; - -/** - * Gets the window of the test runner frame. Always favor executeAction() - * instead of this method since it prevents you from getting a stale window. - * - * @private - * @return {Object} the window of the frame - */ -angular.scenario.Application.prototype.getWindow_ = function() { - var contentWindow = this.getFrame_().prop('contentWindow'); - if (!contentWindow) { - throw new Error('Frame window is not accessible.'); - } - return contentWindow; -}; - -/** - * Changes the location of the frame. - * - * @param {string} url The URL. If it begins with a # then only the - * hash of the page is changed. - * @param {function()} loadFn function($window, $document) Called when frame loads. - * @param {function()} errorFn function(error) Called if any error when loading. - */ -angular.scenario.Application.prototype.navigateTo = function(url, loadFn, errorFn) { - var self = this; - var frame = self.getFrame_(); - //TODO(esprehn): Refactor to use rethrow() - errorFn = errorFn || function(e) { throw e; }; - if (url === 'about:blank') { - errorFn('Sandbox Error: Navigating to about:blank is not allowed.'); - } else if (url.charAt(0) === '#') { - url = frame.attr('src').split('#')[0] + url; - frame.attr('src', url); - self.executeAction(loadFn); - } else { - frame.remove(); - self.context.find('#test-frames').append('