Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Appearance settings

Commit fe69aa8

Browse filesBrowse files
committed
refactor(breadcrumb): signal inputs, host bindings, cleanup, tests
1 parent 34007f4 commit fe69aa8
Copy full SHA for fe69aa8

File tree

Expand file treeCollapse file tree

8 files changed

+141
-70
lines changed
Filter options
Expand file treeCollapse file tree

8 files changed

+141
-70
lines changed

‎projects/coreui-angular/src/lib/breadcrumb/breadcrumb-item/breadcrumb-item.component.html

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/breadcrumb/breadcrumb-item/breadcrumb-item.component.html
+12-12Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,19 +1,19 @@
1-
@if (!active) {
2-
<a [routerLink]="url"
3-
[cHtmlAttr]="attributes ?? {}"
4-
[target]="attributes?.['target']"
5-
[queryParams]="linkProps?.queryParams ?? null"
6-
[fragment]="linkProps?.fragment"
7-
[queryParamsHandling]="linkProps?.queryParamsHandling ?? null"
8-
[preserveFragment]="linkProps?.preserveFragment ?? false"
9-
[skipLocationChange]="linkProps?.skipLocationChange ?? false"
10-
[replaceUrl]="linkProps?.replaceUrl ?? false"
11-
[state]="linkProps?.state ?? {}"
1+
@if (!active()) {
2+
<a [routerLink]="url()"
3+
[cHtmlAttr]="attributes() ?? {}"
4+
[target]="attributes()?.['target']"
5+
[queryParams]="linkProps()?.queryParams ?? null"
6+
[fragment]="linkProps()?.fragment"
7+
[queryParamsHandling]="linkProps()?.queryParamsHandling ?? null"
8+
[preserveFragment]="linkProps()?.preserveFragment ?? false"
9+
[skipLocationChange]="linkProps()?.skipLocationChange ?? false"
10+
[replaceUrl]="linkProps()?.replaceUrl ?? false"
11+
[state]="linkProps()?.state ?? {}"
1212
>
1313
<ng-container *ngTemplateOutlet="defaultBreadcrumbItemContentTemplate" />
1414
</a>
1515
} @else {
16-
<span [cHtmlAttr]="attributes ?? {}">
16+
<span [cHtmlAttr]="attributes() ?? {}">
1717
<ng-container *ngTemplateOutlet="defaultBreadcrumbItemContentTemplate" />
1818
</span>
1919
}
+24-4Lines changed: 24 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,21 +1,24 @@
11
import { ComponentFixture, TestBed } from '@angular/core/testing';
2-
import { RouterTestingModule } from '@angular/router/testing';
32
import { BreadcrumbItemComponent } from './breadcrumb-item.component';
3+
import { provideRouter } from '@angular/router';
4+
import { ComponentRef } from '@angular/core';
45

56
describe('BreadcrumbItemComponent', () => {
67
let component: BreadcrumbItemComponent;
8+
let componentRef: ComponentRef<BreadcrumbItemComponent>;
79
let fixture: ComponentFixture<BreadcrumbItemComponent>;
810

911
beforeEach(async () => {
1012
await TestBed.configureTestingModule({
11-
imports: [RouterTestingModule, BreadcrumbItemComponent]
12-
})
13-
.compileComponents();
13+
imports: [BreadcrumbItemComponent],
14+
providers: [provideRouter([])]
15+
}).compileComponents();
1416
});
1517

1618
beforeEach(() => {
1719
fixture = TestBed.createComponent(BreadcrumbItemComponent);
1820
component = fixture.componentInstance;
21+
componentRef = fixture.componentRef;
1922
fixture.detectChanges();
2023
});
2124

@@ -25,5 +28,22 @@ describe('BreadcrumbItemComponent', () => {
2528

2629
it('should have css classes', () => {
2730
expect(fixture.nativeElement).toHaveClass('breadcrumb-item');
31+
expect(fixture.nativeElement).not.toHaveClass('active');
32+
});
33+
34+
it('should have active class', () => {
35+
componentRef.setInput('active', true);
36+
fixture.detectChanges();
37+
expect(fixture.nativeElement).toHaveClass('active');
38+
});
39+
40+
it('should have aria-current attribute', () => {
41+
expect(fixture.nativeElement.getAttribute('aria-current')).toBeNull();
42+
componentRef.setInput('active', true);
43+
fixture.detectChanges();
44+
expect(fixture.nativeElement.getAttribute('aria-current')).toBe('page');
45+
componentRef.setInput('active', false);
46+
fixture.detectChanges();
47+
expect(fixture.nativeElement.getAttribute('aria-current')).toBeNull();
2848
});
2949
});
+25-19Lines changed: 25 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -1,48 +1,54 @@
1-
import { booleanAttribute, Component, HostBinding, Input } from '@angular/core';
1+
import { booleanAttribute, Component, computed, input } from '@angular/core';
22
import { NgTemplateOutlet } from '@angular/common';
33
import { RouterModule } from '@angular/router';
44

55
import { HtmlAttributesDirective } from '../../shared';
66
import { INavAttributes, INavLinkProps } from './breadcrumb-item';
77

88
@Component({
9-
selector: 'c-breadcrumb-item',
10-
templateUrl: './breadcrumb-item.component.html',
11-
styleUrls: ['./breadcrumb-item.component.scss'],
12-
imports: [RouterModule, NgTemplateOutlet, HtmlAttributesDirective]
9+
selector: 'c-breadcrumb-item',
10+
templateUrl: './breadcrumb-item.component.html',
11+
styleUrls: ['./breadcrumb-item.component.scss'],
12+
imports: [RouterModule, NgTemplateOutlet, HtmlAttributesDirective],
13+
exportAs: 'breadcrumbItem',
14+
host: {
15+
'[attr.aria-current]': 'ariaCurrent()',
16+
'[class]': 'hostClasses()'
17+
}
1318
})
1419
export class BreadcrumbItemComponent {
15-
1620
/**
1721
* Toggle the active state for the component. [docs]
18-
* @type boolean
22+
* @return boolean
1923
*/
20-
@Input({ transform: booleanAttribute }) active?: boolean;
24+
readonly active = input<boolean, unknown>(undefined, { transform: booleanAttribute });
25+
2126
/**
2227
* The `url` prop for the inner `[routerLink]` directive. [docs]
2328
* @type string
2429
*/
25-
@Input() url?: string | any[];
30+
readonly url = input<string | any[]>();
31+
2632
/**
2733
* Additional html attributes for link. [docs]
2834
* @type INavAttributes
2935
*/
30-
@Input() attributes?: INavAttributes;
36+
readonly attributes = input<INavAttributes>();
37+
3138
/**
3239
* Some `NavigationExtras` props for the inner `[routerLink]` directive and `routerLinkActiveOptions`. [docs]
3340
* @type INavLinkProps
3441
*/
35-
@Input() linkProps?: INavLinkProps;
42+
readonly linkProps = input<INavLinkProps>();
3643

37-
@HostBinding('attr.aria-current') get ariaCurrent(): string | null {
38-
return this.active ? 'page' : null;
39-
}
44+
readonly ariaCurrent = computed((): string | null => {
45+
return this.active() ? 'page' : null;
46+
});
4047

41-
@HostBinding('class')
42-
get hostClasses(): any {
48+
readonly hostClasses = computed(() => {
4349
return {
4450
'breadcrumb-item': true,
45-
active: this.active
46-
};
47-
}
51+
active: this.active()
52+
} as Record<string, boolean>;
53+
});
4854
}

‎projects/coreui-angular/src/lib/breadcrumb/breadcrumb-item/breadcrumb-item.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/breadcrumb/breadcrumb-item/breadcrumb-item.ts
+1Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ interface IBreadcrumbItem {
66
attributes?: INavAttributes;
77
linkProps?: INavLinkProps;
88
class?: string;
9+
queryParams?: { [key: string]: any };
910
}
1011

1112
export { INavAttributes, INavLinkProps, IBreadcrumbItem };
+49-8Lines changed: 49 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,31 +1,72 @@
1+
import { ComponentRef } from '@angular/core';
12
import { ComponentFixture, TestBed, waitForAsync } from '@angular/core/testing';
2-
import { provideRouter, Router } from '@angular/router';
3+
import { provideRouter, Route } from '@angular/router';
4+
import { RouterTestingHarness } from '@angular/router/testing';
5+
import { take } from 'rxjs';
36

47
import { BreadcrumbRouterComponent } from './breadcrumb-router.component';
58
import { BreadcrumbRouterService } from './breadcrumb-router.service';
69

710
describe('BreadcrumbComponent', () => {
811
let component: BreadcrumbRouterComponent;
12+
let componentRef: ComponentRef<BreadcrumbRouterComponent>;
913
let fixture: ComponentFixture<BreadcrumbRouterComponent>;
10-
let router: Router;
14+
let harness: RouterTestingHarness;
15+
16+
const routes: Route[] = [
17+
{ path: 'home', component: BreadcrumbRouterComponent, data: { title: 'Home' } },
18+
{ path: 'color', component: BreadcrumbRouterComponent, title: 'Color' },
19+
{ path: '', component: BreadcrumbRouterComponent }
20+
];
1121

1222
beforeEach(waitForAsync(() => {
1323
TestBed.configureTestingModule({
14-
imports: [
15-
BreadcrumbRouterComponent
16-
],
17-
providers: [BreadcrumbRouterService, provideRouter([])]
24+
imports: [BreadcrumbRouterComponent],
25+
providers: [BreadcrumbRouterService, provideRouter(routes)]
1826
}).compileComponents();
1927
}));
2028

21-
beforeEach(() => {
29+
beforeEach(async () => {
2230
fixture = TestBed.createComponent(BreadcrumbRouterComponent);
23-
router = TestBed.inject(Router);
2431
component = fixture.componentInstance;
32+
componentRef = fixture.componentRef;
33+
34+
harness = await RouterTestingHarness.create();
2535
fixture.detectChanges();
2636
});
2737

2838
it('should create', () => {
2939
expect(component).toBeTruthy();
3040
});
41+
42+
it('should have breadcrumbs', () => {
43+
expect(component.breadcrumbs).toBeDefined();
44+
});
45+
46+
it('should get breadcrumbs from service', async () => {
47+
const comp = await harness.navigateByUrl('/home');
48+
component.breadcrumbs?.pipe(take(1)).subscribe((breadcrumbs) => {
49+
expect(breadcrumbs).toEqual([{ label: 'Home', url: '/home', queryParams: {} }]);
50+
});
51+
});
52+
it('should get breadcrumbs from service', async () => {
53+
const comp = await harness.navigateByUrl('/color?id=1&test=2');
54+
component.breadcrumbs?.pipe(take(1)).subscribe((breadcrumbs) => {
55+
expect(breadcrumbs).toEqual([{ label: 'Color', url: '/color', queryParams: { id: '1', test: '2' } }]);
56+
});
57+
});
58+
it('should get breadcrumbs from service', async () => {
59+
const comp = await harness.navigateByUrl('/');
60+
component.breadcrumbs?.pipe(take(1)).subscribe((breadcrumbs) => {
61+
expect(breadcrumbs).toEqual([{ label: '', url: '/', queryParams: {} }]);
62+
});
63+
});
64+
65+
it('should emit breadcrumbs on items change', () => {
66+
componentRef.setInput('items', [{ label: 'test' }]);
67+
fixture.detectChanges();
68+
component.breadcrumbs?.pipe(take(1)).subscribe((breadcrumbs) => {
69+
expect(breadcrumbs).toEqual([{ label: 'test' }]);
70+
});
71+
});
3172
});

‎projects/coreui-angular/src/lib/breadcrumb/breadcrumb-router/breadcrumb-router.component.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/breadcrumb/breadcrumb-router/breadcrumb-router.component.ts
+11-15Lines changed: 11 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
import { Component, inject, Input, OnChanges, OnDestroy, OnInit, SimpleChanges } from '@angular/core';
1+
import { Component, effect, inject, input, OnDestroy, OnInit } from '@angular/core';
22
import { Observable, Observer } from 'rxjs';
33
import { AsyncPipe } from '@angular/common';
44

@@ -12,35 +12,31 @@ import { BreadcrumbItemComponent } from '../breadcrumb-item/breadcrumb-item.comp
1212
templateUrl: './breadcrumb-router.component.html',
1313
imports: [BreadcrumbComponent, BreadcrumbItemComponent, AsyncPipe]
1414
})
15-
export class BreadcrumbRouterComponent implements OnChanges, OnDestroy, OnInit {
15+
export class BreadcrumbRouterComponent implements OnDestroy, OnInit {
1616
readonly service = inject(BreadcrumbRouterService);
1717

1818
/**
1919
* Optional array of IBreadcrumbItem to override default BreadcrumbRouter behavior. [docs]
20-
* @type IBreadcrumbItem[]
20+
* @return IBreadcrumbItem[]
2121
*/
22-
@Input() items?: IBreadcrumbItem[];
22+
readonly items = input<IBreadcrumbItem[]>();
2323
public breadcrumbs: Observable<IBreadcrumbItem[]> | undefined;
2424

2525
ngOnInit(): void {
2626
this.breadcrumbs = this.service.breadcrumbs$;
2727
}
2828

29-
public ngOnChanges(changes: SimpleChanges): void {
30-
if (changes['items']) {
31-
this.setup();
32-
}
33-
}
34-
35-
setup(): void {
36-
if (this.items && this.items.length > 0) {
29+
readonly setup = effect(() => {
30+
const items = this.items();
31+
if (items && items.length > 0) {
3732
this.breadcrumbs = new Observable<IBreadcrumbItem[]>((observer: Observer<IBreadcrumbItem[]>) => {
38-
if (this.items) {
39-
observer.next(this.items);
33+
const itemsValue = this.items();
34+
if (itemsValue) {
35+
observer.next(itemsValue);
4036
}
4137
});
4238
}
43-
}
39+
});
4440

4541
ngOnDestroy(): void {
4642
this.breadcrumbs = undefined;

‎projects/coreui-angular/src/lib/breadcrumb/breadcrumb/breadcrumb.component.spec.ts

Copy file name to clipboardExpand all lines: projects/coreui-angular/src/lib/breadcrumb/breadcrumb/breadcrumb.component.spec.ts
+9-2Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,7 @@ describe('BreadcrumbComponent', () => {
99
beforeEach(async () => {
1010
await TestBed.configureTestingModule({
1111
imports: [BreadcrumbComponent]
12-
})
13-
.compileComponents();
12+
}).compileComponents();
1413
});
1514

1615
beforeEach(() => {
@@ -26,4 +25,12 @@ describe('BreadcrumbComponent', () => {
2625
it('should have css classes', () => {
2726
expect(fixture.nativeElement).toHaveClass('breadcrumb');
2827
});
28+
29+
it('should have aria-label attribute', () => {
30+
expect(fixture.nativeElement.getAttribute('aria-label')).toBe('breadcrumb');
31+
});
32+
33+
it('should have role attribute', () => {
34+
expect(fixture.nativeElement.getAttribute('role')).toBe('navigation');
35+
});
2936
});
+10-10Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,26 +1,26 @@
1-
import { Component, HostBinding, Input } from '@angular/core';
1+
import { Component, input } from '@angular/core';
22

33
@Component({
44
selector: 'c-breadcrumb',
55
template: '<ng-content />',
6-
host: { class: 'breadcrumb' }
6+
host: {
7+
class: 'breadcrumb',
8+
'[attr.aria-label]': 'ariaLabel()',
9+
'[attr.role]': 'role()'
10+
}
711
})
812
export class BreadcrumbComponent {
913
/**
1014
* Default aria-label for breadcrumb. [docs]
11-
* @type string
15+
* @return string
1216
* @default 'breadcrumb'
1317
*/
14-
@HostBinding('attr.aria-label')
15-
@Input()
16-
ariaLabel = 'breadcrumb';
18+
readonly ariaLabel = input('breadcrumb');
1719

1820
/**
1921
* Default role for breadcrumb. [docs]
20-
* @type string
22+
* @return string
2123
* @default 'navigation'
2224
*/
23-
@HostBinding('attr.role')
24-
@Input()
25-
role = 'navigation';
25+
readonly role = input('navigation');
2626
}

0 commit comments

Comments
0 (0)
Morty Proxy This is a proxified and sanitized view of the page, visit original site.