Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion app/components/data/action/add/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,2 @@
export * from "./file-group-create-form.component";
export * from "./file-group-options-picker.component";
export * from "./file-or-directory-picker.component";
14 changes: 9 additions & 5 deletions app/components/data/data.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,6 @@ import {
DeleteContainerDialogComponent,
FileGroupCreateFormComponent,
FileGroupOptionsPickerComponent,
FileOrDirectoryPickerComponent,
} from "app/components/data/action";
import { DataSharedModule } from "app/components/data/shared";
import { FileBrowseModule } from "app/components/file/browse";
Expand All @@ -20,10 +19,15 @@ import {
import { DataHomeComponent } from "./home";

const components = [
DataContainerConfigurationComponent, DataContainerFilesComponent, DataHomeComponent,
DataDefaultComponent, DataDetailsComponent, FileGroupCreateFormComponent, DeleteContainerDialogComponent,
DataContainerListComponent, FileGroupOptionsPickerComponent, FileGroupPreviewComponent,
FileOrDirectoryPickerComponent,
DataContainerConfigurationComponent,
DataContainerFilesComponent,
DataHomeComponent,
DataDefaultComponent, DataDetailsComponent,
FileGroupCreateFormComponent,
DeleteContainerDialogComponent,
DataContainerListComponent,
FileGroupOptionsPickerComponent,
FileGroupPreviewComponent,
];

@NgModule({
Expand Down
5 changes: 4 additions & 1 deletion app/components/data/shared/data.shared.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,16 @@ import { StorageErrorDisplayComponent } from "./errors";
import { FileGroupPickerComponent } from "./file-group-picker";
import { FileGroupSasComponent } from "./file-group-sas";
import { FileGroupsPickerComponent } from "./file-groups-picker";
import { FileOrDirectoryPickerComponent } from "./file-or-directory-picker";
import { StorageAccountPickerComponent } from "./storage-account-picker";

const components = [
FileGroupPickerComponent, FileGroupSasComponent, FileGroupsPickerComponent,
CloudFilePickerComponent, CloudFilePickerDialogComponent,
StorageErrorDisplayComponent, BlobContainerPickerComponent,
StorageErrorDisplayComponent,
BlobContainerPickerComponent,
StorageAccountPickerComponent,
FileOrDirectoryPickerComponent,
];

@NgModule({
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
import { Component, DebugElement, NO_ERRORS_SCHEMA } from "@angular/core";
import { ComponentFixture, TestBed, fakeAsync, tick } from "@angular/core/testing";
import { FormControl, ReactiveFormsModule } from "@angular/forms";
import { BrowserModule, By } from "@angular/platform-browser";

import { EditableTableColumnComponent, EditableTableComponent } from "@batch-flask/ui/form/editable-table";
import { FileSystemService } from "app/services";
import { FileOrDirectoryPickerComponent } from "./file-or-directory-picker.component";

@Component({
template: `<bl-file-or-directory-picker [formControl]="paths"></bl-file-or-directory-picker>`,
})
class TestComponent {
public paths = new FormControl([]);
}

describe("FileOrDirectoryPickerComponent", () => {
let fixture: ComponentFixture<TestComponent>;
let testComponent: TestComponent;
let de: DebugElement;
let fsSpy;

beforeEach(() => {
fsSpy = {
exists: jasmine.createSpy("fs.exists").and.callFake((path) => {
return Promise.resolve(path === "/Users/test/files");
}),
};
TestBed.configureTestingModule({
imports: [ReactiveFormsModule, BrowserModule],
declarations: [
FileOrDirectoryPickerComponent,
TestComponent,
EditableTableComponent,
EditableTableColumnComponent,
],
providers: [
{ provide: FileSystemService, useValue: fsSpy },
],
schemas: [NO_ERRORS_SCHEMA],
});
fixture = TestBed.createComponent(TestComponent);
testComponent = fixture.componentInstance;
de = fixture.debugElement.query(By.css("bl-file-or-directory-picker"));
fixture.detectChanges();
});

it("Validate valid paths", fakeAsync(() => {
testComponent.paths.setValue([
{ path: "/Users/test/files" },
]);
fixture.detectChanges();
tick();
fixture.detectChanges();

expect(testComponent.paths.valid).toBe(true);
expect(testComponent.paths.status).toBe("VALID");

expect(de.nativeElement.textContent).not.toContain(`Path "/Users/test/files" is not found on this computer.`);
}));

it("Validate invalid paths", fakeAsync(() => {
testComponent.paths.setValue([
{ path: "/invalid" },
]);
fixture.detectChanges();
tick();
fixture.detectChanges();

expect(testComponent.paths.valid).toBe(false);
expect(testComponent.paths.status).toBe("INVALID");
expect(de.nativeElement.textContent).toContain(`Path "/invalid" is not found on this computer.`);
}));

it("Validate at least one invalid paths", fakeAsync(() => {
testComponent.paths.setValue([
{ path: "/Users/test/files" },
{ path: "/invalid" },
]);
fixture.detectChanges();
tick();
fixture.detectChanges();

expect(testComponent.paths.valid).toBe(false);
expect(testComponent.paths.status).toBe("INVALID");

expect(de.nativeElement.textContent).not.toContain(`Path "/Users/test/files" is not found on this computer.`);
expect(de.nativeElement.textContent).toContain(`Path "/invalid" is not found on this computer.`);
}));
});
Original file line number Diff line number Diff line change
@@ -1,37 +1,54 @@
import { Component, HostListener, Input, OnDestroy, forwardRef } from "@angular/core";
import {
ControlValueAccessor, FormBuilder, FormControl, NG_VALIDATORS, NG_VALUE_ACCESSOR,
ChangeDetectionStrategy,
ChangeDetectorRef,
Component,
HostListener,
Input,
OnDestroy,
forwardRef,
} from "@angular/core";
import {
AsyncValidator,
ControlValueAccessor,
FormBuilder,
FormControl,
NG_ASYNC_VALIDATORS,
NG_VALUE_ACCESSOR,
} from "@angular/forms";
import { Subscription } from "rxjs";
import { Observable, Subscription } from "rxjs";

import { FileOrDirectoryDto } from "app/models/dtos";
import { DragUtils } from "app/utils";

import { autobind } from "@batch-flask/core";
import { FileSystemService } from "app/services";
import "./file-or-directory-picker.scss";

// tslint:disable:no-forward-ref
@Component({
selector: "bl-file-or-directory-picker",
templateUrl: "file-or-directory-picker.html",
providers: [
{ provide: NG_VALUE_ACCESSOR, useExisting: forwardRef(() => FileOrDirectoryPickerComponent), multi: true },
{ provide: NG_VALIDATORS, useExisting: forwardRef(() => FileOrDirectoryPickerComponent), multi: true },
{ provide: NG_ASYNC_VALIDATORS, useExisting: forwardRef(() => FileOrDirectoryPickerComponent), multi: true },
],
changeDetection: ChangeDetectionStrategy.OnPush,
})
export class FileOrDirectoryPickerComponent implements ControlValueAccessor, OnDestroy {
@Input()
public dragMessage: string = "Drag and drop files or folders here";
export class FileOrDirectoryPickerComponent implements AsyncValidator, ControlValueAccessor, OnDestroy {
@Input() public dragMessage: string = "Drag and drop files or folders here";

public invalidPath: string;
public isDraging = 0;
public paths: FormControl<FileOrDirectoryDto[]>;

private _propagateChange: (value: FileOrDirectoryDto[]) => void = null;
private _sub: Subscription;

constructor(
private fs: FileSystemService,
private changeDetector: ChangeDetectorRef,
private formBuilder: FormBuilder) {

this.paths = this.formBuilder.control([]);
this.paths = this.formBuilder.control([], null, this._validatePaths);
this._sub = this.paths.valueChanges.subscribe((paths) => {
if (this._propagateChange) {
this._propagateChange(paths);
Expand All @@ -58,7 +75,7 @@ export class FileOrDirectoryPickerComponent implements ControlValueAccessor, OnD
}

public validate(c: FormControl) {
return null;
return this._validatePaths(c);
}

@HostListener("dragover", ["$event"])
Expand All @@ -72,12 +89,14 @@ export class FileOrDirectoryPickerComponent implements ControlValueAccessor, OnD
if (!this._canDrop(event.dataTransfer)) { return; }
event.stopPropagation();
this.isDraging++;
this.changeDetector.markForCheck();
}

@HostListener("dragleave", ["$event"])
public dragLeave(event: DragEvent) {
if (!this._canDrop(event.dataTransfer)) { return; }
this.isDraging--;
this.changeDetector.markForCheck();
}

@HostListener("drop", ["$event"])
Expand All @@ -102,4 +121,30 @@ export class FileOrDirectoryPickerComponent implements ControlValueAccessor, OnD
private _hasFile(dataTransfer: DataTransfer) {
return dataTransfer.types.includes("Files");
}

@autobind()
private _validatePaths(control: FormControl) {
return Observable.fromPromise(this._validatePathsAsync(control.value)).map((path) => {
if (path) {
return {
invalidFileOrPath: false,
};
}
return null;
});
}

private async _validatePathsAsync(paths: Array<{ path: string }>) {
for (const { path } of paths) {
const exists = await this.fs.exists(path);
if (!exists) {
this.invalidPath = path;
this.changeDetector.markForCheck();
return path;
}
}
this.invalidPath = null;
this.changeDetector.markForCheck();
return null;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
<bl-editable-table [formControl]="paths">
<bl-editable-table-column name="path" ng-readonly="true">File or directory path</bl-editable-table-column>
</bl-editable-table>
<div class="danger" *ngIf="invalidPath !== null">
Path "{{invalidPath}}" is not found on this computer.
</div>
<div class="help">{{dragMessage}}</div>
<div *ngIf="isDraging" class="drop-overlay">
<i class="fa fa-upload"></i>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
export * from "./file-or-directory-picker.component";
2 changes: 2 additions & 0 deletions karma.conf.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
const webpackConfig = require("./config/webpack.config.test");

process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";

// Only enable coverage if env is defined(So we don't enable it in watch mode as it duplicate logs)
const coverageReporters = process.env.COVERAGE ? ["coverage", "remap-coverage"] : [];
// Karma config for testing the code running the browser environemnt.
Expand Down
4 changes: 3 additions & 1 deletion src/client/startup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ async function startApplication(batchLabsApp: BatchLabsApplication) {
}

export async function startBatchLabs() {
process.env["ELECTRON_DISABLE_SECURITY_WARNINGS"] = "true";
// Those warnings are electron complaining we are loading remote data
// But this is a false positive when using dev server has it doesn't seem to ignore localhost
process.env.ELECTRON_DISABLE_SECURITY_WARNINGS = "true";
localStorage.load();
const batchLabsApp = new BatchLabsApplication(autoUpdater);
setupSingleInstance(batchLabsApp);
Expand Down
11 changes: 5 additions & 6 deletions test/app/components/base/tabs/tabs.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,14 +62,13 @@ describe("Tabs", () => {
expect(fixture.nativeElement.textContent).not.toContain("Content 2");
});

it("changing the route should update the tab", async(() => {
it("changing the route should update the tab", async () => {
activeRouteSpy.queryParams.next({ tab: "second" });
fixture.detectChanges();
fixture.whenStable().then(() => {
expect(fixture.nativeElement.textContent).toContain("Content 2");
expect(routerSpy.navigate).not.toHaveBeenCalled();
});
}));
await fixture.whenStable();
expect(fixture.nativeElement.textContent).toContain("Content 2");
expect(routerSpy.navigate).not.toHaveBeenCalled();
});

it("clicking on a tab label should update the route", async(() => {
const labels = fixture.debugElement.queryAll(By.css(".mat-tab-label"));
Expand Down