Skip to content

Commit 4f1abc2

Browse files
committedNov 8, 2019
feat: add attributes to improve e2e testing
If you were depending on the existance of date values in the classes of buttons, or other similar changes, your code will break after upgrading. I did not consider those classes part of the public API so I am not listing this as a breaking change.
1 parent 1729bdb commit 4f1abc2

19 files changed

+211
-113
lines changed
 

‎.travis.yml

+3
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,14 @@
11
sudo: required
2+
dist: xenial
23
language: node_js
34
notifications:
45
email: false
56
node_js:
67
- '10'
78
services:
89
- xvfb
10+
addons:
11+
chrome: stable
912
before_script:
1013
- npm prune
1114
script:

‎README.es_MX.md

+10
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,16 @@ Utiliza las clases `row` y `col` de bootstrap flex para el acomodo del component
102102
Si el contenedor padre no es lo suficientemente ancho (mayor a 340px) el diseño de la fila y columna que contiene el componente puede que no se muestre de manera atractiva.
103103
Otros lenguajes/locales es probable que requieran un contenedor un poco mas ancho para poder mostrar apropiadamente el contenido.
104104

105+
## End-to-End (e2e) testing with protractor
106+
107+
**Translation Pull request needed for this section**
108+
109+
The user interactions with a date-time picker make it difficult to write e2e tests that exactly replicate the users interaction with the picker.
110+
111+
Fortunately, this repository contains a file you can use in your e2e tests to cause the date/time picker to select any specified date.
112+
113+
See [./e2e/src/dl-date-time-picker-protractor.ts](./e2e/src/dl-date-time-picker-protractor.ts) for details.
114+
105115
## Configuración
106116
Utiliza el [generador de configuración automatizado](https://stackblitz.com/github/dalelotts/angular-bootstrap-datetimepicker-demo) (por favor hazme saber si no funciona para tu caso!),
107117
o ve a [https://dalelotts.github.io/angular-bootstrap-datetimepicker/](https://dalelotts.github.io/angular-bootstrap-datetimepicker/)

‎README.md

+8
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,14 @@ It uses bootstrap's flex `row` and `col` classes to layout the date/time picker
104104
If the parent container is too narrow (less than 340px in english), the row and column layout may wrap in ways that are not attractive.
105105
Other languages/locals may require a wider container to fit the contents.
106106

107+
## End-to-End (e2e) testing with protractor
108+
109+
The user interactions with a date-time picker make it difficult to write e2e tests that exactly replicate the users interaction with the picker.
110+
111+
Fortunately, this repository contains a file you can use in your e2e tests to cause the date/time picker to select any specified date.
112+
113+
See [./e2e/src/dl-date-time-picker-protractor.ts](./e2e/src/dl-date-time-picker-protractor.ts) for details.
114+
107115
## Configuration
108116

109117
Use the [automated configuration generator](https://stackblitz.com/github/dalelotts/angular-bootstrap-datetimepicker-demo) (please let me know if it does not work for your use case!),

‎e2e/src/app.e2e-spec.ts

+12-4
Original file line numberDiff line numberDiff line change
@@ -1,14 +1,22 @@
1-
import { AppPage } from './app.po';
1+
import {AppPage} from './app.po';
2+
import pickTime from './dl-date-time-picker-protractor';
3+
import moment = require('moment');
24

35
describe('workspace-project App', () => {
46
let page: AppPage;
57

68
beforeEach(() => {
79
page = new AppPage();
10+
return page.navigateTo();
811
});
912

10-
it('should display welcome message', () => {
11-
page.navigateTo();
12-
expect(page.getParagraphText()).toEqual('Welcome to angular-bootstrap-datetimepicker!');
13+
it('Picking time updates Selected Date:', async () => {
14+
const todayAtMidnight = moment('2003-11-07T21:32:17.800Z');
15+
const expectedDate = new Date('2003-11-07T21:30:00.000Z').toString();
16+
17+
await pickTime(page.getDateTimePicker(), todayAtMidnight.valueOf());
18+
19+
const selectedDate = page.getSelectedDate().getText();
20+
expect(selectedDate).toBe(`Selected Date: ${expectedDate}`);
1321
});
1422
});

‎e2e/src/app.po.ts

+8
Original file line numberDiff line numberDiff line change
@@ -8,4 +8,12 @@ export class AppPage {
88
getParagraphText() {
99
return element(by.css('app-root h1')).getText();
1010
}
11+
12+
getDateTimePicker() {
13+
return element(by.tagName('dl-date-time-picker'));
14+
}
15+
16+
getSelectedDate() {
17+
return element(by.id('selectedDate'));
18+
}
1119
}
+83
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
import {by, ElementArrayFinder, ElementFinder} from 'protractor';
2+
3+
/**
4+
* This file is an example of how you can implement automated end-to-end tests for the
5+
* date/time picker component.
6+
*
7+
* The overall strategy here is to use the `dl-abdtp-value` attributes, which contain numeric date values,
8+
* to determine which buttons to click on the picker in order to select a target dates.
9+
*/
10+
11+
/**
12+
* Clicks the nearest date button with a value less than or equal to the specified time.
13+
* @param dateButtons
14+
* the possible date buttons.
15+
* @param time
16+
* the desired selected time.
17+
*/
18+
19+
export function clickNearestDateButton(dateButtons: ElementArrayFinder, time: number) {
20+
return dateButtons
21+
.filter(button => button.getAttribute('dl-abdtp-value').then(buttonValue => Number(buttonValue) <= time))
22+
.last().click();
23+
}
24+
25+
/**
26+
* Have the dateTimePicker select the best possible value that is less than or equal to the specified time.
27+
* based on the current configuration of the dateTimePicker.
28+
*
29+
* This function will `not` select a time value `greater than` the specified time value.
30+
*
31+
* Additionally, this function depends on `ng-reflect-*` attributes which will never exist in a production build.
32+
*
33+
* @param dateTimePicker
34+
* the target dateTimePicker
35+
*
36+
* @param time
37+
* the desired selected time.
38+
*/
39+
async function pickTime(dateTimePicker: ElementFinder, time: number) {
40+
const dateButtons = dateTimePicker.all(by.className('dl-abdtp-date-button'));
41+
const leftButton = dateTimePicker.element(by.className('dl-abdtp-left-button'));
42+
const rightButton = dateTimePicker.element(by.className('dl-abdtp-right-button'));
43+
const upButton = dateTimePicker.element(by.className('dl-abdtp-up-button'));
44+
const viewAttributeName = 'data-dl-abdtp-view';
45+
const viewElement = dateTimePicker.element(by.css(`[${viewAttributeName}]`));
46+
47+
const maxView = await dateTimePicker.getAttribute('ng-reflect-max-view');
48+
const minView = await dateTimePicker.getAttribute('ng-reflect-min-view');
49+
50+
let currentView = await viewElement.getAttribute(viewAttributeName);
51+
52+
// Go up to the max view in order to drill down by selecting the nearest button value.
53+
while (maxView !== currentView) {
54+
await upButton.click();
55+
currentView = await viewElement.getAttribute(viewAttributeName);
56+
}
57+
58+
let firstButtonValue = await dateButtons.first().getAttribute('dl-abdtp-value');
59+
60+
// This left and right navigation to find the target date range assumes that earlier times are to the left.
61+
// This is true for the default implementation but may not be true for all implementations.
62+
63+
while (Number(firstButtonValue) > time) {
64+
await leftButton.click();
65+
firstButtonValue = await dateButtons.first().getAttribute('dl-abdtp-value');
66+
}
67+
68+
let lastButtonValue = await dateButtons.last().getAttribute('dl-abdtp-value');
69+
70+
while (Number(lastButtonValue) <= time) {
71+
await rightButton.click();
72+
lastButtonValue = await dateButtons.last().getAttribute('dl-abdtp-value');
73+
}
74+
75+
while (minView !== currentView) {
76+
await clickNearestDateButton(dateButtons, time);
77+
currentView = await viewElement.getAttribute(viewAttributeName);
78+
}
79+
80+
return clickNearestDateButton(dateButtons, time);
81+
}
82+
83+
export default pickTime;

‎package.json

+2-2
Original file line numberDiff line numberDiff line change
@@ -31,12 +31,12 @@
3131
"build:styles": "scss-bundle -c scss-bundle.config.json",
3232
"coverage:upload": "cat build/coverage/lcov.info | coveralls",
3333
"document": "compodoc --disableInternal --disablePrivate --disableLifeCycleHooks --assetsFolder screenshots -p src/tsconfig.doc.json --gaID UA-325325-19 -n \"Angular Bootstrap Date/Time Picker\"",
34-
"e2e": "ng e2e",
3534
"lint": "ng lint",
3635
"ng": "ng",
3736
"start": "ng serve",
38-
"test": "ng lint && ng test --watch=false --code-coverage && ng build --prod && npm run build:lib",
37+
"test": "ng lint && ng test --watch=false --code-coverage && npm run-script test:e2e && ng build --prod && npm run build:lib",
3938
"test:tdd": "ng test",
39+
"test:e2e": "ng e2e",
4040
"semantic-release": "semantic-release",
4141
"travis-deploy-once": "travis-deploy-once"
4242
},

‎src/app/app.component.html

+1-1
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,6 @@
7979
[(ngModel)]="selectedDate"
8080
(change)="onCustomDateChange($event)"></dl-date-time-picker>
8181
</div>
82-
<p>Selected Date: {{selectedDate}}</p>
82+
<p id="selectedDate">Selected Date: {{selectedDate}}</p>
8383
</div>
8484
</div>

‎src/lib/dl-date-time-picker/dl-date-time-picker.component.html

+11-8
Original file line numberDiff line numberDiff line change
@@ -1,36 +1,38 @@
1-
<div class="text-center dl-abdtp-{{_model.viewName}}-view ">
1+
<div class="text-center dl-abdtp-{{_model.viewName}}-view" [attr.data-dl-abdtp-view]="_model.viewName">
22
<div class="row align-items-center no-gutters">
33
<button class="col dl-abdtp-left-button align-items-center"
44
type="button"
55
[attr.aria-label]="_model.leftButton.ariaLabel"
6+
[attr.dl-abdtp-value]="_model.leftButton.value"
67
[attr.title]="_model.leftButton.ariaLabel"
7-
[ngClass]="_model.leftButton.classes"
8-
(click)="_onLeftClick()">
9-
<span class="left-icon" [ngClass]="leftIconClass"></span>
8+
(click)="_onLeftClick()"
9+
><span class="left-icon" [ngClass]="leftIconClass"></span>
1010
</button>
1111

1212
<div *ngIf="_model.viewName === (this.maxView || 'year'); then maxViewLabel else defaultViewLabel;"></div>
1313

1414
<button class="col dl-abdtp-right-button"
1515
type="button"
1616
[attr.aria-label]="_model.rightButton.ariaLabel"
17+
[attr.dl-abdtp-value]="_model.rightButton.value"
1718
[attr.title]="_model.rightButton.ariaLabel"
1819
(click)="_onRightClick()"
19-
[ngClass]="_model.rightButton.classes">
20-
<span class="right-icon" [ngClass]="rightIconClass"></span>
20+
><span class="right-icon" [ngClass]="rightIconClass"></span>
2121
</button>
2222
</div>
2323
<div (keydown)="_handleKeyDown($event)">
2424
<div *ngIf="_model.rowLabels?.length" class="row no-gutters">
25-
<div *ngFor="let label of _model.rowLabels" class="col align-items-center no-gutters dl-abdtp-col-label">{{label}}</div>
25+
<div *ngFor="let label of _model.rowLabels"
26+
class="col align-items-center no-gutters dl-abdtp-col-label">{{label}}</div>
2627
</div>
2728
<div *ngFor="let row of _model.rows" class="row align-items-center no-gutters">
2829
<div *ngFor="let cell of row.cells"
2930
role="gridcell"
30-
class="col dl-abdtp-date-button dl-abdtp-{{_model.viewName}} {{cell.value}}"
31+
class="col dl-abdtp-date-button dl-abdtp-{{_model.viewName}}"
3132
[ngClass]="cell.classes"
3233
[attr.aria-label]="cell.ariaLabel"
3334
[attr.aria-disabled]="cell.classes['dl-abdtp-disabled']"
35+
[attr.dl-abdtp-value]="cell.value"
3436
[attr.tabindex]="cell.classes['dl-abdtp-active'] ? 0 : -1"
3537
(click)="_onDateClick(cell)"
3638
(keydown.space)="_onDateClick(cell)"
@@ -47,6 +49,7 @@
4749
<button class="col-10 dl-abdtp-view-label dl-abdtp-up-button"
4850
type="button"
4951
[attr.aria-label]="_model.upButton.ariaLabel"
52+
[attr.dl-abdtp-value]="_model.upButton.value"
5053
[attr.title]="_model.upButton.ariaLabel"
5154
(click)="_onUpClick()"
5255
[ngClass]="_model.upButton.classes"

‎src/lib/dl-date-time-picker/dl-model-provider-day.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -75,7 +75,7 @@ export class DlDayModelProvider implements DlModelProvider {
7575
? selectedMilliseconds
7676
: moment(selectedMilliseconds).startOf('day').valueOf();
7777

78-
const result: DlDateTimePickerModel = {
78+
return {
7979
viewName: 'day',
8080
viewLabel: startOfMonth.format('MMM YYYY'),
8181
activeDate: activeValue,
@@ -98,11 +98,6 @@ export class DlDayModelProvider implements DlModelProvider {
9898
rows: rowNumbers.map(rowOfDays)
9999
};
100100

101-
result.leftButton.classes[`${result.leftButton.value}`] = true;
102-
result.rightButton.classes[`${result.rightButton.value}`] = true;
103-
104-
return result;
105-
106101
function rowOfDays(rowNumber) {
107102
const currentMoment = moment();
108103
const cells = columnNumbers.map((columnNumber) => {

‎src/lib/dl-date-time-picker/dl-model-provider-hour.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class DlHourModelProvider implements DlModelProvider {
7373
? selectedMilliseconds
7474
: moment(selectedMilliseconds).startOf('hour').valueOf();
7575

76-
const result: DlDateTimePickerModel = {
76+
return {
7777
viewName: 'hour',
7878
viewLabel: startDate.format('ll'),
7979
activeDate: activeValue,
@@ -95,11 +95,6 @@ export class DlHourModelProvider implements DlModelProvider {
9595
rows: rowNumbers.map(rowOfHours)
9696
};
9797

98-
result.leftButton.classes[`${result.leftButton.value}`] = true;
99-
result.rightButton.classes[`${result.rightButton.value}`] = true;
100-
101-
return result;
102-
10398
function rowOfHours(rowNumber) {
10499

105100
const currentMoment = moment();

‎src/lib/dl-date-time-picker/dl-model-provider-minute.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -107,7 +107,7 @@ export class DlMinuteModelProvider implements DlModelProvider {
107107
return {cells: minuteSteps.slice((value * 4), (value * 4) + 4).map(rowOfMinutes)};
108108
});
109109

110-
const result: DlDateTimePickerModel = {
110+
return {
111111
viewName: 'minute',
112112
viewLabel: startDate.format('lll'),
113113
activeDate: activeValue,
@@ -129,11 +129,6 @@ export class DlMinuteModelProvider implements DlModelProvider {
129129
rows
130130
};
131131

132-
result.leftButton.classes[`${result.leftButton.value}`] = true;
133-
result.rightButton.classes[`${result.rightButton.value}`] = true;
134-
135-
return result;
136-
137132
function rowOfMinutes(stepMinutes): {
138133
display: string;
139134
ariaLabel: string;

‎src/lib/dl-date-time-picker/dl-model-provider-month.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ export class DlMonthModelProvider implements DlModelProvider {
7373
? selectedMilliseconds
7474
: moment(selectedMilliseconds).startOf('month').valueOf();
7575

76-
const result = {
76+
return {
7777
viewName: 'month',
7878
viewLabel: startDate.format('YYYY'),
7979
activeDate: activeValue,
@@ -95,11 +95,6 @@ export class DlMonthModelProvider implements DlModelProvider {
9595
rows: rowNumbers.map(rowOfMonths)
9696
};
9797

98-
result.leftButton.classes[`${result.leftButton.value}`] = true;
99-
result.rightButton.classes[`${result.rightButton.value}`] = true;
100-
101-
return result;
102-
10398
function rowOfMonths(rowNumber) {
10499

105100
const currentMoment = moment();

‎src/lib/dl-date-time-picker/dl-model-provider-year.ts

+1-6
Original file line numberDiff line numberDiff line change
@@ -91,7 +91,7 @@ export class DlYearModelProvider implements DlModelProvider {
9191
? selectedMilliseconds
9292
: moment(selectedMilliseconds).startOf('year').valueOf();
9393

94-
const result: DlDateTimePickerModel = {
94+
return {
9595
viewName: 'year',
9696
viewLabel: `${pastYear}-${futureYear}`,
9797
activeDate: activeValue,
@@ -108,11 +108,6 @@ export class DlYearModelProvider implements DlModelProvider {
108108
rows: rowNumbers.map(rowOfYears.bind(this))
109109
};
110110

111-
result.leftButton.classes[`${result.leftButton.value}`] = true;
112-
result.rightButton.classes[`${result.rightButton.value}`] = true;
113-
114-
return result;
115-
116111
function rowOfYears(rowNumber) {
117112

118113
const currentMoment = moment();

0 commit comments

Comments
 (0)