Skip to content
This repository has been archived by the owner on Aug 29, 2023. It is now read-only.

Commit

Permalink
fix(input): correct initial animation state of messages (#10246)
Browse files Browse the repository at this point in the history
  • Loading branch information
clshortfuse authored and kara committed Jul 18, 2017
1 parent 75237c6 commit 0151b4b
Show file tree
Hide file tree
Showing 2 changed files with 61 additions and 104 deletions.
148 changes: 56 additions & 92 deletions src/components/input/input-animations.spec.js
@@ -1,70 +1,73 @@
describe('md-input-container animations', function() {
var $rootScope, $compile, $animateCss, $material, $$mdInput,
el, pageScope, invalidAnimation, messagesAnimation, messageAnimation,
cssTransitionsDisabled = false, lastAnimateCall;
var $rootScope, $compile, $material, $$mdInput, $window, $animate, $rootElement, $document, $timeout,
el, root, body, pageScope, computedStyle;

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

// Run pre-test setup
beforeEach(decorateAnimateCss);
beforeEach(injectGlobals);
beforeEach(setupVariables);

// Run after-test teardown
afterEach(teardown);

it('set the proper styles when showing messages on an input', function() {
it('set the proper styles when showing messages on an input', performInputAnimationTests);
it('set the proper styles when showing messages on an input with animations disabled', function() {
$animate.enabled(false);
performInputAnimationTests();
$animate.enabled(true);
});

function performInputAnimationTests() {
compile(
'<form name="testForm">' +
' <md-input-container>' +
' <input name="foo" ng-model="foo" required ng-pattern="/^1234$/" />' +
' <div class="errors" ng-messages="testForm.foo.$error">' +
' <div ng-message="required">required</div>' +
' <div ng-message="pattern">pattern</div>' +
' <div ng-message="required" style="transition: 0s none">required</div>' +
' <div ng-message="pattern" style="transition: 0s none">pattern</div>' +
' </div>' +
' </md-input-container>' +
'</form>'
);

var container = el.find('md-input-container'),
input = el.find('input'),
doneSpy = jasmine.createSpy('done');
errors;


// Mimic the real validations/animations that fire

/*
* 1. Set to an invalid pattern but don't blur (so it's not invalid yet)
*
* Expect nothing to happen ($animateCss called with no options)
* Expect nothing to happen (message is hidden)
*/

setFoo('asdf');
messageAnimation.enter(getError(), doneSpy);
flush();

expectError(getError(), 'pattern');
expect(doneSpy).toHaveBeenCalled();
errors = getError();
expectError(errors, 'pattern');
expect(container).not.toHaveClass('md-input-invalid');
expect(lastAnimateCall).toEqual({element: getError(), options: {}});
computedStyle = $window.getComputedStyle(errors[0]);
expect(parseInt(computedStyle.opacity)).toEqual(0);
expect(parseInt(computedStyle.marginTop)).toBeLessThan(0);

/*
* 2. Blur the input, which adds the md-input-invalid class
*
* Expect to animate in the pattern message
*/

doneSpy.calls.reset();
input.triggerHandler('blur');
invalidAnimation.addClass(container, 'md-input-invalid', doneSpy);
flush();

expectError(getError(), 'pattern');
expect(doneSpy).toHaveBeenCalled();
errors = getError();
expectError(errors, 'pattern');
expect(container).toHaveClass('md-input-invalid');
expect(lastAnimateCall.element).toEqual(getError());
expect(lastAnimateCall.options.event).toEqual('enter');
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
computedStyle = $window.getComputedStyle(errors[0]);
expect(parseInt(computedStyle.opacity)).toEqual(1);
expect(parseInt(computedStyle.marginTop)).toEqual(0);

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

// Grab the pattern error before we change foo and it disappears
var patternError = getError();

doneSpy.calls.reset();
messageAnimation.leave(patternError, doneSpy);
flush();

expect(doneSpy).toHaveBeenCalled();
expect(lastAnimateCall.element).toEqual(patternError);
expect(lastAnimateCall.options.event).toEqual('leave');
expect(parseInt(lastAnimateCall.options.to["margin-top"])).toBeLessThan(0);

setFoo('');
expectError(getError(), 'required');

doneSpy.calls.reset();
messageAnimation.enter(getError(), doneSpy);
flush();

expect(doneSpy).toHaveBeenCalled();
expect(container).toHaveClass('md-input-invalid');
expect(lastAnimateCall.element).toEqual(getError());
expect(lastAnimateCall.options.event).toEqual('enter');
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
});
computedStyle = $window.getComputedStyle(getError()[0]);
expect(parseInt(computedStyle.opacity)).toEqual(1);
expect(parseInt(computedStyle.marginTop)).toEqual(0);
}

describe('method tests', function() {

Expand Down Expand Up @@ -185,82 +175,83 @@ describe('md-input-container animations', function() {
});
});

it('set the proper styles when showing messages on an md-checkbox', function() {
it('set the proper styles when showing messages on an md-checkbox', performCheckboxAnimationTests);
it('set the proper styles when showing messages on an md-checkbox with animations disabled', function() {
$animate.enabled(false);
performCheckboxAnimationTests();
$animate.enabled(true);
});

function performCheckboxAnimationTests() {
compile(
'<form name="testForm">' +
' <md-input-container>' +
' <md-checkbox name="cb" ng-model="foo" required>Test</md-checkbox>' +
' <div class="errors" ng-messages="testForm.cb.$error">' +
' <div ng-message="required">required</div>' +
' <div ng-message="required" style="transition: 0s none">required</div>' +
' </div>' +
' </md-input-container>' +
'</form>'
);

var container = el.find('md-input-container'),
checkbox = el.find('md-checkbox'),
doneSpy = jasmine.createSpy('done');
checkbox = el.find('md-checkbox');

// Mimic the real validations/animations that fire

/*
* 1. Uncheck the checkbox but don't blur (so it's not invalid yet)
*
* Expect nothing to happen ($animateCss called with no options)
* Expect nothing to happen (message is hidden)
*/

setFoo(true);
checkbox.triggerHandler('click');
messageAnimation.enter(getError(), doneSpy);
flush();

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

/*
* 2. Blur the checkbox, which adds the md-input-invalid class
*
* Expect to animate in the required message
*/

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

expectError(getError(), 'required');
expect(doneSpy).toHaveBeenCalled();
expect(container).toHaveClass('md-input-invalid');
expect(lastAnimateCall.element).toEqual(getError());
expect(lastAnimateCall.options.event).toEqual('enter');
expect(lastAnimateCall.options.to).toEqual({"opacity": 1, "margin-top": "0"});
computedStyle = $window.getComputedStyle(getError()[0]);
expect(parseInt(computedStyle.opacity)).toEqual(1);
expect(parseInt(computedStyle.marginTop)).toEqual(0);

/*
* 3. Clear the field
*
* Expect to animate away required message
*/

doneSpy.calls.reset();
messageAnimation.leave(getError(), doneSpy);
setFoo(true);
flush();

expect(doneSpy).toHaveBeenCalled();
expect(lastAnimateCall.element).toEqual(getError());
expect(lastAnimateCall.options.event).toEqual('leave');
expect(parseInt(lastAnimateCall.options.to["margin-top"])).toBeLessThan(0);
expect(getError().length).toBe(0);

});
}

/*
* Test Helper Functions
*/

function compile(template) {
el = $compile(template)(pageScope);
angular.element(document.body).append(el);
root = $rootElement.append(el)[0];
body = $document[0].body;
body.appendChild(root);

pageScope.$apply();

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

// Decorate the $animateCss service so we can spy on it and disable any CSS transitions
function decorateAnimateCss() {
module(function($provide) {
$provide.decorator('$animateCss', function($delegate) {
return jasmine.createSpy('$animateCss').and.callFake(function(element, options) {

// Store the last call to $animateCss
//
// NOTE: We handle this manually because the actual code modifies the options
// and can make the tests fail if it executes before the expect() fires
lastAnimateCall = {
element: element,
options: angular.copy(options)
};

// Make sure any transitions happen immediately; NOTE: this is REQUIRED for the above
// tests to pass without using window.setTimeout to wait for the animations
if (cssTransitionsDisabled) {
element.css('transition', '0s none');
}

return $delegate(element, options);
});
});
});
}

// Setup/grab our variables
function injectGlobals() {
inject(function($injector) {
$rootScope = $injector.get('$rootScope');
$compile = $injector.get('$compile');
$animateCss = $injector.get('$animateCss');
$material = $injector.get('$material');
$$mdInput = $injector.get('$$mdInput');
$window = $injector.get('$window');
$animate = $injector.get('$animate');
$rootElement = $injector.get('$rootElement');
$document = $injector.get('$document');

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

// Teardown our tests by resetting variables and removing our element
function teardown() {
cssTransitionsDisabled = false;

el && el.remove && el.remove();
}
});
17 changes: 5 additions & 12 deletions src/components/input/input.scss
Expand Up @@ -219,13 +219,6 @@ md-input-container {
overflow: hidden;
@include rtl(clear, left, right);

&.ng-enter {
// Upon entering the DOM, messages should be hidden
.md-input-message-animation {
opacity: 0;
margin-top: -100px;
}
}
}

.md-input-message-animation, .md-char-counter {
Expand Down Expand Up @@ -259,16 +252,16 @@ md-input-container {
}
}

// Note: This is a workaround to fix an ng-enter flicker bug
.md-input-message-animation {
&:not(.ng-animate) {
// Enter animation
// Pre-animation state is transparent and off target
&.ng-enter-prepare {
opacity: 0;
margin-top: -100px;
}
}

.md-input-message-animation {
&.ng-enter {
// First keyframe of entry animation
&.ng-enter:not(.ng-enter-active) {
opacity: 0;
margin-top: -100px;
}
Expand Down

0 comments on commit 0151b4b

Please sign in to comment.