forked from hyperledger-archives/composer
-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add ability to import identity cards (hyperledger-archives#1872)
Also fixes drawer animation Closes #1415 Signed-off-by: James Taylor <[email protected]>
- Loading branch information
Showing
13 changed files
with
444 additions
and
6 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
42 changes: 42 additions & 0 deletions
42
packages/composer-playground/src/app/login/import-identity/import-identity.component.html
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,42 @@ | ||
<section class="import-identity" fileDragDrop (fileDragDropFileAccepted)="fileAccepted($event)" | ||
(fileDragDropFileRejected)="fileRejected($event)" | ||
(fileDragDropDragOver)="fileDetected($event)" (fileDragDropDragLeave)="fileLeft($event)" | ||
[maxFileSize]="maxFileSize" [supportedFileTypes]="supportedFileTypes"> | ||
<header class="drawer-header"> | ||
<h1>Import ID Card</h1> | ||
<p>Drop a Composer ID (.card file) here to add it to My Wallet:</p> | ||
|
||
<button class="icon modal-exit" (click)="activeDrawer.dismiss();"> | ||
<svg class="ibm-icon" aria-hidden="true"> | ||
<use xlink:href="#icon-close_new"></use> | ||
</svg> | ||
</button> | ||
</header> | ||
<section class="drawer-body"> | ||
<div class="import-card" *ngIf="!identityCard"> | ||
<file-importer (fileAccepted)="fileAccepted($event)" (fileRejected)="fileRejected($event)" [expandInput]="expandInput" | ||
[ngClass]="{'expandFile': expandInput}" [svgName]="'#icon-Card_Upload'" [maxFileSize]="maxFileSize" [supportedFileTypes]="supportedFileTypes"></file-importer> | ||
</div> | ||
|
||
<div class="current-card" *ngIf="identityCard"> | ||
<identity-card [identity]="identityCard" [preview]="true" | ||
(onDismiss)="removeFile()"> | ||
</identity-card> | ||
</div> | ||
</section> | ||
<footer class="drawer-footer"> | ||
<button type="button" class="secondary" (click)="activeDrawer.dismiss();"> | ||
<span>Cancel</span> | ||
</button> | ||
<button type="button" class="primary" (click)="import();" [disabled]="!identityCard"> | ||
<span *ngIf="!importInProgress">Import</span> | ||
<div *ngIf="importInProgress" class="ibm-spinner-indeterminate small loop"> | ||
<div class="loader"> | ||
<svg class="circular" viewBox="25 25 50 50"> | ||
<circle class="circle-path" cx="50" cy="50" r="20"/> | ||
</svg> | ||
</div> | ||
</div> | ||
</button> | ||
</footer> | ||
</section> |
20 changes: 20 additions & 0 deletions
20
packages/composer-playground/src/app/login/import-identity/import-identity.component.scss
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
@import '../../../assets/styles/base/colors'; | ||
@import '../../../assets/styles/base/variables'; | ||
|
||
import-identity { | ||
file-importer.expandFile { | ||
padding: 0; | ||
} | ||
|
||
.import-card, .current-card { | ||
padding: 0 $space-large 0 $space-large; | ||
} | ||
|
||
.current-card { | ||
background-color: $fourth-highlight; | ||
padding: $space-large; | ||
|
||
display: flex; | ||
justify-content: space-around; | ||
} | ||
} |
242 changes: 242 additions & 0 deletions
242
packages/composer-playground/src/app/login/import-identity/import-identity.component.spec.ts
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,242 @@ | ||
/* tslint:disable:no-unused-variable */ | ||
/* tslint:disable:no-unused-expression */ | ||
/* tslint:disable:no-var-requires */ | ||
/* tslint:disable:max-classes-per-file */ | ||
import { ComponentFixture, TestBed, fakeAsync, tick } from '@angular/core/testing'; | ||
import { Component, Directive, EventEmitter, Output, Input } from '@angular/core'; | ||
import { FormsModule } from '@angular/forms'; | ||
import { By } from '@angular/platform-browser'; | ||
|
||
import { ImportIdentityComponent } from './import-identity.component'; | ||
|
||
import { ActiveDrawer, DrawerService } from '../../common/drawer'; | ||
import { AlertService } from '../../basic-modals/alert.service'; | ||
import { Logger, IdCard } from 'composer-common'; | ||
|
||
import * as sinon from 'sinon'; | ||
import * as chai from 'chai'; | ||
|
||
let should = chai.should(); | ||
|
||
@Directive({ | ||
selector: '[fileDragDrop]' | ||
}) | ||
class MockDragDropDirective { | ||
@Output() | ||
public fileDragDropFileAccepted: EventEmitter<File> = new EventEmitter<File>(); | ||
@Output() | ||
public fileDragDropFileRejected: EventEmitter<string> = new EventEmitter<string>(); | ||
@Output() | ||
public fileDragDropDragOver: EventEmitter<string> = new EventEmitter<string>(); | ||
@Output() | ||
public fileDragDropDragLeave: EventEmitter<string> = new EventEmitter<string>(); | ||
|
||
@Input() | ||
public supportedFileTypes: string[] = []; | ||
@Input() | ||
maxFileSize: number = 0; | ||
} | ||
|
||
@Directive({ | ||
selector: 'file-importer' | ||
}) | ||
class MockFileImporterDirective { | ||
@Output() | ||
public fileAccepted: EventEmitter<File> = new EventEmitter<File>(); | ||
|
||
@Output() | ||
public fileRejected: EventEmitter<File> = new EventEmitter<File>(); | ||
|
||
@Input() | ||
public expandInput: boolean = false; | ||
|
||
@Input() | ||
public svgName: string = '#icon-BNA_Upload'; | ||
|
||
@Input() | ||
public maxFileSize: number = 0; | ||
|
||
@Input() | ||
public supportedFileTypes: string[] = []; | ||
} | ||
|
||
@Component({ | ||
selector: 'identity-card', | ||
template: '' | ||
}) | ||
class MockIdentityCardComponent { | ||
@Input() identity: any; | ||
@Input() preview: boolean; | ||
} | ||
|
||
describe('ImportIdentityComponent', () => { | ||
let component: ImportIdentityComponent; | ||
let fixture: ComponentFixture<ImportIdentityComponent>; | ||
|
||
let mockDragDropComponent; | ||
|
||
let mockAlertService; | ||
let mockActiveDrawer; | ||
let mockDrawerService; | ||
|
||
beforeEach(() => { | ||
mockAlertService = sinon.createStubInstance(AlertService); | ||
mockActiveDrawer = sinon.createStubInstance(ActiveDrawer); | ||
mockDrawerService = sinon.createStubInstance(DrawerService); | ||
|
||
mockAlertService.errorStatus$ = { | ||
next: sinon.stub() | ||
}; | ||
|
||
mockAlertService.busyStatus$ = { | ||
next: sinon.stub() | ||
}; | ||
|
||
TestBed.configureTestingModule({ | ||
imports: [FormsModule], | ||
declarations: [ | ||
ImportIdentityComponent, | ||
MockIdentityCardComponent, | ||
MockDragDropDirective, | ||
MockFileImporterDirective | ||
], | ||
providers: [ | ||
{provide: ActiveDrawer, useValue: mockActiveDrawer}, | ||
{provide: DrawerService, useValue: mockDrawerService}, | ||
{provide: AlertService, useValue: mockAlertService}] | ||
}); | ||
|
||
mockDrawerService.open.returns({componentInstance: {}}); | ||
|
||
fixture = TestBed.createComponent(ImportIdentityComponent); | ||
component = fixture.componentInstance; | ||
|
||
let mockDragDropElement = fixture.debugElement.query(By.directive(MockDragDropDirective)); | ||
mockDragDropComponent = mockDragDropElement.injector.get(MockDragDropDirective) as MockDragDropDirective; | ||
}); | ||
|
||
it('should create', () => { | ||
component.should.be.ok; | ||
}); | ||
|
||
describe('fileDetected', () => { | ||
it('should set expand input to true', () => { | ||
component['expandInput'].should.equal(false); | ||
mockDragDropComponent.fileDragDropDragOver.emit(); | ||
|
||
component['expandInput'].should.equal(true); | ||
}); | ||
}); | ||
|
||
describe('fileLeft', () => { | ||
it('should set expand input to false', () => { | ||
component['expandInput'] = true; | ||
mockDragDropComponent.fileDragDropDragLeave.emit(); | ||
|
||
component['expandInput'].should.equal(false); | ||
}); | ||
}); | ||
|
||
describe('fileAccepted', () => { | ||
let sandbox; | ||
let mockFile; | ||
let mockIdCard; | ||
let mockFileReader; | ||
let fileReaderStub; | ||
let idCardFromArchiveStub; | ||
let bufferFromStub; | ||
|
||
beforeEach(() => { | ||
// webpack can't handle dymanically creating a logger | ||
Logger.setFunctionalLogger({ | ||
log: sinon.stub() | ||
}); | ||
|
||
sandbox = sinon.sandbox.create(); | ||
mockFile = 'card file'; | ||
mockIdCard = sinon.createStubInstance(IdCard); | ||
idCardFromArchiveStub = sandbox.stub(IdCard, 'fromArchive'); | ||
bufferFromStub = sandbox.stub(Buffer.prototype, 'from'); | ||
fileReaderStub = sandbox.stub(window, 'FileReader'); | ||
|
||
mockFileReader = { | ||
onload: sinon.stub(), | ||
readAsArrayBuffer: sinon.stub(), | ||
result: 'idcard file' | ||
}; | ||
|
||
bufferFromStub.returns('id card data'); | ||
fileReaderStub.returns(mockFileReader); | ||
}); | ||
|
||
afterEach(() => { | ||
sandbox.restore(); | ||
}); | ||
|
||
it('should read an identity card file', fakeAsync(() => { | ||
idCardFromArchiveStub.returns(Promise.resolve(mockIdCard)); | ||
mockDragDropComponent.fileDragDropFileAccepted.emit(mockFile); | ||
|
||
mockFileReader.readAsArrayBuffer.should.have.been.calledWith(mockFile); | ||
|
||
mockFileReader.onload(); | ||
|
||
tick(); | ||
|
||
component['identityCard'].should.equal(mockIdCard); | ||
component['expandInput'].should.equal(true); | ||
})); | ||
|
||
it('should handle error', fakeAsync(() => { | ||
idCardFromArchiveStub.returns(Promise.reject('some error')); | ||
mockDragDropComponent.fileDragDropFileAccepted.emit(mockFile); | ||
|
||
mockFileReader.readAsArrayBuffer.should.have.been.calledWith(mockFile); | ||
|
||
mockFileReader.onload(); | ||
|
||
tick(); | ||
|
||
mockActiveDrawer.dismiss.should.have.been.calledWith('Could not read ID card'); | ||
component['expandInput'].should.equal(false); | ||
})); | ||
}); | ||
|
||
describe('file rejected', () => { | ||
it('should reject the file', () => { | ||
mockDragDropComponent.fileDragDropFileRejected.emit('some error'); | ||
|
||
mockActiveDrawer.dismiss.should.have.been.calledWith('some error'); | ||
component['expandInput'].should.equal(false); | ||
should.not.exist(component['identityCard']); | ||
}); | ||
}); | ||
|
||
describe('remove file', () => { | ||
it('should remove the file', () => { | ||
component.removeFile(); | ||
|
||
component['expandInput'].should.equal(false); | ||
should.not.exist(component['identityCard']); | ||
}); | ||
}); | ||
|
||
describe('import', () => { | ||
it('should close the drawer with imported identity card', () => { | ||
let mockIdCard = sinon.createStubInstance(IdCard); | ||
component['identityCard'] = mockIdCard; | ||
|
||
fixture.detectChanges(); | ||
let button = fixture.debugElement.query(By.css('button.primary')); | ||
button.nativeElement.click(); | ||
|
||
mockActiveDrawer.close.should.have.been.calledWith(mockIdCard); | ||
}); | ||
|
||
it('should not be possible to import without identity card', () => { | ||
fixture.detectChanges(); | ||
let button = fixture.debugElement.query(By.css('button.primary')); | ||
button.nativeElement.disabled.should.be.true; | ||
}); | ||
}); | ||
}); |
Oops, something went wrong.