Finish input service, get tests running
This commit is contained in:
Родитель
e78974074f
Коммит
a638d840cb
|
@ -1,12 +1,11 @@
|
|||
# top-most EditorConfig file
|
||||
# editorconfig.org
|
||||
|
||||
root = true
|
||||
|
||||
# Unix-style newlines with a newline ending every file
|
||||
[*]
|
||||
end_of_line = lf
|
||||
charset = utf-8
|
||||
insert_final_newline = true
|
||||
trim_trailing_whitespace = true
|
||||
charset = utf-8
|
||||
end_of_line = lf
|
||||
indent_style = space
|
||||
indent_size = 4
|
||||
|
||||
indent_size = 2
|
||||
|
|
|
@ -1,70 +1,71 @@
|
|||
import { ArcModule, InputService } from '../../../../src';
|
||||
import { ArcModule, InputService, FocusService } from '../../../../src';
|
||||
import { Component, NgModule } from '@angular/core';
|
||||
import { BrowserModule } from '@angular/platform-browser';
|
||||
|
||||
@Component({
|
||||
selector: 'demo-app',
|
||||
styles: [`
|
||||
:host {
|
||||
font-family: monospace;
|
||||
}
|
||||
selector: 'demo-app',
|
||||
styles: [`
|
||||
:host {
|
||||
font-family: monospace;
|
||||
}
|
||||
|
||||
.area {
|
||||
max-width: 960px;
|
||||
border: 1px solid #000;
|
||||
margin: 15px auto;
|
||||
.area {
|
||||
max-width: 960px;
|
||||
border: 1px solid #000;
|
||||
margin: 15px auto;
|
||||
|
||||
.arc--selected {
|
||||
border-color: #f00;
|
||||
}
|
||||
}
|
||||
.arc--selected {
|
||||
border-color: #f00;
|
||||
}
|
||||
}
|
||||
|
||||
.box {
|
||||
width: 100px;
|
||||
float: left;
|
||||
margin: 15px;
|
||||
background: #000;
|
||||
color: #fff;
|
||||
.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>
|
||||
`,
|
||||
.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[] = [];
|
||||
public boxes: string[] = [];
|
||||
|
||||
constructor(input: InputService) {
|
||||
for (let i = 0; i < 100; i++) {
|
||||
this.boxes.push(String(`Box ${i}`));
|
||||
}
|
||||
|
||||
input.bootstrap();
|
||||
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,
|
||||
],
|
||||
imports: [
|
||||
BrowserModule,
|
||||
ArcModule,
|
||||
],
|
||||
providers: [
|
||||
FocusService,
|
||||
InputService,
|
||||
],
|
||||
declarations: [
|
||||
DemoComponent,
|
||||
],
|
||||
bootstrap: [
|
||||
DemoComponent,
|
||||
],
|
||||
})
|
||||
export class AppModule {
|
||||
}
|
||||
|
|
|
@ -1,13 +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>
|
||||
<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>
|
||||
|
|
|
@ -12,62 +12,61 @@ 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: {
|
||||
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: [
|
||||
{
|
||||
test: /\.ts$/,
|
||||
loaders: [
|
||||
{
|
||||
loader: 'awesome-typescript-loader',
|
||||
query: {
|
||||
useForkChecker: true,
|
||||
tsconfig: paths.tsconfig
|
||||
}
|
||||
},
|
||||
],
|
||||
{
|
||||
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]),
|
||||
}),
|
||||
},
|
||||
],
|
||||
}
|
||||
]
|
||||
},
|
||||
plugins: [
|
||||
new HtmlWebpackPlugin({
|
||||
template: paths.indexTemplate,
|
||||
hash: true,
|
||||
chunksSortMode: (a, b) => chunkOrder.indexOf(a.names[0]) > chunkOrder.indexOf(b.names[0]),
|
||||
}),
|
||||
|
||||
new atl.ForkCheckerPlugin(),
|
||||
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
|
||||
}
|
||||
// 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
|
||||
}
|
||||
};
|
||||
|
|
83
gulpfile.js
83
gulpfile.js
|
@ -17,13 +17,13 @@ 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',
|
||||
});
|
||||
const tsProject = tsc.createProject('tsconfig.json', {
|
||||
module: isTestRun ? 'commonjs' : 'es2015',
|
||||
});
|
||||
|
||||
gulp.src(['./src/**/*.ts'])
|
||||
.pipe(tsProject())
|
||||
.pipe(gulp.dest('./dist'));
|
||||
gulp.src(['./src/**/*.ts'])
|
||||
.pipe(tsProject())
|
||||
.pipe(gulp.dest('./dist'));
|
||||
});
|
||||
|
||||
/**
|
||||
|
@ -35,75 +35,74 @@ 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
|
||||
'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'
|
||||
];
|
||||
const npmVendorFiles = [
|
||||
'@angular', 'core-js/client', 'rxjs', 'systemjs/dist', 'zone.js/dist'
|
||||
];
|
||||
|
||||
return gulpMerge(
|
||||
npmVendorFiles.map(function (root) {
|
||||
const glob = path.join(root, '**/*.+(js|js.map)');
|
||||
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)));
|
||||
}));
|
||||
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'));
|
||||
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
|
||||
);
|
||||
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();
|
||||
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);
|
||||
webpack(require('./demo/webpack.config.js'), cb);
|
||||
});
|
||||
|
||||
gulp.task('demo', (cb) => gulpRunSequence(
|
||||
':demo:clean',
|
||||
[
|
||||
':demo:build:ts',
|
||||
],
|
||||
cb
|
||||
':demo:clean',
|
||||
[
|
||||
':demo:build:ts',
|
||||
],
|
||||
cb
|
||||
));
|
||||
|
||||
|
|
15
package.json
15
package.json
|
@ -8,7 +8,7 @@
|
|||
"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",
|
||||
"test": "npm-run-all --parallel test:lint test:unit",
|
||||
"start": "npm run serve",
|
||||
"serve": "webpack-dev-server --config demo/webpack.config.js"
|
||||
},
|
||||
|
@ -22,16 +22,15 @@
|
|||
"@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",
|
||||
"rxjs": "^5.0.0-rc.1",
|
||||
"systemjs": "0.19.38",
|
||||
"zone.js": "^0.6.23"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/jasmine": "^2.2.34",
|
||||
"@types/karma": "^0.13.33",
|
||||
"@types/jasmine": "^2.5.35",
|
||||
"@types/lodash": "^4.14.36",
|
||||
"@types/node": "^6.0.41",
|
||||
"@types/node": "^6.0.45",
|
||||
"autoprefixer": "^6.5.0",
|
||||
"awesome-typescript-loader": "^2.2.4",
|
||||
"clean-webpack-plugin": "^0.1.10",
|
||||
|
@ -45,18 +44,18 @@
|
|||
"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",
|
||||
"jasmine-core": "^2.5.2",
|
||||
"jasmine-spec-reporter": "^2.7.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",
|
||||
"npm-run-all": "^3.1.1",
|
||||
"phantomjs-prebuilt": "^2.1.4",
|
||||
"reflect-metadata": "^0.1.8",
|
||||
"rimraf": "^2.5.1",
|
||||
|
|
|
@ -1,8 +1,22 @@
|
|||
import { Injectable } from '@angular/core';
|
||||
|
||||
export enum Direction {
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN,
|
||||
SUBMIT,
|
||||
BACK,
|
||||
LEFT,
|
||||
RIGHT,
|
||||
UP,
|
||||
DOWN,
|
||||
SUBMIT,
|
||||
BACK,
|
||||
}
|
||||
|
||||
@Injectable()
|
||||
export class FocusService {
|
||||
|
||||
/**
|
||||
* Attempts to effect the focus command, returning a
|
||||
* boolean if it was handled.
|
||||
*/
|
||||
public fire(direction: Direction): boolean {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,10 +1,11 @@
|
|||
import { NgModule } from '@angular/core';
|
||||
|
||||
export { InputService } from './input.service';
|
||||
export { FocusService } from './focus.service';
|
||||
|
||||
@NgModule({
|
||||
imports: [],
|
||||
exports: [],
|
||||
imports: [],
|
||||
exports: [],
|
||||
})
|
||||
export class ArcModule {
|
||||
}
|
||||
|
|
|
@ -0,0 +1,140 @@
|
|||
import { Direction } from './focus.service';
|
||||
import { InputService } from './input.service';
|
||||
|
||||
describe('input service', () => {
|
||||
let fire: jasmine.Spy;
|
||||
let input: InputService;
|
||||
beforeEach(() => {
|
||||
fire = jasmine.createSpy('fire');
|
||||
input = new InputService(<any> { fire });
|
||||
});
|
||||
|
||||
afterEach(() => input.teardown());
|
||||
|
||||
// from http://stackoverflow.com/questions/18001169/how-do-i-trigger-a-keyup
|
||||
// -keydown-event-in-an-angularjs-unit-test
|
||||
const sendKeyDown = (target: HTMLElement, keyCode: number) => {
|
||||
const e = new KeyboardEvent('keydown', {
|
||||
bubbles: true,
|
||||
cancelable: true,
|
||||
shiftKey: true,
|
||||
});
|
||||
|
||||
delete e.keyCode;
|
||||
Object.defineProperty(e, 'keyCode', { value: keyCode });
|
||||
target.dispatchEvent(e);
|
||||
};
|
||||
|
||||
describe('keyboard events', () => {
|
||||
beforeEach(() => input.bootstrap());
|
||||
|
||||
it('triggers key events', () => {
|
||||
sendKeyDown(document.body, 40);
|
||||
expect(fire).toHaveBeenCalledWith(Direction.DOWN);
|
||||
});
|
||||
|
||||
it('does not trigger events when defaults have been prevented', () => {
|
||||
const handler = (ev: KeyboardEvent) => ev.preventDefault();
|
||||
document.body.addEventListener('keydown', handler);
|
||||
sendKeyDown(document.body, 40);
|
||||
expect(fire).not.toHaveBeenCalledWith(Direction.DOWN);
|
||||
});
|
||||
});
|
||||
|
||||
it('enables virtual keyboards when they\'re present', () => {
|
||||
const nav: any = navigator;
|
||||
nav.gamepadInputEmulation = 'mouse';
|
||||
input.bootstrap();
|
||||
expect(nav.gamepadInputEmulation).toEqual('keyboard');
|
||||
input.teardown();
|
||||
expect(nav.gamepadInputEmulation).toEqual('mouse');
|
||||
delete nav.gamepadInputEmulation;
|
||||
});
|
||||
|
||||
describe('gamepads', () => {
|
||||
beforeEach(() => input.bootstrap());
|
||||
|
||||
const createFakeGamepad = () => {
|
||||
const pad = {
|
||||
id: 'xbox one controller',
|
||||
connected: true,
|
||||
axes: [0, 0],
|
||||
buttons: <{ pressed: boolean }[]> [],
|
||||
};
|
||||
for (let i = 0; i < 15; i++) {
|
||||
pad.buttons.push({ pressed: false });
|
||||
}
|
||||
return pad;
|
||||
};
|
||||
|
||||
const afterTwoFrames = (fn: () => void) => {
|
||||
requestAnimationFrame(() => requestAnimationFrame(fn));
|
||||
};
|
||||
|
||||
it('polls gamepads when they are connected, and disconnects', done => {
|
||||
const pad = createFakeGamepad();
|
||||
input.gamepadSrc.next({ gamepad: <any> pad });
|
||||
pad.axes[0] = -1;
|
||||
pad.buttons[0].pressed = true;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire).toHaveBeenCalledWith(Direction.LEFT);
|
||||
expect(fire).toHaveBeenCalledWith(Direction.SUBMIT);
|
||||
expect((<any> input).pollRaf).toBeTruthy();
|
||||
pad.connected = false;
|
||||
|
||||
afterTwoFrames(() => {
|
||||
expect((<any> input).pollRaf).toBeNull();
|
||||
done();
|
||||
})
|
||||
});
|
||||
});
|
||||
|
||||
it('starts triggering fast presses after an amount of time', done => {
|
||||
const pad = createFakeGamepad();
|
||||
input.gamepadSrc.next({ gamepad: <any> pad });
|
||||
pad.axes[0] = -1;
|
||||
setTimeout(() => expect(fire.calls.count()).toEqual(1), 400);
|
||||
setTimeout(() => expect(fire.calls.count()).toEqual(2), 550);
|
||||
setTimeout(() => {
|
||||
expect(fire.calls.count()).toEqual(4);
|
||||
done();
|
||||
}, 850);
|
||||
});
|
||||
|
||||
it('starts triggers when joysticks are newly moved', done => {
|
||||
const pad = createFakeGamepad();
|
||||
input.gamepadSrc.next({ gamepad: <any> pad });
|
||||
pad.axes[0] = -1;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(1);
|
||||
pad.axes[0] = 0;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(1);
|
||||
pad.axes[0] = -1;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
it('starts triggers when buttons are newly pressed', done => {
|
||||
const pad = createFakeGamepad();
|
||||
input.gamepadSrc.next({ gamepad: <any> pad });
|
||||
pad.buttons[0].pressed = true;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(1);
|
||||
pad.buttons[0].pressed = false;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(1);
|
||||
pad.buttons[0].pressed = true;
|
||||
afterTwoFrames(() => {
|
||||
expect(fire.calls.count()).toEqual(2);
|
||||
done();
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
|
@ -1,122 +1,173 @@
|
|||
import { Direction } from './focus.service';
|
||||
import { Direction, FocusService } from './focus.service';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { Observable } from 'rxjs/Observable';
|
||||
import { Subject } from 'rxjs/Subject';
|
||||
import { Subscription } from 'rxjs/Subscription';
|
||||
|
||||
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;
|
||||
import 'rxjs/add/observable/fromEvent';
|
||||
import 'rxjs/add/observable/merge';
|
||||
|
||||
/**
|
||||
* Returns if the user is pressing the "back" button.
|
||||
*/
|
||||
back(now: number): boolean;
|
||||
interface IGamepadWrapper {
|
||||
// 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 "submit" button.
|
||||
*/
|
||||
submit(now: number): boolean;
|
||||
/**
|
||||
* Returns if the user is pressing the "back" button.
|
||||
*/
|
||||
back(now: number): boolean;
|
||||
|
||||
/**
|
||||
* Returns whether the gamepad is still connected;
|
||||
*/
|
||||
isConnected(): 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,
|
||||
IDLE,
|
||||
HELD,
|
||||
FAST,
|
||||
}
|
||||
|
||||
/**
|
||||
* DirectionalDebouncer debounces directional navigation like arrow keys,
|
||||
* handling "holding" states.
|
||||
*/
|
||||
class DirectionalDebouncer {
|
||||
|
||||
/**
|
||||
* fn is a bound function that can be called to check if the key is held.
|
||||
*/
|
||||
public fn: (time: number) => boolean;
|
||||
/**
|
||||
* 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;
|
||||
/**
|
||||
* Initial debounce after a joystick is pressed before beginning shorter
|
||||
* press debouncded.
|
||||
*/
|
||||
public static initialDebounce = 500;
|
||||
|
||||
/**
|
||||
* Fast debounce time for joysticks when they're being held in a direction.
|
||||
*/
|
||||
public static JoystickFastDebounce = 200;
|
||||
/**
|
||||
* Fast debounce time for joysticks when they're being held in a direction.
|
||||
*/
|
||||
public static fastDebounce = 150;
|
||||
|
||||
private heldAt = 0;
|
||||
private stage = DebouncerStage.IDLE;
|
||||
private heldAt = 0;
|
||||
private stage = DebouncerStage.IDLE;
|
||||
|
||||
constructor(private predicate: () => boolean) {}
|
||||
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}!`);
|
||||
}
|
||||
/**
|
||||
* 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;
|
||||
this.heldAt = now;
|
||||
return true;
|
||||
|
||||
case DebouncerStage.HELD:
|
||||
if (now - this.heldAt < DirectionalDebouncer.initialDebounce) {
|
||||
return false;
|
||||
}
|
||||
this.heldAt = now;
|
||||
this.stage = DebouncerStage.FAST;
|
||||
return true;
|
||||
|
||||
case DebouncerStage.FAST:
|
||||
if (now - this.heldAt < DirectionalDebouncer.fastDebounce) {
|
||||
return false;
|
||||
}
|
||||
this.heldAt = now;
|
||||
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;
|
||||
/**
|
||||
* FiredDebouncer handles single "fired" states that happen from button presses.
|
||||
*/
|
||||
class FiredDebouncer {
|
||||
private fired = false;
|
||||
|
||||
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);
|
||||
constructor(private predicate: () => boolean) {}
|
||||
|
||||
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);
|
||||
}
|
||||
/**
|
||||
* Returns whether the key should be registered as pressed.
|
||||
*/
|
||||
public attempt(): boolean {
|
||||
const result = this.predicate();
|
||||
const hadFired = this.fired;
|
||||
this.fired = result;
|
||||
|
||||
public isConnected() {
|
||||
return this.pad.connected;
|
||||
}
|
||||
return !hadFired && result;
|
||||
}
|
||||
}
|
||||
|
||||
class XboxGamepadWrapper implements IGamepadWrapper {
|
||||
|
||||
/**
|
||||
* Mangitude that joysticks have to go in one direction to be translated
|
||||
* into a direction key press.
|
||||
*/
|
||||
public static joystickThreshold = 0.5;
|
||||
|
||||
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(() => {
|
||||
/* left joystick */ /* left dpad arrow */
|
||||
return pad.axes[0] < -XboxGamepadWrapper.joystickThreshold || pad.buttons[13].pressed;
|
||||
});
|
||||
const right = new DirectionalDebouncer(() => {
|
||||
/* right joystick */ /* right dpad arrow */
|
||||
return pad.axes[0] > XboxGamepadWrapper.joystickThreshold || pad.buttons[14].pressed;
|
||||
});
|
||||
const up = new DirectionalDebouncer(() => {
|
||||
/* up joystick */ /* up dpad arrow */
|
||||
return pad.axes[1] < -XboxGamepadWrapper.joystickThreshold || pad.buttons[11].pressed;
|
||||
});
|
||||
const down = new DirectionalDebouncer(() => {
|
||||
/* down joystick */ /* down dpad arrow */
|
||||
return pad.axes[1] > XboxGamepadWrapper.joystickThreshold || pad.buttons[12].pressed;
|
||||
});
|
||||
|
||||
const back = new FiredDebouncer(() => pad.buttons[1].pressed); // B button
|
||||
const submit = new FiredDebouncer(() => pad.buttons[0].pressed); // A button
|
||||
|
||||
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 = () => back.attempt();
|
||||
this.submit = () => submit.attempt();
|
||||
}
|
||||
|
||||
public isConnected() {
|
||||
return this.pad.connected;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -126,143 +177,223 @@ class XboxGamepadWrapper implements GamepadWrapper {
|
|||
@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.LEFT, [
|
||||
37, // LeftArrow
|
||||
214, // GamepadLeftThumbstickLeft
|
||||
205, // GamepadDPadLeft
|
||||
140, // NavigationLeft
|
||||
]],
|
||||
[Direction.RIGHT, [
|
||||
39, // RightArrow
|
||||
213, // GamepadLeftThumbstickRight
|
||||
206, // GamepadDPadRight
|
||||
141, // NavigationRight
|
||||
]],
|
||||
[Direction.UP, [
|
||||
38, // UpArrow
|
||||
211, // GamepadLeftThumbstickUp
|
||||
203, // GamepadDPadUp
|
||||
138, // NavigationUp
|
||||
]],
|
||||
[Direction.DOWN, [
|
||||
40, // UpArrow
|
||||
212, // GamepadLeftThumbstickDown
|
||||
204, // GamepadDPadDown
|
||||
139, // NavigationDown
|
||||
]],
|
||||
[Direction.SUBMIT, [
|
||||
13, // Enter
|
||||
142, // NavigationAccept
|
||||
195, // GamepadA
|
||||
]],
|
||||
[Direction.BACK, [
|
||||
8, // Backspace
|
||||
196, // GamepadB
|
||||
]],
|
||||
]);
|
||||
|
||||
/**
|
||||
* DirectionCodes is a map of directions to key code names.
|
||||
*/
|
||||
public static DirectionCodes = new Map<Direction, number[]>([
|
||||
[Direction.DOWN, []]
|
||||
]);
|
||||
/**
|
||||
* Mock source for gamepad connections. You can provide gamepads manually
|
||||
* here, but this is mostly for testing purposes.
|
||||
*/
|
||||
public gamepadSrc = new Subject<{ gamepad: Gamepad }>();
|
||||
|
||||
private gamepads: GamepadWrapper[];
|
||||
private pollRaf: number;
|
||||
/**
|
||||
* Mock source for keyboard events. You can provide events manually
|
||||
* here, but this is mostly for testing purposes.
|
||||
*/
|
||||
public keyboardSrc = new Subject<{
|
||||
defaultPrevented: boolean,
|
||||
keyCode: number,
|
||||
preventDefault: () => void,
|
||||
}>();
|
||||
|
||||
/**
|
||||
* 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();
|
||||
private gamepads: IGamepadWrapper[] = [];
|
||||
private subscriptions: Subscription[] = [];
|
||||
private pollRaf: number = null;
|
||||
|
||||
constructor(private focus: FocusService) {}
|
||||
|
||||
/**
|
||||
* 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 ('gamepadInputEmulation' in navigator) {
|
||||
// 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();
|
||||
}
|
||||
|
||||
/**
|
||||
* Unregisters all listeners and frees resources associated with the service.
|
||||
*/
|
||||
public teardown() {
|
||||
this.gamepads = [];
|
||||
cancelAnimationFrame(this.pollRaf);
|
||||
while (this.subscriptions.length) {
|
||||
this.subscriptions.pop().unsubscribe();
|
||||
}
|
||||
|
||||
if ('gamepadInputEmulation' in navigator) {
|
||||
(<any> navigator).gamepadInputEmulation = 'mouse';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 addGamepad = (pad: Gamepad) => {
|
||||
let gamepad: IGamepadWrapper;
|
||||
if (/xbox/i.test(pad.id)) {
|
||||
gamepad = new XboxGamepadWrapper(pad);
|
||||
}
|
||||
if (!gamepad) {
|
||||
// We can try, at least ¯\_(ツ)_/¯ and this should
|
||||
// usually be OK due to remapping.
|
||||
gamepad = new XboxGamepadWrapper(pad);
|
||||
}
|
||||
|
||||
this.gamepads.push(gamepad);
|
||||
};
|
||||
|
||||
Array.from(navigator.getGamepads())
|
||||
.filter(pad => !!pad)
|
||||
.forEach(addGamepad);
|
||||
|
||||
if (this.gamepads.length > 0) {
|
||||
this.scheduleGamepadPoll();
|
||||
}
|
||||
|
||||
this.subscriptions.push(
|
||||
Observable.merge(
|
||||
this.gamepadSrc,
|
||||
Observable.fromEvent(window, 'gamepadconnected')
|
||||
).subscribe(ev => {
|
||||
addGamepad((<any> ev).gamepad);
|
||||
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) {
|
||||
navigator.getGamepads(); // refreshes all checked-out gamepads
|
||||
|
||||
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);
|
||||
}
|
||||
if (gamepad.right(now)) {
|
||||
this.handleDirection(Direction.RIGHT);
|
||||
}
|
||||
if (gamepad.down(now)) {
|
||||
this.handleDirection(Direction.DOWN);
|
||||
}
|
||||
if (gamepad.up(now)) {
|
||||
this.handleDirection(Direction.UP);
|
||||
}
|
||||
if (gamepad.submit(now)) {
|
||||
this.handleDirection(Direction.SUBMIT);
|
||||
}
|
||||
if (gamepad.back(now)) {
|
||||
this.handleDirection(Direction.BACK);
|
||||
}
|
||||
}
|
||||
|
||||
if (this.gamepads.length > 0) {
|
||||
this.scheduleGamepadPoll();
|
||||
} else {
|
||||
this.pollRaf = null;
|
||||
}
|
||||
}
|
||||
|
||||
private handleDirection(direction: Direction): boolean {
|
||||
return this.focus.fire(direction);
|
||||
}
|
||||
|
||||
/**
|
||||
* 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() {
|
||||
this.subscriptions.push(
|
||||
Observable.merge(
|
||||
this.keyboardSrc,
|
||||
Observable.fromEvent<KeyboardEvent>(window, 'keydown')
|
||||
).subscribe(ev => {
|
||||
if (!ev.defaultPrevented && this.handleKeyDown(ev.keyCode)) {
|
||||
ev.preventDefault();
|
||||
}
|
||||
|
||||
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();
|
||||
}
|
||||
});
|
||||
}
|
||||
})
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
@ -1,6 +1,5 @@
|
|||
/*global jasmine, __karma__, window*/
|
||||
/*global __karma__, window*/
|
||||
Error.stackTraceLimit = Infinity;
|
||||
jasmine.DEFAULT_TIMEOUT_INTERVAL = 3000;
|
||||
|
||||
__karma__.loaded = function () {
|
||||
};
|
||||
|
|
|
@ -2,97 +2,95 @@ import path = require('path');
|
|||
|
||||
export function config(config: any) {
|
||||
|
||||
config.set({
|
||||
config.set({
|
||||
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: path.join(__dirname, '..'),
|
||||
// base path that will be used to resolve all patterns (eg. files, exclude)
|
||||
basePath: path.join(__dirname, '..'),
|
||||
|
||||
failOnEmptyTestSuite: false,
|
||||
failOnEmptyTestSuite: false,
|
||||
|
||||
// frameworks to use
|
||||
// available frameworks: https://npmjs.org/browse/keyword/karma-adapter
|
||||
frameworks: ['jasmine'],
|
||||
// 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'),
|
||||
],
|
||||
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 },
|
||||
// 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/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: 'test/karma-shim.js', included: true, watched: false },
|
||||
{ pattern: 'dist/bin/system-config-spec.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 },
|
||||
],
|
||||
|
||||
// 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/',
|
||||
},
|
||||
|
||||
proxies: {
|
||||
// required for component assets fetched by Angular's compiler
|
||||
'/components/': '/base/dist/components/',
|
||||
'/core/': '/base/dist/core/',
|
||||
},
|
||||
// list of files to exclude
|
||||
exclude: [],
|
||||
|
||||
// list of files to exclude
|
||||
exclude: [],
|
||||
coverageReporter: {
|
||||
dir: 'coverage/',
|
||||
reporters: [
|
||||
{type: 'text-summary'},
|
||||
{type: 'html'}
|
||||
]
|
||||
},
|
||||
|
||||
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: {},
|
||||
|
||||
// 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'],
|
||||
|
||||
// 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,
|
||||
|
||||
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,
|
||||
// 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,
|
||||
// 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'],
|
||||
// start these browsers
|
||||
// available browser launchers: https://npmjs.org/browse/keyword/karma-launcher
|
||||
browsers: ['Chrome'],
|
||||
|
||||
browserDisconnectTimeout: 2000000,
|
||||
browserNoActivityTimeout: 2400000,
|
||||
captureTimeout: 12000000,
|
||||
browserDisconnectTimeout: 2000000,
|
||||
browserNoActivityTimeout: 2400000,
|
||||
captureTimeout: 12000000,
|
||||
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true
|
||||
});
|
||||
// Continuous Integration mode
|
||||
// if true, Karma captures browsers, runs the tests and exits
|
||||
singleRun: true
|
||||
});
|
||||
|
||||
};
|
||||
|
|
Загрузка…
Ссылка в новой задаче