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

Commit fa6213d

Browse files
topherfangioThomasBurleson
authored andcommitted
fix(input): Fix message animation not running.
If an input switched from one error state directly to another error state, the second error animation would fail. Update input animation code to handle this situation and be drastically cleaner and fix a bug in `getMessagesElement()` method which disallowed multiple `ng-messages` elements. Fixes #8635. Fixes #8864. Fixes #8973. Closes #9044
1 parent bdc5a08 commit fa6213d

File tree

8 files changed

+666
-432
lines changed

8 files changed

+666
-432
lines changed

.travis.yml

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -50,9 +50,7 @@ before_script:
5050
- "./scripts/sauce/start-tunnel.sh"
5151

5252
script:
53-
- gulp ddescribe-iit
54-
- gulp build
55-
- gulp karma --config=config/karma-sauce.conf.js --browsers=$BROWSER --reporters='dots'
53+
- ./scripts/travis-run-script.sh
5654

5755
after_script:
5856
- "./scripts/sauce/stop-tunnel.sh"

gulp/tasks/server.js

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,17 +4,19 @@ var LR_PORT = require('../const').LR_PORT;
44
var util = require('../util');
55

66
exports.task = function() {
7-
var openUrl = false;
8-
if (typeof util.args.o === 'string' ||
9-
typeof util.args.o === 'boolean') {
10-
openUrl = util.args.o;
11-
}
12-
return gulp.src('.')
13-
.pipe(webserver({
14-
host: '0.0.0.0',
15-
livereload: true,
16-
port: LR_PORT,
17-
directoryListing: true,
18-
open: openUrl
19-
}));
7+
var openUrl = false;
8+
9+
if (typeof util.args.o === 'string' ||
10+
typeof util.args.o === 'boolean') {
11+
openUrl = util.args.o;
12+
}
13+
14+
return gulp.src('.')
15+
.pipe(webserver({
16+
host: '0.0.0.0',
17+
livereload: true,
18+
port: LR_PORT,
19+
directoryListing: false,
20+
open: openUrl
21+
}));
2022
};

scripts/travis-run-script.sh

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,8 @@
1+
#!/bin/bash
2+
3+
# Run our check to make sure all tests will actually run
4+
gulp ddescribe-iit
5+
6+
# Run our actual build
7+
gulp build
8+
gulp karma --config=config/karma-sauce.conf.js --browsers=$BROWSER --reporters='dots'

src/components/input/demoErrors/index.html

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@
88
<input md-maxlength="30" required md-no-asterisk name="description" ng-model="project.description">
99
<div ng-messages="projectForm.description.$error">
1010
<div ng-message="required">This is required.</div>
11-
<div ng-message="md-maxlength">The name has to be less than 30 characters long.</div>
11+
<div ng-message="md-maxlength">The description must be less than 30 characters long.</div>
1212
</div>
1313
</md-input-container>
1414

Lines changed: 174 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,174 @@
1+
describe('md-input-container animations', function() {
2+
var $rootScope, $compile, $animate, $animateCss,
3+
el, pageScope, invalidAnimation, messagesAnimation, messageAnimation,
4+
cssTransitionsDisabled = false;
5+
6+
// Load our modules
7+
beforeEach(module('ngAnimate', 'ngMessages', 'material.components.input'));
8+
9+
// Run pre-test setup
10+
beforeEach(decorateAnimateCss);
11+
beforeEach(injectGlobals);
12+
beforeEach(setupVariables);
13+
14+
// Run after-test teardown
15+
afterEach(teardown);
16+
17+
it('set the proper styles when showing messages', function() {
18+
compile(
19+
'<form name="testForm">' +
20+
' <md-input-container>' +
21+
' <input name="foo" ng-model="foo" required ng-pattern="/^1234$/" />' +
22+
' <div class="errors" ng-messages="testForm.foo.$error">' +
23+
' <div ng-message="required">required</div>' +
24+
' <div ng-message="pattern">pattern</div>' +
25+
' </div>' +
26+
' </md-input-container>' +
27+
'</form>'
28+
);
29+
30+
var container = el.find('md-input-container'),
31+
input = el.find('input');
32+
33+
// Mimic the real validations/animations that fire
34+
35+
/*
36+
* 1. Set to an invalid pattern but don't blur (so it's not invalid yet)
37+
*
38+
* Expect nothing to happen ($animateCss called with no options)
39+
*/
40+
41+
setFoo('asdf');
42+
messageAnimation.enter(getError()).start().done(angular.noop);
43+
$animate.flush();
44+
45+
expectError(getError(), 'pattern');
46+
expect(container).not.toHaveClass('md-input-invalid');
47+
expect(lastAnimateCall()).toEqual({element: getError(), options: {}});
48+
49+
/*
50+
* 2. Blur the input, which adds the md-input-invalid class
51+
*
52+
* Expect to animate in the pattern message
53+
*/
54+
55+
input.triggerHandler('blur');
56+
invalidAnimation.addClass(container, 'md-input-invalid', angular.noop);
57+
$animate.flush();
58+
59+
expectError(getError(), 'pattern');
60+
expect(container).toHaveClass('md-input-invalid');
61+
expect(lastAnimateCall().element).toEqual(getError());
62+
expect(lastAnimateCall().options.event).toEqual('enter');
63+
expect(lastAnimateCall().options.to).toEqual({"opacity": 1, "margin-top": "0"});
64+
65+
/*
66+
* 3. Clear the field
67+
*
68+
* Expect to animate away pattern message and animate in the required message
69+
*/
70+
71+
// Grab the pattern error before we change foo and it disappears
72+
var patternError = getError();
73+
74+
messageAnimation.leave(patternError).start().done(angular.noop);
75+
$animate.flush();
76+
77+
expect(lastAnimateCall().element).toEqual(patternError);
78+
expect(lastAnimateCall().options.event).toEqual('leave');
79+
expect(parseInt(lastAnimateCall().options.to["margin-top"])).toBeLessThan(0);
80+
81+
setFoo('');
82+
expectError(getError(), 'required');
83+
84+
messageAnimation.enter(getError()).start().done(angular.noop);
85+
$animate.flush();
86+
87+
expect(container).toHaveClass('md-input-invalid');
88+
expect(lastAnimateCall().element).toEqual(getError());
89+
expect(lastAnimateCall().options.event).toEqual('enter');
90+
expect(lastAnimateCall().options.to).toEqual({"opacity": 1, "margin-top": "0"});
91+
});
92+
93+
/*
94+
* Test Helper Functions
95+
*/
96+
97+
function compile(template) {
98+
el = $compile(template)(pageScope);
99+
angular.element(document.body).append(el);
100+
101+
pageScope.$apply();
102+
103+
return el;
104+
}
105+
106+
function lastAnimateCall() {
107+
return {
108+
element: $animateCss.calls.mostRecent().args[0],
109+
options: $animateCss.calls.mostRecent().args[1]
110+
}
111+
}
112+
113+
function setFoo(value) {
114+
pageScope.foo = value;
115+
pageScope.$digest();
116+
}
117+
118+
function getError() {
119+
return angular.element(el[0].querySelector('.errors div'));
120+
}
121+
122+
function expectError(element, message) {
123+
expect(element.text().trim()).toBe(message);
124+
}
125+
126+
/*
127+
* before/afterEach Helper Functions
128+
*/
129+
130+
// Decorate the $animateCss service so we can spy on it and disable any CSS transitions
131+
function decorateAnimateCss() {
132+
module(function($provide) {
133+
$provide.decorator('$animateCss', function($delegate) {
134+
return jasmine.createSpy('$animateCss').and.callFake(function(element, options) {
135+
// Make sure any transitions happen immediately; NOTE: this is REQUIRED for the above
136+
// tests to pass without using window.setTimeout to wait for the animations
137+
if (cssTransitionsDisabled) {
138+
element.css('transition', '0s none');
139+
}
140+
141+
return $delegate(element, options);
142+
});
143+
});
144+
});
145+
}
146+
147+
// Setup/grab our variables
148+
function injectGlobals() {
149+
inject(function($injector) {
150+
$rootScope = $injector.get('$rootScope');
151+
$compile = $injector.get('$compile');
152+
$animate = $injector.get('$animate');
153+
$animateCss = $injector.get('$animateCss');
154+
155+
// Grab our input animations
156+
invalidAnimation = $injector.get('mdInputInvalidAnimation');
157+
messagesAnimation = $injector.get('mdInputMessagesAnimation');
158+
messageAnimation = $injector.get('mdInputMessageAnimation');
159+
});
160+
}
161+
162+
// Setup some custom variables for these tests
163+
function setupVariables() {
164+
pageScope = $rootScope.$new();
165+
cssTransitionsDisabled = true;
166+
}
167+
168+
// Teardown our tests by resetting variables and removing our element
169+
function teardown() {
170+
cssTransitionsDisabled = false;
171+
172+
el && el.remove && el.remove();
173+
}
174+
});

0 commit comments

Comments
 (0)