docs(forms): fix css, form validation e2e, other tweaks (#2235)
This commit is contained in:
parent
0c962712b3
commit
18ec087f07
|
@ -0,0 +1,180 @@
|
|||
/// <reference path="../_protractor/e2e.d.ts" />
|
||||
'use strict'; // necessary for node!
|
||||
|
||||
// THESE TESTS ARE INCOMPLETE
|
||||
describeIf(browser.appIsTs || browser.appIsJs, 'Form Validation Tests', function () {
|
||||
|
||||
beforeAll(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
describe('Hero Form 1', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-template1');
|
||||
});
|
||||
|
||||
tests();
|
||||
});
|
||||
|
||||
describe('Hero Form 2', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-template2');
|
||||
});
|
||||
|
||||
tests();
|
||||
bobTests();
|
||||
});
|
||||
|
||||
describe('Hero Form 3 (Reactive)', () => {
|
||||
beforeAll(() => {
|
||||
getPage('hero-form-reactive3');
|
||||
makeNameTooLong();
|
||||
});
|
||||
|
||||
tests();
|
||||
bobTests();
|
||||
});
|
||||
});
|
||||
|
||||
//////////
|
||||
|
||||
const testName = 'Test Name';
|
||||
|
||||
let page: {
|
||||
section: protractor.ElementFinder,
|
||||
form: protractor.ElementFinder,
|
||||
title: protractor.ElementFinder,
|
||||
nameInput: protractor.ElementFinder,
|
||||
alterEgoInput: protractor.ElementFinder,
|
||||
powerSelect: protractor.ElementFinder,
|
||||
errorMessages: protractor.ElementArrayFinder,
|
||||
heroFormButtons: protractor.ElementArrayFinder,
|
||||
heroSubmitted: protractor.ElementFinder
|
||||
};
|
||||
|
||||
function getPage(sectionTag: string) {
|
||||
let section = element(by.css(sectionTag));
|
||||
let buttons = section.all(by.css('button'));
|
||||
|
||||
page = {
|
||||
section: section,
|
||||
form: section.element(by.css('form')),
|
||||
title: section.element(by.css('h1')),
|
||||
nameInput: section.element(by.css('#name')),
|
||||
alterEgoInput: section.element(by.css('#alterEgo')),
|
||||
powerSelect: section.element(by.css('#power')),
|
||||
errorMessages: section.all(by.css('div.alert')),
|
||||
heroFormButtons: buttons,
|
||||
heroSubmitted: section.element(by.css('hero-submitted > div'))
|
||||
};
|
||||
}
|
||||
|
||||
function tests() {
|
||||
it('should display correct title', function () {
|
||||
expect(page.title.getText()).toContain('Hero Form');
|
||||
});
|
||||
|
||||
it('should not display submitted message before submit', function () {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(false);
|
||||
});
|
||||
|
||||
it('should have form buttons', function () {
|
||||
expect(page.heroFormButtons.count()).toEqual(2);
|
||||
});
|
||||
|
||||
it('should have error at start', function () {
|
||||
expectFormIsInvalid();
|
||||
});
|
||||
|
||||
// it('showForm', function () {
|
||||
// page.form.getInnerHtml().then(html => console.log(html));
|
||||
// });
|
||||
|
||||
it('should have disabled submit button', function () {
|
||||
expect(page.heroFormButtons.get(0).isEnabled()).toBe(false);
|
||||
});
|
||||
|
||||
it('resetting name to valid name should clear errors', function () {
|
||||
const ele = page.nameInput;
|
||||
expect(ele.isPresent()).toBe(true, 'nameInput should exist');
|
||||
ele.clear();
|
||||
ele.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
});
|
||||
|
||||
it('should produce "required" error after clearing name', function () {
|
||||
page.nameInput.clear();
|
||||
// page.alterEgoInput.click(); // to blur ... didn't work
|
||||
page.nameInput.sendKeys('x', protractor.Key.BACK_SPACE); // ugh!
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
expect(page.errorMessages.get(0).getText()).toContain('required');
|
||||
});
|
||||
|
||||
it('should produce "at least 4 characters" error when name="x"', function () {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('x'); // too short
|
||||
expectFormIsInvalid();
|
||||
expect(page.errorMessages.get(0).getText()).toContain('at least 4 characters');
|
||||
});
|
||||
|
||||
it('resetting name to valid name again should clear errors', function () {
|
||||
page.nameInput.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
});
|
||||
|
||||
it('should have enabled submit button', function () {
|
||||
const submitBtn = page.heroFormButtons.get(0);
|
||||
expect(submitBtn.isEnabled()).toBe(true);
|
||||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
page.heroFormButtons.get(0).click();
|
||||
expect(page.title.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('submitted form should be displayed', function () {
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2'))).toBe(true);
|
||||
});
|
||||
|
||||
it('submitted form should have new hero name', function () {
|
||||
expect(page.heroSubmitted.getText()).toContain(testName);
|
||||
});
|
||||
|
||||
it('clicking edit button should reveal form again', function () {
|
||||
const editBtn = page.heroSubmitted.element(by.css('button'));
|
||||
editBtn.click();
|
||||
expect(page.heroSubmitted.isElementPresent(by.css('h2')))
|
||||
.toBe(false, 'submitted hidden again');
|
||||
expect(page.title.isDisplayed()).toBe(true, 'can see form title');
|
||||
});
|
||||
}
|
||||
|
||||
function expectFormIsValid() {
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-valid');
|
||||
}
|
||||
|
||||
function expectFormIsInvalid() {
|
||||
expect(page.form.getAttribute('class')).toMatch('ng-invalid');
|
||||
}
|
||||
|
||||
function bobTests() {
|
||||
const emsg = 'Someone named "Bob" cannot be a hero.';
|
||||
|
||||
it('should produce "no bob" error after setting name to "Bobby"', function () {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys('Bobby');
|
||||
expectFormIsInvalid();
|
||||
expect(page.errorMessages.get(0).getText()).toBe(emsg);
|
||||
});
|
||||
|
||||
it('should be ok again with valid name', function () {
|
||||
page.nameInput.clear();
|
||||
page.nameInput.sendKeys(testName);
|
||||
expectFormIsValid();
|
||||
});
|
||||
}
|
||||
|
||||
function makeNameTooLong() {
|
||||
// make the first name invalid
|
||||
page.nameInput.sendKeys('ThisHeroNameHasWayWayTooManyLetters');
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/// <reference path="../_protractor/e2e.d.ts" />
|
||||
'use strict'; // necessary for node!
|
||||
describeIf(browser.appIsTs || browser.appIsJs, 'Forms Tests', function () {
|
||||
|
||||
beforeEach(function () {
|
||||
browser.get('');
|
||||
});
|
||||
|
||||
it('should display correct title', function () {
|
||||
expect(element.all(by.css('h1')).get(0).getText()).toEqual('Hero Form');
|
||||
});
|
||||
|
||||
|
||||
it('should not display message before submit', function () {
|
||||
let ele = element(by.css('h2'));
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
let ele = element.all(by.css('h1')).get(0);
|
||||
expect(ele.isDisplayed()).toBe(true);
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(function() {
|
||||
expect(ele.isDisplayed()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should display message after submit', function () {
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
b.click().then(function() {
|
||||
expect(element(by.css('h2')).getText()).toContain('You submitted the following');
|
||||
});
|
||||
});
|
||||
|
||||
it('should hide form after submit', function () {
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
expect(alterEgoEle.isDisplayed()).toBe(true);
|
||||
let submitButtonEle = element.all(by.css('button[type=submit]')).get(0);
|
||||
submitButtonEle.click().then(function() {
|
||||
expect(alterEgoEle.isDisplayed()).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
it('should reflect submitted data after submit', function () {
|
||||
let test = 'testing 1 2 3';
|
||||
let newValue: string;
|
||||
let alterEgoEle = element.all(by.css('input[ngcontrol=alterEgo]')).get(0);
|
||||
alterEgoEle.getAttribute('value').then(function(value) {
|
||||
// alterEgoEle.sendKeys(test);
|
||||
sendKeys(alterEgoEle, test);
|
||||
newValue = value + test;
|
||||
expect(alterEgoEle.getAttribute('value')).toEqual(newValue);
|
||||
}).then(function() {
|
||||
let b = element.all(by.css('button[type=submit]')).get(0);
|
||||
return b.click();
|
||||
}).then(function() {
|
||||
let alterEgoTextEle = element(by.cssContainingText('div', 'Alter Ego'));
|
||||
expect(alterEgoTextEle.isPresent()).toBe(true, 'cannot locate "Alter Ego" label');
|
||||
let divEle = element(by.cssContainingText('div', newValue));
|
||||
expect(divEle.isPresent()).toBe(true, 'cannot locate div with this text: ' + newValue);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
@ -38,7 +38,6 @@ export class HeroFormReactiveComponent implements OnInit {
|
|||
addHero() {
|
||||
this.hero = new Hero(42, '', '');
|
||||
this.buildForm();
|
||||
this.onValueChanged();
|
||||
// #enddocregion add-hero
|
||||
// #enddocregion class
|
||||
|
||||
|
@ -74,17 +73,20 @@ export class HeroFormReactiveComponent implements OnInit {
|
|||
|
||||
this.heroForm.valueChanges
|
||||
.subscribe(data => this.onValueChanged(data));
|
||||
|
||||
this.onValueChanged(); // (re)set validation messages now
|
||||
}
|
||||
|
||||
// #enddocregion form-builder
|
||||
|
||||
onValueChanged(data?: any) {
|
||||
const controls = this.heroForm ? this.heroForm.controls : {};
|
||||
if (!this.heroForm) { return; }
|
||||
const form = this.heroForm;
|
||||
|
||||
for (const field in this.formErrors) {
|
||||
// clear previous error message (if any)
|
||||
this.formErrors[field] = '';
|
||||
const control = controls[field];
|
||||
const control = form.get(field);
|
||||
|
||||
if (control && control.dirty && !control.valid) {
|
||||
const messages = this.validationMessages[field];
|
||||
|
|
|
@ -60,12 +60,13 @@ export class HeroFormTemplate2Component implements AfterViewChecked {
|
|||
|
||||
// #docregion handler
|
||||
onValueChanged(data?: any) {
|
||||
const controls = this.heroForm ? this.heroForm.controls : {};
|
||||
if (!this.heroForm) { return; }
|
||||
const form = this.heroForm.form;
|
||||
|
||||
for (const field in this.formErrors) {
|
||||
// clear previous error message (if any)
|
||||
this.formErrors[field] = '';
|
||||
const control = controls[field];
|
||||
const control = form.get(field);
|
||||
|
||||
if (control && control.dirty && !control.valid) {
|
||||
const messages = this.validationMessages[field];
|
||||
|
|
|
@ -1,11 +1,7 @@
|
|||
.ng-valid[required] {
|
||||
.ng-valid[required], .ng-valid.required {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
.ng-invalid:not(form) {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
|
||||
.ng-valid.required {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
|
@ -1,9 +1,9 @@
|
|||
/* #docregion */
|
||||
.ng-valid[required] {
|
||||
.ng-valid[required], .ng-valid.required {
|
||||
border-left: 5px solid #42A948; /* green */
|
||||
}
|
||||
|
||||
.ng-invalid {
|
||||
.ng-invalid:not(form) {
|
||||
border-left: 5px solid #a94442; /* red */
|
||||
}
|
||||
/* #enddocregion */
|
||||
/* #enddocregion */
|
||||
|
|
|
@ -327,7 +327,9 @@ a#reactive
|
|||
A real app would retrieve the hero asynchronously from a data service, a task best performed in the `ngOnInit` hook.
|
||||
:marked
|
||||
- the `buildForm` method uses the `FormBuilder` (`fb`) to declare the form control model.
|
||||
Then it attaches the same `onValueChanged` handler to the form.
|
||||
Then it attaches the same `onValueChanged` handler (there's a one line difference)
|
||||
to the form's `valueChanged` event and calls it immediately
|
||||
to set error messages for the new control model.
|
||||
|
||||
:marked
|
||||
#### _FormBuilder_ declaration
|
||||
|
@ -371,9 +373,6 @@ a#reactive
|
|||
Then it calls `buildForm` again which replaces the previous `heroForm` control model with a new one.
|
||||
The `<form>` tag's `[formGroup]` binding refreshes the page with the new control model.
|
||||
|
||||
Finally, it calls the `onValueChanged` handler to clear previous error messages and reset them
|
||||
to reflect Angular's validation of the new `hero` object.
|
||||
|
||||
Here's the complete reactive component file, compared to the two template-driven component files.
|
||||
+makeTabs(
|
||||
`cb-form-validation/ts/app/reactive/hero-form-reactive.component.ts,
|
||||
|
|
Loading…
Reference in New Issue