diff --git a/gulpfile.js b/gulpfile.js
index 5e3aa9da38..baa0edfd4c 100644
--- a/gulpfile.js
+++ b/gulpfile.js
@@ -638,6 +638,7 @@ gulp.task('test.unit.dart', function (done) {
'!build/pubget.angular2.dart',
'!build/change_detect.dart',
'!build/remove-pub-symlinks',
+ 'build.dart.material.css',
'!test.unit.dart/karma-server',
'!test.unit.dart/karma-run',
function(error) {
@@ -950,6 +951,7 @@ gulp.task('!broccoli.js.prod', function() {
gulp.task('build.js.dev', ['build/clean.js'], function(done) {
runSequence(
'broccoli.js.dev',
+ 'build.css.material',
sequenceComplete(done)
);
});
diff --git a/karma-js.conf.js b/karma-js.conf.js
index e8d5add617..f49c9f754a 100644
--- a/karma-js.conf.js
+++ b/karma-js.conf.js
@@ -45,7 +45,7 @@ module.exports = function(config) {
}
},
- browsers: ['ChromeCanary'],
+ browsers: ['Chrome'],
port: 9876
});
diff --git a/modules/angular2_material/pubspec.yaml b/modules/angular2_material/pubspec.yaml
index 46a741318a..37fe1b831d 100644
--- a/modules/angular2_material/pubspec.yaml
+++ b/modules/angular2_material/pubspec.yaml
@@ -14,3 +14,5 @@ dependencies:
dependency_overrides:
angular2:
path: ../angular2
+dev_dependencies:
+ guinness: '^0.1.17'
diff --git a/modules/angular2_material/src/components/button/button.html b/modules/angular2_material/src/components/button/button.html
index 6dcfd11d1a..4e3df0e226 100644
--- a/modules/angular2_material/src/components/button/button.html
+++ b/modules/angular2_material/src/components/button/button.html
@@ -1,2 +1 @@
-
diff --git a/modules/angular2_material/src/components/button/button.ts b/modules/angular2_material/src/components/button/button.ts
index 9c4cf95dcb..fb0691e361 100644
--- a/modules/angular2_material/src/components/button/button.ts
+++ b/modules/angular2_material/src/components/button/button.ts
@@ -3,6 +3,7 @@ import {Component, View, LifecycleEvent, ViewEncapsulation, OnChanges} from 'ang
import {TimerWrapper} from 'angular2/src/core/facade/async';
import {isPresent} from 'angular2/src/core/facade/lang';
+
// TODO(jelbourn): Ink ripples.
// TODO(jelbourn): Make the `isMosueDown` stuff done with one global listener.
@@ -17,6 +18,7 @@ import {isPresent} from 'angular2/src/core/facade/lang';
})
@View({
templateUrl: 'package:angular2_material/src/components/button/button.html',
+ styleUrls: ['package:angular2_material/src/components/button/button.css'],
encapsulation: ViewEncapsulation.None,
})
export class MdButton {
@@ -55,7 +57,7 @@ export class MdButton {
'(blur)': 'onBlur()',
'[tabIndex]': 'tabIndex',
'[class.md-button-focus]': 'isKeyboardFocused',
- '[attr.aria-disabled]': 'disabled',
+ '[attr.aria-disabled]': 'isAriaDisabled',
},
})
@View({
@@ -64,8 +66,6 @@ export class MdButton {
})
export class MdAnchor extends MdButton implements OnChanges {
tabIndex: number;
-
- /** Whether the component is disabled. */
disabled_: boolean;
get disabled(): boolean {
@@ -89,4 +89,9 @@ export class MdAnchor extends MdButton implements OnChanges {
// A disabled anchor should not be in the tab flow.
this.tabIndex = this.disabled ? -1 : 0;
}
+
+ /** Gets the aria-disabled value for the component, which must be a string for Dart. */
+ get isAriaDisabled(): string {
+ return this.disabled ? 'true' : 'false';
+ }
}
diff --git a/modules/angular2_material/test/button_spec.ts b/modules/angular2_material/test/button_spec.ts
new file mode 100644
index 0000000000..6f8f5751b4
--- /dev/null
+++ b/modules/angular2_material/test/button_spec.ts
@@ -0,0 +1,140 @@
+import {
+ AsyncTestCompleter,
+ TestComponentBuilder,
+ beforeEach,
+ beforeEachBindings,
+ ddescribe,
+ describe,
+ el,
+ expect,
+ iit,
+ inject,
+ it,
+ xit,
+} from 'angular2/test_lib';
+import {DebugElement} from 'angular2/src/core/debug/debug_element';
+
+import {Component, View, ViewMetadata, UrlResolver, bind} from 'angular2/core';
+
+import {MdButton, MdAnchor} from 'angular2_material/src/components/button/button';
+
+import {TestUrlResolver} from './test_url_resolver';
+
+import {XHR} from 'angular2/src/core/render/xhr';
+import {XHRImpl} from 'angular2/src/core/render/xhr_impl';
+
+
+export function main() {
+ describe('MdButton', () => {
+ let builder: TestComponentBuilder;
+
+ beforeEachBindings(() => [
+ // Need a custom URL resolver for ng-material template files in order for them to work
+ // with both JS and Dart output.
+ bind(UrlResolver)
+ .toValue(new TestUrlResolver()),
+
+ // Need to use the real XHR implementation (instead of the mock) so we can actually request
+ // the template files, since Angular 2 doesn't have anything like $templateCache. This should
+ // eventually be replaced with a preprocessor that inlines templates.
+ bind(XHR).toClass(XHRImpl)
+ ]);
+
+ beforeEach(inject([TestComponentBuilder], (tcb) => { builder = tcb; }));
+
+ describe('button[md-button]', () => {
+ it('should handle a click on the button', inject([AsyncTestCompleter], (async) => {
+ builder.createAsync(TestApp).then(rootTestComponent => {
+ let testComponent = rootTestComponent.debugElement.componentInstance;
+ let buttonDebugElement =
+ getChildDebugElement(rootTestComponent.debugElement, 'button');
+
+ buttonDebugElement.nativeElement.click();
+ expect(testComponent.clickCount).toBe(1);
+
+ async.done();
+ });
+ }));
+
+ it('should disable the button', inject([AsyncTestCompleter], (async) => {
+ builder.createAsync(TestApp).then(rootTestComponent => {
+ let testAppComponent = rootTestComponent.debugElement.componentInstance;
+ let buttonDebugElement =
+ getChildDebugElement(rootTestComponent.debugElement, 'button');
+ let buttonElement = buttonDebugElement.nativeElement;
+
+ // The button should initially be enabled.
+ expect(buttonElement.disabled).toBe(false);
+
+ // After the disabled binding has been changed.
+ testAppComponent.isDisabled = true;
+ rootTestComponent.detectChanges();
+
+ // The button should should now be disabled.
+ expect(buttonElement.disabled).toBe(true);
+
+ // Clicking the button should not invoke the handler.
+ buttonElement.click();
+ expect(testAppComponent.clickCount).toBe(0);
+ async.done();
+ });
+ }));
+ });
+
+ describe('a[md-button]', () => {
+ const anchorTemplate = `Go`;
+
+ beforeEach(() => {
+ builder = builder.overrideView(
+ TestApp, new ViewMetadata({template: anchorTemplate, directives: [MdAnchor]}));
+ });
+
+ it('should remove disabled anchors from tab order', inject([AsyncTestCompleter], (async) => {
+ builder.createAsync(TestApp).then(rootTestComponent => {
+ let testAppComponent = rootTestComponent.debugElement.componentInstance;
+ let anchorDebugElement = getChildDebugElement(rootTestComponent.debugElement, 'a');
+ let anchorElement = anchorDebugElement.nativeElement;
+
+ // The anchor should initially be in the tab order.
+ expect(anchorElement.tabIndex).toBe(0);
+
+ // After the disabled binding has been changed.
+ testAppComponent.isDisabled = true;
+ rootTestComponent.detectChanges();
+
+ // The anchor should now be out of the tab order.
+ expect(anchorElement.tabIndex).toBe(-1);
+
+ async.done();
+ });
+
+ it('should preventDefault for disabled anchor clicks',
+ inject([AsyncTestCompleter], (async) => {
+ // No clear way to test this; see https://github.com/angular/angular/issues/3782
+ async.done();
+ }));
+ }));
+ });
+ });
+}
+
+/** Gets a child DebugElement by tag name. */
+function getChildDebugElement(parent: DebugElement, tagName: string): DebugElement {
+ return parent.query(debugEl => debugEl.nativeElement.tagName.toLowerCase() == tagName);
+}
+
+/** Test component that contains an MdButton. */
+@Component({selector: 'test-app'})
+@View({
+ directives: [MdButton],
+ template:
+ ``
+})
+class TestApp {
+ clickCount: number = 0;
+ isDisabled: boolean = false;
+
+ increment() {
+ this.clickCount++;
+ }
+}
diff --git a/modules/angular2_material/test/test_url_resolver.dart b/modules/angular2_material/test/test_url_resolver.dart
new file mode 100644
index 0000000000..cc1a15f650
--- /dev/null
+++ b/modules/angular2_material/test/test_url_resolver.dart
@@ -0,0 +1,21 @@
+library ng_material.test_url_resolver;
+
+import 'package:angular2/src/core/dom/browser_adapter.dart';
+import 'package:angular2/src/core/services/url_resolver.dart';
+
+void commonDemoSetup() {
+ BrowserDomAdapter.makeCurrent();
+}
+
+class TestUrlResolver extends UrlResolver {
+ @override
+ String resolve(String baseUrl, String url) {
+ const MATERIAL_PKG = 'package:angular2_material/';
+
+ if (url.startsWith(MATERIAL_PKG)) {
+ return '/packages/angular2_material/' +
+ url.substring(MATERIAL_PKG.length);
+ }
+ return super.resolve(baseUrl, url);
+ }
+}
diff --git a/modules/angular2_material/test/test_url_resolver.ts b/modules/angular2_material/test/test_url_resolver.ts
new file mode 100644
index 0000000000..b941422c43
--- /dev/null
+++ b/modules/angular2_material/test/test_url_resolver.ts
@@ -0,0 +1,16 @@
+import {UrlResolver} from 'angular2/src/core/services/url_resolver';
+
+export class TestUrlResolver extends UrlResolver {
+ constructor() {
+ super();
+ }
+
+ resolve(baseUrl: string, url: string): string {
+ // The standard UrlResolver looks for "package:" templateUrls in
+ // node_modules, however in our repo we host material widgets at the root.
+ if (url.startsWith('package:angular2_material/')) {
+ return '/base/dist/js/dev/es5/' + url.substring(8);
+ }
+ return super.resolve(baseUrl, url);
+ }
+}
diff --git a/test-main.js b/test-main.js
index 661b10f8cc..1d343aaf75 100644
--- a/test-main.js
+++ b/test-main.js
@@ -13,6 +13,7 @@ System.config({
paths: {
'benchpress/*': 'dist/js/dev/es5/benchpress/*.js',
'angular2/*': 'dist/js/dev/es5/angular2/*.js',
+ 'angular2_material/*': 'dist/js/dev/es5/angular2_material/*.js',
'rtts_assert/*': 'dist/js/dev/es5/rtts_assert/*.js',
'rx': 'node_modules/rx/dist/rx.js'
}