Skip to content

Commit

Permalink
feat: add attributes to improve e2e testing
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
dalelotts committed Nov 8, 2019
1 parent 1729bdb commit 4f1abc2
Show file tree
Hide file tree
Showing 19 changed files with 211 additions and 113 deletions.
3 changes: 3 additions & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
@@ -1,11 +1,14 @@
sudo: required
dist: xenial
language: node_js
notifications:
email: false
node_js:
- '10'
services:
- xvfb
addons:
chrome: stable
before_script:
- npm prune
script:
Expand Down
10 changes: 10 additions & 0 deletions README.es_MX.md
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,16 @@ Utiliza las clases `row` y `col` de bootstrap flex para el acomodo del component
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.
Otros lenguajes/locales es probable que requieran un contenedor un poco mas ancho para poder mostrar apropiadamente el contenido.

## End-to-End (e2e) testing with protractor

**Translation Pull request needed for this section**

The user interactions with a date-time picker make it difficult to write e2e tests that exactly replicate the users interaction with the picker.

Fortunately, this repository contains a file you can use in your e2e tests to cause the date/time picker to select any specified date.

See [./e2e/src/dl-date-time-picker-protractor.ts](./e2e/src/dl-date-time-picker-protractor.ts) for details.

## Configuración
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!),
o ve a [https://dalelotts.github.io/angular-bootstrap-datetimepicker/](https://dalelotts.github.io/angular-bootstrap-datetimepicker/)
Expand Down
8 changes: 8 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,14 @@ It uses bootstrap's flex `row` and `col` classes to layout the date/time picker
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.
Other languages/locals may require a wider container to fit the contents.

## End-to-End (e2e) testing with protractor

The user interactions with a date-time picker make it difficult to write e2e tests that exactly replicate the users interaction with the picker.

Fortunately, this repository contains a file you can use in your e2e tests to cause the date/time picker to select any specified date.

See [./e2e/src/dl-date-time-picker-protractor.ts](./e2e/src/dl-date-time-picker-protractor.ts) for details.

## Configuration

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!),
Expand Down
16 changes: 12 additions & 4 deletions e2e/src/app.e2e-spec.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,22 @@
import { AppPage } from './app.po';
import {AppPage} from './app.po';
import pickTime from './dl-date-time-picker-protractor';
import moment = require('moment');

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

beforeEach(() => {
page = new AppPage();
return page.navigateTo();
});

it('should display welcome message', () => {
page.navigateTo();
expect(page.getParagraphText()).toEqual('Welcome to angular-bootstrap-datetimepicker!');
it('Picking time updates Selected Date:', async () => {
const todayAtMidnight = moment('2003-11-07T21:32:17.800Z');
const expectedDate = new Date('2003-11-07T21:30:00.000Z').toString();

await pickTime(page.getDateTimePicker(), todayAtMidnight.valueOf());

const selectedDate = page.getSelectedDate().getText();
expect(selectedDate).toBe(`Selected Date: ${expectedDate}`);
});
});
8 changes: 8 additions & 0 deletions e2e/src/app.po.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,12 @@ export class AppPage {
getParagraphText() {
return element(by.css('app-root h1')).getText();
}

getDateTimePicker() {
return element(by.tagName('dl-date-time-picker'));
}

getSelectedDate() {
return element(by.id('selectedDate'));
}
}
83 changes: 83 additions & 0 deletions e2e/src/dl-date-time-picker-protractor.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import {by, ElementArrayFinder, ElementFinder} from 'protractor';

/**
* This file is an example of how you can implement automated end-to-end tests for the
* date/time picker component.
*
* The overall strategy here is to use the `dl-abdtp-value` attributes, which contain numeric date values,
* to determine which buttons to click on the picker in order to select a target dates.
*/

/**
* Clicks the nearest date button with a value less than or equal to the specified time.
* @param dateButtons
* the possible date buttons.
* @param time
* the desired selected time.
*/

export function clickNearestDateButton(dateButtons: ElementArrayFinder, time: number) {
return dateButtons
.filter(button => button.getAttribute('dl-abdtp-value').then(buttonValue => Number(buttonValue) <= time))
.last().click();
}

/**
* Have the dateTimePicker select the best possible value that is less than or equal to the specified time.
* based on the current configuration of the dateTimePicker.
*
* This function will `not` select a time value `greater than` the specified time value.
*
* Additionally, this function depends on `ng-reflect-*` attributes which will never exist in a production build.
*
* @param dateTimePicker
* the target dateTimePicker
*
* @param time
* the desired selected time.
*/
async function pickTime(dateTimePicker: ElementFinder, time: number) {
const dateButtons = dateTimePicker.all(by.className('dl-abdtp-date-button'));
const leftButton = dateTimePicker.element(by.className('dl-abdtp-left-button'));
const rightButton = dateTimePicker.element(by.className('dl-abdtp-right-button'));
const upButton = dateTimePicker.element(by.className('dl-abdtp-up-button'));
const viewAttributeName = 'data-dl-abdtp-view';
const viewElement = dateTimePicker.element(by.css(`[${viewAttributeName}]`));

const maxView = await dateTimePicker.getAttribute('ng-reflect-max-view');
const minView = await dateTimePicker.getAttribute('ng-reflect-min-view');

let currentView = await viewElement.getAttribute(viewAttributeName);

// Go up to the max view in order to drill down by selecting the nearest button value.
while (maxView !== currentView) {
await upButton.click();
currentView = await viewElement.getAttribute(viewAttributeName);
}

let firstButtonValue = await dateButtons.first().getAttribute('dl-abdtp-value');

// This left and right navigation to find the target date range assumes that earlier times are to the left.
// This is true for the default implementation but may not be true for all implementations.

while (Number(firstButtonValue) > time) {
await leftButton.click();
firstButtonValue = await dateButtons.first().getAttribute('dl-abdtp-value');
}

let lastButtonValue = await dateButtons.last().getAttribute('dl-abdtp-value');

while (Number(lastButtonValue) <= time) {
await rightButton.click();
lastButtonValue = await dateButtons.last().getAttribute('dl-abdtp-value');
}

while (minView !== currentView) {
await clickNearestDateButton(dateButtons, time);
currentView = await viewElement.getAttribute(viewAttributeName);
}

return clickNearestDateButton(dateButtons, time);
}

export default pickTime;
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,12 +31,12 @@
"build:styles": "scss-bundle -c scss-bundle.config.json",
"coverage:upload": "cat build/coverage/lcov.info | coveralls",
"document": "compodoc --disableInternal --disablePrivate --disableLifeCycleHooks --assetsFolder screenshots -p src/tsconfig.doc.json --gaID UA-325325-19 -n \"Angular Bootstrap Date/Time Picker\"",
"e2e": "ng e2e",
"lint": "ng lint",
"ng": "ng",
"start": "ng serve",
"test": "ng lint && ng test --watch=false --code-coverage && ng build --prod && npm run build:lib",
"test": "ng lint && ng test --watch=false --code-coverage && npm run-script test:e2e && ng build --prod && npm run build:lib",
"test:tdd": "ng test",
"test:e2e": "ng e2e",
"semantic-release": "semantic-release",
"travis-deploy-once": "travis-deploy-once"
},
Expand Down
2 changes: 1 addition & 1 deletion src/app/app.component.html
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,6 @@
[(ngModel)]="selectedDate"
(change)="onCustomDateChange($event)"></dl-date-time-picker>
</div>
<p>Selected Date: {{selectedDate}}</p>
<p id="selectedDate">Selected Date: {{selectedDate}}</p>
</div>
</div>
19 changes: 11 additions & 8 deletions src/lib/dl-date-time-picker/dl-date-time-picker.component.html
Original file line number Diff line number Diff line change
@@ -1,36 +1,38 @@
<div class="text-center dl-abdtp-{{_model.viewName}}-view ">
<div class="text-center dl-abdtp-{{_model.viewName}}-view" [attr.data-dl-abdtp-view]="_model.viewName">
<div class="row align-items-center no-gutters">
<button class="col dl-abdtp-left-button align-items-center"
type="button"
[attr.aria-label]="_model.leftButton.ariaLabel"
[attr.dl-abdtp-value]="_model.leftButton.value"
[attr.title]="_model.leftButton.ariaLabel"
[ngClass]="_model.leftButton.classes"
(click)="_onLeftClick()">
<span class="left-icon" [ngClass]="leftIconClass"></span>
(click)="_onLeftClick()"
><span class="left-icon" [ngClass]="leftIconClass"></span>
</button>

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

<button class="col dl-abdtp-right-button"
type="button"
[attr.aria-label]="_model.rightButton.ariaLabel"
[attr.dl-abdtp-value]="_model.rightButton.value"
[attr.title]="_model.rightButton.ariaLabel"
(click)="_onRightClick()"
[ngClass]="_model.rightButton.classes">
<span class="right-icon" [ngClass]="rightIconClass"></span>
><span class="right-icon" [ngClass]="rightIconClass"></span>
</button>
</div>
<div (keydown)="_handleKeyDown($event)">
<div *ngIf="_model.rowLabels?.length" class="row no-gutters">
<div *ngFor="let label of _model.rowLabels" class="col align-items-center no-gutters dl-abdtp-col-label">{{label}}</div>
<div *ngFor="let label of _model.rowLabels"
class="col align-items-center no-gutters dl-abdtp-col-label">{{label}}</div>
</div>
<div *ngFor="let row of _model.rows" class="row align-items-center no-gutters">
<div *ngFor="let cell of row.cells"
role="gridcell"
class="col dl-abdtp-date-button dl-abdtp-{{_model.viewName}} {{cell.value}}"
class="col dl-abdtp-date-button dl-abdtp-{{_model.viewName}}"
[ngClass]="cell.classes"
[attr.aria-label]="cell.ariaLabel"
[attr.aria-disabled]="cell.classes['dl-abdtp-disabled']"
[attr.dl-abdtp-value]="cell.value"
[attr.tabindex]="cell.classes['dl-abdtp-active'] ? 0 : -1"
(click)="_onDateClick(cell)"
(keydown.space)="_onDateClick(cell)"
Expand All @@ -47,6 +49,7 @@
<button class="col-10 dl-abdtp-view-label dl-abdtp-up-button"
type="button"
[attr.aria-label]="_model.upButton.ariaLabel"
[attr.dl-abdtp-value]="_model.upButton.value"
[attr.title]="_model.upButton.ariaLabel"
(click)="_onUpClick()"
[ngClass]="_model.upButton.classes"
Expand Down
7 changes: 1 addition & 6 deletions src/lib/dl-date-time-picker/dl-model-provider-day.ts
Original file line number Diff line number Diff line change
Expand Up @@ -75,7 +75,7 @@ export class DlDayModelProvider implements DlModelProvider {
? selectedMilliseconds
: moment(selectedMilliseconds).startOf('day').valueOf();

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

result.leftButton.classes[`${result.leftButton.value}`] = true;
result.rightButton.classes[`${result.rightButton.value}`] = true;

return result;

function rowOfDays(rowNumber) {
const currentMoment = moment();
const cells = columnNumbers.map((columnNumber) => {
Expand Down
7 changes: 1 addition & 6 deletions src/lib/dl-date-time-picker/dl-model-provider-hour.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class DlHourModelProvider implements DlModelProvider {
? selectedMilliseconds
: moment(selectedMilliseconds).startOf('hour').valueOf();

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

result.leftButton.classes[`${result.leftButton.value}`] = true;
result.rightButton.classes[`${result.rightButton.value}`] = true;

return result;

function rowOfHours(rowNumber) {

const currentMoment = moment();
Expand Down
7 changes: 1 addition & 6 deletions src/lib/dl-date-time-picker/dl-model-provider-minute.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ export class DlMinuteModelProvider implements DlModelProvider {
return {cells: minuteSteps.slice((value * 4), (value * 4) + 4).map(rowOfMinutes)};
});

const result: DlDateTimePickerModel = {
return {
viewName: 'minute',
viewLabel: startDate.format('lll'),
activeDate: activeValue,
Expand All @@ -129,11 +129,6 @@ export class DlMinuteModelProvider implements DlModelProvider {
rows
};

result.leftButton.classes[`${result.leftButton.value}`] = true;
result.rightButton.classes[`${result.rightButton.value}`] = true;

return result;

function rowOfMinutes(stepMinutes): {
display: string;
ariaLabel: string;
Expand Down
7 changes: 1 addition & 6 deletions src/lib/dl-date-time-picker/dl-model-provider-month.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ export class DlMonthModelProvider implements DlModelProvider {
? selectedMilliseconds
: moment(selectedMilliseconds).startOf('month').valueOf();

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

result.leftButton.classes[`${result.leftButton.value}`] = true;
result.rightButton.classes[`${result.rightButton.value}`] = true;

return result;

function rowOfMonths(rowNumber) {

const currentMoment = moment();
Expand Down
7 changes: 1 addition & 6 deletions src/lib/dl-date-time-picker/dl-model-provider-year.ts
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ export class DlYearModelProvider implements DlModelProvider {
? selectedMilliseconds
: moment(selectedMilliseconds).startOf('year').valueOf();

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

result.leftButton.classes[`${result.leftButton.value}`] = true;
result.rightButton.classes[`${result.rightButton.value}`] = true;

return result;

function rowOfYears(rowNumber) {

const currentMoment = moment();
Expand Down
Loading

0 comments on commit 4f1abc2

Please sign in to comment.