Commit 4c6134d6 authored by Matt Teeter's avatar Matt Teeter

Merge branch 'develop' into 'master'

Develop

See merge request !5
parents d6b2cbd2 443c5aa6
Pipeline #78147 passed with stages
in 4 minutes and 8 seconds
......@@ -231,6 +231,34 @@
"styleext": "scss"
}
}
},
"utils-angular": {
"projectType": "library",
"root": "libs/utils/angular",
"sourceRoot": "libs/utils/angular/src",
"prefix": "psu",
"architect": {
"lint": {
"builder": "@angular-devkit/build-angular:tslint",
"options": {
"tsConfig": ["libs/utils/angular/tsconfig.lib.json", "libs/utils/angular/tsconfig.spec.json"],
"exclude": ["**/node_modules/**", "!libs/utils/angular/**"]
}
},
"test": {
"builder": "@nrwl/jest:jest",
"options": {
"jestConfig": "libs/utils/angular/jest.config.js",
"tsConfig": "libs/utils/angular/tsconfig.spec.json",
"setupFile": "libs/utils/angular/src/test-setup.ts"
}
}
},
"schematics": {
"@nrwl/angular:component": {
"styleext": "scss"
}
}
}
},
"cli": {
......
# utils-angular
This library was generated with [Nx](https://nx.dev).
## Running unit tests
Run `nx test utils-angular` to execute the unit tests.
module.exports = {
name: 'utils-angular',
preset: '../../../jest.config.js',
coverageDirectory: '../../../coverage/libs/utils/angular',
snapshotSerializers: [
'jest-preset-angular/AngularSnapshotSerializer.js',
'jest-preset-angular/HTMLCommentSerializer.js'
]
};
{
"lib": {
"entryFile": "src/index.ts"
}
}
export * from './lib/before-unload/before-unload.directive';
export * from './lib/before-unload/before-unload.module';
export * from './lib/caps-lock/caps-lock.directive';
export * from './lib/caps-lock/caps-lock.module';
export * from './lib/coalescing-component-factory-resolver/coalescing-component-factory.resolver';
export * from './lib/ng-let/ng-let.directive';
export * from './lib/ng-let/ng-let.module';
export * from './lib/safe-pipe/safe-pipe.module';
export * from './lib/safe-pipe/safe-pipe.pipe';
export * from './lib/yes-no/yes-no.module';
export * from './lib/yes-no/yes-no.pipe';
import { Component } from '@angular/core';
import { FormGroup, FormControl, Validators, ReactiveFormsModule, AbstractControl } from '@angular/forms';
import { ComponentFixture, async, TestBed } from '@angular/core/testing';
import { BeforeUnloadDirective } from './before-unload.directive';
@Component({
template: '<div [ngBeforeUnload]="form"></div>'
})
class TestComponent {
public form = new FormGroup({
control: new FormControl('', Validators.required)
});
public get control(): AbstractControl {
return this.form.get('control');
}
}
describe('BeforeUnloadDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
let directive: BeforeUnloadDirective;
beforeEach(async(() => {
TestBed.configureTestingModule({
imports: [ReactiveFormsModule],
declarations: [TestComponent, BeforeUnloadDirective]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create driver component', () => {
expect(component).toBeDefined();
});
describe('on window unload', () => {
let event;
let control;
beforeEach(() => {
event = {
returnValue: true
};
control = new FormControl('', Validators.required);
directive = new BeforeUnloadDirective();
directive.form = control;
});
describe('when form is pristine', () => {
beforeEach(() => {
control.markAsPristine();
directive.handleBrowserUnload(event);
});
it('event return value should be true', () => {
expect(event.returnValue).toBe(true);
});
});
describe('when form is dirty', () => {
beforeEach(() => {
control.markAsDirty();
directive.handleBrowserUnload(event);
});
it('should set a return value on the event', () => {
expect(event.returnValue).toBe('You have unsaved changes that will be lost if you leave.');
});
});
});
});
import { Directive, Input, HostListener } from '@angular/core';
import { AbstractControl } from '@angular/forms';
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[ngBeforeUnload]'
})
export class BeforeUnloadDirective {
@Input('ngBeforeUnload')
public form: AbstractControl;
@HostListener('window:beforeunload', ['$event'])
public handleBrowserUnload($event): void {
if (!this.form.pristine) {
$event.returnValue = 'You have unsaved changes that will be lost if you leave.';
}
}
}
import { BeforeUnloadModule } from './before-unload.module';
describe('BeforeUnloadModule', () => {
let moduleref: BeforeUnloadModule;
beforeEach(() => {
moduleref = new BeforeUnloadModule();
});
it('should create an instance', () => {
expect(moduleref).toBeTruthy();
});
});
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { ReactiveFormsModule } from '@angular/forms';
import { BeforeUnloadDirective } from './before-unload.directive';
@NgModule({
imports: [CommonModule, ReactiveFormsModule],
declarations: [BeforeUnloadDirective],
exports: [BeforeUnloadDirective]
})
export class BeforeUnloadModule {}
import { Component, ViewChild } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { CapsLockDirective } from './caps-lock.directive';
@Component({
template: `
<input type="password" #input [(utCapsLock)]="isCapsLockOn" />
`
})
class TestComponent {
@ViewChild('input', { static: true })
public input: HTMLElement;
public isCapsLockOn: boolean;
}
describe('CapsLockDirective', () => {
let fixture: ComponentFixture<TestComponent>;
let component: TestComponent;
let input: HTMLInputElement;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestComponent, CapsLockDirective]
});
fixture = TestBed.createComponent(TestComponent);
component = fixture.componentInstance;
fixture.detectChanges();
input = fixture.debugElement.query(By.directive(CapsLockDirective)).nativeElement;
});
it('should work in a driver component', () => {
expect(component).toBeDefined();
expect(input).toBeDefined();
});
it('should add caps-lock class when input gets keyup events with caps lock on', () => {
input.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'A',
modifierCapsLock: true
})
);
fixture.detectChanges();
expect(input.classList.contains('caps-lock')).toBe(true);
expect(component.isCapsLockOn).toBe(true);
input.dispatchEvent(
new KeyboardEvent('keyup', {
key: 'A',
modifierCapsLock: false
})
);
fixture.detectChanges();
expect(input.classList.contains('caps-lock')).toBe(false);
expect(component.isCapsLockOn).toBe(false);
});
});
import { Directive, EventEmitter, HostBinding, HostListener, Input, Output } from '@angular/core';
@Directive({ selector: '[utCapsLock]' })
export class CapsLockDirective {
@Input('utCapsLock')
public targetProperty: boolean;
@Output()
public utCapsLockChange = new EventEmitter();
private caps = false;
@HostBinding('class.caps-lock')
public get isCapsLockOn(): boolean {
return this.caps;
}
@HostListener('keyup', ['$event'])
public checkForCapsLock(event: KeyboardEvent): void {
this.caps = event.getModifierState('CapsLock');
this.utCapsLockChange.emit(this.caps);
}
}
import { CapsLockModule } from './caps-lock.module';
describe('CapsLockModule', () => {
let moduleref: CapsLockModule;
beforeEach(() => {
moduleref = new CapsLockModule();
});
it('should create an instance', () => {
expect(moduleref).toBeTruthy();
});
});
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { CapsLockDirective } from './caps-lock.directive';
@NgModule({
imports: [CommonModule],
declarations: [CapsLockDirective],
exports: [CapsLockDirective]
})
export class CapsLockModule {}
import { ComponentFactoryResolver } from '@angular/core';
import { Mock } from 'ts-mocks';
import { CoalescingComponentFactoryResolver } from './coalescing-component-factory.resolver';
describe('CoalescingComponentFactoryResolver', () => {
let resolver: CoalescingComponentFactoryResolver;
let rootResolver: Mock<ComponentFactoryResolver>;
beforeEach(() => {
rootResolver = new Mock<ComponentFactoryResolver>({
resolveComponentFactory: () => ({} as any)
});
resolver = new CoalescingComponentFactoryResolver(rootResolver.Object);
});
it('should create', () => {
expect(resolver).toBeDefined();
});
});
import { ComponentFactory, ComponentFactoryResolver, Injectable, Type } from '@angular/core';
/**
* This is a hack to enable entry components of lazily-loaded modules to be available outside of the module in which
* they were declared.
*
* From: https://github.com/jonrimmer/angular-coalescing-component-factory-resolver/blob/master/src/app/coalescing-component-factory-resolver.service.ts
*
* GitHub issue: https://github.com/angular/angular/issues/14324#issuecomment-481898762
*/
@Injectable({ providedIn: 'root' })
export class CoalescingComponentFactoryResolver extends ComponentFactoryResolver {
private rootResolve: (component: Type<any>) => ComponentFactory<any>;
private inCall = false;
private readonly resolvers = new Map<ComponentFactoryResolver, (component: Type<any>) => ComponentFactory<any>>();
constructor(private readonly rootResolver: ComponentFactoryResolver) {
super();
this.rootResolve = this.rootResolver.resolveComponentFactory;
this.rootResolver.resolveComponentFactory = this.resolveComponentFactory;
}
public registerResolver(resolver: ComponentFactoryResolver): void {
const original = resolver.resolveComponentFactory;
this.resolvers.set(resolver, original);
}
public resolveComponentFactory = <T>(component: Type<T>): ComponentFactory<T> => {
// Prevents cyclic calls.
if (this.inCall) {
return null;
}
this.inCall = true;
try {
const result = this.resolveInternal(component);
return result;
} finally {
this.inCall = false;
}
};
private resolveInternal = <T>(component: Type<T>): ComponentFactory<T> => {
for (const [resolver, fn] of Array.from(this.resolvers.entries())) {
try {
const factory = fn.call(resolver, component);
if (factory) {
return factory;
}
} catch {}
}
return this.rootResolve.call(this.rootResolver, component);
};
}
import { CommonModule } from '@angular/common';
import { Component, NgModule, ViewChild } from '@angular/core';
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
import { By } from '@angular/platform-browser';
import { Observable, of } from 'rxjs';
import { NgLetDirective } from './ng-let.directive';
import { NgLetModule } from './ng-let.module';
@Component({
template: '',
// tslint:disable-next-line: component-selector
selector: 'utils-sand-test'
})
class TestComponent {
@ViewChild(NgLetDirective, { static: false })
public ngLetDirective: NgLetDirective;
public test$: Observable<number>;
public test = 10;
public nestedTest = 20;
public functionTest = (a: number, b: number) => a + b;
}
@NgModule({
declarations: [TestComponent],
imports: [NgLetModule, CommonModule],
exports: [NgLetModule, TestComponent]
})
class TestModule {}
describe('ngLet directive', () => {
let fixture: ComponentFixture<TestComponent>;
function getComponent(): TestComponent {
return fixture.componentInstance;
}
beforeEach(() => {
TestBed.configureTestingModule({
imports: [TestModule]
});
});
afterEach(() => {
fixture = null;
});
it('should create NgLetModule', () => {
expect(new NgLetModule()).toBeTruthy();
});
it('should work in a template attribute', async(() => {
const template = '<span *ngLet="test as i">hello{{ i }}</span>';
fixture = createTestComponent(template);
getComponent().test = 7;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement.textContent).toBe('hello7');
}));
it('should work on a template element', async(() => {
const template = '<ng-template [ngLet]="test" let-i>hello{{ i }}</ng-template>';
fixture = createTestComponent(template);
getComponent().test = 5;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toBe('hello5');
}));
it('should handle nested ngLet correctly', async(() => {
const template = '<div *ngLet="test as i"><span *ngLet="nestedTest as k">hello{{ i + k }}</span></div>';
fixture = createTestComponent(template);
getComponent().test = 3;
getComponent().nestedTest = 5;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement.textContent).toBe('hello8');
}));
it('should update several nodes', async(() => {
const template =
'<span *ngLet="test + 1; let i">helloNumber{{ i }}</span>' +
'<span *ngLet="functionTest(5, 8) as j">helloFunction{{ j }}</span>';
fixture = createTestComponent(template);
getComponent().test = 4;
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(2);
expect(fixture.nativeElement.textContent).toContain('helloNumber5helloFunction13');
}));
it('should work on async pipe', async(() => {
const template = '<span *ngLet="test$ | async as t">helloAsync{{ t }}</span>';
fixture = createTestComponent(template);
getComponent().test$ = of(15);
fixture.detectChanges();
expect(fixture.debugElement.queryAll(By.css('span')).length).toEqual(1);
expect(fixture.nativeElement.textContent).toContain('helloAsync15');
}));
it('should accept input', async(() => {
const template = '<span *ngLet="test as i">hello{{ i }}</span>';
fixture = createTestComponent(template);
fixture.detectChanges();
expect(getComponent().ngLetDirective).toBeTruthy();
getComponent().ngLetDirective.ngLet = 21;
fixture.detectChanges();
expect(fixture.nativeElement.textContent).toContain('hello21');
}));
});
function createTestComponent(template: string): ComponentFixture<TestComponent> {
return TestBed.overrideComponent(TestComponent, {
set: { template }
}).createComponent(TestComponent);
}
import { Directive, Input, TemplateRef, ViewContainerRef, OnInit } from '@angular/core';
export class NgLetContext {
public $implicit: any = null;
public ngLet: any = null;
}
@Directive({
// tslint:disable-next-line:directive-selector
selector: '[ngLet]'
})
export class NgLetDirective implements OnInit {
private _context = new NgLetContext();
@Input()
set ngLet(value: any) {
this._context.$implicit = this._context.ngLet = value;
}
constructor(private _vcr: ViewContainerRef, private _templateRef: TemplateRef<NgLetContext>) {}
public ngOnInit(): void {
this._vcr.createEmbeddedView(this._templateRef, this._context);
}
}
import { NgLetModule } from './ng-let.module';
describe('NgLetModule', () => {
let moduleref: NgLetModule;
beforeEach(() => {
moduleref = new NgLetModule();
});
it('should create an instance', () => {
expect(moduleref).toBeTruthy();
});
});
import { NgModule } from '@angular/core';
import { NgLetDirective } from './ng-let.directive';
@NgModule({
declarations: [NgLetDirective],
exports: [NgLetDirective]
})
export class NgLetModule {}
import { SafePipeModule } from './safe-pipe.module';
describe('SafePipeModule', () => {
let moduleref: SafePipeModule;