diff --git a/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.html b/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.html index cf30425555..426120c26f 100644 --- a/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.html +++ b/apps/code-examples/src/app/code-examples/pages/page/record-page-blocks-layout-demo/demo.component.html @@ -13,4 +13,17 @@ + + + + Analysis + + + Tools + + + + + + diff --git a/apps/e2e/pages-storybook/src/app/app.module.ts b/apps/e2e/pages-storybook/src/app/app.module.ts index 72f264486d..5ffabf8db3 100644 --- a/apps/e2e/pages-storybook/src/app/app.module.ts +++ b/apps/e2e/pages-storybook/src/app/app.module.ts @@ -1,10 +1,16 @@ import { NgModule } from '@angular/core'; import { NoopAnimationsModule } from '@angular/platform-browser/animations'; import { Route, RouterModule } from '@angular/router'; +import { provideInitialTheme } from '@skyux/theme'; import { AppComponent } from './app.component'; const routes: Route[] = [ + { + path: '', + redirectTo: '/page/layouts/blocks-with-links', + pathMatch: 'full', + }, { path: 'action-hub', loadChildren: () => @@ -28,6 +34,7 @@ if (routes.length > 0 && routes.findIndex((r) => r.path === '') === -1) { bindToComponentInputs: true, }), ], + providers: [provideInitialTheme('modern')], bootstrap: [AppComponent], }) export class AppModule {} diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html index 50e43a1c34..f63fbb82c0 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.html @@ -93,6 +93,6 @@ @if (showLinks()) { - Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts index 563e99d012..3277b81ba3 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/blocks-page/blocks-page.component.ts @@ -6,6 +6,8 @@ import { SkyBoxModule, SkyFluidGridModule } from '@skyux/layout'; import { SkyPageModule } from '@skyux/pages'; import { SkyDropdownModule } from '@skyux/popovers'; +import { LinksComponent } from '../../../shared/links/links.component'; + @Component({ selector: 'app-blocks-page', standalone: true, @@ -18,6 +20,7 @@ import { SkyDropdownModule } from '@skyux/popovers'; SkyFluidGridModule, SkyLabelModule, SkyPageModule, + LinksComponent, ], templateUrl: './blocks-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html index 8ab0dfc8a4..d0f80edc2b 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.html @@ -120,6 +120,6 @@ @if (showLinks()) { - Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts index bf0f1fe9c2..7c1b739261 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/fit-page/fit-page.component.ts @@ -22,6 +22,8 @@ import { import { Subject } from 'rxjs'; +import { LinksComponent } from '../../../shared/links/links.component'; + interface WorkspaceItem { id: number; amount: number; @@ -46,6 +48,7 @@ interface WorkspaceItem { SkyRepeaterModule, SkySplitViewModule, SkySummaryActionBarModule, + LinksComponent, ], templateUrl: './fit-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html index 2266134a89..f3f456a60b 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.html @@ -6,6 +6,6 @@ List content @if (showLinks()) { - Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts index 06e1469ad1..26f0665b57 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/list-page/list-page.component.ts @@ -2,10 +2,12 @@ import { CommonModule } from '@angular/common'; import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { SkyPageModule } from '@skyux/pages'; +import { LinksComponent } from '../../../shared/links/links.component'; + @Component({ selector: 'app-list-page', standalone: true, - imports: [CommonModule, SkyPageModule], + imports: [CommonModule, SkyPageModule, LinksComponent], templateUrl: './list-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html index e865a58ab7..02e8d601c7 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.html @@ -18,6 +18,6 @@ @if (showLinks()) { - Links. + } diff --git a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts index 8940872d06..e5fdfae7b8 100644 --- a/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts +++ b/apps/e2e/pages-storybook/src/app/page/layouts/tabs-page/tabs-page.component.ts @@ -3,10 +3,12 @@ import { ChangeDetectionStrategy, Component, input } from '@angular/core'; import { SkyPageModule } from '@skyux/pages'; import { SkyTabsModule } from '@skyux/tabs'; +import { LinksComponent } from '../../../shared/links/links.component'; + @Component({ selector: 'app-tabs-page', standalone: true, - imports: [CommonModule, SkyPageModule, SkyTabsModule], + imports: [CommonModule, SkyPageModule, SkyTabsModule, LinksComponent], templateUrl: './tabs-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) diff --git a/apps/e2e/pages-storybook/src/app/shared/links/links.component.html b/apps/e2e/pages-storybook/src/app/shared/links/links.component.html new file mode 100644 index 0000000000..38f52e9182 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/shared/links/links.component.html @@ -0,0 +1,14 @@ + + + Link 1 + + + Link 2 + + + Link 3 + + + + + diff --git a/apps/e2e/pages-storybook/src/app/shared/links/links.component.ts b/apps/e2e/pages-storybook/src/app/shared/links/links.component.ts new file mode 100644 index 0000000000..1ea3794c04 --- /dev/null +++ b/apps/e2e/pages-storybook/src/app/shared/links/links.component.ts @@ -0,0 +1,10 @@ +import { Component } from '@angular/core'; +import { SkyLinkListModule } from '@skyux/pages'; + +@Component({ + selector: 'app-links', + standalone: true, + imports: [SkyLinkListModule], + templateUrl: './links.component.html', +}) +export class LinksComponent {} diff --git a/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.html b/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.html index 5c6d4b3281..4e469a2ea5 100644 --- a/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.html +++ b/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.html @@ -42,4 +42,15 @@ + + + + Components + + + diff --git a/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.ts b/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.ts index 34d6c15e8d..20594aaf58 100644 --- a/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.ts +++ b/apps/playground/src/app/components/pages/layouts/blocks-page/blocks-page.component.ts @@ -1,4 +1,4 @@ -import { ChangeDetectionStrategy, Component } from '@angular/core'; +import { ChangeDetectionStrategy, Component, signal } from '@angular/core'; import { SkyBoxModule, SkyFluidGridModule } from '@skyux/layout'; import { SkyPageModule } from '@skyux/pages'; @@ -8,4 +8,6 @@ import { SkyPageModule } from '@skyux/pages'; templateUrl: './blocks-page.component.html', changeDetection: ChangeDetectionStrategy.OnPush, }) -export default class BlocksPageComponent {} +export default class BlocksPageComponent { + protected readonly linksLoading = signal<'loading' | undefined>(undefined); +} diff --git a/libs/components/colorpicker/testing/project.json b/libs/components/colorpicker/testing/project.json index 1d1db07942..b9240fd05d 100644 --- a/libs/components/colorpicker/testing/project.json +++ b/libs/components/colorpicker/testing/project.json @@ -5,7 +5,15 @@ "sourceRoot": "libs/components/colorpicker/testing/src", "prefix": "sky", "tags": ["testing"], - "implicitDependencies": ["colorpicker", "testing", "theme"], + "implicitDependencies": [ + "colorpicker", + "core-testing", + "forms-testing", + "help-inline-testing", + "icon-testing", + "testing", + "theme" + ], "targets": { "build": { "command": "echo ' 🏗️ build colorpicker-testing'", diff --git a/libs/components/pages/src/index.ts b/libs/components/pages/src/index.ts index 5cbbf20d76..15c5c02650 100644 --- a/libs/components/pages/src/index.ts +++ b/libs/components/pages/src/index.ts @@ -4,6 +4,7 @@ export { SkyActionHubNeedsAttentionClickHandler, SkyActionHubNeedsAttentionClickHandlerArgs, } from './lib/modules/action-hub/types/action-hub-needs-attention-click-handler'; +export { SkyLinkListModule } from './lib/modules/link-list/link-list.module'; export { SkyPageLink } from './lib/modules/action-hub/types/page-link'; export { SkyPageLinksInput } from './lib/modules/action-hub/types/page-links-input'; export { SkyPageModalLink } from './lib/modules/action-hub/types/page-modal-link'; @@ -19,6 +20,8 @@ export { SkyPageLayoutType } from './lib/modules/page/types/page-layout-type'; export { SkyActionHubButtonsComponent as λ2 } from './lib/modules/action-hub/action-hub-buttons.component'; export { SkyActionHubContentComponent as λ3 } from './lib/modules/action-hub/action-hub-content.component'; export { SkyActionHubComponent as λ1 } from './lib/modules/action-hub/action-hub.component'; +export { SkyLinkListComponent as λ13 } from './lib/modules/link-list/link-list.component'; +export { SkyLinkListItemComponent as λ14 } from './lib/modules/link-list/link-list-item.component'; export { SkyModalLinkListComponent as λ5 } from './lib/modules/modal-link-list/modal-link-list.component'; export { SkyPageHeaderActionsComponent as λ9 } from './lib/modules/page-header/page-header-actions.component'; export { SkyPageHeaderAlertsComponent as λ11 } from './lib/modules/page-header/page-header-alerts.component'; diff --git a/libs/components/pages/src/lib/modules/action-hub/action-hub.component.html b/libs/components/pages/src/lib/modules/action-hub/action-hub.component.html index 7f72eb87ec..31651dd07d 100644 --- a/libs/components/pages/src/lib/modules/action-hub/action-hub.component.html +++ b/libs/components/pages/src/lib/modules/action-hub/action-hub.component.html @@ -24,11 +24,11 @@ { expect(fixture.nativeElement.querySelectorAll('.sky-wait').length).toBe( 0, ); - const recent1 = fixture.nativeElement.querySelector( - 'sky-link-list[ng-reflect-title="Recently accessed"] a', - ); + const recent1 = fixture.nativeElement.querySelector('sky-link-list a'); expect(recent1).toHaveText('Recent link'); })); diff --git a/libs/components/pages/src/lib/modules/link-list/fixtures/link-list-fixture.component.ts b/libs/components/pages/src/lib/modules/link-list/fixtures/link-list-fixture.component.ts new file mode 100644 index 0000000000..69cad2eed8 --- /dev/null +++ b/libs/components/pages/src/lib/modules/link-list/fixtures/link-list-fixture.component.ts @@ -0,0 +1,26 @@ +import { Component, input } from '@angular/core'; + +import { SkyPageLinksInput } from '../../action-hub/types/page-links-input'; +import { SkyLinkListModule } from '../link-list.module'; + +@Component({ + standalone: true, + selector: 'sky-link-list-fixture', + template: ` + + @if (showLinks()) { + + Link 1 + + + Link 2 + + } + + `, + imports: [SkyLinkListModule], +}) +export class LinkListFixtureComponent { + public readonly links = input(); + public readonly showLinks = input(false); +} diff --git a/libs/components/pages/src/lib/modules/link-list/link-list-item.component.ts b/libs/components/pages/src/lib/modules/link-list/link-list-item.component.ts new file mode 100644 index 0000000000..3e5fabf38f --- /dev/null +++ b/libs/components/pages/src/lib/modules/link-list/link-list-item.component.ts @@ -0,0 +1,20 @@ +import { Component } from '@angular/core'; + +/** + * A wrapper for each link in a link list. + */ +@Component({ + standalone: true, + selector: 'sky-link-list-item', + template: ``, + styles: ` + :host { + display: block; + margin: 0 0 var(--sky-margin-stacked-sm) 0; + } + `, + host: { + '[attr.role]': '"listitem"', + }, +}) +export class SkyLinkListItemComponent {} diff --git a/libs/components/pages/src/lib/modules/link-list/link-list.component.html b/libs/components/pages/src/lib/modules/link-list/link-list.component.html index 3cc412236a..af902f4ad1 100644 --- a/libs/components/pages/src/lib/modules/link-list/link-list.component.html +++ b/libs/components/pages/src/lib/modules/link-list/link-list.component.html @@ -1,11 +1,11 @@ - -@if (links === 'loading') { + +@if (links() === 'loading') { } @else { - @if (linksArray.length > 0) { + @if (hasLinks()) { } } -

- {{ title }} -

+

{{ headingText() }}

diff --git a/libs/components/pages/src/lib/modules/link-list/link-list.component.spec.ts b/libs/components/pages/src/lib/modules/link-list/link-list.component.spec.ts index 80fc9ecad6..019256e77d 100644 --- a/libs/components/pages/src/lib/modules/link-list/link-list.component.spec.ts +++ b/libs/components/pages/src/lib/modules/link-list/link-list.component.spec.ts @@ -1,7 +1,8 @@ import { ComponentFixture, TestBed } from '@angular/core/testing'; -import { RouterTestingModule } from '@angular/router/testing'; -import { expect } from '@skyux-sdk/testing'; +import { provideRouter } from '@angular/router'; +import { expect, expectAsync } from '@skyux-sdk/testing'; +import { LinkListFixtureComponent } from './fixtures/link-list-fixture.component'; import { SkyLinkListComponent } from './link-list.component'; import { SkyLinkListModule } from './link-list.module'; @@ -10,14 +11,15 @@ describe('Link list component', () => { beforeEach(() => { TestBed.configureTestingModule({ - imports: [SkyLinkListModule, RouterTestingModule.withRoutes([])], + imports: [SkyLinkListModule, LinkListFixtureComponent], + providers: [provideRouter([])], }); fixture = TestBed.createComponent(SkyLinkListComponent); }); it('should show links', async () => { - fixture.componentInstance.title = 'Full List'; - fixture.componentInstance.links = [ + fixture.componentRef.setInput('headingText', 'Full List'); + fixture.componentRef.setInput('links', [ { label: 'Link 1', permalink: { @@ -36,7 +38,7 @@ describe('Link list component', () => { url: '#', }, }, - ]; + ]); fixture.detectChanges(); const links = fixture.nativeElement.getElementsByTagName('a'); @@ -45,11 +47,29 @@ describe('Link list component', () => { }); it('should disappear when empty', async () => { - fixture.componentInstance.title = 'Empty List'; - fixture.componentInstance.links = []; + fixture.componentRef.setInput('headingText', 'Empty List'); + fixture.componentRef.setInput('links', []); fixture.detectChanges(); expect(fixture.nativeElement).not.toHaveText('Empty List'); }); + + it('should show links with link items', async () => { + const fixture = TestBed.createComponent(LinkListFixtureComponent); + fixture.componentRef.setInput('showLinks', true); + fixture.detectChanges(); + await fixture.whenStable(); + expect( + fixture.nativeElement.querySelector('ul.sky-link-list'), + ).toBeVisible(); + }); + + it('should be accessible', async () => { + const fixture = TestBed.createComponent(LinkListFixtureComponent); + fixture.componentRef.setInput('showLinks', true); + fixture.detectChanges(); + await fixture.whenStable(); + await expectAsync(fixture.nativeElement).toBeAccessible(); + }); }); diff --git a/libs/components/pages/src/lib/modules/link-list/link-list.component.ts b/libs/components/pages/src/lib/modules/link-list/link-list.component.ts index abe353cc64..80b2c6cfe0 100644 --- a/libs/components/pages/src/lib/modules/link-list/link-list.component.ts +++ b/libs/components/pages/src/lib/modules/link-list/link-list.component.ts @@ -1,28 +1,58 @@ -import { Component, Input } from '@angular/core'; +import { NgTemplateOutlet } from '@angular/common'; +import { Component, computed, contentChildren, input } from '@angular/core'; +import { SkyWaitModule } from '@skyux/indicators'; +import { SkyAppLinkModule, SkyHrefModule } from '@skyux/router'; import { SkyPageLink } from '../action-hub/types/page-link'; import { SkyPageLinksInput } from '../action-hub/types/page-links-input'; +import { LinkAsModule } from '../link-as/link-as.module'; +import { SkyLinkListItemComponent } from './link-list-item.component'; + +/** + * A component that displays a list of links on the right side of the page, or + * below the page content on mobile devices. + */ @Component({ + standalone: true, selector: 'sky-link-list', templateUrl: './link-list.component.html', styleUrls: ['./link-list.component.scss'], + imports: [ + LinkAsModule, + NgTemplateOutlet, + SkyAppLinkModule, + SkyHrefModule, + SkyWaitModule, + ], }) export class SkyLinkListComponent { - @Input() - public set links(value: SkyPageLinksInput | undefined) { - this.#_links = value; - this.linksArray = Array.isArray(value) ? value : []; - } + /** + * The text to display as the list's heading. + */ + public readonly headingText = input(); - public get links(): SkyPageLinksInput | undefined { - return this.#_links; - } + /** + * Option to pass links as an array of `SkyPageLink` objects or `'loading'` to display a loading indicator. + */ + public readonly links = input(); - @Input() - public title: string | undefined; + protected readonly linkItems = contentChildren(SkyLinkListItemComponent); - public linksArray: SkyPageLink[] = []; + protected readonly hasLinks = computed(() => { + const linkItems = this.linkItems(); + const links = this.links(); + if (linkItems.length > 0) { + return true; + } + return Array.isArray(links) && links.length > 0; + }); - #_links: SkyPageLinksInput | undefined; + protected readonly linksArray = computed(() => { + const links = this.links(); + if (Array.isArray(links)) { + return links; + } + return []; + }); } diff --git a/libs/components/pages/src/lib/modules/link-list/link-list.module.ts b/libs/components/pages/src/lib/modules/link-list/link-list.module.ts index 944c72a943..7800dd84f7 100644 --- a/libs/components/pages/src/lib/modules/link-list/link-list.module.ts +++ b/libs/components/pages/src/lib/modules/link-list/link-list.module.ts @@ -1,23 +1,10 @@ -import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; -import { SkyWaitModule } from '@skyux/indicators'; -import { SkyAppLinkModule, SkyHrefModule } from '@skyux/router'; -import { SkyThemeModule } from '@skyux/theme'; - -import { LinkAsModule } from '../link-as/link-as.module'; +import { SkyLinkListItemComponent } from './link-list-item.component'; import { SkyLinkListComponent } from './link-list.component'; @NgModule({ - declarations: [SkyLinkListComponent], - exports: [SkyLinkListComponent], - imports: [ - CommonModule, - SkyAppLinkModule, - SkyHrefModule, - SkyThemeModule, - SkyWaitModule, - LinkAsModule, - ], + exports: [SkyLinkListComponent, SkyLinkListItemComponent], + imports: [SkyLinkListComponent, SkyLinkListItemComponent], }) export class SkyLinkListModule {} diff --git a/libs/components/pages/src/lib/modules/page/page-links.component.ts b/libs/components/pages/src/lib/modules/page/page-links.component.ts index 8aca6e17ff..297a567f2b 100644 --- a/libs/components/pages/src/lib/modules/page/page-links.component.ts +++ b/libs/components/pages/src/lib/modules/page/page-links.component.ts @@ -2,7 +2,6 @@ import { Component } from '@angular/core'; /** * Displays page links within a block page layout. - * @internal */ @Component({ standalone: true, diff --git a/libs/components/pages/src/lib/modules/page/page.module.ts b/libs/components/pages/src/lib/modules/page/page.module.ts index ca0f80e780..b5d79f9c0f 100644 --- a/libs/components/pages/src/lib/modules/page/page.module.ts +++ b/libs/components/pages/src/lib/modules/page/page.module.ts @@ -1,6 +1,8 @@ import { CommonModule } from '@angular/common'; import { NgModule } from '@angular/core'; +import { SkyLinkListItemComponent } from '../link-list/link-list-item.component'; +import { SkyLinkListComponent } from '../link-list/link-list.component'; import { SkyPageHeaderModule } from '../page-header/page-header.module'; import { SkyPageContentComponent } from './page-content.component'; @@ -9,8 +11,16 @@ import { SkyPageComponent } from './page.component'; @NgModule({ declarations: [SkyPageComponent, SkyPageContentComponent], - imports: [CommonModule, SkyPageLinksComponent], + imports: [ + CommonModule, + SkyPageLinksComponent, + SkyLinkListComponent, + SkyLinkListItemComponent, + SkyPageLinksComponent, + ], exports: [ + SkyLinkListComponent, + SkyLinkListItemComponent, SkyPageComponent, SkyPageHeaderModule, SkyPageContentComponent, diff --git a/libs/components/pages/testing/project.json b/libs/components/pages/testing/project.json index cebf3221d8..06458c553a 100644 --- a/libs/components/pages/testing/project.json +++ b/libs/components/pages/testing/project.json @@ -5,7 +5,7 @@ "sourceRoot": "libs/components/pages/testing/src", "prefix": "sky", "tags": ["testing"], - "implicitDependencies": ["core-testing", "pages"], + "implicitDependencies": ["core-testing", "indicators-testing", "pages"], "targets": { "build": { "command": "echo ' 🏗️ build pages-testing'", diff --git a/libs/components/pages/testing/src/link-list/link-list-harness-filters.ts b/libs/components/pages/testing/src/link-list/link-list-harness-filters.ts new file mode 100644 index 0000000000..3ea90a61c1 --- /dev/null +++ b/libs/components/pages/testing/src/link-list/link-list-harness-filters.ts @@ -0,0 +1,7 @@ +import { SkyHarnessFilters } from '@skyux/core/testing'; + +/** + * A set of criteria that can be used to filter a list of SkyLinkListHarness instances. + */ +// eslint-disable-next-line @typescript-eslint/no-empty-interface +export interface SkyLinkListHarnessFilters extends SkyHarnessFilters {} diff --git a/libs/components/pages/testing/src/link-list/link-list-harness.spec.ts b/libs/components/pages/testing/src/link-list/link-list-harness.spec.ts new file mode 100644 index 0000000000..418cf8e2a6 --- /dev/null +++ b/libs/components/pages/testing/src/link-list/link-list-harness.spec.ts @@ -0,0 +1,90 @@ +import { HarnessLoader } from '@angular/cdk/testing'; +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; +import { Component, input } from '@angular/core'; +import { ComponentFixture, TestBed } from '@angular/core/testing'; +import { SkyLinkListModule, SkyPageLinksInput } from '@skyux/pages'; + +import { SkyLinkListHarness } from './link-list-harness'; + +//#region Test component +@Component({ + standalone: true, + selector: 'sky-link-list-test', + template: ` + @if (showLinks()) { +
Link 1 + } +
`, + imports: [SkyLinkListModule], +}) +class TestComponent { + public readonly showLinks = input(true); + public readonly inputLinks = input(); +} +//#endregion Test component + +describe('Link list harness', () => { + async function setupTest(options: { dataSkyId?: string } = {}): Promise<{ + harness: SkyLinkListHarness; + fixture: ComponentFixture; + loader: HarnessLoader; + }> { + TestBed.configureTestingModule({ + imports: [TestComponent], + }); + + const fixture = TestBed.createComponent(TestComponent); + const loader = TestbedHarnessEnvironment.loader(fixture); + + let harness: SkyLinkListHarness; + + if (options.dataSkyId) { + harness = await loader.getHarness( + SkyLinkListHarness.with({ + dataSkyId: options.dataSkyId, + }), + ); + } else { + harness = await loader.getHarness(SkyLinkListHarness); + } + + return { harness, fixture, loader }; + } + + it('should return the wait status', async () => { + const { harness, fixture } = await setupTest({ + dataSkyId: 'test-list', + }); + fixture.detectChanges(); + + await expectAsync(harness.isWaiting()).toBeResolvedTo(false); + + fixture.componentRef.setInput('showLinks', false); + fixture.componentRef.setInput('inputLinks', 'loading'); + await expectAsync(harness.isWaiting()).toBeResolvedTo(true); + }); + + it('should return the heading text', async () => { + const { harness, fixture } = await setupTest(); + fixture.detectChanges(); + + await expectAsync(harness.getHeadingText()).toBeResolvedTo('Heading Text'); + + fixture.componentRef.setInput('showLinks', false); + await expectAsync(harness.getHeadingText()).toBeResolvedTo(undefined); + }); + + it('should return the visibility', async () => { + const { harness, fixture } = await setupTest(); + fixture.detectChanges(); + + await expectAsync(harness.isVisible()).toBeResolvedTo(true); + + fixture.componentRef.setInput('showLinks', false); + await expectAsync(harness.isVisible()).toBeResolvedTo(false); + }); +}); diff --git a/libs/components/pages/testing/src/link-list/link-list-harness.ts b/libs/components/pages/testing/src/link-list/link-list-harness.ts new file mode 100644 index 0000000000..0e37ecfaf8 --- /dev/null +++ b/libs/components/pages/testing/src/link-list/link-list-harness.ts @@ -0,0 +1,56 @@ +import { HarnessPredicate } from '@angular/cdk/testing'; +import { SkyComponentHarness } from '@skyux/core/testing'; +import { SkyWaitHarness } from '@skyux/indicators/testing'; + +import { SkyLinkListHarnessFilters } from './link-list-harness-filters'; + +/** + * Harness for interacting with a link list component in tests. + */ +export class SkyLinkListHarness extends SkyComponentHarness { + /** + * @internal + */ + public static hostSelector = 'sky-link-list'; + + #getHeading = this.locatorFor('h2.sky-font-heading-4'); + #getList = this.locatorFor('ul.sky-link-list'); + + /** + * Gets a `HarnessPredicate` that can be used to search for a + * `SkyLinkListHarness` that meets certain criteria. + */ + public static with( + filters: SkyLinkListHarnessFilters, + ): HarnessPredicate { + return SkyLinkListHarness.getDataSkyIdPredicate(filters); + } + + /** + * Gets the link list's heading text. If there are no links, this will return `undefined`. + */ + public async getHeadingText(): Promise { + return await this.#getHeading().then( + (el) => el.text(), + () => undefined, + ); + } + + /** + * Whether the link list is showing a list of links. + */ + public async isVisible(): Promise { + return await this.#getList().then( + () => true, + () => false, + ); + } + + /** + * Gets the status of the wait indicator. + */ + public async isWaiting(): Promise { + const waitHarness = await (await this.locatorFor(SkyWaitHarness))(); + return await waitHarness.isWaiting(); + } +} diff --git a/libs/components/pages/testing/src/public-api.ts b/libs/components/pages/testing/src/public-api.ts index 438a1096df..fb2005af74 100644 --- a/libs/components/pages/testing/src/public-api.ts +++ b/libs/components/pages/testing/src/public-api.ts @@ -1,2 +1,6 @@ export { SkyPageHarness } from './page/page-harness'; export { SkyPageHarnessFilters } from './page/page-harness-filters'; +export { SkyPageHeaderHarness } from './page-header/page-header-harness'; +export { SkyPageHeaderHarnessFilters } from './page-header/page-header-harness-filters'; +export { SkyLinkListHarness } from './link-list/link-list-harness'; +export { SkyLinkListHarnessFilters } from './link-list/link-list-harness-filters';