Skip to content

Commit 030843e

Browse files
committed
Added unit and e2e tests
1 parent a9e5b4c commit 030843e

20 files changed

+703
-156
lines changed

README.md

+22-24
Original file line numberDiff line numberDiff line change
@@ -1,43 +1,41 @@
1-
# Angular multiple Cross-Field Validation
1+
# An angular custom cross-field validator for multiple groups of form input fields
22

3-
A demo angular application for my article at: http://clade.co.uk/adewole/blog/angular/cross-field-validation
3+
A demo angular application with unit and e2e tests, for the article at: http://clade.co.uk/adewole/blog/angular/cross-field-validation
44

5-
I wanted to share my experience and gather feedback on creating an Angular cross-field validator that compares and validates the values of multiple groups of input fields and their relative confirm input fields. The app is built in Angular 10 – strict mode, for both reactive and template-driven forms.
5+
I wanted to share my experience and gather feedback on creating a custom Angular cross-field validator that compares and validates the values of multiple groups of input fields and their relative confirm input fields. The app is built with Angular 10 – strict mode, for both reactive and template-driven forms.
66

77
By [Ade Oyebadejo](http://www.clade.co.uk/adewole)
88

9-
## Technology
9+
### Technology
1010

1111
This project was generated with [Angular CLI](https://github.com/angular/angular-cli) version 10.0.5.
1212

13-
## Getting Started
13+
### Getting Started
1414

15-
1. Clone this repository
1615

17-
```bash
18-
git clone https://github.com/ade-dev/angular-cross-field-validation.git angular-cross-field-validation
19-
cd angular-custom-cross-validator
20-
```
16+
```bash
17+
# Clone the repository
18+
git clone https://github.com/ade-dev/angular-cross-field-validation.git
2119

22-
1. Install the npm packages
20+
# Navigate to the app's root directory
21+
cd angular-custom-cross-validator
2322

24-
```bash
25-
npm install
26-
```
23+
# Install the dependencies
24+
npm install
2725

28-
1. Run the app!
26+
# Run the application
27+
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files
2928

30-
```bash
31-
Run `ng serve` for a dev server. Navigate to `http://localhost:4200/`. The app will automatically reload if you change any of the source files.
32-
```
33-
## Code scaffolding
29+
```
3430
35-
Run `ng generate component component-name` to generate a new component. You can also use `ng generate directive|pipe|service|class|guard|interface|enum|module`.
36-
37-
## Build
31+
### Build
3832
3933
Run `ng build` to build the project. The build artifacts will be stored in the `dist/` directory. Use the `--prod` flag for a production build.
4034
41-
## Further help
35+
### Running unit tests
36+
37+
Run `ng test` to execute the unit tests via Karma.
38+
39+
### Running end-to-end tests
4240
43-
To get more help on the Angular CLI use `ng help` or go check out the [Angular CLI README](https://github.com/angular/angular-cli/blob/master/README.md).
41+
Run `ng e2e` to execute the end-to-end tests via Protractor.

angular.json

+9-4
Original file line numberDiff line numberDiff line change
@@ -94,7 +94,10 @@
9494
"styles": [
9595
"src/styles.css"
9696
],
97-
"scripts": []
97+
"scripts": [],
98+
"codeCoverageExclude": [
99+
"src/tests/**"
100+
]
98101
}
99102
},
100103
"lint": {
@@ -114,7 +117,8 @@
114117
"builder": "@angular-devkit/build-angular:protractor",
115118
"options": {
116119
"protractorConfig": "e2e/protractor.conf.js",
117-
"devServerTarget": "angular-form-input-cross-validator:serve"
120+
"devServerTarget": "angular-form-input-cross-validator:serve",
121+
"port": 4300
118122
},
119123
"configurations": {
120124
"production": {
@@ -123,6 +127,7 @@
123127
}
124128
}
125129
}
126-
}},
130+
}
131+
},
127132
"defaultProject": "angular-form-input-cross-validator"
128-
}
133+
}

e2e/src/app.e2e-spec.ts

+143-13
Original file line numberDiff line numberDiff line change
@@ -1,23 +1,153 @@
11
import { AppPage } from './app.po';
2-
import { browser, logging } from 'protractor';
2+
import { browser, by, element, ElementArrayFinder, ElementFinder } from 'protractor';
33

4-
describe('workspace-project App', () => {
5-
let page: AppPage;
4+
describe('Cross-field validation App', () => {
5+
6+
let appPage: AppPage;
67

78
beforeEach(() => {
8-
page = new AppPage();
9+
appPage = new AppPage();
910
});
1011

11-
it('should display welcome message', () => {
12-
page.navigateTo();
13-
expect(page.getTitleText()).toEqual('angular-form-input-cross-validator app is running!');
12+
it('Should render page title', () => {
13+
appPage.navigateTo();
14+
expect(appPage.getTitleText()).toEqual('Angular - Reactive form input value cross-validation');
1415
});
16+
});
17+
18+
describe('Reactive and template-driven form validation tests: ', () => {
19+
20+
beforeAll(() => browser.get(''));
21+
22+
describe('Reactive form', () => {
23+
beforeAll(() => {
24+
getElements('app-reactive-form');
25+
});
1526

16-
afterEach(async () => {
17-
// Assert that there are no errors emitted from the browser
18-
const logs = await browser.manage().logs().get(logging.Type.BROWSER);
19-
expect(logs).not.toContain(jasmine.objectContaining({
20-
level: logging.Level.SEVERE,
21-
} as logging.Entry));
27+
initialStateTest();
28+
emailTests();
29+
compareEmails();
30+
passwordTests();
31+
comparePasswords();
32+
});
33+
34+
describe('Template-driven form', () => {
35+
beforeAll(() => {
36+
navigateToPage('templateForm');
37+
getElements('app-template-driven-form');
38+
});
39+
40+
initialStateTest();
41+
emailTests();
42+
compareEmails();
43+
passwordTests();
44+
comparePasswords();
2245
});
2346
});
47+
48+
let page: {
49+
form: ElementFinder,
50+
nameInput: ElementFinder,
51+
emailInput: ElementFinder,
52+
confirmEmailInput: ElementFinder,
53+
passwordInput: ElementFinder,
54+
confirmPasswordInput: ElementFinder,
55+
errorMessages: ElementArrayFinder,
56+
};
57+
58+
function getElements(selector: string) {
59+
const template = element(by.css(selector));
60+
page = {
61+
form: template.element(by.css('form')),
62+
nameInput: template.element(by.css('#name')),
63+
emailInput: template.element(by.css('#email')),
64+
confirmEmailInput: template.element(by.css('#confirmEmail')),
65+
passwordInput: template.element(by.css('#password')),
66+
confirmPasswordInput: template.element(by.css('#confirmPassword')),
67+
errorMessages: template.all(by.css('div.alert')),
68+
};
69+
}
70+
71+
function navigateToPage(linkId: string) {
72+
element(by.id(linkId)).click().then(() => {
73+
browser.sleep(2000).then(() => {
74+
browser.getCurrentUrl();
75+
// .then((actualUrl) => { console.log("actualUrl: ", actualUrl); });
76+
});
77+
});
78+
}
79+
80+
function initialStateTest() {
81+
it('Should have error on load', async () => {
82+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
83+
});
84+
}
85+
86+
function emailTests() {
87+
88+
it('Should render "email required" error when Email has no value', async () => {
89+
await browser.actions().click(page.emailInput).perform();
90+
await browser.actions().click(page.confirmEmailInput).perform();
91+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
92+
expect(await page.errorMessages.get(0).getText()).toContain('Email is required');
93+
});
94+
95+
it("Should render 'Invalid email' error when Email value is 'abc'", async () => {
96+
await page.emailInput.sendKeys('abc');
97+
await browser.actions().click(page.confirmEmailInput).perform();
98+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
99+
expect(await page.errorMessages.get(0).getText()).toContain('Please enter a valid email.');
100+
});
101+
102+
it("Should clear 'Invalid email' error when Email value is '[email protected]'", async () => {
103+
await page.emailInput.clear();
104+
await page.emailInput.sendKeys('[email protected]');
105+
await browser.actions().click(page.confirmEmailInput).perform();
106+
expect(await page.errorMessages.get(0).getText()).not.toContain('Please enter a valid email.');
107+
});
108+
};
109+
110+
function compareEmails() {
111+
112+
it("Should render 'Email entries do not match' error if 'confirmEmail' value is '[email protected]'", async () => {
113+
await page.confirmEmailInput.sendKeys('[email protected]');
114+
await browser.actions().click(page.passwordInput).perform();
115+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
116+
expect(await page.errorMessages.get(0).getText()).toBe('Email entries do not match.');
117+
});
118+
119+
it("Should clear 'Email do not match' error if 'email' and 'confirmEmail' values match", async () => {
120+
await page.confirmEmailInput.clear();
121+
await page.confirmEmailInput.sendKeys('[email protected]');
122+
await browser.actions().click(page.passwordInput).perform();
123+
expect(await page.errorMessages.get(0).getText()).not.toContain('Email entries do not match.');
124+
});
125+
};
126+
127+
function passwordTests() {
128+
129+
it('Should render "password required" error when password has no value', async () => {
130+
await browser.actions().click(page.passwordInput).perform();
131+
await browser.actions().click(page.confirmPasswordInput).perform();
132+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
133+
expect(await page.errorMessages.get(0).getText()).toContain('Password is required');
134+
});
135+
};
136+
137+
function comparePasswords() {
138+
139+
it("should render 'password do not match' error if 'password' and 'confirmPassword' values do not match", async () => {
140+
await page.passwordInput.sendKeys('abcdef');
141+
await page.confirmPasswordInput.sendKeys('uvxyz');
142+
await browser.actions().mouseDown(page.passwordInput).perform();
143+
expect(await page.form.getAttribute('class')).toMatch('ng-invalid');
144+
expect(await page.errorMessages.get(0).getText()).toBe('Password entries do not match');
145+
});
146+
147+
it("Should clear 'Password do not match' error if 'password' and 'confirmPassword' values match", async () => {
148+
await page.confirmPasswordInput.clear();
149+
await page.confirmPasswordInput.sendKeys('abcdef');
150+
await element(by.css('app-root')).click();
151+
expect(await page.errorMessages.get(0).isPresent()).toBe(false);
152+
});
153+
};

e2e/src/app.po.ts

+1-1
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,6 @@ export class AppPage {
66
}
77

88
getTitleText(): Promise<string> {
9-
return element(by.css('app-root .content span')).getText() as Promise<string>;
9+
return element(by.css('app-root h1')).getText() as Promise<string>;
1010
}
1111
}

package-lock.json

+24-24
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)