feat(): Dartanalyzer running on travis.

This commit is contained in:
Hans Larsen
2016-01-29 14:01:19 -08:00
parent f167934875
commit 3a1d518df0
25 changed files with 905 additions and 290 deletions
+7
View File
@@ -8,6 +8,13 @@
/node_modules
/bower_components
# Dart
/.pub
/.packages
/packages
/pubspec.lock
# IDEs
/.idea
+9 -1
View File
@@ -10,6 +10,7 @@ node_js:
cache:
directories:
- node_modules
- $HOME/.pub-cache
env:
global:
@@ -19,6 +20,8 @@ env:
- BROWSER_STACK_USERNAME=angularteam1
- BROWSER_STACK_ACCESS_KEY=BWCd4SynLzdDcv8xtzsB
- ARCH=linux-x64
- DART_DEV_VERSION=latest
- DART_STABLE_VERSION=latest
# Token for tsd to increase github rate limit
# See https://github.com/DefinitelyTyped/tsd#tsdrc
# This does not use http://docs.travis-ci.com/user/environment-variables/#Secure-Variables
@@ -32,6 +35,7 @@ env:
# Order: a slower build first, so that we don't occupy an idle travis worker waiting for others to complete.
- MODE=saucelabs_required
- MODE=browserstack_required
- MODE=dart_required DART_CHANNEL=stable DART_VERSION=$DART_STABLE_VERSION
- MODE=saucelabs_optional
- MODE=browserstack_optional
@@ -40,6 +44,10 @@ matrix:
- env: "MODE=saucelabs_optional"
- env: "MODE=browserstack_optional"
before_install:
- ./scripts/ci/install_dart.sh
install:
- npm install
@@ -52,7 +60,7 @@ before_script:
script:
- ./scripts/ci/build-and-test.sh ${MODE}
- ./scripts/ci/build-and-test.sh
cache:
directories:
@@ -0,0 +1,40 @@
# Writing Dart Compatible TypeScript
When writing TypeScript files in Material 2, it is important to keep in mind that these files will be automatically compiled and verified in Dart. There is valid TypeScript that will work in Angular 2, but will be transpiled to invalid Dart code.
Because of this, there's certain things to keep in mind.
## Requirements
This document refers to the following concepts, but do not explain them:
* The Dart language: https://www.dartlang.org
* The `pub` Dart packages management: https://pub.dartlang.org/
* The `angular-cli` toolchain: https://github.com/angular/angular-cli
## Dart Toolchain
When running `ng build` (or `ng server` which also builds), a Broccoli tree will automatically compile and format your TypeScript code into Dart. These files live along the JavaScript output of the build process, and also are moved into a special directory structure that is compatible with `pub`.
For example, let say you have a `md-awesome` component for Material, which contains the `awesome.ts` implementation. This file is contained in the source code as `src/components/awesome/awesome.ts`. Building this component (assuming no specs or `scss`) will result in the following files:
* `dist/components/awesome/awesome.ts`. The original copy.
* `dist/components/awesome/awesome.js`. The Javascript code.
* `dist/components/awesome/awesome.js.map`. Sourcemap.
* `dist/components/awesome/awesome.dart`. The Dart code, transpiled by `ts2dart` and formatted by `dartfmt`.
* `dist/dart/lib/components/awesome/awesome.dart`. The same file, copied from above into a directory structure compatible with `pub`.
If you have transpilation errors (ie. your TypeScript is invalid in Dart), these will fail the `ng build` step now and will output the errors in the console.
## Gotchas
Here's a list of gotchas to keep in mind when writing TypeScript code that will be compiled to Dart:
* **Unused imports are enforced in Dart.** Importing a file but not using it is an error, not a warning.
* **`void` is not a proper type for templates.** This is important when creating `EventEmitter<>` or other templates. Instead of `void`, use `Object` and pass in `null`.
* **There's no global execution.** Every executed code need to be in a function.
* **Unit tests need to live inside a `main()` function.** Because of the point above, `describe()` is technically executed code and has to live in a function. Additionally, spec files are programs in Dart and as such much have a `void main() {}` function containing the test code.
* **Boolean expressions are required to be boolean.** There's no type coercion or truthiness concept in Dart. Code like this: `if (!aNumber) {}` must be refactored to be `if (aNumber != 0) {}`.
* **Accessing any platform primitive must be done through a Facade.** For example, Promises or DOM manipulation. Facades are going to be provided on a need-to-have basis.
* **Union types are a no-go.** Dart has them on its roadmap, but for now we must avoid them.
* **Dart files cannot have the same name as a reserved keyword.** For example, `for.dart`, `switch.dart` or `class.dart` are all invalid names. Since the TypeScript files have the same name when transpiled to Dart, they also have the same restriction.
+27 -7
View File
@@ -4,11 +4,13 @@ var path = require('path');
// Import the require hook. Enables us to require TS files natively.
require('ts-node/register');
const detect = require('./tools/build/dart').detect;
var mergeTrees = require('broccoli-merge-trees');
var Angular2App = require('angular-cli/lib/broccoli/angular2-app');
var BroccoliSass = require('broccoli-sass');
var broccoliAutoprefixer = require('broccoli-autoprefixer');
var BroccoliTs2Dart = require('./tools/broccoli/broccoli-ts2dart').default;
var dartfmt = require('./tools/broccoli/broccoli-dartfmt').default;
var autoprefixerOptions = require('./build/autoprefixer-options');
@@ -17,21 +19,39 @@ module.exports = function(defaults) {
var demoCssTree = getCssTree('demo-app');
var componentCssTree = getCssTree('components');
var angularAppTree = new Angular2App(defaults);
var dartAppTree = new BroccoliTs2Dart('src/', {
generateLibraryName: true,
generateSourceMap: false,
translateBuiltins: true,
});
var dartAppTree = getDartTree('src/');
return mergeTrees([
angularAppTree.toTree(),
componentCssTree,
demoAppCssTree,
demoCssTree,
dartAppTree,
]);
].concat(dartAppTree || []));
};
/** Gets the Dart tree - Transpile Dart files and format them afterward. */
function getDartTree(root) {
const ts2dart = new BroccoliTs2Dart('src/', {
generateLibraryName: true,
generateSourceMap: false,
translateBuiltins: true,
});
const dartSDK = detect();
if (dartSDK) {
// If Dart isn't found, detect() will throw an error.
return dartfmt(ts2dart, { dartSDK });
} else {
console.warn('---------------------------------------');
console.warn('You do not have the Dart SDK installed.');
console.warn('In order to contribute to this repo, please refer to');
console.warn('https://github.com/angular/material2/blob/master/CONTRIBUTING.md');
console.warn('');
console.warn('You can still build and serve the demo app without dart support.');
return null;
}
}
/** Gets the tree for all of the components' CSS. */
function getCssTree(folder) {
var srcPath = `src/${folder}/`;
+8 -1
View File
@@ -9,6 +9,7 @@
},
"scripts": {
"build": "ng build",
"dartanalyzer": "ts-node scripts/ci/dart_analyzer",
"demo-app": "cd src && ng serve",
"test": "karma start test/karma.conf.js"
},
@@ -34,17 +35,23 @@
"broccoli-sass": "^0.7.0",
"browserstacktunnel-wrapper": "^1.4.2",
"ember-cli-inject-live-reload": "^1.3.0",
"fs-extra": "^0.26.5",
"glob": "^6.0.4",
"jasmine-core": "^2.3.4",
"js-yaml": "^3.5.2",
"karma": "^0.13.15",
"karma-browserstack-launcher": "^0.1.7",
"karma-chrome-launcher": "^0.2.1",
"karma-dart": "^0.3.0",
"karma-firefox-launcher": "^0.1.7",
"karma-jasmine": "^0.3.6",
"karma-sauce-launcher": "^0.2.14",
"strip-ansi": "^3.0.0",
"symlink-or-copy": "^1.0.1",
"ts-node": "^0.5.5",
"ts2dart": "^0.7.20",
"tslint": "^3.2.2",
"typescript": "^1.7.5"
"typescript": "^1.7.5",
"which": "^1.2.4"
}
}
+16
View File
@@ -0,0 +1,16 @@
name: material2
environment:
sdk: '>=1.12.0-dev.5.10 <2.0.0'
dependencies:
observe: '^0.13.1'
angular2: '2.0.0-beta.0'
dev_dependencies:
guinness: '^0.1.18'
intl: '^0.12.4'
unittest: '^0.11.5+4'
quiver: '^0.21.4'
transformers:
- angular2:
platform_directives: 'package:angular2/src/common/directives.dart#CORE_DIRECTIVES'
entry_points:
- web/src/demo-app/demo-app.dart
+12
View File
@@ -0,0 +1,12 @@
# Continuous Integration Scripts
This directory contains scripts that are related to CI only.
### `build-and-test.sh`
The main script. Setup the tests environment, build the files using the `angular-cli` tool and run the tests.
### `install_dart.sh`
Only run in Dart environments. Install Dart locally on the CI.
+15 -45
View File
@@ -1,57 +1,27 @@
#!/usr/bin/env bash
set -ex
echo "======= Starting build-and-test.sh ========================================"
# Go to project dir
SCRIPT_DIR=$(dirname $0)
cd ${SCRIPT_DIR}/../..
cd $(dirname $0)/../..
# Include sources.
source scripts/ci/sources/mode.sh
source scripts/ci/sources/tunnel.sh
start_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_setup.sh
;;
browserstack*)
./scripts/browserstack/start_tunnel.sh
;;
*)
;;
esac
}
wait_for_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_block.sh
;;
browserstack*)
./scripts/browserstack/waitfor_tunnel.sh
;;
*)
;;
esac
sleep 10
}
teardown_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_teardown.sh
;;
browserstack*)
# ./scripts/browserstack/teardown_tunnel.sh
;;
*)
;;
esac
}
# Setup environment.
is_dart && source scripts/ci/sources/env_dart.sh
start_tunnel
wait_for_tunnel
npm run build
karma start test/karma.conf.js --single-run --no-auto-watch --reporters='dots'
is_dart && pub install
wait_for_tunnel
if is_dart; then
# npm run dartanalyzer
echo 'TODO(hans): Implement dartanalyzer'
else
karma start test/karma.conf.js --single-run --no-auto-watch --reporters='dots'
fi
teardown_tunnel
+12
View File
@@ -0,0 +1,12 @@
import {analyze} from '../../tools/build/dart_analyzer';
import {detect} from '../../tools/build/dart';
const command = detect()['ANALYZER'];
analyze('dist/dart', command, true)
.then(() => {
console.log('Done.');
}, (err) => {
console.error('Error:', err);
});
+33
View File
@@ -0,0 +1,33 @@
#!/bin/bash
set -e -x
if [ -z "${DART_CHANNEL}" -o -z "${DART_VERSION}" -o -z "${ARCH}" ]; then
echo "No Dart necessary. Not installing."
exit 0
fi
AVAILABLE_DART_VERSION=$(curl "https://storage.googleapis.com/dart-archive/channels/${DART_CHANNEL}/release/${DART_VERSION}/VERSION" | python -c \
'import sys, json; print(json.loads(sys.stdin.read())["version"])')
echo Fetch Dart channel: ${DART_CHANNEL}
URL_PREFIX=https://storage.googleapis.com/dart-archive/channels/${DART_CHANNEL}/release/${DART_VERSION}
DART_SDK_URL="$URL_PREFIX/sdk/dartsdk-$ARCH-release.zip"
DARTIUM_URL="$URL_PREFIX/dartium/dartium-$ARCH-release.zip"
download_and_unzip() {
ZIPFILE=${1/*\//}
curl -O -L $1 && unzip -q $ZIPFILE && rm $ZIPFILE
}
# TODO: do these downloads in parallel
download_and_unzip $DART_SDK_URL
download_and_unzip $DARTIUM_URL
echo Fetched new dart version $(<dart-sdk/version)
if [[ -n $DARTIUM_URL ]]; then
mv dartium-* chromium
fi
+95
View File
@@ -0,0 +1,95 @@
#!/bin/bash
set -e -o pipefail
if [[ -z $ENV_SET ]]; then
export ENV_SET=1
unset DART
PLATFORM="$(uname -s)"
case "$PLATFORM" in
(Darwin)
path=$(readlink ${BASH_SOURCE[0]}||echo './scripts/env.sh')
export NGDART_SCRIPT_DIR=$(dirname $path)
;;
(Linux)
export NGDART_SCRIPT_DIR=$(dirname $(readlink -f ${BASH_SOURCE[0]}))
;;
(*)
echo Unsupported platform $PLATFORM. Exiting ... >&2
exit 3
;;
esac
export NGDART_BASE_DIR=$(dirname $NGDART_SCRIPT_DIR)
# Try to find the SDK alongside the dart command first.
if [[ -z "$DART_SDK" ]]; then
DART=$(which dart) || true
if [[ -x "$DART" ]]; then
DART_SDK="${DART/dart-sdk\/*/dart-sdk}"
if [[ ! -e "$DART_SDK" ]]; then
unset DART DART_SDK
fi
fi
fi
# Fallback: Assume it's alongside the current directory (e.g. Travis).
if [[ -z "$DART_SDK" ]]; then
DART_SDK="$(pwd)/dart-sdk"
fi
: "${DART:=$DART_SDK/bin/dart}"
if [[ ! -x "$DART" ]]; then
echo Unable to locate the dart binary / SDK. Exiting >&2
exit 3
fi
if [[ -z "$DARTIUM" ]]; then
dartiumRoot="$DART_SDK/../chromium"
if [[ -e "$dartiumRoot" ]]; then
case "$PLATFORM" in
(Linux) export DARTIUM="$dartiumRoot/chrome" ;;
(Darwin) export DARTIUM="$dartiumRoot/Chromium.app/Contents/MacOS/Chromium" ;;
(*) echo Unsupported platform $PLATFORM. Exiting ... >&2 ; exit 3 ;;
esac
fi
fi
case "$PLATFORM" in
(Linux) export DART_SDK_LIB_SEARCH_PATH="$DART_SDK" ;;
(Darwin) export DART_SDK_LIB_SEARCH_PATH="$DART_SDK/libexec" ;;
(*) echo Unsupported platform $PLATFORM. Exiting ... >&2 ; exit 3 ;;
esac
export DART_SDK
export DARTSDK
export DART
export PUB=${PUB:-"$DART_SDK/bin/pub"}
if [ -z "$PUB_CACHE" ]; then
export PUB_CACHE=$DART_SDK/pub-cache
fi
export DARTANALYZER=${DARTANALYZER:-"$DART_SDK/bin/dartanalyzer"}
export DARTDOC=${DARTDOC:-"$DART_SDK/bin/dartdoc"}
export DART_DOCGEN=${DART_DOCGEN:-"$DART_SDK/bin/docgen"}
export DART_VM_OPTIONS="--old_gen_heap_size=2048"
export DARTIUM_BIN=${DARTIUM_BIN:-"$DARTIUM"}
export CHROME_BIN=${CHROME_BIN:-"google-chrome"}
export PATH=$PATH:$DART_SDK/bin
echo '*********'
echo '** ENV **'
echo '*********'
echo DART_SDK=$DART_SDK
echo DART_SDK_LIB_SEARCH_PATH=$DART_SDK_LIB_SEARCH_PATH
echo DART=$DART
echo PUB=$PUB
echo DARTANALYZER=$DARTANALYZER
echo DARTDOC=$DARTDOC
echo DART_DOCGEN=$DART_DOCGEN
echo DARTIUM_BIN=$DARTIUM_BIN
echo CHROME_BIN=$CHROME_BIN
echo PATH=$PATH
echo NGDART_BASE_DIR=$NGDART_BASE_DIR
echo NGDART_SCRIPT_DIR=$NGDART_SCRIPT_DIR
fi
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
source ./scripts/ci/sources/tunnel.sh
is_dart() {
[[ "$MODE" = dart* ]]
}
+43
View File
@@ -0,0 +1,43 @@
#!/usr/bin/env bash
start_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_setup.sh
;;
browserstack*)
./scripts/browserstack/start_tunnel.sh
;;
*)
;;
esac
}
wait_for_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_block.sh
;;
browserstack*)
./scripts/browserstack/waitfor_tunnel.sh
;;
*)
;;
esac
sleep 10
}
teardown_tunnel() {
case "$MODE" in
saucelabs*)
./scripts/sauce/sauce_connect_teardown.sh
;;
browserstack*)
# ./scripts/browserstack/teardown_tunnel.sh
;;
*)
;;
esac
}
+32 -30
View File
@@ -16,42 +16,44 @@ import {By} from 'angular2/platform/browser';
import {MdButton} from './button';
import {AsyncTestFn, FunctionWithParamTokens} from 'angular2/testing';
export function main() {
describe('MdButton', () => {
let builder: TestComponentBuilder;
describe('MdButton', () => {
let builder: TestComponentBuilder;
beforeEach(inject([TestComponentBuilder], (tcb:TestComponentBuilder) => {
builder = tcb;
}));
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; }));
describe('button[md-button]', () => {
it('should handle a click on the button', (done:() => void) => {
return builder.createAsync(TestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
describe('button[md-button]', () => {
it('should handle a click on the button', (done: () => void) => {
return builder.createAsync(TestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
buttonDebugElement.nativeElement.click();
expect(testComponent.clickCount).toBe(1);
done();
buttonDebugElement.nativeElement.click();
expect(testComponent.clickCount).toBe(1);
done();
});
});
it('should not increment if disabled', (done:() => void) => {
return builder.createAsync(TestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
testComponent.isDisabled = true;
fixture.detectChanges();
buttonDebugElement.nativeElement.click();
expect(testComponent.clickCount).toBe(0);
done();
})
});
});
it('should not increment if disabled', (done: () => void) => {
return builder.createAsync(TestApp).then((fixture) => {
let testComponent = fixture.debugElement.componentInstance;
let buttonDebugElement = fixture.debugElement.query(By.css('button'));
testComponent.isDisabled = true;
fixture.detectChanges();
buttonDebugElement.nativeElement.click();
expect(testComponent.clickCount).toBe(0);
done();
})
});
});
});
}
/** Shortcut function to use instead of `injectAsync` for less boilerplate on each `it`. */
function testAsync(fn: Function): FunctionWithParamTokens {
+204 -189
View File
@@ -21,7 +21,7 @@ import {Predicate} from "angular2/src/facade/collection";
function wait(msec: number) {
return new Promise<void>(resolve => window.setTimeout(() => resolve(), msec));
return new Promise(resolve => window.setTimeout(() => resolve(), msec));
}
@@ -38,75 +38,117 @@ function waitOnEvent(fixture: ComponentFixture,
}
describe('MdSidenav', () => {
let builder: TestComponentBuilder;
export function main() {
describe('MdSidenav', () => {
let builder: TestComponentBuilder;
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => { builder = tcb; }));
beforeEach(inject([TestComponentBuilder], (tcb: TestComponentBuilder) => {
builder = tcb;
}));
describe('methods', () => {
it('should be able to open and close', (done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
describe('methods', () => {
it('should be able to open and close', (done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
testComponent = fixture.debugElement.componentInstance;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
testComponent = fixture.debugElement.componentInstance;
const openButtonElement = fixture.debugElement.query(By.css('.open'));
openButtonElement.nativeElement.click();
})
.then(() => wait(1))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(0);
})
.then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onOpen'))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(0);
expect(testComponent.closeCount).toBe(0);
const openButtonElement = fixture.debugElement.query(By.css('.open'));
openButtonElement.nativeElement.click();
})
.then(() => wait(1))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(0);
})
.then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onOpen'))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(0);
expect(testComponent.closeCount).toBe(0);
const sidenavElement = fixture.debugElement.query(By.css('md-sidenav'));
const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop'));
expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible');
expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible');
const sidenavElement = fixture.debugElement.query(By.css('md-sidenav'));
const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop'));
expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('visible');
expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('visible');
// Close it.
const closeButtonElement = fixture.debugElement.query(By.css('.close'));
closeButtonElement.nativeElement.click();
})
.then(() => wait(1))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(1);
expect(testComponent.closeCount).toBe(0);
})
.then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onClose'))
.then(() => fixture.detectChanges())
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(1);
expect(testComponent.closeCount).toBe(1);
// Close it.
const closeButtonElement = fixture.debugElement.query(By.css('.close'));
closeButtonElement.nativeElement.click();
})
.then(() => wait(1))
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(1);
expect(testComponent.closeCount).toBe(0);
})
.then(() => waitOnEvent(fixture, By.directive(MdSidenav), 'onClose'))
.then(() => fixture.detectChanges())
.then(() => {
expect(testComponent.openStartCount).toBe(1);
expect(testComponent.openCount).toBe(1);
expect(testComponent.closeStartCount).toBe(1);
expect(testComponent.closeCount).toBe(1);
const sidenavElement = fixture.debugElement.query(By.css('md-sidenav'));
const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop'));
expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden');
expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden');
})
.then(done, done.fail);
}, 8000);
const sidenavElement = fixture.debugElement.query(By.css('md-sidenav'));
const sidenavBackdropElement = fixture.debugElement.query(By.css('.md-sidenav-backdrop'));
expect(window.getComputedStyle(sidenavElement.nativeElement).visibility).toEqual('hidden');
expect(window.getComputedStyle(sidenavBackdropElement.nativeElement).visibility).toEqual('hidden');
})
.then(done, done.fail);
}, 8000);
it('open() and close() return a promise that resolves after the animation ended',
(done: any) => {
it('open() and close() return a promise that resolves after the animation ended',
(done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let promise: Promise<{}>;
let called: boolean = false;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
promise = sidenav.open();
promise.then(() => called = true);
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
.then(() => {
expect(called).toBe(false);
})
.then(() => promise)
.then(() => expect(called).toBe(true))
.then(() => {
// Close it now.
called = false;
promise = sidenav.close();
promise.then(() => called = true);
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
.then(() => {
expect(called).toBe(false);
})
.then(() => promise)
.then(() => expect(called).toBe(true))
.then(done, done.fail);
}, 8000);
it('open() twice returns the same promise', (done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let promise: Promise<void>;
let promise: Promise<{}>;
let called: boolean = false;
return builder.createAsync(BasicTestApp)
@@ -115,151 +157,124 @@ describe('MdSidenav', () => {
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
promise = sidenav.open();
promise.then(() => called = true);
expect(sidenav.open()).toBe(promise);
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
.then(() => {
expect(called).toBe(false);
fixture.detectChanges();
return promise;
})
.then(() => promise)
.then(() => expect(called).toBe(true))
.then(() => {
// Close it now.
called = false;
promise = sidenav.close();
promise.then(() => called = true);
expect(sidenav.close()).toBe(promise);
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
.then(() => {
expect(called).toBe(false);
})
.then(() => promise)
.then(() => expect(called).toBe(true))
.then(done, done.fail);
}, 8000);
});
it('open() twice returns the same promise', (done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
it('open() then close() cancel animations when called too fast',
(done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let promise: Promise<void>;
let called: boolean = false;
let openPromise: Promise<any>;
let closePromise: Promise<any>;
let openCalled: boolean = false;
let openCancelled: boolean = false;
let closeCalled: boolean = false;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
promise = sidenav.open();
expect(sidenav.open()).toBe(promise);
})
.then(() => wait(1))
.then(() => {
fixture.detectChanges();
return promise;
})
.then(() => {
promise = sidenav.close();
expect(sidenav.close()).toBe(promise);
})
.then(done, done.fail);
openPromise = sidenav.open().then(() => {
openCalled = true;
},
() => {
openCancelled = true;
});
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
// We need to wait for the browser to start the transition.
.then(() => wait(50))
.then(() => {
closePromise = sidenav.close().then(() => {
closeCalled = true;
}, done.fail);
return wait(1);
})
.then(() => {
fixture.detectChanges();
return closePromise;
})
.then(() => {
expect(openCalled).toBe(false);
expect(openCancelled).toBe(true);
expect(closeCalled).toBe(true);
})
.then(done, done.fail);
}, 8000);
it('close() then open() cancel animations when called too fast',
(done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let openPromise: Promise<any>;
let closePromise: Promise<any>;
let closeCalled: boolean = false;
let closeCancelled: boolean = false;
let openCalled: boolean = false;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
/** First, open it. */
openPromise = sidenav.open();
})
.then(() => wait(1))
.then(() => {
fixture.detectChanges();
return openPromise;
})
.then(() => {
// Then close and check behavior.
closePromise = sidenav.close().then(() => {
closeCalled = true;
},
() => {
closeCancelled = true;
});
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
// We need to wait for the browser to start the transition.
.then(() => wait(50))
.then(() => {
openPromise = sidenav.open().then(() => {
openCalled = true;
}, done.fail);
return wait(1);
})
.then(() => {
fixture.detectChanges();
return openPromise;
})
.then(() => {
expect(closeCalled).toBe(false);
expect(closeCancelled).toBe(true);
expect(openCalled).toBe(true);
})
.then(done, done.fail);
}, 8000);
});
it('open() then close() cancel animations when called too fast',
(done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let openPromise: Promise<void>;
let closePromise: Promise<void>;
let openCalled: boolean = false;
let openCancelled: boolean = false;
let closeCalled: boolean = false;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
openPromise = sidenav.open().then(() => { openCalled = true; },
() => { openCancelled = true; });
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
// We need to wait for the browser to start the transition.
.then(() => wait(50))
.then(() => {
closePromise = sidenav.close().then(() => { closeCalled = true; }, done.fail);
return wait(1);
})
.then(() => {
fixture.detectChanges();
return closePromise;
})
.then(() => {
expect(openCalled).toBe(false);
expect(openCancelled).toBe(true);
expect(closeCalled).toBe(true);
})
.then(done, done.fail);
}, 8000);
it('close() then open() cancel animations when called too fast',
(done: any) => {
let testComponent: BasicTestApp;
let fixture: ComponentFixture;
let sidenav: MdSidenav;
let openPromise: Promise<void>;
let closePromise: Promise<void>;
let closeCalled: boolean = false;
let closeCancelled: boolean = false;
let openCalled: boolean = false;
return builder.createAsync(BasicTestApp)
.then((f) => {
fixture = f;
sidenav = fixture.debugElement.query(By.directive(MdSidenav)).componentInstance;
/** First, open it. */
openPromise = sidenav.open();
})
.then(() => wait(1))
.then(() => {
fixture.detectChanges();
return openPromise;
})
.then(() => {
// Then close and check behavior.
closePromise = sidenav.close().then(() => { closeCalled = true; },
() => { closeCancelled = true; });
})
.then(() => wait(1))
.then(() => fixture.detectChanges())
// We need to wait for the browser to start the transition.
.then(() => wait(50))
.then(() => {
openPromise = sidenav.open().then(() => { openCalled = true; }, done.fail);
return wait(1);
})
.then(() => {
fixture.detectChanges();
return openPromise;
})
.then(() => {
expect(closeCalled).toBe(false);
expect(closeCancelled).toBe(true);
expect(openCalled).toBe(true);
})
.then(done, done.fail);
}, 8000);
});
});
}
/** Test component that contains an MdSidenavLayout and one MdSidenav. */
@Component({
+12 -12
View File
@@ -61,16 +61,16 @@ export class MdSidenav {
@Input('opened') private opened_: boolean;
/** Event emitted when the sidenav is being opened. Use this to synchronize animations. */
@Output('open-start') onOpenStart = new EventEmitter<void>();
@Output('open-start') onOpenStart = new EventEmitter<Object>();
/** Event emitted when the sidenav is fully opened. */
@Output('open') onOpen = new EventEmitter<void>();
@Output('open') onOpen = new EventEmitter<Object>();
/** Event emitted when the sidenav is being closed. Use this to synchronize animations. */
@Output('close-start') onCloseStart = new EventEmitter<void>();
@Output('close-start') onCloseStart = new EventEmitter<Object>();
/** Event emitted when the sidenav is fully closed. */
@Output('close') onClose = new EventEmitter<void>();
@Output('close') onClose = new EventEmitter<Object>();
/**
@@ -91,7 +91,7 @@ export class MdSidenav {
/** Open this sidenav, and return a Promise that will resolve when it's fully opened (or get
* rejected if it didn't). */
open(): Promise<void> {
open(): Promise<{}> {
return this.toggle(true);
}
@@ -99,7 +99,7 @@ export class MdSidenav {
* Close this sidenav, and return a Promise that will resolve when it's fully closed (or get
* rejected if it didn't).
*/
close(): Promise<void> {
close(): Promise<{}> {
return this.toggle(false);
}
@@ -108,11 +108,11 @@ export class MdSidenav {
* close() when it's closed.
* @param isOpen
*/
toggle(isOpen: boolean = !this.opened): Promise<void> {
toggle(isOpen: boolean = !this.opened): Promise<{}> {
// Shortcut it if we're already opened.
if (isOpen === this.opened) {
if (!this.transition_) {
return Promise.resolve();
return Promise.resolve(null);
} else {
return isOpen ? this.openPromise_ : this.closePromise_;
}
@@ -132,7 +132,7 @@ export class MdSidenav {
if (isOpen) {
if (!this.openPromise_) {
this.openPromise_ = new Promise<void>((resolve, reject) => {
this.openPromise_ = new Promise<{}>((resolve, reject) => {
this.openPromiseResolve_ = resolve;
this.openPromiseReject_ = reject;
});
@@ -140,7 +140,7 @@ export class MdSidenav {
return this.openPromise_;
} else {
if (!this.closePromise_) {
this.closePromise_ = new Promise<void>((resolve, reject) => {
this.closePromise_ = new Promise<{}>((resolve, reject) => {
this.closePromiseResolve_ = resolve;
this.closePromiseReject_ = reject;
});
@@ -225,10 +225,10 @@ export class MdSidenav {
}
private transition_: boolean = false;
private openPromise_: Promise<void>;
private openPromise_: Promise<{}>;
private openPromiseResolve_: () => void;
private openPromiseReject_: () => void;
private closePromise_: Promise<void>;
private closePromise_: Promise<{}>;
private closePromiseResolve_: () => void;
private closePromiseReject_: () => void;
}
+1 -1
View File
@@ -16,7 +16,7 @@ import {OneOf} from "../../core/annotations/one-of";
export class Dir {
@Input('dir') @OneOf(['ltr', 'rtl']) private dir_: string = 'ltr';
@Output() dirChange = new EventEmitter<void>();
@Output() dirChange = new EventEmitter<{}>();
@HostBinding('attr.dir')
get dir(): string {
+7
View File
@@ -0,0 +1,7 @@
import "package:angular2/platform/browser.dart" show bootstrap;
import "./demo-app/demo-app.dart" show DemoApp;
import "package:angular2/router" show ROUTER_PROVIDERS;
void main() {
bootstrap(DemoApp, [ROUTER_PROVIDERS]);
}
+8 -2
View File
@@ -1,6 +1,6 @@
Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 1000;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 2000;
__karma__.loaded = function() {};
@@ -49,7 +49,13 @@ System.import('angular2/platform/browser').then(function(browser_adapter) {
Object.keys(window.__karma__.files)
.filter(isSpecFile)
.map(function(moduleName) {
return System.import(moduleName);
return System.import(moduleName).then(function(module) {
if (module.hasOwnProperty('main')) {
return module.main();
} else {
return module;
}
});
}));
}).then(function() {
__karma__.start();
+1 -1
View File
@@ -14,9 +14,9 @@ export function config(config) {
plugins: [
require('karma-jasmine'),
require('karma-browserstack-launcher'),
require('karma-sauce-launcher'),
require('karma-chrome-launcher'),
require('karma-firefox-launcher'),
require('karma-sauce-launcher'),
],
files: [
{pattern: 'node_modules/es6-shim/es6-shim.js', included: true, watched: true},
+1
View File
@@ -0,0 +1 @@
../packages
-1
View File
@@ -26,7 +26,6 @@ class DartFormatter implements DiffingBroccoliPlugin {
constructor(public inputPath: string, public cachePath: string, options) {
if (!options.dartSDK) throw new Error("Missing Dart SDK");
this.DARTFMT = options.dartSDK.DARTFMT;
this.verbose = options.logs.dartfmt;
}
rebuild(treeDiff: DiffResult): Promise<any> {
+51
View File
@@ -0,0 +1,51 @@
/// <reference path="../typings/node/node.d.ts" />
// TODO(hansl): Get rid of this file. It's unneeded.
const which = require('which');
const child_process = require('child_process');
export type DartSdkExecutableMap = {
ANALYZER: string,
DARTDOCGEN: string,
DARTFMT: string,
PUB: string,
VM: string,
};
/**
* Return a map of {EXECUTABLE => Path} for the Dart SDK.
* The executables are different on Windows / Unix. Returns null if the Dart SDK cannot be
* found.
*/
export function detect(): DartSdkExecutableMap {
let sdk: DartSdkExecutableMap;
try {
const dartExecutable = which.sync('dart');
if (process.platform === 'win32') {
sdk = {
ANALYZER: 'dartanalyzer.bat',
DARTDOCGEN: 'dartdoc.bat',
DARTFMT: 'dartfmt.bat',
PUB: 'pub.bat',
VM: 'dart.exe'
};
} else {
sdk = {
ANALYZER: 'dartanalyzer',
DARTDOCGEN: 'dartdoc',
DARTFMT: 'dartfmt',
PUB: 'pub',
VM: 'dart'
};
}
console.log('Dart SDK detected:', dartExecutable);
console.log('- dart: ' + child_process.spawnSync(sdk.VM, ['--version']).stderr.toString().replace(/\n/g, ''));
console.log('- pub: ' + child_process.spawnSync(sdk.PUB, ['--version']).stdout.toString().replace(/\n/g, ''));
return sdk;
} catch (e) {
console.log('Dart SDK is not available.');
return null;
}
}
+249
View File
@@ -0,0 +1,249 @@
/// <reference path="../typings/es6-promise/es6-promise.d.ts" />
/// <reference path="../typings/node/node.d.ts" />
import {travisFoldStart} from '../travis/fold';
import readline = require('readline');
import child_process = require('child_process');
import path = require('path');
import fs = require('fs');
const glob = require('glob');
const yaml = require('js-yaml');
const tempFile = '_analyzer.dart';
/**
* Call the Dart Analyzer on every dart files below.
* Specialized for Travis.
*
* @param dir The directory to scan.
* @param command The command to use for dartanalyzer.
* @param use_ddc Enable strong static type checking (see https://goo.gl/DqcBsw).
*/
export function analyze(dir: string, command: string, use_ddc: boolean = false): Promise<void> {
var travisFoldEnd = travisFoldStart(`dartanalyzer-${use_ddc ? 'ddc' : ''}-${dir}`);
var pubspecContents = fs.readFileSync(path.join(dir, 'pubspec.yaml'));
var pubspec = yaml.safeLoad(pubspecContents);
var packageName = pubspec.name;
var libFiles = [].slice.call(glob.sync('lib/**/*.dart', {cwd: dir}));
var webFiles = [].slice.call(glob.sync('web/**/*.dart', {cwd: dir}));
var testFiles = [].slice.call(glob.sync('test/**/*_spec.dart', {cwd: dir}));
var analyzeFile = ['library _analyzer;'];
libFiles.concat(testFiles).concat(webFiles).forEach((fileName, index) => {
if (fileName !== tempFile && fileName.indexOf("/packages/") === -1) {
if (fileName.indexOf('lib') == 0) {
fileName = 'package:' + packageName + '/' +
path.relative('lib', fileName).replace(/\\/g, '/');
}
analyzeFile.push('import "' + fileName + '" as mod' + index + ';');
}
});
fs.writeFileSync(path.join(dir, tempFile), analyzeFile.join('\n'));
return new Promise<void>((resolve, reject) => {
analyze_(command, dir, resolve, use_ddc);
});
}
function analyze_(command, dirName, done, useDdc) {
// TODO remove --package-warnings once dartanalyzer handles transitive libraries
var flags = ['--fatal-warnings', '--package-warnings', '--format=machine'];
if (useDdc) {
console.log('Using DDC analyzer to analyze', dirName);
flags.push('--strong');
}
var args = flags.concat(tempFile);
var stream = child_process.spawn(command, args, {
// inherit stdin and stderr, but filter stdout
stdio: [process.stdin, process.stdout, 'pipe'],
cwd: dirName
});
// Filter out unused imports from our generated file.
// We don't reexports from the generated file
// as this could lead to name clashes when two files
// export the same thing.
var rl =
readline.createInterface({input: stream.stderr, output: process.stdout, terminal: false});
var hintCount = 0;
var errorCount = 0;
var warningCount = 0;
rl.on('line', function(line) {
if (line == "find: > bin [: No such file or directory") {
// Skip bad output from Dart SDK .bat files on Windows
return;
}
var parsedLine = _AnalyzerOutputLine.parse(line);
if (!parsedLine) {
errorCount++;
console.log('Unexpected output: ' + line);
return;
}
// TODO remove once dartanalyzer handles transitive libraries
// skip errors in third-party packages
if (parsedLine.sourcePath.indexOf(dirName) == -1) {
return;
}
if (parsedLine.shouldIgnore()) {
return;
}
if (parsedLine.isHint) {
hintCount++;
} else if (parsedLine.isWarning) {
warningCount++;
} else {
errorCount++;
}
console.log(dirName + ':' + parsedLine);
});
stream.on('close', function() {
var error;
var report = [];
if (errorCount > 0) {
report.push(errorCount + ' error(s)');
}
if (warningCount > 0) {
report.push(warningCount + ' warning(s)');
}
if (hintCount > 0) {
report.push(hintCount + ' hint(s)');
}
if (report.length > 0) {
error = 'Dartanalyzer showed ' + report.join(', ');
}
done(error);
});
stream.on('error', function(error) { done(error); });
}
// See https://github.com/dart-lang/analyzer_cli/blob/master/lib/src/error_formatter.dart
class _AnalyzerOutputLine {
severity: string;
errorType: string;
errorCode: string;
sourcePath: string;
lineNum: number;
colNum: string;
asciiLineLength: number;
errorMsg: string;
isError: boolean;
isHint: boolean;
isWarning: boolean;
constructor(result: any[]) {
this.severity = result[1];
this.errorType = result[2];
this.errorCode = result[3];
this.sourcePath = result[4];
this.lineNum = parseInt(result[5], 10);
this.colNum = result[6];
this.asciiLineLength = parseInt(result[7], 10);
this.errorMsg = result[8];
this.isError = Boolean(this.severity.match(/ERROR/i));
this.isHint = Boolean(this.severity.match(/INFO/i));
this.isWarning = Boolean(this.severity.match(/WARNING/i));
}
static parse(line: string) {
const result = this.analyzerParseRegExp_.exec(line);
return result ? new this(result) : null;
}
private static analyzerParseRegExp_ =
new RegExp('([^\|]+)\\|' + // #1, severity (NONE, INFO, WARNING, ERROR)
'([^\|]+)\\|' + // #2, errorCode.type (HINT, *_WARNING, *_ERROR, etc)
'([^\|]+)\\|' + // #3, errorCode (UNUSED_IMPORT, UNUSED_CATCH_STACK, etc)
'([^\|]+[^\|\\\\])\\|' + // #4, sourcePath with '|' chars backslash-escaped.
'([^\|]+)\\|' + // #5, line number
'([^\|]+)\\|' + // #6, column number
'([^\|]+)\\|' + // #7, length of the ASCII line to draw
'(.*)$'); // #8, error message
/* Maps file path (as string) to file source (an array of strings, one per line). */
private static cache_ = {};
private static ERR_NO_SOURCE = '(Could not find source line).';
toString() {
var sourceLine = this._getSourceLine();
var lineText = _AnalyzerOutputLine.ERR_NO_SOURCE;
if (sourceLine) {
var repeat = function(str, num) {
if (str.repeat) return str.repeat(num);
return Array.prototype.join.call({length: num}, str);
};
lineText =
'\n' + sourceLine + '\n' + repeat(' ', this.colNum) + repeat('^', this.asciiLineLength);
}
return '[' + this.severity + '] type: ' + this.errorType + ' ' +
this.errorMsg + ' (' + this.sourcePath + ', line ' + this.lineNum +
', col ' + this.colNum + ')' + lineText;
}
shouldIgnore() {
if (this.errorCode.match(/UNUSED_IMPORT/i)) {
if (this.sourcePath.match(/_analyzer\.dart/)) {
return true;
}
// TODO remove it once ts2dart properly generates abstract getters
if (this.errorMsg.match(/unimplemented/)) {
return true;
}
}
if (this.errorCode.match(/DEPRECATED_MEMBER_USE/i)) {
return true;
}
// TODO: https://github.com/angular/ts2dart/issues/168
if (this.errorCode.match(/UNUSED_CATCH_STACK/i)) {
return true;
}
// TODOs shouldn't break our build...
if (this.errorCode.match(/TODO/i)) {
return true;
}
// Don't worry about hints in generated files.
if (this.isHint && this.sourcePath.match(/generated/i)) {
return true;
}
return false;
}
// Reads the source file for the Analyzer output, caching it for future use.
_getSourceLine() {
var cache = _AnalyzerOutputLine.cache_;
var sourceLines = null;
if (cache.hasOwnProperty(this.sourcePath)) {
sourceLines = cache[this.sourcePath];
} else {
try {
sourceLines = String(fs.readFileSync(this.sourcePath));
sourceLines = sourceLines.split('\n');
} catch (e) {
sourceLines = null;
} finally {
// Even if this fails, cache `null` so we don't try again.
cache[this.sourcePath] = sourceLines;
}
}
return (sourceLines && this.lineNum <= sourceLines.length) ? sourceLines[this.lineNum - 1] : null;
}
};
+16
View File
@@ -0,0 +1,16 @@
function encode(str: string) {
return str.replace(/\W/g, '-').replace(/-$/, '');
}
export function travisFoldStart(name: string): () => void {
if (process.env['TRAVIS']) {
console.log('travis_fold:start:' + encode(name));
}
return () => {
if (process.env['TRAVIS']) {
console.log('travis_fold:end:' + encode(name));
}
};
}