Skip to content

Commit

Permalink
feat(tasks): Kill tasks via ctrl-c (#327)
Browse files Browse the repository at this point in the history
  • Loading branch information
mrmeku authored Nov 9, 2018
1 parent 0bfae75 commit 1d41c69
Show file tree
Hide file tree
Showing 14 changed files with 524 additions and 58 deletions.
8 changes: 8 additions & 0 deletions angular.json
Original file line number Diff line number Diff line change
Expand Up @@ -344,6 +344,14 @@
"**/node_modules/**"
]
}
},
"test": {
"builder": "@angular-devkit/build-angular:karma",
"options": {
"main": "libs/feature-action-bar/src/test.ts",
"tsConfig": "libs/feature-action-bar/tsconfig.spec.json",
"karmaConfig": "libs/feature-action-bar/karma.conf.js"
}
}
}
},
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ describe('Extensions', () => {
goToGenerate();
cy.wait(300); // Needed to de-flake this test
taskListHeaders($p => {
expect(texts($p)[0]).to.equal('@angular/material');
expect(texts($p)[1]).to.equal('@angular/material');
});
});

Expand Down
4 changes: 2 additions & 2 deletions apps/angular-console-e2e/src/integration/forms.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,13 +85,13 @@ describe('Forms', () => {
);

cy.get('mat-select[name="export"]').click();
cy.contains('.mat-select-content .mat-option', 'true').click();
cy.contains('.mat-select-panel .mat-option', 'true').click();
checkDisplayedCommand(
'$ ng generate @schematics/angular:component cmp --export --dry-run'
);

cy.get('mat-select[name="export"]').click();
cy.contains('.mat-select-content .mat-option', 'false').click();
cy.contains('.mat-select-panel .mat-option', 'false').click();
checkDisplayedCommand(
'$ ng generate @schematics/angular:component cmp --dry-run'
);
Expand Down
32 changes: 32 additions & 0 deletions libs/feature-action-bar/karma.conf.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
// Karma configuration file, see link for more information
// https://karma-runner.github.io/1.0/config/configuration-file.html

module.exports = function (config) {
config.set({
basePath: '',
frameworks: ['jasmine', '@angular-devkit/build-angular'],
plugins: [
require('karma-jasmine'),
require('karma-chrome-launcher'),
require('karma-jasmine-html-reporter'),
require('karma-coverage-istanbul-reporter'),
require('@angular-devkit/build-angular/plugins/karma')
],
client: {
clearContext: false // leave Jasmine Spec Runner output visible in browser
},
coverageIstanbulReporter: {
dir: require('path').join(__dirname, '../../coverage'),
reports: ['html', 'lcovonly'],
fixWebpackSourcePaths: true
},
failOnEmptyTestSuite: false,
reporters: ['progress', 'kjhtml'],
port: 9876,
colors: true,
logLevel: config.LOG_INFO,
autoWatch: true,
browsers: ['Chrome'],
singleRun: false
});
};
115 changes: 115 additions & 0 deletions libs/feature-action-bar/src/lib/action-bar.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,115 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';

import {
CommandStatus,
CommandRunner,
CommandResponse
} from '@angular-console/utils';
import { ActionBarComponent } from './action-bar.component';
import { FeatureActionBarModule } from './feature-action-bar.module';
import { BehaviorSubject, Subject } from 'rxjs';
import { NoopAnimationsModule } from '@angular/platform-browser/animations';

describe('ActionBarComponent', () => {
let component: ActionBarComponent;
let fixture: ComponentFixture<ActionBarComponent>;
let mockCommandRunner: jasmine.SpyObj<CommandRunner>;
let mockCommands: Subject<CommandResponse[]>;

beforeEach(async(() => {
mockCommands = new Subject<CommandResponse[]>();
mockCommandRunner = jasmine.createSpyObj<CommandRunner>('CommandRunner', [
'stopCommandViaCtrlC',
'listAllCommands'
]);
mockCommandRunner.listAllCommands.and.returnValue(mockCommands);
TestBed.configureTestingModule({
imports: [NoopAnimationsModule, FeatureActionBarModule],
providers: [{ provide: CommandRunner, useValue: mockCommandRunner }]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(ActionBarComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

describe('ctrl-c', () => {
it('should not kill multiple commands', () => {
const commandResponse: CommandResponse = {
id: 'commandId',
out: '',
outChunk: '',
detailedStatus: {},
status: CommandStatus.IN_PROGRESS,
command: ''
};
mockCommands.next([commandResponse, commandResponse]);

// Dont call if multiple events
document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);
expect(mockCommandRunner.stopCommandViaCtrlC).not.toHaveBeenCalled();
});

it('should not kill an opened command', () => {
const commandResponse: CommandResponse = {
id: 'commandId',
out: '',
outChunk: '',
detailedStatus: {},
status: CommandStatus.IN_PROGRESS,
command: ''
};
mockCommands.next([commandResponse]);

component.actionsExpanded.next(true);
mockCommands.next([commandResponse]);
document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);
expect(mockCommandRunner.stopCommandViaCtrlC).not.toHaveBeenCalled();
});

it('should kill a single command', () => {
const commandResponse: CommandResponse = {
id: 'commandId',
out: '',
outChunk: '',
detailedStatus: {},
status: CommandStatus.IN_PROGRESS,
command: ''
};
mockCommands.next([commandResponse]);

document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);
expect(mockCommandRunner.stopCommandViaCtrlC).toHaveBeenCalledWith(
commandResponse.id
);
});

it('should only kill running events', () => {
const commandResponse: CommandResponse = {
id: 'commandId',
out: '',
outChunk: '',
detailedStatus: {},
status: CommandStatus.TERMINATED,
command: ''
};
mockCommands.next([commandResponse]);
document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);
expect(mockCommandRunner.stopCommandViaCtrlC).not.toHaveBeenCalled();
});
});
});
33 changes: 25 additions & 8 deletions libs/feature-action-bar/src/lib/action-bar.component.ts
Original file line number Diff line number Diff line change
@@ -1,27 +1,33 @@
import { CommandOutputComponent } from '@angular-console/ui';
import {
CommandResponse,
CommandRunner,
CommandStatus
} from '@angular-console/utils';
import {
animate,
state,
style,
transition,
trigger
} from '@angular/animations';
import { Component, QueryList, ViewChildren } from '@angular/core';
import {
Component,
HostListener,
QueryList,
ViewChildren
} from '@angular/core';
import { ContextualActionBarService } from '@nrwl/angular-console-enterprise-frontend';
import { BehaviorSubject, combineLatest, Observable } from 'rxjs';
import {
distinctUntilChanged,
map,
shareReplay,
startWith,
switchMap,
take,
tap
} from 'rxjs/operators';
import {
CommandResponse,
CommandRunner,
CommandStatus
} from '@angular-console/utils';
import { ContextualActionBarService } from '@nrwl/angular-console-enterprise-frontend';
import { CommandOutputComponent } from '@angular-console/ui';

const TERMINAL_PADDING = 44;
const COMMAND_HEIGHT = 64;
Expand Down Expand Up @@ -137,6 +143,17 @@ export class ActionBarComponent {
return command.id;
}

@HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (!this.actionsExpanded.value && event.ctrlKey && event.key === 'c') {
this.commands$.pipe(take(1)).subscribe(commands => {
if (commands.length === 1 && commands[0].status === 'in-progress') {
this.commandRunner.stopCommandViaCtrlC(commands[0].id);
}
});
}
}

constructor(
readonly commandRunner: CommandRunner,
private readonly contextualActionBarService: ContextualActionBarService
Expand Down
22 changes: 22 additions & 0 deletions libs/feature-action-bar/src/test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// This file is required by karma.conf.js and loads recursively all the .spec and framework files

import 'core-js/es7/reflect';
import 'zone.js/dist/zone';
import 'zone.js/dist/zone-testing';
import { getTestBed } from '@angular/core/testing';
import {
BrowserDynamicTestingModule,
platformBrowserDynamicTesting
} from '@angular/platform-browser-dynamic/testing';

declare const require: any;

// First, initialize the Angular testing environment.
getTestBed().initTestEnvironment(
BrowserDynamicTestingModule,
platformBrowserDynamicTesting()
);
// Then we find all the tests.
const context = require.context('./', true, /\.spec\.ts$/);
// And load the modules.
context.keys().map(context);
9 changes: 9 additions & 0 deletions libs/feature-action-bar/tsconfig.spec.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"outDir": "../../dist/out-tsc/libs/feature-action-bar",
"types": ["jasmine", "node"]
},
"files": ["src/test.ts"],
"include": ["**/*.spec.ts", "**/*.d.ts"]
}
56 changes: 56 additions & 0 deletions libs/ui/src/lib/command-output/command-output.component.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { CommandOutputComponent } from './command-output.component';
import { UiModule } from '../ui.module';
import { CommandStatus, CommandRunner } from '@angular-console/utils';

describe('CommandOutputComponent', () => {
let component: CommandOutputComponent;
let fixture: ComponentFixture<CommandOutputComponent>;
let mockCommandRunner: jasmine.SpyObj<CommandRunner>;

beforeEach(async(() => {
mockCommandRunner = jasmine.createSpyObj<CommandRunner>('CommandRunner', [
'stopCommandViaCtrlC'
]);
TestBed.configureTestingModule({
imports: [UiModule],
providers: [{ provide: CommandRunner, useValue: mockCommandRunner }]
}).compileComponents();
}));

beforeEach(() => {
fixture = TestBed.createComponent(CommandOutputComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});

it('should create', () => {
expect(component).toBeTruthy();
});

it('should kill running commands via ctrl-c', () => {
component.commandResponse = {
id: 'commandId',
out: '',
outChunk: '',
detailedStatus: {},
status: CommandStatus.IN_PROGRESS,
command: ''
};

document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);

expect(mockCommandRunner.stopCommandViaCtrlC).toHaveBeenCalledWith(
component.commandResponse.id
);

mockCommandRunner.stopCommandViaCtrlC.calls.reset();
component.commandResponse.status = CommandStatus.TERMINATED;
document.dispatchEvent(
new KeyboardEvent('keypress', { ctrlKey: true, key: 'c' })
);
expect(mockCommandRunner.stopCommandViaCtrlC).not.toHaveBeenCalled();
});
});
20 changes: 18 additions & 2 deletions libs/ui/src/lib/command-output/command-output.component.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,14 @@ import {
Input,
OnDestroy,
TemplateRef,
ViewChild
ViewChild,
HostListener
} from '@angular/core';
import { TerminalComponent } from '../terminal/terminal.component';
import { CommandResponse } from '@angular-console/utils';
import { CommandResponse, CommandRunner } from '@angular-console/utils';
import { BehaviorSubject, combineLatest, Subject } from 'rxjs';
import { map, scan, take } from 'rxjs/operators';
import { MatSnackBar } from '@angular/material';

type View = 'details' | 'terminal';
const INITIAL_VIEW: View = 'details';
Expand Down Expand Up @@ -49,6 +51,18 @@ export class CommandOutputComponent implements OnDestroy {
}
@Input() emptyTemplate?: TemplateRef<void>;

@HostListener('document:keypress', ['$event'])
handleKeyboardEvent(event: KeyboardEvent) {
if (
event.ctrlKey &&
event.key === 'c' &&
this.commandResponse &&
this.commandResponse.status === 'in-progress'
) {
this.commandRunner.stopCommandViaCtrlC(this.commandResponse.id);
}
}

private readonly outputValue$ = new Subject<string>();
private readonly detailedStatus$ = new Subject<any>();
readonly activeView$ = new BehaviorSubject<View>(INITIAL_VIEW);
Expand Down Expand Up @@ -83,6 +97,8 @@ export class CommandOutputComponent implements OnDestroy {
_commandResponse: CommandResponse;
hasUnreadResponse = false;

constructor(private readonly commandRunner: CommandRunner) {}

ngOnDestroy() {
this.switchToTerminalSubscription.unsubscribe();
}
Expand Down
2 changes: 2 additions & 0 deletions libs/ui/src/lib/ui.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -42,8 +42,10 @@ import { TerminalComponent } from './terminal/terminal.component';
import { BuildStatusComponent } from './build-status/build-status.component';
import { CommandOutputComponent } from './command-output/command-output.component';
import { TestStatusComponent } from './test-status/test-status.component';
import { HttpClientModule } from '@angular/common/http';

const IMPORTS = [
HttpClientModule,
CdkTreeModule,
CdkTreeModule,
CommonModule,
Expand Down
Loading

0 comments on commit 1d41c69

Please sign in to comment.