Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: allow specifying icons for header objectDetails #101

Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<div class="onecx-page-header" data-testid="ocx-page-header-wrapper">
<div class="onecx-page-header" name="ocx-page-header-wrapper">
<ng-container *ngIf="showBreadcrumbs">
<p-breadcrumb
[model]="items"
Expand Down Expand Up @@ -47,7 +47,7 @@ <h4>{{ subheader }}</h4>
(onClick)="action.actionCallback()"
[title]="(action.titleKey ? (action.titleKey | translate) : action.title) || (action.labelKey ? (action.labelKey | translate) : action.label)"
[disabled]="action.disabled ? action.disabled : false"
[attr.data-testid]="action.icon ? 'ocx-page-header-inline-action-icon-button' : 'ocx-page-header-inline-action-button'"
[attr.name]="action.icon ? 'ocx-page-header-inline-action-icon-button' : 'ocx-page-header-inline-action-button'"
></p-button>
</ng-container>
</div>
Expand All @@ -62,7 +62,7 @@ <h4>{{ subheader }}</h4>
title="{{ 'OCX_PAGE_HEADER.MORE_ACTIONS' | translate }}"
class="more-actions-menu-button action-button ml-2"
(click)="menu.toggle($event)"
data-testid="ocx-page-header-overflow-action-button"
name="ocx-page-header-overflow-action-button"
></button>
<p-menu #menu [popup]="true" [model]="overflowActions" appendTo="body"></p-menu>
</div>
Expand All @@ -82,9 +82,16 @@ <h4>{{ subheader }}</h4>
class="object-info flex flex-row md:flex-column align-items-baseline md:align-items-center justify-content-between"
*ngFor="let item of objectDetails"
>
<label class="flex font-medium text-600">{{ item.label | dynamicPipe:item.labelPipe }}</label>
<span class="flex text-900 mt-2" [title]="item.tooltip || ''"
>{{ item.value | dynamicPipe:item.valuePipe:item.valuePipeArgs}}</span
<label class="flex font-medium text-600" name="object-detail-label">{{ item.label | dynamicPipe:item.labelPipe }}</label>
<span
*ngIf="item.icon || item.value"
class="flex text-900 mt-2"
[ngClass]="item.icon ? 'gap-1 align-items-center' : null"
[title]="item.tooltip || ''"
name="object-detail-value"
>
<i *ngIf="item.icon" class="{{item.icon}}" name="object-detail-icon"></i>
{{ item.value | dynamicPipe:item.valuePipe:item.valuePipeArgs}}</span
>
</div>
</ng-container>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,14 +3,16 @@ 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'
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[] = [
Expand Down Expand Up @@ -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: '<ocx-page-header [actions]="actions"></ocx-page-header>',
})
class TestHostComponent {
actions: Action[] | undefined
}

describe('PageHeaderComponent', () => {
const origAddEventListener = window.addEventListener
const origPostMessage = window.postMessage
Expand All @@ -73,13 +66,14 @@ describe('PageHeaderComponent', () => {
window.postMessage = origPostMessage
})

let component: TestHostComponent
let fixture: ComponentFixture<TestHostComponent>
let component: PageHeaderComponent
let fixture: ComponentFixture<PageHeaderComponent>
let pageHeaderHarness: PageHeaderHarness
let userServiceSpy: jest.SpyInstance<boolean, [permissionKey: string], any>

beforeEach(async () => {
await TestBed.configureTestingModule({
declarations: [PageHeaderComponent, TestHostComponent],
declarations: [PageHeaderComponent, PageHeaderComponent, DynamicPipe],
imports: [
RouterTestingModule,
HttpClientTestingModule,
Expand All @@ -104,76 +98,153 @@ 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 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', () => {
component.actions = mockActions
fixture.detectChanges()

const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
'[data-testid="ocx-page-header-overflow-action-button"]'
'[name="ocx-page-header-overflow-action-button"]'
bastianjakobi marked this conversation as resolved.
Show resolved Hide resolved
)
expect(menuOverflowButton).toBeTruthy()
menuOverflowButton.click()
Expand All @@ -190,7 +261,7 @@ describe('PageHeaderComponent', () => {
fixture.detectChanges()

const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
'[data-testid="ocx-page-header-overflow-action-button"]'
'[name="ocx-page-header-overflow-action-button"]'
)
expect(menuOverflowButton).toBeTruthy()
menuOverflowButton.click()
Expand All @@ -210,7 +281,7 @@ describe('PageHeaderComponent', () => {
fixture.detectChanges()

const menuOverflowButton = fixture.debugElement.nativeElement.querySelector(
'[data-testid="ocx-page-header-overflow-action-button"]'
'[name="ocx-page-header-overflow-action-button"]'
)
expect(menuOverflowButton).toBeTruthy()
menuOverflowButton.click()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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: [],
})
}
}

Expand Down Expand Up @@ -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,
},
}
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -46,6 +46,7 @@ export interface ObjectDetailItem {
label: string
value?: string
tooltip?: string
icon?: PrimeIcons
labelPipe?: Type<any>
valuePipe?: Type<any>
valuePipeArgs?: string
Expand Down
Loading
Loading