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

Commit 832090f

Browse files
committed
fix(input): correct initial animation state of messages
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. * 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 computedStyle Fixes #6767, #9543, #9723, #10240 Related: angular/angular.js#13408
1 parent 32235b2 commit 832090f

File tree

2 files changed

+61
-104
lines changed

2 files changed

+61
-104
lines changed

src/components/input/input-animations.spec.js

+56-92
Original file line numberDiff line numberDiff line change
@@ -1,70 +1,73 @@
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, $$mdInput, $window, $animate, $rootElement, $document, $timeout,
3+
el, root, body, 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

1412
// Run after-test teardown
1513
afterEach(teardown);
1614

17-
it('set the proper styles when showing messages on an input', function() {
15+
it('set the proper styles when showing messages on an input', performInputAnimationTests);
16+
it('set the proper styles when showing messages on an input with animations disabled', function() {
17+
$animate.enabled(false);
18+
performInputAnimationTests();
19+
$animate.enabled(true);
20+
});
21+
22+
function performInputAnimationTests() {
1823
compile(
1924
'<form name="testForm">' +
2025
' <md-input-container>' +
2126
' <input name="foo" ng-model="foo" required ng-pattern="/^1234$/" />' +
2227
' <div class="errors" ng-messages="testForm.foo.$error">' +
23-
' <div ng-message="required">required</div>' +
24-
' <div ng-message="pattern">pattern</div>' +
28+
' <div ng-message="required" style="transition: 0s none">required</div>' +
29+
' <div ng-message="pattern" style="transition: 0s none">pattern</div>' +
2530
' </div>' +
2631
' </md-input-container>' +
2732
'</form>'
2833
);
2934

3035
var container = el.find('md-input-container'),
3136
input = el.find('input'),
32-
doneSpy = jasmine.createSpy('done');
37+
errors;
38+
3339

3440
// Mimic the real validations/animations that fire
3541

3642
/*
3743
* 1. Set to an invalid pattern but don't blur (so it's not invalid yet)
3844
*
39-
* Expect nothing to happen ($animateCss called with no options)
45+
* Expect nothing to happen (message is hidden)
4046
*/
4147

4248
setFoo('asdf');
43-
messageAnimation.enter(getError(), doneSpy);
4449
flush();
45-
46-
expectError(getError(), 'pattern');
47-
expect(doneSpy).toHaveBeenCalled();
50+
errors = getError();
51+
expectError(errors, 'pattern');
4852
expect(container).not.toHaveClass('md-input-invalid');
49-
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
53+
computedStyle = $window.getComputedStyle(errors[0]);
54+
expect(parseInt(computedStyle.opacity)).toEqual(0);
55+
expect(parseInt(computedStyle.marginTop)).toBeLessThan(0);
5056

5157
/*
5258
* 2. Blur the input, which adds the md-input-invalid class
5359
*
5460
* Expect to animate in the pattern message
5561
*/
5662

57-
doneSpy.calls.reset();
5863
input.triggerHandler('blur');
59-
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
6064
flush();
61-
62-
expectError(getError(), 'pattern');
63-
expect(doneSpy).toHaveBeenCalled();
65+
errors = getError();
66+
expectError(errors, 'pattern');
6467
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"});
68+
computedStyle = $window.getComputedStyle(errors[0]);
69+
expect(parseInt(computedStyle.opacity)).toEqual(1);
70+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
6871

6972
/*
7073
* 3. Clear the field
@@ -73,30 +76,17 @@ describe('md-input-container animations', function() {
7376
*/
7477

7578
// 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);
8679

8780
setFoo('');
8881
expectError(getError(), 'required');
8982

90-
doneSpy.calls.reset();
91-
messageAnimation.enter(getError(), doneSpy);
9283
flush();
9384

94-
expect(doneSpy).toHaveBeenCalled();
9585
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-
});
86+
computedStyle = $window.getComputedStyle(getError()[0]);
87+
expect(parseInt(computedStyle.opacity)).toEqual(1);
88+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
89+
}
10090

10191
describe('method tests', function() {
10292

@@ -185,82 +175,83 @@ describe('md-input-container animations', function() {
185175
});
186176
});
187177

188-
it('set the proper styles when showing messages on an md-checkbox', function() {
178+
it('set the proper styles when showing messages on an md-checkbox', performCheckboxAnimationTests);
179+
it('set the proper styles when showing messages on an md-checkbox with animations disabled', function() {
180+
$animate.enabled(false);
181+
performCheckboxAnimationTests();
182+
$animate.enabled(true);
183+
});
184+
185+
function performCheckboxAnimationTests() {
189186
compile(
190187
'<form name="testForm">' +
191188
' <md-input-container>' +
192189
' <md-checkbox name="cb" ng-model="foo" required>Test</md-checkbox>' +
193190
' <div class="errors" ng-messages="testForm.cb.$error">' +
194-
' <div ng-message="required">required</div>' +
191+
' <div ng-message="required" style="transition: 0s none">required</div>' +
195192
' </div>' +
196193
' </md-input-container>' +
197194
'</form>'
198195
);
199196

200197
var container = el.find('md-input-container'),
201-
checkbox = el.find('md-checkbox'),
202-
doneSpy = jasmine.createSpy('done');
198+
checkbox = el.find('md-checkbox');
203199

204200
// Mimic the real validations/animations that fire
205201

206202
/*
207203
* 1. Uncheck the checkbox but don't blur (so it's not invalid yet)
208204
*
209-
* Expect nothing to happen ($animateCss called with no options)
205+
* Expect nothing to happen (message is hidden)
210206
*/
211207

212208
setFoo(true);
213209
checkbox.triggerHandler('click');
214-
messageAnimation.enter(getError(), doneSpy);
215210
flush();
216211

217212
expectError(getError(), 'required');
218-
expect(doneSpy).toHaveBeenCalled();
219213
expect(container).not.toHaveClass('md-input-invalid');
220-
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
214+
computedStyle = $window.getComputedStyle(getError()[0]);
215+
expect(parseInt(computedStyle.opacity)).toEqual(0);
216+
expect(parseInt(computedStyle.marginTop)).toBeLessThan(0);
221217

222218
/*
223219
* 2. Blur the checkbox, which adds the md-input-invalid class
224220
*
225221
* Expect to animate in the required message
226222
*/
227223

228-
doneSpy.calls.reset();
229224
checkbox.triggerHandler('blur');
230-
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
231225
flush();
232226

233227
expectError(getError(), 'required');
234-
expect(doneSpy).toHaveBeenCalled();
235228
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"});
229+
computedStyle = $window.getComputedStyle(getError()[0]);
230+
expect(parseInt(computedStyle.opacity)).toEqual(1);
231+
expect(parseInt(computedStyle.marginTop)).toEqual(0);
239232

240233
/*
241234
* 3. Clear the field
242235
*
243236
* Expect to animate away required message
244237
*/
245238

246-
doneSpy.calls.reset();
247-
messageAnimation.leave(getError(), doneSpy);
239+
setFoo(true);
248240
flush();
249241

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);
242+
expect(getError().length).toBe(0);
254243

255-
});
244+
}
256245

257246
/*
258247
* Test Helper Functions
259248
*/
260249

261250
function compile(template) {
262251
el = $compile(template)(pageScope);
263-
angular.element(document.body).append(el);
252+
root = $rootElement.append(el)[0];
253+
body = $document[0].body;
254+
body.appendChild(root);
264255

265256
pageScope.$apply();
266257

@@ -290,41 +281,17 @@ describe('md-input-container animations', function() {
290281
* before/afterEach Helper Functions
291282
*/
292283

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-
320284
// Setup/grab our variables
321285
function injectGlobals() {
322286
inject(function($injector) {
323287
$rootScope = $injector.get('$rootScope');
324288
$compile = $injector.get('$compile');
325-
$animateCss = $injector.get('$animateCss');
326289
$material = $injector.get('$material');
327290
$$mdInput = $injector.get('$$mdInput');
291+
$window = $injector.get('$window');
292+
$animate = $injector.get('$animate');
293+
$rootElement = $injector.get('$rootElement');
294+
$document = $injector.get('$document');
328295

329296
// Grab our input animations (we MUST use the injector to setup dependencies)
330297
invalidAnimation = $injector.get('mdInputInvalidAnimation');
@@ -336,13 +303,10 @@ describe('md-input-container animations', function() {
336303
// Setup some custom variables for these tests
337304
function setupVariables() {
338305
pageScope = $rootScope.$new();
339-
cssTransitionsDisabled = true;
340306
}
341307

342308
// Teardown our tests by resetting variables and removing our element
343309
function teardown() {
344-
cssTransitionsDisabled = false;
345-
346310
el && el.remove && el.remove();
347311
}
348312
});

src/components/input/input.scss

+5-12
Original file line numberDiff line numberDiff line change
@@ -219,13 +219,6 @@ md-input-container {
219219
overflow: hidden;
220220
@include rtl(clear, left, right);
221221

222-
&.ng-enter {
223-
// Upon entering the DOM, messages should be hidden
224-
.md-input-message-animation {
225-
opacity: 0;
226-
margin-top: -100px;
227-
}
228-
}
229222
}
230223

231224
.md-input-message-animation, .md-char-counter {
@@ -259,16 +252,16 @@ md-input-container {
259252
}
260253
}
261254

262-
// Note: This is a workaround to fix an ng-enter flicker bug
263255
.md-input-message-animation {
264-
&:not(.ng-animate) {
256+
// Enter animation
257+
// Pre-animation state is transparent and off target
258+
&.ng-enter-prepare {
265259
opacity: 0;
266260
margin-top: -100px;
267261
}
268-
}
269262

270-
.md-input-message-animation {
271-
&.ng-enter {
263+
// First keyframe of entry animation
264+
&.ng-enter:not(.ng-enter-active) {
272265
opacity: 0;
273266
margin-top: -100px;
274267
}

0 commit comments

Comments
 (0)