Skip to content

Commit

Permalink
Merge branch '11.x.x' into dropdown-custom-trigger-harness
Browse files Browse the repository at this point in the history
  • Loading branch information
Blackbaud-PaulCrowder authored Feb 10, 2025
2 parents 89631e3 + 1dde88d commit 393f2b0
Show file tree
Hide file tree
Showing 9 changed files with 124 additions and 77 deletions.
1 change: 1 addition & 0 deletions libs/components/pages/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ export {
SkyActionHubNeedsAttentionClickHandlerArgs,
} from './lib/modules/action-hub/types/action-hub-needs-attention-click-handler';
export { SkyLinkListModule } from './lib/modules/link-list/link-list.module';
export { SkyModalLinkListModule } from './lib/modules/modal-link-list/modal-link-list.module';
export { SkyNeedsAttentionModule } from './lib/modules/needs-attention/needs-attention.module';
export { SkyActionHubNeedsAttentionInput } from './lib/modules/action-hub/types/action-hub-needs-attention-input';
export { SkyPageLink } from './lib/modules/action-hub/types/page-link';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@
<sky-link-list-recently-accessed [recentLinks]="recentLinks" />
<sky-modal-link-list
[links]="settingsLinks"
[title]="'sky_action_hub_settings_links' | skyLibResources"
[headingText]="'sky_action_hub_settings_links' | skyLibResources"
/>
</sky-page-links>
</sky-page>
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
<sky-wait [isWaiting]="links === 'loading'" />
@if (links === 'loading') {
<sky-wait [isWaiting]="links() === 'loading'" />
@if (links() === 'loading') {
<ng-container [ngTemplateOutlet]="headingTemplateRef" />
} @else {
@if (linksArray.length > 0) {
@if (linksArray().length > 0) {
<ng-container [ngTemplateOutlet]="headingTemplateRef" />
<ul class="sky-link-list">
@for (link of linksArray; track link) {
@for (link of linksArray(); track link) {
<li>
@if (link | linkAs: 'skyHref') {
<a class="sky-link-list-item" [skyHref]="link.permalink?.url">
Expand Down Expand Up @@ -50,6 +50,6 @@

<ng-template #headingTemplateRef>
<h2 class="sky-font-heading-4">
{{ title }}
{{ headingText() }}
</h2>
</ng-template>
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
import { Component } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { SkyAppTestUtility } from '@skyux-sdk/testing';
import { SkyLogService } from '@skyux/core';
import { SkyModalService } from '@skyux/modals';

Expand Down Expand Up @@ -43,28 +44,46 @@ describe('SkyModalLinkListComponent', () => {

it('should create', () => {
expect(component).toBeTruthy();
component.openModal({
label: 'Link 1',
modal: { component: MockStandaloneComponent, config: {} },
});
fixture.componentRef.setInput('links', [
{
label: 'Link 1',
modal: { component: MockStandaloneComponent, config: {} },
},
]);
fixture.detectChanges();
const link = Array.from<HTMLButtonElement>(
fixture.nativeElement.querySelectorAll('button.sky-link-list-item'),
);
expect(link.length).toBe(1);
SkyAppTestUtility.fireDomEvent(link[0], 'click');
expect(openModalSpy).toHaveBeenCalledWith(MockStandaloneComponent, {});
});

it('should log when modal is not standalone', () => {
const logger = TestBed.inject(SkyLogService);
spyOn(logger, 'deprecated');
expect(component).toBeTruthy();
component.openModal({
label: 'Link 1',
modal: { component: MockComponent, config: {} },
});
fixture.componentRef.setInput('links', [
{
label: 'Link 1',
modal: { component: MockComponent, config: {} },
},
]);
fixture.detectChanges();
const link = Array.from<HTMLButtonElement>(
fixture.nativeElement.querySelectorAll('button.sky-link-list-item'),
);
expect(link.length).toBe(1);
SkyAppTestUtility.fireDomEvent(link[0], 'click');
expect(logger.deprecated).toHaveBeenCalled();
expect(openModalSpy).toHaveBeenCalledWith(MockComponent, {});
});

it('should handle empty input', () => {
expect(component).toBeTruthy();
component.links = undefined;
expect(component.links).toBeUndefined();
fixture.componentRef.setInput('links', undefined);
fixture.detectChanges();
expect(component.links()).toBeUndefined();
expect(fixture.nativeElement.querySelector('ul.sky-link-list')).toBeFalsy();
});
});
Original file line number Diff line number Diff line change
@@ -1,37 +1,48 @@
import { Component, Input, inject, isStandalone } from '@angular/core';
import {
Component,
computed,
inject,
input,
isStandalone,
} from '@angular/core';
import { SkyLogService } from '@skyux/core';
import { SkyModalLegacyService, SkyModalService } from '@skyux/modals';

import { SkyPageModalLink } from '../action-hub/types/page-modal-link';
import { SkyPageModalLinksInput } from '../action-hub/types/page-modal-links-input';

/**
* A component that displays a list of links such as within a `<sky-page-links>` component.
*/
@Component({
selector: 'sky-modal-link-list',
templateUrl: './modal-link-list.component.html',
styleUrls: ['./modal-link-list.component.scss'],
})
export class SkyModalLinkListComponent {
@Input()
public set links(value: SkyPageModalLinksInput | undefined) {
this.#_links = value;
this.linksArray = Array.isArray(value) ? value : [];
}

public get links(): SkyPageModalLinksInput | undefined {
return this.#_links;
}
/**
* Option to pass links as an array of `SkyPageModalLink` objects or `'loading'` to display a loading indicator.
*/
public readonly links = input<SkyPageModalLinksInput>();

@Input()
public title: string | undefined;
/**
* The text to display as the list's heading.
*/
public readonly headingText = input<string>();

public linksArray: SkyPageModalLink[] = [];

#_links: SkyPageModalLinksInput | undefined;
protected readonly linksArray = computed<SkyPageModalLink[]>(() => {
const links = this.links();
if (Array.isArray(links)) {
return links;
} else {
return [];
}
});

readonly #logger = inject(SkyLogService, { optional: true });
readonly #modalSvc = inject(SkyModalService);

public openModal(link: SkyPageModalLink): void {
protected openModal(link: SkyPageModalLink): void {
const modal = link.modal;

if (modal) {
Expand Down
6 changes: 6 additions & 0 deletions libs/components/pages/src/lib/modules/page/page.module.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ import { NgModule } from '@angular/core';
import { SkyLinkListRecentlyAccessedComponent } from '../link-list-recently-accessed/link-list-recently-accessed.component';
import { SkyLinkListItemComponent } from '../link-list/link-list-item.component';
import { SkyLinkListComponent } from '../link-list/link-list.component';
import { SkyLinkListModule } from '../link-list/link-list.module';
import { SkyModalLinkListModule } from '../modal-link-list/modal-link-list.module';
import { SkyPageHeaderModule } from '../page-header/page-header.module';

import { SkyPageContentComponent } from './page-content.component';
Expand All @@ -12,15 +14,19 @@ import { SkyPageComponent } from './page.component';
@NgModule({
declarations: [SkyPageComponent, SkyPageContentComponent],
imports: [
SkyLinkListModule,
SkyPageLinksComponent,
SkyLinkListComponent,
SkyLinkListItemComponent,
SkyLinkListRecentlyAccessedComponent,
SkyModalLinkListModule,
],
exports: [
SkyLinkListComponent,
SkyLinkListItemComponent,
SkyLinkListRecentlyAccessedComponent,
SkyLinkListModule,
SkyModalLinkListModule,
SkyPageComponent,
SkyPageHeaderModule,
SkyPageContentComponent,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,8 @@ export class HrefResolverFixtureService implements SkyHrefResolver {
});
} else if (url.startsWith('error://')) {
throw new Error(`Error while resolving ${url}`);
} else if (url.startsWith('reject://')) {
return Promise.reject(`Unable to resolve ${url}`);
} else {
return Promise.resolve<SkyHref>({
url,
Expand Down
12 changes: 12 additions & 0 deletions libs/components/router/src/lib/modules/href/href.directive.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -290,6 +290,18 @@ describe('SkyHref Directive', () => {
tick();

expect(element?.getAttribute('hidden')).toBeNull();

fixture.componentInstance.dynamicLink = 'reject://simple-app/example/page';
fixture.detectChanges();
tick();

expect(element?.getAttribute('hidden')).toBe('hidden');

fixture.componentInstance.dynamicLink = '1bb-nav://simple-app/fixed';
fixture.detectChanges();
tick();

expect(element?.getAttribute('hidden')).toBeNull();
}));

it('should handle the else parameter', fakeAsync(() => {
Expand Down
86 changes: 41 additions & 45 deletions libs/components/router/src/lib/modules/href/href.directive.ts
Original file line number Diff line number Diff line change
@@ -1,18 +1,22 @@
import {
ApplicationRef,
ChangeDetectorRef,
DestroyRef,
Directive,
ElementRef,
EventEmitter,
HostListener,
Input,
Optional,
Output,
Renderer2,
inject,
} from '@angular/core';
import { takeUntilDestroyed } from '@angular/core/rxjs-interop';
import { Router, UrlTree } from '@angular/router';
import { SkyAppConfig, SkyAppRuntimeConfigParamsProvider } from '@skyux/config';

import { Subscription, catchError, finalize, from, of } from 'rxjs';

import { SkyHrefResolverService } from './href-resolver.service';
import { SkyHref } from './types/href';
import { SkyHrefChange } from './types/href-change';
Expand Down Expand Up @@ -41,7 +45,7 @@ export class SkyHrefDirective {
this.#_skyHref = skyHref ? skyHref.join('/') : '';
}

void this.#checkRouteAccess();
this.#checkRouteAccess();
}

public get skyHref(): string {
Expand Down Expand Up @@ -92,34 +96,18 @@ export class SkyHrefDirective {
#_skyHref = '';
#_skyHrefElse: 'hide' | 'unlink' | undefined = 'hide';

#router: Router;
#renderer: Renderer2;
#element: ElementRef;
#skyAppConfig: SkyAppConfig | undefined;
#paramsProvider: SkyAppRuntimeConfigParamsProvider | undefined;
#hrefResolver: SkyHrefResolverService | undefined;
#applicationRef: ApplicationRef | undefined;
#changeDetectorRef: ChangeDetectorRef | undefined;

constructor(
router: Router,
renderer: Renderer2,
element: ElementRef,
@Optional() skyAppConfig?: SkyAppConfig,
@Optional() paramsProvider?: SkyAppRuntimeConfigParamsProvider,
@Optional() hrefResolver?: SkyHrefResolverService,
@Optional() applicationRef?: ApplicationRef,
@Optional() changeDetectorRef?: ChangeDetectorRef,
) {
this.#router = router;
this.#renderer = renderer;
this.#element = element;
this.#skyAppConfig = skyAppConfig;
this.#paramsProvider = paramsProvider;
this.#hrefResolver = hrefResolver;
this.#applicationRef = applicationRef;
this.#changeDetectorRef = changeDetectorRef;
}
readonly #router = inject(Router);
readonly #renderer = inject(Renderer2);
readonly #element = inject(ElementRef);
readonly #skyAppConfig = inject(SkyAppConfig, { optional: true });
readonly #paramsProvider = inject(SkyAppRuntimeConfigParamsProvider, {
optional: true,
});
readonly #hrefResolver = inject(SkyHrefResolverService, { optional: true });
readonly #applicationRef = inject(ApplicationRef);
readonly #changeDetectorRef = inject(ChangeDetectorRef);
readonly #destroyRef = inject(DestroyRef);
#checkingSubscription: Subscription | undefined;

@HostListener('click', [
'$event.button',
Expand Down Expand Up @@ -179,29 +167,37 @@ export class SkyHrefDirective {
this.skyHrefChange.emit({ userHasAccess: !change.hidden });
}

async #checkRouteAccess(): Promise<void> {
#checkRouteAccess(): void {
this.#route = {
url: this.skyHref,
userHasAccess: false,
};
/* istanbul ignore else */
if (this.#hrefResolver && this.skyHref) {
this.#applyChanges(this.#getChanges());
this.#checkingSubscription?.unsubscribe();
try {
const route = await this.#hrefResolver.resolveHref({
url: this.skyHref,
});

this.#route = { ...route };
this.#applyChanges(this.#getChanges());

/* istanbul ignore else */
if (this.#changeDetectorRef && this.#applicationRef) {
this.#changeDetectorRef.markForCheck();
this.#applicationRef.tick();
}
this.#checkingSubscription = from(
this.#hrefResolver.resolveHref({ url: this.skyHref }),
)
.pipe(
catchError(() =>
of({
url: this.skyHref,
userHasAccess: false,
} as SkyHref),
),
takeUntilDestroyed(this.#destroyRef),
finalize(() => {
this.#applyChanges(this.#getChanges());
}),
)
.subscribe((route) => {
this.#route = { ...route };
this.#changeDetectorRef.markForCheck();
this.#applicationRef.tick();
});
} catch {
this.#applyChanges(this.#getChanges());
// Unable to resolve.
}
} else {
// no resolver or skyHref is falsy
Expand Down

0 comments on commit 393f2b0

Please sign in to comment.