docs(forms): fix css, form validation e2e, other tweaks (#2235)

This commit is contained in:
Ward Bell 2016-08-31 18:15:14 -07:00 committed by GitHub
parent 0c962712b3
commit 18ec087f07
7 changed files with 196 additions and 82 deletions

View File

@ -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');
}

View File

@ -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);
});
});
});

View File

@ -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];

View File

@ -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];

View File

@ -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 */
}

View File

@ -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 */

View File

@ -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,