Skip to content

Commit 83f7011

Browse files
Merge branch 'gcca-emit-stateparams'
2 parents b88401c + 0687e19 commit 83f7011

File tree

4 files changed

+159
-12
lines changed

4 files changed

+159
-12
lines changed

src/directives/uiSref.ts

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
/** @ng2api @module directives */
22
/** */
33
import { UIRouter, extend, Obj, TransitionOptions, TargetState } from "@uirouter/core";
4-
import { Directive, Inject, Input, Optional, ElementRef, Renderer2 } from "@angular/core";
4+
import { Directive, Inject, Input, Optional, ElementRef, Renderer2, OnChanges, SimpleChanges } from "@angular/core";
55
import { UIView, ParentUIViewInject } from "./uiView";
66
import { ReplaySubject } from "rxjs/ReplaySubject";
77
import { Subscription } from "rxjs/Subscription";
@@ -70,7 +70,8 @@ export class AnchorUISref {
7070
selector: '[uiSref]',
7171
host: { '(click)': 'go()' }
7272
})
73-
export class UISref {
73+
export class UISref implements OnChanges {
74+
7475
/**
7576
* `@Input('uiSref')` The name of the state to link to
7677
*
@@ -134,6 +135,10 @@ export class UISref {
134135
this.update();
135136
}
136137

138+
ngOnChanges(changes: SimpleChanges): void {
139+
this.update();
140+
}
141+
137142
ngOnDestroy() {
138143
this._emit = false;
139144
this._statesSub.unsubscribe();

src/directives/uiSrefStatus.ts

Lines changed: 12 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
import { Directive, Output, EventEmitter, ContentChildren, QueryList } from '@angular/core';
44
import { UISref } from './uiSref';
55
import {
6-
PathNode, Transition, TargetState, StateObject, anyTrueR, tail, unnestR, Predicate, UIRouterGlobals, Param, PathUtils
6+
PathNode, Transition, TargetState, StateObject, anyTrueR, tail, unnestR, Predicate, UIRouterGlobals, Param, PathUtils, StateOrName
77
} from '@uirouter/core';
88

99
import { Subscription } from 'rxjs/Subscription';
@@ -32,14 +32,17 @@ export interface SrefStatus {
3232
entering: boolean;
3333
/** A transition is exiting the sref's target state */
3434
exiting: boolean;
35+
/** The enclosed sref(s) target state(s) */
36+
targetStates: TargetState[];
3537
}
3638

3739
/** @internalapi */
3840
const inactiveStatus: SrefStatus = {
3941
active: false,
4042
exact: false,
4143
entering: false,
42-
exiting: false
44+
exiting: false,
45+
targetStates: [],
4346
};
4447

4548
/**
@@ -117,16 +120,18 @@ function getSrefStatus(event: TransEvt, srefTarget: TargetState): SrefStatus {
117120
exact: isExact(),
118121
entering: isStartEvent ? isEntering() : false,
119122
exiting: isStartEvent ? isExiting() : false,
123+
targetStates: [srefTarget],
120124
} as SrefStatus;
121125
}
122126

123127
/** @internalapi */
124-
function mergeSrefStatus(left: SrefStatus, right: SrefStatus) {
128+
function mergeSrefStatus(left: SrefStatus, right: SrefStatus): SrefStatus {
125129
return {
126-
active: left.active || right.active,
127-
exact: left.exact || right.exact,
130+
active: left.active || right.active,
131+
exact: left.exact || right.exact,
128132
entering: left.entering || right.entering,
129-
exiting: left.exiting || right.exiting,
133+
exiting: left.exiting || right.exiting,
134+
targetStates: left.targetStates.concat(right.targetStates),
130135
};
131136
}
132137

@@ -152,7 +157,7 @@ function mergeSrefStatus(left: SrefStatus, right: SrefStatus) {
152157
* ```
153158
*
154159
* The `uiSrefStatus` event is emitted whenever an enclosed `uiSref`'s status changes.
155-
* The event emitted is of type [[SrefStatus]], and has boolean values for `active`, `exact`, `entering`, and `exiting`.
160+
* The event emitted is of type [[SrefStatus]], and has boolean values for `active`, `exact`, `entering`, and `exiting`; also has a [[StateOrName]] `identifier`value.
156161
*
157162
* The values from this event can be captured and stored on a component (then applied, e.g., using ngClass).
158163
*

test/uiSref/uiSref.spec.ts

Lines changed: 88 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,41 @@
1-
import { Component, DebugElement } from '@angular/core';
1+
import { Component, DebugElement, ViewChildren, QueryList } from '@angular/core';
22
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
33
import { By } from '@angular/platform-browser';
44

55
import { UIRouterModule } from '../../src/uiRouterNgModule';
66
import { UISref } from '../../src/directives/uiSref';
7-
import { UIRouter } from '@uirouter/core';
7+
import { UIRouter, TargetState, TransitionOptions } from '@uirouter/core';
88
import { Subject } from 'rxjs/Subject';
9+
import { Subscription } from "rxjs/Subscription";
910

1011
describe('uiSref', () => {
1112
@Component({
1213
template: `
13-
<a [uiSref]="linkA" [target]="targetA"></a>
14+
<a [uiSref]="linkA" [target]="targetA" [uiParams]="linkAParams" [uiOptions]="linkAOptions"></a>
1415
<a [uiSref]="linkB"></a>
1516
`
1617
})
1718
class TestComponent {
1819
linkA: string;
20+
linkAParams: any;
21+
linkAOptions: TransitionOptions;
1922
targetA: string;
2023
linkB: string;
2124

25+
@ViewChildren(UISref) srefs: QueryList<UISref>;
26+
27+
get linkASref() {
28+
return this.srefs.first;
29+
}
30+
31+
get linkBSref() {
32+
return this.srefs.toArray()[1];
33+
}
34+
2235
constructor() {
2336
this.linkA = null;
37+
this.linkAParams = null;
38+
this.linkAOptions = null;
2439
this.targetA = '';
2540
this.linkB = '';
2641
}
@@ -40,6 +55,7 @@ describe('uiSref', () => {
4055
des = fixture.debugElement.queryAll(By.directive(UISref));
4156
});
4257

58+
4359
it('should not bind "null" string to `href`', () => {
4460
expect(des[0].nativeElement.hasAttribute('href')).toBeFalsy();
4561
expect(des[1].nativeElement.hasAttribute('href')).toBeFalsy();
@@ -114,6 +130,75 @@ describe('uiSref', () => {
114130
});
115131
});
116132
});
133+
134+
describe('when the bound values change', () => {
135+
let fixture: ComponentFixture<TestComponent>;
136+
let comp: TestComponent;
137+
let logger: TargetState[];
138+
let subscription: Subscription;
139+
140+
beforeEach(() => {
141+
fixture = TestBed.configureTestingModule({
142+
declarations: [TestComponent],
143+
imports: [UIRouterModule.forRoot({ useHash: true })]
144+
}).createComponent(TestComponent);
145+
fixture.detectChanges();
146+
comp = fixture.componentInstance;
147+
logger = [];
148+
subscription = comp.linkASref.targetState$.subscribe(evt => logger.push(evt));
149+
});
150+
151+
afterEach(() => {
152+
subscription.unsubscribe();
153+
});
154+
155+
describe('when the uiSref is empty', () => {
156+
it('should emit an empty target state event', () =>{
157+
expect(logger.length).toBe(1);
158+
expect(logger[0].name()).toBeNull();
159+
});
160+
})
161+
162+
describe('when the target state changes', () => {
163+
beforeEach(() => {
164+
comp.linkA = 'stateA';
165+
fixture.detectChanges();
166+
});
167+
168+
it('should emit an event', () => {
169+
expect(logger.length).toBe(2);
170+
expect(logger[1].name()).toBe('stateA');
171+
});
172+
});
173+
174+
describe('when the target params change', () => {
175+
const params = { paramA: 'paramA' };
176+
177+
beforeEach(() => {
178+
comp.linkAParams = params;
179+
fixture.detectChanges();
180+
});
181+
182+
it('should emit an event', () => {
183+
expect(logger.length).toBe(2);
184+
expect(logger[1].params()).toEqual(params);
185+
});
186+
});
187+
188+
describe('when the transition options change', () => {
189+
const options: TransitionOptions = { custom: 'custom' };
190+
191+
beforeEach(() => {
192+
comp.linkAOptions = options;
193+
fixture.detectChanges();
194+
});
195+
196+
it ('should emit an event', () => {
197+
expect(logger.length).toBe(2);
198+
expect(logger[1].options().custom).toEqual(options.custom);
199+
});
200+
})
201+
});
117202
});
118203
});
119204

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
import { Component, DebugElement } from '@angular/core';
2+
import { async, ComponentFixture, TestBed } from '@angular/core/testing';
3+
import { By } from '@angular/platform-browser';
4+
5+
import { SrefStatus, UISrefStatus } from '../../src/directives/uiSrefStatus';
6+
import { UIRouterModule } from '../../src/uiRouterNgModule';
7+
8+
describe('uiSrefStatus', () => {
9+
@Component({
10+
template: '<a uiSref="foo" (uiSrefStatus)="updated($event)"></a>',
11+
})
12+
class TestComponent {
13+
updated(event: SrefStatus) {
14+
throw new Error('updated() method must be spied');
15+
}
16+
}
17+
18+
let component: TestComponent;
19+
let de: DebugElement;
20+
let fixture: ComponentFixture<TestComponent>;
21+
22+
beforeEach(async(() => {
23+
TestBed.configureTestingModule({
24+
declarations: [TestComponent],
25+
imports: [UIRouterModule.forRoot({
26+
states: [{ name: 'foo' }],
27+
useHash: true,
28+
})]
29+
}).compileComponents();
30+
}));
31+
32+
beforeEach(() => {
33+
fixture = TestBed.createComponent(TestComponent);
34+
component = fixture.componentInstance;
35+
fixture.detectChanges();
36+
de = fixture.debugElement.query(By.directive(UISrefStatus));
37+
});
38+
39+
describe('when click on `foo` uiSref', () => {
40+
beforeEach(async(() => {
41+
spyOn(component, 'updated');
42+
de.triggerEventHandler('click', {});
43+
}));
44+
45+
it('should emit a event with a TargetState pointing to `foo`', () => {
46+
expect(component.updated).toHaveBeenCalled();
47+
const arg: SrefStatus = (component.updated as jasmine.Spy).calls.mostRecent().args[0];
48+
expect(arg.targetStates.length).toEqual(1);
49+
expect(arg.targetStates[0].state()).toEqual(jasmine.objectContaining({ name: 'foo' }));
50+
});
51+
});
52+
});

0 commit comments

Comments
 (0)