diff --git a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.html b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.html
index a05110ed..cb9d8ab7 100644
--- a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.html
+++ b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.html
@@ -1,4 +1,4 @@
-
@@ -82,9 +82,16 @@ {{ subheader }}
class="object-info flex flex-row md:flex-column align-items-baseline md:align-items-center justify-content-between"
*ngFor="let item of objectDetails"
>
-
- {{ item.value | dynamicPipe:item.valuePipe:item.valuePipeArgs}}{{ item.label | dynamicPipe:item.labelPipe }}
+
+
+ {{ item.value | dynamicPipe:item.valuePipe:item.valuePipeArgs}}
diff --git a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.spec.ts
index 287a6931..ebb3f250 100644
--- a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.spec.ts
+++ b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.spec.ts
@@ -3,7 +3,6 @@ import { Action, PageHeaderComponent } from './page-header.component'
import { RouterTestingModule } from '@angular/router/testing'
import { ConfigurationService } from '../../../services/configuration.service'
import { HttpClientTestingModule } from '@angular/common/http/testing'
-import { Component } from '@angular/core'
import { TranslateTestingModule } from 'ngx-translate-testing'
import { BreadcrumbModule } from 'primeng/breadcrumb'
import { MenuModule } from 'primeng/menu'
@@ -11,6 +10,9 @@ import { ButtonModule } from 'primeng/button'
import { AppStateService } from '../../../services/app-state.service'
import { UserService } from '../../../services/user.service'
import { MockUserService } from '../../../../../mocks/mock-user-service'
+import { PageHeaderHarness, TestbedHarnessEnvironment } from '../../../../../testing'
+import { DynamicPipe } from '../../pipes/dynamic.pipe'
+import { PrimeIcons } from 'primeng/api'
import { NoopAnimationsModule } from '@angular/platform-browser/animations'
const mockActions: Action[] = [
@@ -41,15 +43,6 @@ const mockActions: Action[] = [
},
]
-// Mock host component that's used in our testBed instead of ocx-page-header
-// Using this mock host allows us to simulate Angular @Input mechanisms
-@Component({
- template: '',
-})
-class TestHostComponent {
- actions: Action[] | undefined
-}
-
describe('PageHeaderComponent', () => {
const origAddEventListener = window.addEventListener
const origPostMessage = window.postMessage
@@ -73,13 +66,14 @@ describe('PageHeaderComponent', () => {
window.postMessage = origPostMessage
})
- let component: TestHostComponent
- let fixture: ComponentFixture
+ let component: PageHeaderComponent
+ let fixture: ComponentFixture
+ let pageHeaderHarness: PageHeaderHarness
let userServiceSpy: jest.SpyInstance
beforeEach(async () => {
await TestBed.configureTestingModule({
- declarations: [PageHeaderComponent, TestHostComponent],
+ declarations: [PageHeaderComponent, PageHeaderComponent, DynamicPipe],
imports: [
RouterTestingModule,
HttpClientTestingModule,
@@ -104,124 +98,197 @@ describe('PageHeaderComponent', () => {
})
})
- beforeEach(() => {
- fixture = TestBed.createComponent(TestHostComponent)
+ beforeEach(async () => {
+ fixture = TestBed.createComponent(PageHeaderComponent)
component = fixture.componentInstance
fixture.detectChanges()
+ pageHeaderHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, PageHeaderHarness)
const userService = fixture.debugElement.injector.get(UserService)
jest.restoreAllMocks()
userServiceSpy = jest.spyOn(userService, 'hasPermission')
})
- it('should create', () => {
+ it('should create', async () => {
expect(component).toBeTruthy()
- expect(fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-wrapper"]')).toHaveLength(
- 1
- )
+ const pageHeaderWrapper = await pageHeaderHarness.getPageHeaderWrapperHarness()
+ expect(pageHeaderWrapper).toBeTruthy()
})
- it('should check permissions and render buttons accordingly', () => {
+ it('should check permissions and render buttons accordingly', async () => {
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-inline-action-button"]')
+ await pageHeaderHarness.getInlineActionButtons()
).toHaveLength(0)
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-overflow-action-button"]')
+ await pageHeaderHarness.getOverflowActionButtons()
).toHaveLength(0)
component.actions = mockActions
- fixture.detectChanges()
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-inline-action-button"]')
+ await pageHeaderHarness.getInlineActionButtons()
).toHaveLength(1)
- expect(fixture.debugElement.nativeElement.querySelector('[title="My Test Action"]')).toBeTruthy()
+ expect(await pageHeaderHarness.getElementByTitle('My Test Action')).toBeTruthy()
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-overflow-action-button"]')
+ await pageHeaderHarness.getOverflowActionButtons()
).toHaveLength(1)
- expect(fixture.debugElement.nativeElement.querySelector('[title="More actions"]')).toBeTruthy()
- expect(userServiceSpy).toHaveBeenCalledTimes(6)
+ expect(await pageHeaderHarness.getElementByTitle('More actions')).toBeTruthy()
+ expect(userServiceSpy).toHaveBeenCalledTimes(3)
})
- it("should check permissions and not render button that user isn't allowed to see", () => {
+ it("should check permissions and not render button that user isn't allowed to see", async () => {
userServiceSpy.mockClear()
userServiceSpy.mockReturnValue(false)
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-inline-action-button"]')
+ await pageHeaderHarness.getInlineActionButtons()
).toHaveLength(0)
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-overflow-action-button"]')
+ await pageHeaderHarness.getOverflowActionButtons()
).toHaveLength(0)
component.actions = mockActions
- fixture.detectChanges()
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-inline-action-button"]')
+ await pageHeaderHarness.getInlineActionButtons()
).toHaveLength(0)
- expect(fixture.debugElement.nativeElement.querySelector('[title="My Test Action"]')).toBeFalsy()
+ expect(await pageHeaderHarness.getElementByTitle('My Test Action')).toBeFalsy()
expect(
- fixture.debugElement.nativeElement.querySelectorAll('[data-testid="ocx-page-header-overflow-action-button"]')
+ await pageHeaderHarness.getOverflowActionButtons()
).toHaveLength(0)
- expect(fixture.debugElement.nativeElement.querySelector('[title="More actions"]')).toBeFalsy()
- expect(userServiceSpy).toHaveBeenCalledTimes(6)
+ expect(await pageHeaderHarness.getElementByTitle('More actions')).toBeFalsy()
+ expect(userServiceSpy).toHaveBeenCalledTimes(3)
})
- it('should show overflow actions when menu overflow button clicked', () => {
+ it("should render objectDetails as object info in the page header", async () => {
+ const objectDetailsWithoutIcons = [
+ {
+ label: 'Venue',
+ value: 'AIE Munich',
+ },
+ {
+ label: 'Status',
+ value: 'Confirmed',
+ },
+ ]
+ expect((await pageHeaderHarness.getObjectInfos()).length).toEqual(0)
+
+ component.objectDetails = objectDetailsWithoutIcons
+
+ expect((await pageHeaderHarness.getObjectInfos()).length).toEqual(2)
+ const objectDetailLabels = await pageHeaderHarness.getObjectDetailLabels()
+ const objectDetailValues = await pageHeaderHarness.getObjectDetailValues()
+ const objectDetailIcons = await pageHeaderHarness.getObjectDetailIcons()
+ expect(objectDetailLabels.length).toEqual(2)
+ expect(objectDetailValues.length).toEqual(2)
+ expect(objectDetailIcons.length).toEqual(0)
+
+ objectDetailLabels.forEach(async (label, i) => {
+ expect(await label.text()).toEqual(objectDetailsWithoutIcons[i].label)
+ })
+
+ objectDetailValues.forEach(async (value, i) => {
+ expect(await value.text()).toEqual(objectDetailsWithoutIcons[i].value)
+ })
+ })
+
+ it("should render objectDetails with icons as object info in the page header", async () => {
+ const objectDetailsWithIcons = [
+ {
+ label: 'Venue',
+ value: 'AIE Munich',
+ },
+ {
+ label: 'Status',
+ value: 'Confirmed',
+ icon: PrimeIcons.CHECK
+ },
+ {
+ label: 'Done?',
+ icon: PrimeIcons.EXCLAMATION_CIRCLE
+ },
+ {
+ label: 'Empty'
+ }
+ ]
+ expect((await pageHeaderHarness.getObjectInfos()).length).toEqual(0)
+
+ component.objectDetails = objectDetailsWithIcons
+
+ expect((await pageHeaderHarness.getObjectInfos()).length).toEqual(4)
+ const objectDetailLabels = await pageHeaderHarness.getObjectDetailLabels()
+ const objectDetailValues = await pageHeaderHarness.getObjectDetailValues()
+ const objectDetailIcons = await pageHeaderHarness.getObjectDetailIcons()
+ expect(objectDetailLabels.length).toEqual(4)
+ expect(objectDetailValues.length).toEqual(3)
+ expect(objectDetailIcons.length).toEqual(2)
+
+ objectDetailLabels.forEach(async (label, i) => {
+ expect(await label.text()).toEqual(objectDetailsWithIcons[i].label)
+ })
+
+ objectDetailValues.forEach(async (value, i) => {
+ if(objectDetailsWithIcons[i].value) {
+ expect(await value.text()).toEqual(objectDetailsWithIcons[i].value)
+ }
+ })
+
+ expect(await objectDetailIcons[0].getAttribute('class')).toEqual(objectDetailsWithIcons[1].icon)
+ expect(await objectDetailIcons[1].getAttribute('class')).toEqual(objectDetailsWithIcons[2].icon)
+ })
+
+ it('should show overflow actions when menu overflow button clicked', async () => {
component.actions = mockActions
- fixture.detectChanges()
- const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
- '[data-testid="ocx-page-header-overflow-action-button"]'
- )
- expect(menuOverflowButton).toBeTruthy()
- menuOverflowButton.click()
- fixture.detectChanges()
+ const menuOverflowButtons = await pageHeaderHarness.getOverflowActionButtons()
+
+ expect(menuOverflowButtons).toBeTruthy()
+ expect(menuOverflowButtons.length).toBe(1)
+ await menuOverflowButtons[0].click()
- expect(fixture.debugElement.nativeElement.querySelector('[title="My Test Overflow Action"]')).toBeTruthy()
- expect(fixture.debugElement.nativeElement.querySelector('[title="My Test Overflow Disabled Action"]')).toBeTruthy()
+ const menuItems = await pageHeaderHarness.getOverFlowMenuItems()
+ expect(menuItems.length).toBe(2)
+ expect(await menuItems[0].getText()).toBe("My Test Overflow Action")
+ expect(await menuItems[1].getText()).toBe("My Test Overflow Disabled Action")
})
- it('should use provided action callback on overflow button click', () => {
+ it('should use provided action callback on overflow button click', async () => {
jest.spyOn(console, 'log')
component.actions = mockActions
- fixture.detectChanges()
- const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
- '[data-testid="ocx-page-header-overflow-action-button"]'
- )
- expect(menuOverflowButton).toBeTruthy()
- menuOverflowButton.click()
- fixture.detectChanges()
+ const menuOverflowButtons = await pageHeaderHarness.getOverflowActionButtons()
+
+ expect(menuOverflowButtons).toBeTruthy()
+ expect(menuOverflowButtons.length).toBe(1)
+ await menuOverflowButtons[0].click()
- const enabledActionElement = fixture.debugElement.nativeElement.querySelector('[title="My Test Overflow Action"]')
- expect(enabledActionElement).toBeTruthy()
- expect(enabledActionElement.classList).not.toContain('p-disabled')
- enabledActionElement.click()
+ const menuItems = await pageHeaderHarness.getOverFlowMenuItems()
+ expect(menuItems.length).toBe(2)
+ const enabledActionElement = await menuItems[0].host()
+ expect(await(enabledActionElement.hasClass('p-disabled'))).toBe(false)
+ await enabledActionElement.click()
expect(console.log).toHaveBeenCalledTimes(1)
})
- it('should disable overflow button when action is disabled', () => {
+ it('should disable overflow button when action is disabled', async () => {
jest.spyOn(console, 'log')
component.actions = mockActions
- fixture.detectChanges()
- const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
- '[data-testid="ocx-page-header-overflow-action-button"]'
- )
+ const menuOverflowButton = await pageHeaderHarness.getOverflowActionButtons()
expect(menuOverflowButton).toBeTruthy()
- menuOverflowButton.click()
- fixture.detectChanges()
+ expect(menuOverflowButton.length).toBe(1)
+ menuOverflowButton[0].click()
- const disabledActionElement = fixture.debugElement.nativeElement.querySelector(
- '[title="My Test Overflow Disabled Action"]'
- )
+ const overFlowMenuItems = await pageHeaderHarness.getOverFlowMenuItems()
+ const disabledActionElement = overFlowMenuItems[1]
+
+ expect(overFlowMenuItems).toBeTruthy()
+ expect(overFlowMenuItems?.length).toBe(2)
expect(disabledActionElement).toBeTruthy()
- expect(disabledActionElement.classList).toContain('p-disabled')
- disabledActionElement.click()
+ expect(await (await disabledActionElement.host()).hasClass('p-disabled')).toBe(true)
+ await (await disabledActionElement.host()).click()
expect(console.log).toHaveBeenCalledTimes(0)
})
})
diff --git a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.stories.ts b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.stories.ts
index d082f453..78ea4342 100644
--- a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.stories.ts
+++ b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.stories.ts
@@ -17,20 +17,14 @@ import { BreadcrumbService } from '../../../services/breadcrumb.service'
import { DynamicPipe } from '../../pipes/dynamic.pipe'
import { Action, ObjectDetailItem, PageHeaderComponent } from './page-header.component'
import { HttpClientModule } from '@angular/common/http'
-import { AppStateService } from '../../../services/app-state.service'
+import { PrimeIcons } from 'primeng/api'
-function initFactory(breadcrumbService: BreadcrumbService, appStateService: AppStateService) {
+function initFactory(breadcrumbService: BreadcrumbService) {
return async () => {
breadcrumbService.setItems([
{ label: 'Level 1', routerLink: 'something' },
{ label: 'Level 2', url: '/' },
])
- await appStateService.currentPortal$.publish({
- baseUrl: '/demo',
- portalName: 'Demo',
- id: 'Demo',
- microfrontendRegistrations: [],
- })
}
}
@@ -300,3 +294,61 @@ export const WithCustomContent = {
objectDetails: demoFields,
},
}
+
+const objectDetailsWithoutIcons: ObjectDetailItem[] = [
+ {
+ label: 'Venue',
+ value: 'AIE Munich ',
+ },
+ {
+ label: 'Status',
+ value: 'Confirmed',
+ },
+ {
+ label: 'Start Date',
+ value: '14.3.2022',
+ },
+]
+
+export const WithObjectDetails = {
+ render: Template,
+
+ args: {
+ header: 'Test Page',
+ subheader: 'Page header with text based objectDetails and no icons',
+ loading: false,
+ objectDetails: objectDetailsWithoutIcons,
+ showBreadcrumbs: false,
+ },
+}
+
+const objectDetailsWithIcons: ObjectDetailItem[] = [
+ {
+ label: 'Venue',
+ value: 'AIE Munich ',
+ },
+ {
+ label: 'Event Completed',
+ icon: PrimeIcons.CHECK_CIRCLE
+ },
+ {
+ label: 'Start Date',
+ value: '14.3.2022',
+ icon: PrimeIcons.CLOCK
+ },
+ {
+ label: 'I have no value'
+ },
+]
+
+export const WithObjectDetailsAndIcons = {
+ render: Template,
+
+ args: {
+ header: 'Test Page',
+ subheader: 'Page header with text and icon based objectDetails',
+ loading: false,
+ objectDetails: objectDetailsWithIcons,
+ showBreadcrumbs: false,
+ },
+}
\ No newline at end of file
diff --git a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.ts b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.ts
index 08c93167..490c8404 100644
--- a/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.ts
+++ b/libs/portal-integration-angular/src/lib/core/components/page-header/page-header.component.ts
@@ -11,7 +11,7 @@ import {
Type,
ViewEncapsulation,
} from '@angular/core'
-import { MenuItem } from 'primeng/api'
+import { MenuItem, PrimeIcons } from 'primeng/api'
import { concat, map, Observable, of } from 'rxjs'
import { BreadcrumbService } from '../../../services/breadcrumb.service'
import { TranslateService } from '@ngx-translate/core'
@@ -46,6 +46,7 @@ export interface ObjectDetailItem {
label: string
value?: string
tooltip?: string
+ icon?: PrimeIcons
labelPipe?: Type
valuePipe?: Type
valuePipeArgs?: string
diff --git a/libs/portal-integration-angular/testing/index.ts b/libs/portal-integration-angular/testing/index.ts
index b18f4551..4ff94656 100644
--- a/libs/portal-integration-angular/testing/index.ts
+++ b/libs/portal-integration-angular/testing/index.ts
@@ -31,6 +31,7 @@ export * from './table-row.harness'
export * from './diagram.harness'
export * from './search-config.harness'
export * from './span.harness'
+export * from './page-header.harness'
export * from './p-tableCheckbox.harness'
export * from '@angular/cdk/testing'
diff --git a/libs/portal-integration-angular/testing/page-header.harness.ts b/libs/portal-integration-angular/testing/page-header.harness.ts
new file mode 100644
index 00000000..709ffbc8
--- /dev/null
+++ b/libs/portal-integration-angular/testing/page-header.harness.ts
@@ -0,0 +1,42 @@
+import { ComponentHarness } from "@angular/cdk/testing";
+import { PMenuHarness } from "./primeng/p-menu.harness";
+
+export class PageHeaderHarness extends ComponentHarness {
+ static hostSelector = 'ocx-page-header'
+
+ getPageHeaderWrapperHarness = this.locatorForAll('[name="ocx-page-header-wrapper"]')
+
+ async getInlineActionButtons() {
+ return await this.locatorForAll('[name="ocx-page-header-inline-action-button"]')()
+ }
+
+ async getOverflowActionButtons() {
+ return await this.locatorForAll('[name="ocx-page-header-overflow-action-button"]')()
+ }
+
+ async getElementByTitle(title: string) {
+ return await this.locatorForOptional(`[title="${title}"]`)()
+ }
+
+ async getObjectInfos() {
+ return await this.locatorForAll('.object-info')()
+ }
+
+ async getObjectDetailLabels() {
+ return await this.locatorForAll('[name="object-detail-label"]')()
+ }
+
+ async getObjectDetailValues() {
+ return await this.locatorForAll('[name="object-detail-value"]')()
+ }
+
+ async getObjectDetailIcons() {
+ return await this.locatorForAll('[name="object-detail-icon"]')()
+ }
+
+ async getOverFlowMenuItems() {
+ const menu = await this.locatorFor(PMenuHarness)()
+ const menuItems = await menu.getAllMenuItems()
+ return menuItems ?? []
+ }
+}
\ No newline at end of file