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()) {
- @for (link of linksArray; track link) {
+ @for (link of linksArray(); track link) {
-
@if (link | linkAs: 'skyHref') {
@@ -35,12 +35,11 @@
}
}
+
}
}
-
- {{ 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';