This commit is contained in:
Connor Peet 2016-10-24 17:14:45 -07:00
Коммит e78974074f
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: CF8FD2EA0DBC61BD
20 изменённых файлов: 1111 добавлений и 0 удалений

12
.editorconfig Normal file
Просмотреть файл

@ -0,0 +1,12 @@
# top-most EditorConfig file
root = true
# Unix-style newlines with a newline ending every file
[*]
end_of_line = lf
insert_final_newline = true
trim_trailing_whitespace = true
charset = utf-8
indent_style = space
indent_size = 4

46
.gitignore поставляемый Normal file
Просмотреть файл

@ -0,0 +1,46 @@
#### joe made this: http://goel.io/joe
#### node ####
# Logs
logs
*.log
npm-debug.log*
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# nyc test coverage
.nyc_output
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# node-waf configuration
.lock-wscript
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directories
node_modules
jspm_packages
# Optional npm cache directory
.npm
# Optional REPL history
.node_repl_history
dist
.idea/
.DS_Store
# Editor
.vscode

57
.npmignore Normal file
Просмотреть файл

@ -0,0 +1,57 @@
# Logs
logs
*.log
# Runtime data
pids
*.pid
*.seed
# Directory for instrumented libs generated by jscoverage/JSCover
lib-cov
# Coverage directory used by tools like istanbul
coverage
# Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files)
.grunt
# Compiled binary addons (http://nodejs.org/api/addons.html)
build/Release
# Dependency directory
# Commenting this out is preferred by some people, see
# https://www.npmjs.org/doc/misc/npm-faq.html#should-i-check-my-node_modules-folder-into-git-
node_modules
# Users Environment Variables
.lock-wscript
.tsdrc
#IntelliJ configuration files
.idea
dist
dev
docs
lib
test
Thumbs.db
.DS_Store
*.yml
!*.d.ts
/src
*.spec.*
*.e2e.*
CONTRIBUTING.md
karma-shim.js
karma.conf.js
protractor.conf.js
tsconfig.json
tslint.json
typedoc.json
webpack.config.js
.travis.yml
.jshintrc
.editorconfig

50
LICENSE Normal file
Просмотреть файл

@ -0,0 +1,50 @@
Much of the code and algorithms in this project are from the project at
https://git.io/vPxQQ, which is provided under the following license:
The MIT License (MIT)
Copyright (c) 2016 Microsoft. All rights reserved.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in
all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
THE SOFTWARE.
-------------------------------------------------------------------------------
The original code in this project is available under the following license:
MIT License
Copyright (c) 2016 Beam Interactive, Inc.
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.

Просмотреть файл

@ -0,0 +1,70 @@
import { ArcModule, InputService } from '../../../../src';
import { Component, NgModule } from '@angular/core';
import { BrowserModule } from '@angular/platform-browser';
@Component({
selector: 'demo-app',
styles: [`
:host {
font-family: monospace;
}
.area {
max-width: 960px;
border: 1px solid #000;
margin: 15px auto;
.arc--selected {
border-color: #f00;
}
}
.box {
width: 100px;
float: left;
margin: 15px;
background: #000;
color: #fff;
.arc--selected {
background: #f00;
}
}
`],
template: `
<div class="area">
<div class="box" *ngFor="let box of boxes">
{{ box }}
</div>
</div>
`,
})
export class DemoComponent {
public boxes: string[] = [];
constructor(input: InputService) {
for (let i = 0; i < 100; i++) {
this.boxes.push(String(`Box ${i}`));
}
input.bootstrap();
}
}
@NgModule({
imports: [
BrowserModule,
ArcModule,
],
providers: [
InputService,
],
declarations: [
DemoComponent,
],
bootstrap: [
DemoComponent,
],
})
export class AppModule {
}

4
demo/src/bundles/main.ts Normal file
Просмотреть файл

@ -0,0 +1,4 @@
import { platformBrowserDynamic } from '@angular/platform-browser-dynamic';
import { AppModule } from './demo/demo.module';
platformBrowserDynamic().bootstrapModule(AppModule);

Просмотреть файл

@ -0,0 +1,4 @@
import 'ts-helpers';
import 'core-js/es7/reflect';
import 'zone.js/dist/zone';

13
demo/src/index.ejs Normal file
Просмотреть файл

@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Arcade Machine</title>
<link rel="stylesheet" href="normalize.css">
<base href="/">
</head>
<body>
<demo-app></demo-app>
</body>
</html>

73
demo/webpack.config.js Normal file
Просмотреть файл

@ -0,0 +1,73 @@
const path = require('path');
const webpack = require('webpack');
const atl = require('awesome-typescript-loader');
const CleanWebpackPlugin = require('clean-webpack-plugin');
const HtmlWebpackPlugin = require('html-webpack-plugin');
const chunkOrder = ['inline', 'polyfill', 'main'];
const paths = {};
paths.root = paths.root || path.resolve(__dirname);
paths.dist = path.resolve(__dirname, 'dist');
paths.indexTemplate = path.resolve(__dirname, 'src/index.ejs');
paths.tsconfig = path.resolve(__dirname, '../tsconfig.json');
module.exports = {
devtool: 'source-map',
context: paths.root,
entry: {
main: path.resolve(__dirname, 'src/bundles/main.ts'),
polyfills: path.resolve(__dirname, 'src/bundles/polyfills.ts')
},
output: {
path: paths.dist,
filename: 'bundles/[name].bundle.js',
},
resolve: {
extensions: ['.js', '.ts'],
},
devServer: {
port: 8080,
historyApiFallback: true,
contentBase: 'demo/dist'
},
module: {
loaders: [
{
test: /\.ts$/,
loaders: [
{
loader: 'awesome-typescript-loader',
query: {
useForkChecker: true,
tsconfig: paths.tsconfig
}
},
],
}
]
},
plugins: [
new HtmlWebpackPlugin({
template: paths.indexTemplate,
hash: true,
chunksSortMode: (a, b) => chunkOrder.indexOf(a.names[0]) > chunkOrder.indexOf(b.names[0]),
}),
new atl.ForkCheckerPlugin(),
// Fix for critical dependency warning due to System.import in angular.
// See https://github.com/angular/angular/issues/11580
new webpack.ContextReplacementPlugin(
/angular(\\|\/)core(\\|\/)(esm(\\|\/)src|src)(\\|\/)linker/,
paths.app
),
],
node: {
fs: 'empty',
crypto: 'empty',
module: false,
clearImmediate: false,
setImmediate: false
}
};

109
gulpfile.js Normal file
Просмотреть файл

@ -0,0 +1,109 @@
const gulp = require('gulp');
const gulpMerge = require('merge2');
const gulpRunSequence = require('run-sequence');
const gulpConcat = require('gulp-concat');
const gulpAutoprefixer = require('gulp-autoprefixer');
const tsc = require('gulp-typescript');
const karma = require('karma');
const autoprefixer = require('autoprefixer');
const gulpClean = require('gulp-clean');
const path = require('path');
const webpack = require('webpack');
let isTestRun = false;
/**
* Inline the templates and styles, and the compile to javascript.
*/
gulp.task(':build:app', () => {
const tsProject = tsc.createProject('tsconfig.json', {
module: isTestRun ? 'commonjs' : 'es2015',
});
gulp.src(['./src/**/*.ts'])
.pipe(tsProject())
.pipe(gulp.dest('./dist'));
});
/**
* Cleans the build folder
*/
gulp.task('clean', () => gulp.src('dist', { read: false }).pipe(gulpClean(null)));
/**
* Builds the main framework to the build folder
*/
gulp.task('build', (cb) => gulpRunSequence(
'clean',
[
':build:app',
],
cb
));
/**
* Bundles vendor files for test access
*/
gulp.task(':test:vendor', function () {
const npmVendorFiles = [
'@angular', 'core-js/client', 'systemjs/dist', 'zone.js/dist'
];
return gulpMerge(
npmVendorFiles.map(function (root) {
const glob = path.join(root, '**/*.+(js|js.map)');
return gulp.src(path.join('node_modules', glob))
.pipe(gulp.dest(path.join('dist/vendor', root)));
}));
});
/**
* Bundles systemjs files
*/
gulp.task(':test:system', () => {
gulp.src('test/bin/**.*')
.pipe(tsc())
.pipe(gulp.dest('dist/bin'));
});
/**
* Pre-test setup task
*/
gulp.task(':test:deps', (cb) => {
isTestRun = true;
gulpRunSequence(
'clean',
[
':test:system',
':test:vendor',
':build:app',
],
cb
);
});
/**
* Karma unit-testing
*/
gulp.task('test', [':test:deps'], (done) => {
new karma.Server({
configFile: path.join(process.cwd(), 'test/karma.confloader.js')
}, done).start();
});
gulp.task(':demo:clean', () => gulp.src('demo/dist', { read: false }).pipe(gulpClean(null)));
gulp.task(':demo:build:ts', (cb) => {
webpack(require('./demo/webpack.config.js'), cb);
});
gulp.task('demo', (cb) => gulpRunSequence(
':demo:clean',
[
':demo:build:ts',
],
cb
));

74
package.json Normal file
Просмотреть файл

@ -0,0 +1,74 @@
{
"name": "arcade-machine",
"version": "0.1.0",
"scripts": {
"gulp": "gulp",
"build": "gulp build",
"prepublish": "npm run build",
"clean": "rimraf node_modules doc dist && npm cache clean",
"test:lint": "tslint 'src/**/*.ts' --project tsconfig.json",
"test:unit": "gulp test",
"test": "npm run lint && npm run test:unit",
"start": "npm run serve",
"serve": "webpack-dev-server --config demo/webpack.config.js"
},
"main": "dist/index.js",
"typings": "dist/index.d.ts",
"license": "Proprietary",
"dependencies": {
"@angular/common": "^2.0.1",
"@angular/compiler": "^2.0.1",
"@angular/core": "^2.0.1",
"@angular/platform-browser": "^2.0.1",
"@angular/platform-browser-dynamic": "^2.0.1",
"@types/core-js": "^0.9.34",
"@types/node": "^6.0.40",
"core-js": "^2.4.1",
"systemjs": "0.19.38",
"zone.js": "^0.6.23"
},
"devDependencies": {
"@types/jasmine": "^2.2.34",
"@types/karma": "^0.13.33",
"@types/lodash": "^4.14.36",
"@types/node": "^6.0.41",
"autoprefixer": "^6.5.0",
"awesome-typescript-loader": "^2.2.4",
"clean-webpack-plugin": "^0.1.10",
"codelyzer": "0.0.28",
"copy-webpack-plugin": "^3.0.0",
"extract-text-webpack-plugin": "^1.0.1",
"gulp": "^3.9.1",
"gulp-autoprefixer": "^3.1.1",
"gulp-clean": "^0.3.2",
"gulp-concat": "^2.6.0",
"gulp-typescript": "^3.0.1",
"html-webpack-plugin": "^2.22.0",
"istanbul-instrumenter-loader": "^0.2.0",
"jasmine-core": "^2.3.4",
"jasmine-spec-reporter": "^2.4.0",
"karma": "^1.1.1",
"karma-browserstack-launcher": "^1.0.1",
"karma-chrome-launcher": "^1.0.1",
"karma-coverage": "^1.1.1",
"karma-firefox-launcher": "^1.0.0",
"karma-jasmine": "^1.0.2",
"karma-mocha-reporter": "^2.2.0",
"karma-sauce-launcher": "^1.0.0",
"karma-sourcemap-loader": "^0.3.7",
"merge2": "^1.0.2",
"phantomjs-prebuilt": "^2.1.4",
"reflect-metadata": "^0.1.8",
"rimraf": "^2.5.1",
"run-sequence": "^1.2.2",
"sass-loader": "^4.0.2",
"ts-helpers": "^1.1.1",
"tslint": "^3.15.1",
"tslint-loader": "^2.1.0",
"tslint-microsoft-contrib": "^2.0.12",
"typedoc": "^0.3.12",
"typescript": "2.0.3",
"webpack": "^2.1.0-beta.25",
"webpack-dev-server": "^2.1.0-beta.7"
}
}

8
src/focus.service.ts Normal file
Просмотреть файл

@ -0,0 +1,8 @@
export enum Direction {
LEFT,
RIGHT,
UP,
DOWN,
SUBMIT,
BACK,
}

10
src/index.ts Normal file
Просмотреть файл

@ -0,0 +1,10 @@
import { NgModule } from '@angular/core';
export { InputService } from './input.service';
@NgModule({
imports: [],
exports: [],
})
export class ArcModule {
}

268
src/input.service.ts Normal file
Просмотреть файл

@ -0,0 +1,268 @@
import { Direction } from './focus.service';
import { Injectable } from '@angular/core';
interface GamepadWrapper {
// Directional returns from the gamepad. They debounce themselves and
// trigger again after debounce times.
left(now: number): boolean;
right(now: number): boolean;
up(now: number): boolean;
down(now: number): boolean;
/**
* Returns if the user is pressing the "back" button.
*/
back(now: number): boolean;
/**
* Returns if the user is pressing the "submit" button.
*/
submit(now: number): boolean;
/**
* Returns whether the gamepad is still connected;
*/
isConnected(): boolean;
}
enum DebouncerStage {
IDLE,
HELD,
FAST,
}
class DirectionalDebouncer {
/**
* fn is a bound function that can be called to check if the key is held.
*/
public fn: (time: number) => boolean;
/**
* Initial debounce after a joystick is pressed before beginning shorter
* press debouncded.
*/
public static JoystickInitialDebounce = 500;
/**
* Fast debounce time for joysticks when they're being held in a direction.
*/
public static JoystickFastDebounce = 200;
private heldAt = 0;
private stage = DebouncerStage.IDLE;
constructor(private predicate: () => boolean) {}
/**
* Returns whether the key should be registered as pressed.
*/
public attempt(now: number): boolean {
const result = this.predicate();
if (!result) {
this.stage = DebouncerStage.IDLE;
return false;
}
switch (this.stage) {
case DebouncerStage.IDLE:
this.stage = DebouncerStage.HELD;
return true;
case DebouncerStage.HELD:
if (now - this.heldAt < DirectionalDebouncer.JoystickInitialDebounce) {
this.heldAt = now;
return false;
}
this.stage = DebouncerStage.FAST;
return true;
case DebouncerStage.FAST:
if (now - this.heldAt < DirectionalDebouncer.JoystickFastDebounce) {
this.heldAt = now;
return false;
}
return true;
default:
throw new Error(`Unknown debouncer stage ${this.stage}!`);
}
}
}
class XboxGamepadWrapper implements GamepadWrapper {
public left: (now: number) => boolean;
public right: (now: number) => boolean;
public up: (now: number) => boolean;
public down: (now: number) => boolean;
public back: (now: number) => boolean;
public submit: (now: number) => boolean;
constructor(private pad: Gamepad) {
const left = new DirectionalDebouncer(() => pad.axes[0] < -InputService.JoystickThreshold);
const right = new DirectionalDebouncer(() => pad.axes[0] > InputService.JoystickThreshold);
const up = new DirectionalDebouncer(() => pad.axes[1] < -InputService.JoystickThreshold);
const down = new DirectionalDebouncer(() => pad.axes[0] > InputService.JoystickThreshold);
const back = new DirectionalDebouncer(() => pad.buttons[1].pressed);
const submit = new DirectionalDebouncer(() => pad.buttons[0].pressed);
this.left = now => left.attempt(now);
this.right = now => right.attempt(now);
this.up = now => up.attempt(now);
this.down = now => down.attempt(now);
this.back = now => back.attempt(now);
this.submit = now => submit.attempt(now);
}
public isConnected() {
return this.pad.connected;
}
}
/**
* InputService handles passing input from the external device (gamepad API
* or keyboard) to the arc internals.
*/
@Injectable()
export class InputService {
/**
* Mangitude that joysticks have to go in one direction to be translated
* into a direction key press.
*/
public static JoystickThreshold = 0.5;
/**
* DirectionCodes is a map of directions to key code names.
*/
public static DirectionCodes = new Map<Direction, number[]>([
[Direction.DOWN, []]
]);
private gamepads: GamepadWrapper[];
private pollRaf: number;
/**
* Bootstrap attaches event listeners from the service to the DOM.
*/
public bootstrap() {
// The gamepadInputEmulation is a string property that exists in
// JavaScript UWAs and in WebViews in UWAs. It won't exist in
// Win8.1 style apps or browsers.
if (typeof (<any> navigator).gamepadInputEmulation === "string") {
// We want the gamepad to provide gamepad VK keyboard events rather than moving a
// mouse like cursor. Set to "keyboard", the gamepad will provide such keyboard events
// and provide input to the DOM navigator.getGamepads API.
(<any> navigator).gamepadInputEmulation = "keyboard";
} else if (typeof navigator.getGamepads === 'function') {
// Otherwise poll for connected gamepads and use that for input.
this.watchForGamepad();
}
this.addKeyboardListeners();
}
/**
* Detects any connected gamepads and watches for new ones to start
* polling them. This is the entry point for gamepad input handling.
*/
private watchForGamepad() {
const addGamepads = () => {
// it's not an array, originally, and contains undefined elements.
this.gamepads = Array.from(navigator.getGamepads())
.filter(pad => !!pad)
.map(pad => {
if (/xbox/i.test(pad.id)) {
return new XboxGamepadWrapper(pad);
}
// We can try, at least ¯\_(ツ)_/¯ and this should
// usually be OK due to remapping.
return new XboxGamepadWrapper(pad);
});
};
addGamepads();
if (this.gamepads.length > 0) {
this.scheduleGamepadPoll();
}
addEventListener('gamepadconnected', () => {
addGamepads();
cancelAnimationFrame(this.pollRaf);
this.scheduleGamepadPoll();
});
}
/**
* Schedules a new gamepad poll at the next animation frame.
*/
private scheduleGamepadPoll() {
this.pollRaf = requestAnimationFrame(now => this.pollGamepad(now));
}
/**
* Checks for input provided by the gamepad and fires off events as
* necessary. It schedules itself again provided that there's still
* a connected gamepad somewhere.
*/
private pollGamepad(now: number) {
for (let i = 0; i < this.gamepads.length; i++) {
const gamepad = this.gamepads[i];
if (!gamepad.isConnected()) {
this.gamepads.splice(i, 1);
i -= 1;
continue;
}
if (gamepad.left(now)) {
this.handleDirection(Direction.LEFT);
} else if (gamepad.right(now)) {
this.handleDirection(Direction.RIGHT);
} else if (gamepad.down(now)) {
this.handleDirection(Direction.DOWN);
} else if (gamepad.up(now)) {
this.handleDirection(Direction.UP);
} else if (gamepad.submit(now)) {
this.handleDirection(Direction.SUBMIT);
} else if (gamepad.back(now)) {
this.handleDirection(Direction.BACK);
}
}
if (this.gamepads.length > 0) {
this.scheduleGamepadPoll();
}
}
private handleDirection(direction: Direction): boolean {
console.log('dir', direction);
return true;
}
/**
* Handles a key down event, returns whether the event has resulted
* in a navigation and should be cancelled.
*/
private handleKeyDown(keyCode: number): boolean {
let result: boolean;
InputService.DirectionCodes.forEach((codes, direction) => {
if (result === undefined && codes.indexOf(keyCode) !== -1) {
result = this.handleDirection(direction);
}
});
return result;
}
/**
* Adds listeners for keyboard events.
*/
private addKeyboardListeners() {
addEventListener('keydown', ev => {
if (!ev.defaultPrevented && this.handleKeyDown(ev.keyCode)) {
ev.preventDefault();
}
});
}
}

Просмотреть файл

@ -0,0 +1,37 @@
declare var System: any;
System.config({
map: {
'rxjs': 'vendor/rxjs',
// Angular specific mappings.
'@angular/core': 'vendor/@angular/core/bundles/core.umd.js',
'@angular/core/testing': 'vendor/@angular/core/bundles/core-testing.umd.js',
'@angular/common': 'vendor/@angular/common/bundles/common.umd.js',
'@angular/common/testing': 'vendor/@angular/common/bundles/common-testing.umd.js',
'@angular/compiler': 'vendor/@angular/compiler/bundles/compiler.umd.js',
'@angular/compiler/testing': 'vendor/@angular/compiler/bundles/compiler-testing.umd.js',
'@angular/http': 'vendor/@angular/http/bundles/http.umd.js',
'@angular/http/testing': 'vendor/@angular/http/bundles/http-testing.umd.js',
'@angular/forms': 'vendor/@angular/forms/bundles/forms.umd.js',
'@angular/forms/testing': 'vendor/@angular/forms/bundles/forms-testing.umd.js',
'@angular/router': 'vendor/@angular/router/bundles/router.umd.js',
'@angular/router/testing': 'vendor/@angular/router/bundles/router-testing.umd.js',
'@angular/platform-browser': 'vendor/@angular/platform-browser/bundles/platform-browser.umd.js',
'@angular/platform-browser/testing':
'vendor/@angular/platform-browser/bundles/platform-browser-testing.umd.js',
'@angular/platform-browser-dynamic':
'vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic.umd.js',
'@angular/platform-browser-dynamic/testing':
'vendor/@angular/platform-browser-dynamic/bundles/platform-browser-dynamic-testing.umd.js',
},
packages: {
// Thirdparty barrels.
'rxjs': { main: 'index' },
// Set the default extension for the root package, because otherwise the demo-app can't
// be built within the production mode. Due to missing file extensions.
'.': {
defaultExtension: 'js'
}
}
});

56
test/karma-shim.js Normal file
Просмотреть файл

@ -0,0 +1,56 @@
/*global jasmine, __karma__, window*/
Error.stackTraceLimit = Infinity;
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
__karma__.loaded = function () {
};
var distPath = '/base/dist/';
function isJsFile(path) {
return path.slice(-3) == '.js';
}
function isSpecFile(path) {
return path.slice(-8) == '.spec.js';
}
function isAppFile(path) {
return isJsFile(path) && path.indexOf('vendor') == -1;
}
var allSpecFiles = Object.keys(window.__karma__.files)
.filter(isSpecFile)
.filter(isAppFile);
// Load our SystemJS configuration.
System.config({
baseURL: distPath,
});
// Load and configure the TestComponentBuilder.
System.import(distPath + 'bin/system-config-spec.js').then(function() {
// Load and configure the TestComponentBuilder.
return Promise.all([
System.import('@angular/core/testing'),
System.import('@angular/platform-browser-dynamic/testing')
]).then(function (providers) {
var testing = providers[0];
var testingBrowser = providers[1];
jasmine.DEFAULT_TIMEOUT_INTERVAL = 10000;
testing.TestBed.initTestEnvironment(
testingBrowser.BrowserDynamicTestingModule,
testingBrowser.platformBrowserDynamicTesting());
});
}).then(function() {
// Finally, load all spec files.
// This will run the tests directly.
return Promise.all(
allSpecFiles.map(function (moduleName) {
return System.import(moduleName).then(function(module) {
return module;
});
}));
}).then(__karma__.start, __karma__.error);

98
test/karma.conf.ts Normal file
Просмотреть файл

@ -0,0 +1,98 @@
import path = require('path');
export function config(config: any) {
config.set({
// base path that will be used to resolve all patterns (eg. files, exclude)
basePath: path.join(__dirname, '..'),
failOnEmptyTestSuite: false,
// frameworks to use
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
frameworks: ['jasmine'],
plugins: [
require('karma-jasmine'),
require('karma-mocha-reporter'),
require('karma-coverage'),
require('karma-chrome-launcher'),
require('karma-firefox-launcher'),
],
// list of files / patterns to load in the browser
files: [
{ pattern: 'dist/vendor/core-js/client/core.js', included: true, watched: false },
{ pattern: 'dist/vendor/systemjs/dist/system-polyfills.js', included: true, watched: false },
{ pattern: 'dist/vendor/systemjs/dist/system.src.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/zone.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/sync-test.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/async-test.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/proxy.js', include: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/fake-async-test.js', included: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/long-stack-trace-zone.js', include: true, watched: false },
{ pattern: 'dist/vendor/zone.js/dist/jasmine-patch.js', included: true, watched: false },
{ pattern: 'dist/vendor/hammerjs/hammer.min.js', included: true, watched: false },
{ pattern: 'test/karma-shim.js', included: true, watched: false },
{ pattern: 'dist/bin/system-config-spec.js', included: true, watched: false },
// paths loaded via module imports
{ pattern: 'dist/**/*.js', included: false, watched: true },
],
proxies: {
// required for component assets fetched by Angular's compiler
'/components/': '/base/dist/components/',
'/core/': '/base/dist/core/',
},
// list of files to exclude
exclude: [],
coverageReporter: {
dir: 'coverage/',
reporters: [
{type: 'text-summary'},
{type: 'html'}
]
},
// preprocess matching files before serving them to the browser
// available preprocessors: https://npmjs.org/browse/keyword/karma-preprocessor
preprocessors: {},
// test results reporter to use
// possible values: 'dots', 'progress', 'mocha'
// available reporters: https://npmjs.org/browse/keyword/karma-reporter
reporters: ['mocha', 'coverage'],
port: 9876,
colors: true,
// level of logging
// possible values: config.LOG_DISABLE || config.LOG_ERROR || config.LOG_WARN || config.LOG_INFO || config.LOG_DEBUG
logLevel: config.LOG_INFO,
// enable / disable watching file and executing tests whenever any file changes
autoWatch: false,
// start these browsers
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
browsers: ['Chrome'],
browserDisconnectTimeout: 2000000,
browserNoActivityTimeout: 2400000,
captureTimeout: 12000000,
// Continuous Integration mode
// if true, Karma captures browsers, runs the tests and exits
singleRun: true
});
};

28
test/karma.confloader.js Normal file
Просмотреть файл

@ -0,0 +1,28 @@
const fs = require('fs');
const ts = require('typescript');
const old = require.extensions['.ts'];
require.extensions['.ts'] = function(m, filename) {
// If we're in node module, either call the old hook or simply compile the
// file without transpilation. We do not touch node_modules/**.
if (filename.match(/node_modules/)) {
if (old) {
return old(m, filename);
}
return m._compile(fs.readFileSync(filename), filename);
}
// Node requires all require hooks to be sync.
const source = fs.readFileSync(filename).toString();
const result = ts.transpile(source, {
target: ts.ScriptTarget.ES5,
module: ts.ModuleKind.CommonJs,
});
// Send it to node to execute.
return m._compile(result, filename);
};
// Import the TS once we know it's safe to require.
module.exports = require('./karma.conf.ts').config;

30
tsconfig.json Normal file
Просмотреть файл

@ -0,0 +1,30 @@
{
"compilerOptions": {
"forceConsistentCasingInFileNames": true,
"allowSyntheticDefaultImports": true,
"experimentalDecorators": true,
"emitDecoratorMetadata": true,
"moduleResolution": "node",
"noImplicitAny": true,
"strictNullChecks": false,
"module": "commonjs",
"sourceMap": true,
"target": "es5",
"pretty": true,
"declaration": true,
"lib": [
"dom",
"es2015"
],
"typeRoots": [
"node_modules/@types"
],
"types": [
"jasmine",
"node"
]
},
"exclude": [
"node_modules"
]
}

64
tslint.json Normal file
Просмотреть файл

@ -0,0 +1,64 @@
{
"extends": "tslint-microsoft-contrib",
"rulesDirectory": [
"node_modules/codelyzer",
"node_modules/tslint-microsoft-contrib"
],
"rules": {
"no-increment-decrement": false,
"no-multiline-string": false,
"no-for-in-array": false,
"restrict-plus-operands": false,
"no-constructor-vars": false,
"no-relative-imports": false,
"mocha-no-side-effect-code": false,
"missing-jsdoc": false,
"export-name": false,
"no-stateless-class": false,
"no-any": false,
"trailing-comma": [true, {"multiline": "always", "singleline": "never"}],
// Angular
"directive-selector-name": [
true,
"camelCase"
],
"component-selector-name": [
true,
"kebab-case"
],
"directive-selector-type": [
true,
"attribute"
],
"component-selector-type": [
true,
"element"
],
"directive-selector-prefix": [
true,
"bui"
],
"component-selector-prefix": [
true,
"bui"
],
"use-input-property-decorator": true,
"use-output-property-decorator": true,
"use-host-property-decorator": true,
"no-attribute-parameter-decorator": true,
"no-input-rename": false,
"no-output-rename": false,
"no-forward-ref": false,
"use-life-cycle-interface": true,
"use-pipe-transform-interface": true,
"pipe-naming": [
true,
"camelCase",
"bui"
],
"component-class-suffix": true,
"directive-class-suffix": true,
"import-destructuring-spacing": true
}
}