/test/ng/directive/validatorsSpec.js

https://gitlab.com/x33n/angular.js · JavaScript · 549 lines · 375 code · 173 blank · 1 comment · 5 complexity · 07d0dfaab24d697b14df69adec670b3b MD5 · raw file

  1. 'use strict';
  2. /* globals getInputCompileHelper: false */
  3. describe('validators', function() {
  4. var helper, $rootScope;
  5. beforeEach(function() {
  6. helper = getInputCompileHelper(this);
  7. });
  8. afterEach(function() {
  9. helper.dealoc();
  10. });
  11. beforeEach(inject(function(_$rootScope_) {
  12. $rootScope = _$rootScope_;
  13. }));
  14. describe('pattern', function() {
  15. it('should validate in-lined pattern', function() {
  16. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^\\d\\d\\d-\\d\\d-\\d\\d\\d\\d$/" />');
  17. helper.changeInputValueTo('x000-00-0000x');
  18. expect(inputElm).toBeInvalid();
  19. helper.changeInputValueTo('000-00-0000');
  20. expect(inputElm).toBeValid();
  21. helper.changeInputValueTo('000-00-0000x');
  22. expect(inputElm).toBeInvalid();
  23. helper.changeInputValueTo('123-45-6789');
  24. expect(inputElm).toBeValid();
  25. helper.changeInputValueTo('x');
  26. expect(inputElm).toBeInvalid();
  27. });
  28. it('should listen on ng-pattern when pattern is observed', function() {
  29. var value, patternVal = /^\w+$/;
  30. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="pat" attr-capture />');
  31. helper.attrs.$observe('pattern', function(v) {
  32. value = helper.attrs.pattern;
  33. });
  34. $rootScope.$apply(function() {
  35. $rootScope.pat = patternVal;
  36. });
  37. expect(value).toBe(patternVal);
  38. });
  39. it('should validate in-lined pattern with modifiers', function() {
  40. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="/^abc?$/i" />');
  41. helper.changeInputValueTo('aB');
  42. expect(inputElm).toBeValid();
  43. helper.changeInputValueTo('xx');
  44. expect(inputElm).toBeInvalid();
  45. });
  46. it('should validate pattern from scope', function() {
  47. $rootScope.regexp = /^\d\d\d-\d\d-\d\d\d\d$/;
  48. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
  49. helper.changeInputValueTo('x000-00-0000x');
  50. expect(inputElm).toBeInvalid();
  51. helper.changeInputValueTo('000-00-0000');
  52. expect(inputElm).toBeValid();
  53. helper.changeInputValueTo('000-00-0000x');
  54. expect(inputElm).toBeInvalid();
  55. helper.changeInputValueTo('123-45-6789');
  56. expect(inputElm).toBeValid();
  57. helper.changeInputValueTo('x');
  58. expect(inputElm).toBeInvalid();
  59. $rootScope.$apply(function() {
  60. $rootScope.regexp = /abc?/;
  61. });
  62. helper.changeInputValueTo('ab');
  63. expect(inputElm).toBeValid();
  64. helper.changeInputValueTo('xx');
  65. expect(inputElm).toBeInvalid();
  66. });
  67. it('should perform validations when the ngPattern scope value changes', function() {
  68. $rootScope.regexp = /^[a-z]+$/;
  69. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-pattern="regexp" />');
  70. helper.changeInputValueTo('abcdef');
  71. expect(inputElm).toBeValid();
  72. helper.changeInputValueTo('123');
  73. expect(inputElm).toBeInvalid();
  74. $rootScope.$apply(function() {
  75. $rootScope.regexp = /^\d+$/;
  76. });
  77. expect(inputElm).toBeValid();
  78. helper.changeInputValueTo('abcdef');
  79. expect(inputElm).toBeInvalid();
  80. $rootScope.$apply(function() {
  81. $rootScope.regexp = '';
  82. });
  83. expect(inputElm).toBeValid();
  84. });
  85. it('should register "pattern" with the model validations when the pattern attribute is used', function() {
  86. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" pattern="^\\d+$" />');
  87. helper.changeInputValueTo('abcd');
  88. expect(inputElm).toBeInvalid();
  89. expect($rootScope.form.input.$error.pattern).toBe(true);
  90. helper.changeInputValueTo('12345');
  91. expect(inputElm).toBeValid();
  92. expect($rootScope.form.input.$error.pattern).not.toBe(true);
  93. });
  94. it('should not throw an error when scope pattern can\'t be found', function() {
  95. expect(function() {
  96. var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
  97. $rootScope.$apply("foo = 'bar'");
  98. }).not.toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
  99. });
  100. it('should throw an error when the scope pattern is not a regular expression', function() {
  101. expect(function() {
  102. var inputElm = helper.compileInput('<input type="text" ng-model="foo" ng-pattern="fooRegexp" />');
  103. $rootScope.$apply(function() {
  104. $rootScope.fooRegexp = {};
  105. $rootScope.foo = 'bar';
  106. });
  107. }).toThrowMatching(/^\[ngPattern:noregexp\] Expected fooRegexp to be a RegExp but was/);
  108. });
  109. it('should be invalid if entire string does not match pattern', function() {
  110. var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}">');
  111. helper.changeInputValueTo('1234');
  112. expect($rootScope.form.test.$error.pattern).not.toBe(true);
  113. expect(inputElm).toBeValid();
  114. helper.changeInputValueTo('123');
  115. expect($rootScope.form.test.$error.pattern).toBe(true);
  116. expect(inputElm).not.toBeValid();
  117. helper.changeInputValueTo('12345');
  118. expect($rootScope.form.test.$error.pattern).toBe(true);
  119. expect(inputElm).not.toBeValid();
  120. });
  121. it('should be cope with patterns that start with ^', function() {
  122. var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="^\\d{4}">');
  123. helper.changeInputValueTo('1234');
  124. expect($rootScope.form.test.$error.pattern).not.toBe(true);
  125. expect(inputElm).toBeValid();
  126. helper.changeInputValueTo('123');
  127. expect($rootScope.form.test.$error.pattern).toBe(true);
  128. expect(inputElm).not.toBeValid();
  129. helper.changeInputValueTo('12345');
  130. expect($rootScope.form.test.$error.pattern).toBe(true);
  131. expect(inputElm).not.toBeValid();
  132. });
  133. it('should be cope with patterns that end with $', function() {
  134. var inputElm = helper.compileInput('<input type="text" name="test" ng-model="value" pattern="\\d{4}$">');
  135. helper.changeInputValueTo('1234');
  136. expect($rootScope.form.test.$error.pattern).not.toBe(true);
  137. expect(inputElm).toBeValid();
  138. helper.changeInputValueTo('123');
  139. expect($rootScope.form.test.$error.pattern).toBe(true);
  140. expect(inputElm).not.toBeValid();
  141. helper.changeInputValueTo('12345');
  142. expect($rootScope.form.test.$error.pattern).toBe(true);
  143. expect(inputElm).not.toBeValid();
  144. });
  145. });
  146. describe('minlength', function() {
  147. it('should invalidate values that are shorter than the given minlength', function() {
  148. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="3" />');
  149. helper.changeInputValueTo('aa');
  150. expect(inputElm).toBeInvalid();
  151. helper.changeInputValueTo('aaa');
  152. expect(inputElm).toBeValid();
  153. });
  154. it('should listen on ng-minlength when minlength is observed', function() {
  155. var value = 0;
  156. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-minlength="min" attr-capture />');
  157. helper.attrs.$observe('minlength', function(v) {
  158. value = toInt(helper.attrs.minlength);
  159. });
  160. $rootScope.$apply('min = 5');
  161. expect(value).toBe(5);
  162. });
  163. it('should observe the standard minlength attribute and register it as a validator on the model', function() {
  164. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="{{ min }}" />');
  165. $rootScope.$apply('min = 10');
  166. helper.changeInputValueTo('12345');
  167. expect(inputElm).toBeInvalid();
  168. expect($rootScope.form.input.$error.minlength).toBe(true);
  169. $rootScope.$apply('min = 5');
  170. expect(inputElm).toBeValid();
  171. expect($rootScope.form.input.$error.minlength).not.toBe(true);
  172. });
  173. it('should validate when the model is initalized as a number', function() {
  174. $rootScope.value = 12345;
  175. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
  176. expect($rootScope.value).toBe(12345);
  177. expect($rootScope.form.input.$error.minlength).toBeUndefined();
  178. });
  179. it('should validate emptiness against the viewValue', function() {
  180. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
  181. var ctrl = inputElm.controller('ngModel');
  182. spyOn(ctrl, '$isEmpty').andCallThrough();
  183. ctrl.$parsers.push(function(value) {
  184. return value + '678';
  185. });
  186. helper.changeInputValueTo('12345');
  187. expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
  188. });
  189. });
  190. describe('maxlength', function() {
  191. it('should invalidate values that are longer than the given maxlength', function() {
  192. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="5" />');
  193. helper.changeInputValueTo('aaaaaaaa');
  194. expect(inputElm).toBeInvalid();
  195. helper.changeInputValueTo('aaa');
  196. expect(inputElm).toBeValid();
  197. });
  198. it('should only accept empty values when maxlength is 0', function() {
  199. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="0" />');
  200. helper.changeInputValueTo('');
  201. expect(inputElm).toBeValid();
  202. helper.changeInputValueTo('a');
  203. expect(inputElm).toBeInvalid();
  204. });
  205. it('should accept values of any length when maxlength is negative', function() {
  206. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="-1" />');
  207. helper.changeInputValueTo('');
  208. expect(inputElm).toBeValid();
  209. helper.changeInputValueTo('aaaaaaaaaa');
  210. expect(inputElm).toBeValid();
  211. });
  212. it('should accept values of any length when maxlength is non-numeric', function() {
  213. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="{{maxlength}}" />');
  214. helper.changeInputValueTo('aaaaaaaaaa');
  215. $rootScope.$apply('maxlength = "5"');
  216. expect(inputElm).toBeInvalid();
  217. $rootScope.$apply('maxlength = "abc"');
  218. expect(inputElm).toBeValid();
  219. $rootScope.$apply('maxlength = ""');
  220. expect(inputElm).toBeValid();
  221. $rootScope.$apply('maxlength = null');
  222. expect(inputElm).toBeValid();
  223. $rootScope.someObj = {};
  224. $rootScope.$apply('maxlength = someObj');
  225. expect(inputElm).toBeValid();
  226. });
  227. it('should listen on ng-maxlength when maxlength is observed', function() {
  228. var value = 0;
  229. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-maxlength="max" attr-capture />');
  230. helper.attrs.$observe('maxlength', function(v) {
  231. value = toInt(helper.attrs.maxlength);
  232. });
  233. $rootScope.$apply('max = 10');
  234. expect(value).toBe(10);
  235. });
  236. it('should observe the standard maxlength attribute and register it as a validator on the model', function() {
  237. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
  238. $rootScope.$apply('max = 1');
  239. helper.changeInputValueTo('12345');
  240. expect(inputElm).toBeInvalid();
  241. expect($rootScope.form.input.$error.maxlength).toBe(true);
  242. $rootScope.$apply('max = 6');
  243. expect(inputElm).toBeValid();
  244. expect($rootScope.form.input.$error.maxlength).not.toBe(true);
  245. });
  246. it('should assign the correct model after an observed validator became valid', function() {
  247. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
  248. $rootScope.$apply('max = 1');
  249. helper.changeInputValueTo('12345');
  250. expect($rootScope.value).toBeUndefined();
  251. $rootScope.$apply('max = 6');
  252. expect($rootScope.value).toBe('12345');
  253. });
  254. it('should assign the correct model after an observed validator became invalid', function() {
  255. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
  256. $rootScope.$apply('max = 6');
  257. helper.changeInputValueTo('12345');
  258. expect($rootScope.value).toBe('12345');
  259. $rootScope.$apply('max = 1');
  260. expect($rootScope.value).toBeUndefined();
  261. });
  262. it('should leave the value as invalid if observed maxlength changed, but is still invalid', function() {
  263. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="{{ max }}" />');
  264. $rootScope.$apply('max = 1');
  265. helper.changeInputValueTo('12345');
  266. expect(inputElm).toBeInvalid();
  267. expect($rootScope.form.input.$error.maxlength).toBe(true);
  268. expect($rootScope.value).toBeUndefined();
  269. $rootScope.$apply('max = 3');
  270. expect(inputElm).toBeInvalid();
  271. expect($rootScope.form.input.$error.maxlength).toBe(true);
  272. expect($rootScope.value).toBeUndefined();
  273. });
  274. it('should not notify if observed maxlength changed, but is still invalid', function() {
  275. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" ng-change="ngChangeSpy()" ' +
  276. 'maxlength="{{ max }}" />');
  277. $rootScope.$apply('max = 1');
  278. helper.changeInputValueTo('12345');
  279. $rootScope.ngChangeSpy = jasmine.createSpy();
  280. $rootScope.$apply('max = 3');
  281. expect($rootScope.ngChangeSpy).not.toHaveBeenCalled();
  282. });
  283. it('should leave the model untouched when validating before model initialization', function() {
  284. $rootScope.value = '12345';
  285. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" minlength="3" />');
  286. expect($rootScope.value).toBe('12345');
  287. });
  288. it('should validate when the model is initalized as a number', function() {
  289. $rootScope.value = 12345;
  290. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
  291. expect($rootScope.value).toBe(12345);
  292. expect($rootScope.form.input.$error.maxlength).toBeUndefined();
  293. });
  294. it('should validate emptiness against the viewValue', function() {
  295. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" maxlength="10" />');
  296. var ctrl = inputElm.controller('ngModel');
  297. spyOn(ctrl, '$isEmpty').andCallThrough();
  298. ctrl.$parsers.push(function(value) {
  299. return value + '678';
  300. });
  301. helper.changeInputValueTo('12345');
  302. expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
  303. });
  304. });
  305. describe('required', function() {
  306. it('should allow bindings via ngRequired', function() {
  307. var inputElm = helper.compileInput('<input type="text" ng-model="value" ng-required="required" />');
  308. $rootScope.$apply("required = false");
  309. helper.changeInputValueTo('');
  310. expect(inputElm).toBeValid();
  311. $rootScope.$apply("required = true");
  312. expect(inputElm).toBeInvalid();
  313. $rootScope.$apply("value = 'some'");
  314. expect(inputElm).toBeValid();
  315. helper.changeInputValueTo('');
  316. expect(inputElm).toBeInvalid();
  317. $rootScope.$apply("required = false");
  318. expect(inputElm).toBeValid();
  319. });
  320. it('should invalid initial value with bound required', function() {
  321. var inputElm = helper.compileInput('<input type="text" ng-model="value" required="{{required}}" />');
  322. $rootScope.$apply('required = true');
  323. expect(inputElm).toBeInvalid();
  324. });
  325. it('should be $invalid but $pristine if not touched', function() {
  326. var inputElm = helper.compileInput('<input type="text" ng-model="name" name="alias" required />');
  327. $rootScope.$apply("name = null");
  328. expect(inputElm).toBeInvalid();
  329. expect(inputElm).toBePristine();
  330. helper.changeInputValueTo('');
  331. expect(inputElm).toBeInvalid();
  332. expect(inputElm).toBeDirty();
  333. });
  334. it('should allow empty string if not required', function() {
  335. var inputElm = helper.compileInput('<input type="text" ng-model="foo" />');
  336. helper.changeInputValueTo('a');
  337. helper.changeInputValueTo('');
  338. expect($rootScope.foo).toBe('');
  339. });
  340. it('should set $invalid when model undefined', function() {
  341. var inputElm = helper.compileInput('<input type="text" ng-model="notDefined" required />');
  342. expect(inputElm).toBeInvalid();
  343. });
  344. it('should consider bad input as an error before any other errors are considered', function() {
  345. var inputElm = helper.compileInput('<input type="text" ng-model="value" required />', { badInput: true });
  346. var ctrl = inputElm.controller('ngModel');
  347. ctrl.$parsers.push(function() {
  348. return undefined;
  349. });
  350. helper.changeInputValueTo('abc123');
  351. expect(ctrl.$error.parse).toBe(true);
  352. expect(inputElm).toHaveClass('ng-invalid-parse');
  353. expect(inputElm).toBeInvalid(); // invalid because of the number validator
  354. });
  355. it('should allow `false` as a valid value when the input type is not "checkbox"', function() {
  356. var inputElm = helper.compileInput('<input type="radio" ng-value="true" ng-model="answer" required />' +
  357. '<input type="radio" ng-value="false" ng-model="answer" required />');
  358. $rootScope.$apply();
  359. expect(inputElm).toBeInvalid();
  360. $rootScope.$apply("answer = true");
  361. expect(inputElm).toBeValid();
  362. $rootScope.$apply("answer = false");
  363. expect(inputElm).toBeValid();
  364. });
  365. it('should validate emptiness against the viewValue', function() {
  366. var inputElm = helper.compileInput('<input type="text" name="input" ng-model="value" required />');
  367. var ctrl = inputElm.controller('ngModel');
  368. spyOn(ctrl, '$isEmpty').andCallThrough();
  369. ctrl.$parsers.push(function(value) {
  370. return value + '678';
  371. });
  372. helper.changeInputValueTo('12345');
  373. expect(ctrl.$isEmpty).toHaveBeenCalledWith('12345');
  374. });
  375. });
  376. });