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

Commit 14c6cbf

Browse files
committed
fix(input): move messages animations back to CSS
With commit cf7f21a, animations were moved to input.js in response to an ng-enter flicker issue with Angular 1.4.x. While the flicker was fixed, new animation bugs arised. Angular 1.4.x added and backported a ng-enter-prepare to avoid this bug. * Remove JS animations for input messages * Use `.ng-enter-prepare` to avoid flicker on Angular 1.4+ * Use `.ng-enter:not(.ng-enter-active)` to prepare animations on 1.3 and below * Update spec tests to use getComputedStyle instead of ngAnimate Fixes #6767, #9543, #9723, #10240 Related: angular/angular.js#13408
1 parent 32235b2 commit 14c6cbf

File tree

3 files changed

+66
-399
lines changed

3 files changed

+66
-399
lines changed
+31-175
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,11 @@
11
describe('md-input-container animations', function() {
2-
var $rootScope, $compile, $animateCss, $material, $$mdInput,
3-
el, pageScope, invalidAnimation, messagesAnimation, messageAnimation,
4-
cssTransitionsDisabled = false, lastAnimateCall;
2+
var $rootScope, $compile, $material, $window,
3+
el, pageScope, computedStyle;
54

65
// Load our modules
76
beforeEach(module('ngAnimate', 'ngMessages', 'material.components.input', 'material.components.checkbox'));
87

98
// Run pre-test setup
10-
beforeEach(decorateAnimateCss);
119
beforeEach(injectGlobals);
1210
beforeEach(setupVariables);
1311

@@ -20,169 +18,69 @@ describe('md-input-container animations', function() {
2018
' <md-input-container>' +
2119
' <input name="foo" ng-model="foo" required ng-pattern="/^1234$/" />' +
2220
' <div class="errors" ng-messages="testForm.foo.$error">' +
23-
' <div ng-message="required">required</div>' +
24-
' <div ng-message="pattern">pattern</div>' +
21+
' <div ng-message="required" style="transition:0s none">required</div>' +
22+
' <div ng-message="pattern" style="transition:0s none">pattern</div>' +
2523
' </div>' +
2624
' </md-input-container>' +
2725
'</form>'
2826
);
2927

3028
var container = el.find('md-input-container'),
31-
input = el.find('input'),
32-
doneSpy = jasmine.createSpy('done');
29+
input = el.find('input');
3330

3431
// Mimic the real validations/animations that fire
3532

3633
/*
3734
* 1. Set to an invalid pattern but don't blur (so it's not invalid yet)
3835
*
39-
* Expect nothing to happen ($animateCss called with no options)
36+
* Expect nothing to happen (message is hidden)
4037
*/
4138

4239
setFoo('asdf');
43-
messageAnimation.enter(getError(), doneSpy);
4440
flush();
4541

4642
expectError(getError(), 'pattern');
47-
expect(doneSpy).toHaveBeenCalled();
4843
expect(container).not.toHaveClass('md-input-invalid');
49-
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
44+
45+
computedStyle = $window.getComputedStyle(getError()[0]);
46+
expect(parseInt(computedStyle.opacity)).toEqual(0); // not visible
47+
expect(parseInt(computedStyle.marginTop)).toBeLessThan(0);
5048

5149
/*
5250
* 2. Blur the input, which adds the md-input-invalid class
5351
*
5452
* Expect to animate in the pattern message
5553
*/
5654

57-
doneSpy.calls.reset();
5855
input.triggerHandler('blur');
59-
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
6056
flush();
6157

6258
expectError(getError(), 'pattern');
63-
expect(doneSpy).toHaveBeenCalled();
6459
expect(container).toHaveClass('md-input-invalid');
65-
expect(lastAnimateCall.element).toEqual(getError());
66-
expect(lastAnimateCall.options.event).toEqual('enter');
67-
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
60+
computedStyle = $window.getComputedStyle(getError()[0]);
61+
expect(parseInt(computedStyle.opacity)).toEqual(1); // visisble
62+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
6863

6964
/*
7065
* 3. Clear the field
7166
*
7267
* Expect to animate away pattern message and animate in the required message
7368
*/
7469

75-
// Grab the pattern error before we change foo and it disappears
76-
var patternError = getError();
77-
78-
doneSpy.calls.reset();
79-
messageAnimation.leave(patternError, doneSpy);
80-
flush();
81-
82-
expect(doneSpy).toHaveBeenCalled();
83-
expect(lastAnimateCall.element).toEqual(patternError);
84-
expect(lastAnimateCall.options.event).toEqual('leave');
85-
expect(parseInt(lastAnimateCall.options.to["margin-top"])).toBeLessThan(0);
86-
8770
setFoo('');
8871
expectError(getError(), 'required');
89-
90-
doneSpy.calls.reset();
91-
messageAnimation.enter(getError(), doneSpy);
9272
flush();
9373

94-
expect(doneSpy).toHaveBeenCalled();
9574
expect(container).toHaveClass('md-input-invalid');
96-
expect(lastAnimateCall.element).toEqual(getError());
97-
expect(lastAnimateCall.options.event).toEqual('enter');
98-
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
99-
});
100-
101-
describe('method tests', function() {
102-
103-
describe('#showInputMessages', function() {
104-
it('logs a warning with no messages element', inject(function($log) {
105-
// Note that the element does NOT have a parent md-input-messages-animation class
106-
var element = angular.element('<div><div class="md-input-message-animation"></div></div>');
107-
var done = jasmine.createSpy('done');
108-
var warnSpy = spyOn($log, 'warn');
109-
110-
$$mdInput.messages.show(element, done);
111-
112-
expect(done).toHaveBeenCalled();
113-
expect(warnSpy).toHaveBeenCalled();
114-
}));
115-
116-
it('logs a warning with no messages children', inject(function($log) {
117-
// Note that the element does NOT have any child md-input-message-animation divs
118-
var element = angular.element('<div class="md-input-messages-animation"></div>');
119-
var done = jasmine.createSpy('done');
120-
var warnSpy = spyOn($log, 'warn');
121-
122-
$$mdInput.messages.show(element, done);
123-
124-
expect(done).toHaveBeenCalled();
125-
expect(warnSpy).toHaveBeenCalled();
126-
}));
127-
});
128-
129-
describe('#hideInputMessages', function() {
130-
it('logs a warning with no messages element', inject(function($log) {
131-
// Note that the element does NOT have a parent md-input-messages-animation class
132-
var element = angular.element('<div><div class="md-input-message-animation"></div></div>');
133-
var done = jasmine.createSpy('done');
134-
var warnSpy = spyOn($log, 'warn');
75+
76+
// Expect required messsage to be visible
77+
computedStyle = $window.getComputedStyle(getError()[0]);
78+
expect(parseInt(computedStyle.opacity)).toEqual(1);
79+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
13580

136-
$$mdInput.messages.hide(element, done);
81+
// Expect pattern messsage to not be visible
82+
expect(getError()[1]).toBeUndefined();
13783

138-
expect(done).toHaveBeenCalled();
139-
expect(warnSpy).toHaveBeenCalled();
140-
}));
141-
142-
it('logs a warning with no messages children', inject(function($log) {
143-
// Note that the element does NOT have any child md-input-message-animation divs
144-
var element = angular.element('<div class="md-input-messages-animation"></div>');
145-
var done = jasmine.createSpy('done');
146-
var warnSpy = spyOn($log, 'warn');
147-
148-
$$mdInput.messages.hide(element, done);
149-
150-
expect(done).toHaveBeenCalled();
151-
expect(warnSpy).toHaveBeenCalled();
152-
}));
153-
});
154-
155-
describe('#getMessagesElement', function() {
156-
157-
it('finds the messages element itself', function() {
158-
var template = '<div class="md-input-messages-animation"></div>';
159-
var dom = angular.element(template);
160-
var messages = $$mdInput.messages.getElement(dom);
161-
162-
expect(dom).toEqual(messages);
163-
});
164-
165-
it('finds a child element', function(){
166-
var template = '<div><div class="md-input-messages-animation"></div></div>';
167-
var dom = angular.element(template);
168-
var realMessages = angular.element(dom[0].querySelector('.md-input-messages-animation'));
169-
var messages = $$mdInput.messages.getElement(dom);
170-
171-
expect(realMessages).toEqual(messages);
172-
});
173-
174-
it('finds the parent of a message animation element', function() {
175-
var template =
176-
'<div class="md-input-messages-animation">' +
177-
' <div class="md-input-message-animation"></div>' +
178-
'</div>';
179-
var dom = angular.element(template);
180-
var message = angular.element(dom[0].querySelector('.md-input-message-animation'));
181-
var messages = $$mdInput.messages.getElement(message);
182-
183-
expect(dom).toEqual(messages);
184-
});
185-
});
18684
});
18785

18886
it('set the proper styles when showing messages on an md-checkbox', function() {
@@ -191,7 +89,7 @@ describe('md-input-container animations', function() {
19189
' <md-input-container>' +
19290
' <md-checkbox name="cb" ng-model="foo" required>Test</md-checkbox>' +
19391
' <div class="errors" ng-messages="testForm.cb.$error">' +
194-
' <div ng-message="required">required</div>' +
92+
' <div ng-message="required" style="transition:0s none">required</div>' +
19593
' </div>' +
19694
' </md-input-container>' +
19795
'</form>'
@@ -211,46 +109,40 @@ describe('md-input-container animations', function() {
211109

212110
setFoo(true);
213111
checkbox.triggerHandler('click');
214-
messageAnimation.enter(getError(), doneSpy);
215112
flush();
216113

217114
expectError(getError(), 'required');
218-
expect(doneSpy).toHaveBeenCalled();
219115
expect(container).not.toHaveClass('md-input-invalid');
220-
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
116+
117+
computedStyle = $window.getComputedStyle(getError()[0]);
118+
expect(parseInt(computedStyle.opacity)).toEqual(0); // not visible
119+
expect(parseInt(computedStyle.marginTop)).toBeLessThan(0);
221120

222121
/*
223122
* 2. Blur the checkbox, which adds the md-input-invalid class
224123
*
225124
* Expect to animate in the required message
226125
*/
227126

228-
doneSpy.calls.reset();
229127
checkbox.triggerHandler('blur');
230-
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
231128
flush();
232129

233130
expectError(getError(), 'required');
234-
expect(doneSpy).toHaveBeenCalled();
235131
expect(container).toHaveClass('md-input-invalid');
236-
expect(lastAnimateCall.element).toEqual(getError());
237-
expect(lastAnimateCall.options.event).toEqual('enter');
238-
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
132+
computedStyle = $window.getComputedStyle(getError()[0]);
133+
expect(parseInt(computedStyle.opacity)).toEqual(1); // visisble
134+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
239135

240136
/*
241137
* 3. Clear the field
242138
*
243139
* Expect to animate away required message
244140
*/
245141

246-
doneSpy.calls.reset();
247-
messageAnimation.leave(getError(), doneSpy);
142+
setFoo(true);
248143
flush();
249144

250-
expect(doneSpy).toHaveBeenCalled();
251-
expect(lastAnimateCall.element).toEqual(getError());
252-
expect(lastAnimateCall.options.event).toEqual('leave');
253-
expect(parseInt(lastAnimateCall.options.to["margin-top"])).toBeLessThan(0);
145+
expect(getError()[0]).toBeUndefined();
254146

255147
});
256148

@@ -290,59 +182,23 @@ describe('md-input-container animations', function() {
290182
* before/afterEach Helper Functions
291183
*/
292184

293-
// Decorate the $animateCss service so we can spy on it and disable any CSS transitions
294-
function decorateAnimateCss() {
295-
module(function($provide) {
296-
$provide.decorator('$animateCss', function($delegate) {
297-
return jasmine.createSpy('$animateCss').and.callFake(function(element, options) {
298-
299-
// Store the last call to $animateCss
300-
//
301-
// NOTE: We handle this manually because the actual code modifies the options
302-
// and can make the tests fail if it executes before the expect() fires
303-
lastAnimateCall = {
304-
element: element,
305-
options: angular.copy(options)
306-
};
307-
308-
// Make sure any transitions happen immediately; NOTE: this is REQUIRED for the above
309-
// tests to pass without using window.setTimeout to wait for the animations
310-
if (cssTransitionsDisabled) {
311-
element.css('transition', '0s none');
312-
}
313-
314-
return $delegate(element, options);
315-
});
316-
});
317-
});
318-
}
319-
320185
// Setup/grab our variables
321186
function injectGlobals() {
322187
inject(function($injector) {
323188
$rootScope = $injector.get('$rootScope');
324189
$compile = $injector.get('$compile');
325-
$animateCss = $injector.get('$animateCss');
326190
$material = $injector.get('$material');
327-
$$mdInput = $injector.get('$$mdInput');
328-
329-
// Grab our input animations (we MUST use the injector to setup dependencies)
330-
invalidAnimation = $injector.get('mdInputInvalidAnimation');
331-
messagesAnimation = $injector.get('mdInputMessagesAnimation');
332-
messageAnimation = $injector.get('mdInputMessageAnimation');
191+
$window = $injector.get('$window');
333192
});
334193
}
335194

336195
// Setup some custom variables for these tests
337196
function setupVariables() {
338197
pageScope = $rootScope.$new();
339-
cssTransitionsDisabled = true;
340198
}
341199

342200
// Teardown our tests by resetting variables and removing our element
343201
function teardown() {
344-
cssTransitionsDisabled = false;
345-
346202
el && el.remove && el.remove();
347203
}
348204
});

0 commit comments

Comments
 (0)