diff --git a/.gitignore b/.gitignore index fe6f8a7..478624e 100644 --- a/.gitignore +++ b/.gitignore @@ -32,7 +32,6 @@ testem.log /typings # e2e -/e2e/*.js /e2e/*.map #System Files diff --git a/angular.json b/angular.json index c2e3aaf..d41c4c4 100644 --- a/angular.json +++ b/angular.json @@ -101,8 +101,13 @@ "e2e": { "builder": "@angular-devkit/build-angular:protractor", "options": { - "protractorConfig": "./protractor.conf.js", + "protractorConfig": "e2e/protractor.conf.js", "devServerTarget": "workshop:serve" + }, + "configurations": { + "production": { + "devServerTarget": "angular-e2e:serve:production" + } } }, "lint": { diff --git a/e2e/app.e2e-spec.ts b/e2e/app.e2e-spec.ts deleted file mode 100644 index 70eb87e..0000000 --- a/e2e/app.e2e-spec.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { AngularWorkshopPage } from './app.po'; - -describe('angular-workshop App', function() { - let page: AngularWorkshopPage; - - beforeEach(() => { - page = new AngularWorkshopPage(); - }); - - it('should display message saying app works', () => { - page.navigateTo(); - expect(page.getParagraphText()).toEqual('app works!'); - }); -}); diff --git a/e2e/app.po.ts b/e2e/app.po.ts deleted file mode 100644 index 90657cc..0000000 --- a/e2e/app.po.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { browser, element, by } from 'protractor'; - -export class AngularWorkshopPage { - navigateTo() { - return browser.get('/'); - } - - getParagraphText() { - return element(by.css('app-root h1')).getText(); - } -} diff --git a/protractor.conf.js b/e2e/protractor.conf.js similarity index 54% rename from protractor.conf.js rename to e2e/protractor.conf.js index 8f7a769..73e4e68 100644 --- a/protractor.conf.js +++ b/e2e/protractor.conf.js @@ -1,14 +1,19 @@ +// @ts-check // Protractor configuration file, see link for more information // https://github.com/angular/protractor/blob/master/lib/config.ts -/*global jasmine */ -var SpecReporter = require('jasmine-spec-reporter'); +const { SpecReporter } = require('jasmine-spec-reporter'); +/** + * @type { import("protractor").Config } + */ exports.config = { allScriptsTimeout: 11000, - specs: ['./e2e/**/*.e2e-spec.ts'], + specs: [ + './src/**/*.e2e-spec.ts' + ], capabilities: { - browserName: 'chrome' + 'browserName': 'chrome' }, directConnect: true, baseUrl: 'http://localhost:4200/', @@ -18,13 +23,10 @@ exports.config = { defaultTimeoutInterval: 30000, print: function() {} }, - useAllAngular2AppRoots: true, - beforeLaunch: function() { + onPrepare() { require('ts-node').register({ - project: 'e2e' + project: require('path').join(__dirname, './tsconfig.json') }); - }, - onPrepare: function() { - jasmine.getEnv().addReporter(new SpecReporter()); + jasmine.getEnv().addReporter(new SpecReporter({ spec: { displayStacktrace: true } })); } -}; +}; \ No newline at end of file diff --git a/e2e/src/app.e2e-spec.ts b/e2e/src/app.e2e-spec.ts new file mode 100644 index 0000000..0cb4f0c --- /dev/null +++ b/e2e/src/app.e2e-spec.ts @@ -0,0 +1,16 @@ +import { AngularWorkshopPage } from './app.po'; + +describe('angular-workshop App', () => { + let page: AngularWorkshopPage; + + beforeEach(() => { + page = new AngularWorkshopPage(); + }); + + it('should be a11y conform', (done) => { + page.a11y((results) => { + expect(results.violations.length).toBe(0); + done(); + }); + }); +}); diff --git a/e2e/src/app.po.ts b/e2e/src/app.po.ts new file mode 100644 index 0000000..50257f5 --- /dev/null +++ b/e2e/src/app.po.ts @@ -0,0 +1,12 @@ +import { browser, element, by, WebDriver } from 'protractor'; +const AxeBuilder = require('axe-webdriverjs'); + +export class AngularWorkshopPage { + navigateTo() { + return browser.get('/'); + } + + a11y(resultFn) { + return AxeBuilder(WebDriver).analyze(resultFn) + } +} diff --git a/e2e/tsconfig.json b/e2e/tsconfig.json index 24b8ab9..39b800f 100644 --- a/e2e/tsconfig.json +++ b/e2e/tsconfig.json @@ -1,14 +1,13 @@ { - "compileOnSave": false, + "extends": "../tsconfig.json", "compilerOptions": { - "declaration": false, - "emitDecoratorMetadata": true, - "experimentalDecorators": true, + "outDir": "../out-tsc/e2e", "module": "commonjs", - "moduleResolution": "node", - "outDir": "../dist/out-tsc-e2e", - "sourceMap": true, "target": "es5", - "typeRoots": ["../node_modules/@types"] + "types": [ + "jasmine", + "jasminewd2", + "node" + ] } } diff --git a/package-lock.json b/package-lock.json index b92fc43..c840b17 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1988,6 +1988,15 @@ "semver-intersect": "1.4.0" } }, + "@types/axe-webdriverjs": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@types/axe-webdriverjs/-/axe-webdriverjs-2.1.0.tgz", + "integrity": "sha512-kReHzmOHt3/JRJtyG20xS7UpdO7zyD3wVw1/BiLkgPoo/ImTXNrjXyPOoTbY0hGnN/FK0syfczRII+xF4f3uRw==", + "requires": { + "@types/selenium-webdriver": "*", + "axe-core": "^3.0.3" + } + }, "@types/events": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/@types/events/-/events-3.0.0.tgz", @@ -2041,8 +2050,7 @@ "@types/selenium-webdriver": { "version": "3.0.16", "resolved": "https://registry.npmjs.org/@types/selenium-webdriver/-/selenium-webdriver-3.0.16.tgz", - "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==", - "dev": true + "integrity": "sha512-lMC2G0ItF2xv4UCiwbJGbnJlIuUixHrioOhNGHSCsYCJ8l4t9hMCUimCytvFv7qy6AfSzRxhRHoGa+UqaqwyeA==" }, "@types/source-list-map": { "version": "0.1.2", @@ -2626,6 +2634,30 @@ "integrity": "sha512-ReZxvNHIOv88FlT7rxcXIIC0fPt4KZqZbOlivyWtXLt8ESx84zd3kMC6iK5jVeS2qt+g7ftS7ye4fi06X5rtRQ==", "dev": true }, + "axe-core": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/axe-core/-/axe-core-3.3.2.tgz", + "integrity": "sha512-lRdxsRt7yNhqpcXQk1ao1BL73OZDzmFCWOG0mC4tGR/r14ohH2payjHwCMQjHGbBKm924eDlmG7utAGHiX/A6g==" + }, + "axe-webdriverjs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/axe-webdriverjs/-/axe-webdriverjs-2.3.0.tgz", + "integrity": "sha512-AuUsX5OFTXOJ6reIKjtGay4O656n5G+m8MzhfL1SC8MHINBFFFn3Taucckn8+UZYJuTtNEobllSfiuPTHyKnSA==", + "dev": true, + "requires": { + "axe-core": "^3.3.1", + "babel-runtime": "^6.26.0", + "depd": "^2.0.0" + }, + "dependencies": { + "depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "dev": true + } + } + }, "axobject-query": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/axobject-query/-/axobject-query-2.0.2.tgz", diff --git a/package.json b/package.json index 1b9e626..9f1ac13 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,7 @@ "@angular/platform-browser": "~8.1.0", "@angular/platform-browser-dynamic": "~8.1.0", "@angular/router": "~8.1.0", + "@types/axe-webdriverjs": "^2.1.0", "core-js": "^3.1.4", "hammerjs": "^2.0.8", "rxjs": "~6.4.0", @@ -37,6 +38,8 @@ "@types/jasmine": "~3.3.8", "@types/jasminewd2": "~2.0.3", "@types/node": "~8.9.4", + "axe-core": "^3.3.2", + "axe-webdriverjs": "^2.3.0", "codelyzer": "^5.0.0", "jasmine-core": "~3.4.0", "jasmine-spec-reporter": "~4.2.1", diff --git a/src/app/app.component.spec.ts b/src/app/app.component.spec.ts new file mode 100644 index 0000000..3be6ef4 --- /dev/null +++ b/src/app/app.component.spec.ts @@ -0,0 +1,71 @@ +import * as axe from 'axe-core'; +import { DebugElement } from '@angular/core'; +import { async, ComponentFixture, TestBed } from '@angular/core/testing'; +import { LayoutModule } from '@angular/cdk/layout'; +import { HttpClientModule } from '@angular/common/http'; +import { FormsModule } from '@angular/forms'; +import { MatButtonModule } from '@angular/material/button'; +import { MatIconModule } from '@angular/material/icon'; +import { MatListModule } from '@angular/material/list'; +import { MatSidenavModule } from '@angular/material/sidenav'; +import { MatToolbarModule } from '@angular/material/toolbar'; +import { BrowserModule } from '@angular/platform-browser'; +import { BrowserAnimationsModule } from '@angular/platform-browser/animations'; +import { AppRoutingModule } from './app-routing.module'; +import { MainNavigationComponent } from './main-navigation/main-navigation.component'; + +import { AppComponent } from './app.component'; + +describe('AppComponent', () => { + let component: AppComponent; + let fixture: ComponentFixture; + let appDe: DebugElement; + let appEl: HTMLElement; + + beforeEach(async(() => { + TestBed.configureTestingModule({ + imports: [ + BrowserModule, + FormsModule, + HttpClientModule, + AppRoutingModule, + BrowserAnimationsModule, + LayoutModule, + MatToolbarModule, + MatButtonModule, + MatSidenavModule, + MatIconModule, + MatListModule + ], + declarations: [ + AppComponent, + MainNavigationComponent + ] + }) + .compileComponents(); + })); + + beforeEach(() => { + fixture = TestBed.createComponent(AppComponent); + component = fixture.componentInstance; + appDe = fixture.debugElement; + appEl = appDe.nativeElement; + fixture.detectChanges(); + }); + + it('should create', () => { + expect(component).toBeTruthy(); + }); + + it('should be accessible on initial state', (done) => { + axe.run(appEl, (err, result) => { + if (result.violations.length > 0) { + console.log(JSON.stringify(result.violations, null, 4)); + } + + expect(err).toBe(null); + expect(result.violations.length).toBe(0); + done(); + }); + }); +}); \ No newline at end of file diff --git a/src/app/book/book-detail/book-detail.component.html b/src/app/book/book-detail/book-detail.component.html index 2dcc12e..a8e858d 100644 --- a/src/app/book/book-detail/book-detail.component.html +++ b/src/app/book/book-detail/book-detail.component.html @@ -8,7 +8,8 @@ mat-card-image src="https://github.com/assets/covers/%7B%7B%20book.isbn%20%7D%7D.jpg" class="book-cover" - alt="book cover" + alt="book cover for the book {{book.title}} by {{book.author}}" + tabindex="0" />

diff --git a/src/app/book/book-list/book-list.component.html b/src/app/book/book-list/book-list.component.html index 3b2a378..5cdbff4 100644 --- a/src/app/book/book-list/book-list.component.html +++ b/src/app/book/book-list/book-list.component.html @@ -1,6 +1,7 @@ +

Books

- Books book

{{ book.title }}

{{ book.author }}

-
+ diff --git a/src/app/book/book-list/book-list.component.scss b/src/app/book/book-list/book-list.component.scss index a20ff7b..564c27d 100644 --- a/src/app/book/book-list/book-list.component.scss +++ b/src/app/book/book-list/book-list.component.scss @@ -1,3 +1,7 @@ .book-single { cursor: pointer; } + +.mat-list-base{ + padding-top: 0; +} diff --git a/src/app/main-navigation/main-navigation.component.html b/src/app/main-navigation/main-navigation.component.html index 7b2e13c..d9b5c65 100644 --- a/src/app/main-navigation/main-navigation.component.html +++ b/src/app/main-navigation/main-navigation.component.html @@ -20,10 +20,10 @@ aria-label="Toggle sidenav" mat-icon-button (click)="drawer.toggle()" - *ngIf="isHandset$ | async" + tabindex="0" > menu - + 🐒 Book Monkey diff --git a/src/assets/books.jpg b/src/assets/books.jpg new file mode 100644 index 0000000..8d9b992 Binary files /dev/null and b/src/assets/books.jpg differ diff --git a/src/index.html b/src/index.html index af0ab48..314995f 100644 --- a/src/index.html +++ b/src/index.html @@ -1,5 +1,5 @@ - + AngularWorkshop diff --git a/tslint.json b/tslint.json index d33917e..fb1f75e 100644 --- a/tslint.json +++ b/tslint.json @@ -51,7 +51,13 @@ "template-banana-in-box": true, "template-no-negated-async": true, "use-lifecycle-interface": true, - "use-pipe-transform-interface": true + "use-pipe-transform-interface": true, + "template-accessibility-alt-text": true, + "template-accessibility-elements-content": true, + "template-accessibility-label-for": true, + "template-accessibility-tabindex-no-positive": true, + "template-accessibility-table-scope": true, + "template-accessibility-valid-aria": true }, "rulesDirectory": ["codelyzer"] }