diff --git a/libs/portal-integration-angular/src/lib/core/components/ocx-content-container/ocx-content-container.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/ocx-content-container/ocx-content-container.component.spec.ts index f80d9e18..f2048462 100644 --- a/libs/portal-integration-angular/src/lib/core/components/ocx-content-container/ocx-content-container.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/ocx-content-container/ocx-content-container.component.spec.ts @@ -1,88 +1,50 @@ +import { OcxContentContainerHarness } from './../../../../../testing/ocx-content-container.harness' import { ComponentFixture, TestBed } from '@angular/core/testing' import { OcxContentContainerComponent } from './ocx-content-container.component' import { OcxContentContainerDirective } from '../../directives/ocx-content-container.directive' -import { Component } from '@angular/core' - -// Mock host component that's used in all tests that require a dynamic layout change -// Using this mock host allows us to simulate Angular @Input mechanisms -@Component({ - template: ` - - `, -}) -class TestHostComponent { - layout: 'horizontal' | 'vertical' = 'horizontal'; - breakpoint: 'sm' | 'md' | 'lg' | 'xl' = 'md' -} +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed' describe('OcxContentContainerComponent', () => { - let component: TestHostComponent | OcxContentContainerComponent - let fixture: ComponentFixture + let component: OcxContentContainerComponent + let fixture: ComponentFixture + let ocxContentContainerHarness: OcxContentContainerHarness beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [OcxContentContainerComponent, OcxContentContainerDirective, TestHostComponent], + declarations: [OcxContentContainerComponent, OcxContentContainerDirective], }).compileComponents() fixture = TestBed.createComponent(OcxContentContainerComponent) component = fixture.componentInstance - fixture.detectChanges() + ocxContentContainerHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, OcxContentContainerHarness) }) it('should create', () => { expect(component).toBeTruthy() }) - it('should render a horizontal layout container with all expected css classes', () => { - // Check that layout is horizontal by default - expect(component.layout).toEqual('horizontal') - // Check that breakpoint is md by default - expect(component.breakpoint).toEqual('md') - fixture.detectChanges() - - // Check that the classList of the rendered element contains all expected classes + it('should render a horizontal layout container with md breakpoint by default', async () => { const expectedClasses = ['flex', 'py-3', 'gap-3', 'flex-column', 'md:flex-row'] - expect(Object.keys(fixture.debugElement.children[0].classes)).toEqual(expectedClasses) + expect(await ocxContentContainerHarness.getLayoutClasses()).toEqual(expectedClasses) + expect(await ocxContentContainerHarness.getLayout()).toEqual('horizontal') + expect(await ocxContentContainerHarness.getBreakpoint()).toEqual('md') }) - it('should render a horizontal layout container with all expected css classes and a specified breakpoint', () => { - // Check that layout is horizontal by default - expect(component.layout).toEqual('horizontal') - // Check that breakpoint is md by default - expect(component.breakpoint).toEqual('md') - fixture.detectChanges() - - // Check that the classList of the rendered element contains all expected classes - const expectedClassesMD = ['flex', 'py-3', 'gap-3', 'flex-column', 'md:flex-row'] - expect(Object.keys(fixture.debugElement.children[0].classes)).toEqual(expectedClassesMD) - - component.breakpoint = "lg" - fixture.detectChanges() + it('should render a horizontal layout container while respecting a specified breakpoint', async () => { + component.breakpoint = 'lg' - // Check that breakpoint is now lg and that classes have been updated accordingly - expect(component.breakpoint).toEqual('lg') - const expectedClassesLG = ['flex', 'py-3', 'gap-3', 'flex-column', 'lg:flex-row'] - expect(Object.keys(fixture.debugElement.children[0].classes)).toEqual(expectedClassesLG) + const expectedClassesLG = ['flex', 'py-3', 'gap-3', 'flex-column', 'lg:flex-row'] + expect(await ocxContentContainerHarness.getLayoutClasses()).toEqual(expectedClassesLG) + expect(await ocxContentContainerHarness.getLayout()).toEqual('horizontal') + expect(await ocxContentContainerHarness.getBreakpoint()).toEqual('lg') }) - it('should render a vertical layout container with all expected css classes', () => { - // Replace default component with custom host component to simulate input behavior - fixture = TestBed.createComponent(TestHostComponent) - component = fixture.componentInstance - fixture.detectChanges() - - // Check that layout is horizontal by default - expect(component.layout).toEqual('horizontal') - - // Set layout to 'vertical' + it('should render a vertical layout container if specified', async () => { component.layout = 'vertical' - fixture.detectChanges() - - // Check that layout is now vertical - expect(component.layout).toEqual('vertical') - // Check that the classList of the rendered element contains all expected classes - const expectedClasses = ["flex", "py-3", "gap-3", "flex-column"] - expect(Object.keys(fixture.debugElement.children[0].children[0].classes)).toEqual(expectedClasses) + const expectedClasses = ['flex', 'py-3', 'gap-3', 'flex-column'] + expect(await ocxContentContainerHarness.getLayoutClasses()).toEqual(expectedClasses) + expect(await ocxContentContainerHarness.getLayout()).toEqual('vertical') + expect(await ocxContentContainerHarness.getBreakpoint()).toBeUndefined() }) }) diff --git a/libs/portal-integration-angular/src/lib/core/components/ocx-content/ocx-content.component.spec.ts b/libs/portal-integration-angular/src/lib/core/components/ocx-content/ocx-content.component.spec.ts index f736deb6..88052ee9 100644 --- a/libs/portal-integration-angular/src/lib/core/components/ocx-content/ocx-content.component.spec.ts +++ b/libs/portal-integration-angular/src/lib/core/components/ocx-content/ocx-content.component.spec.ts @@ -1,79 +1,45 @@ +import { OcxContentHarness } from './../../../../../testing/ocx-content.harness'; import { ComponentFixture, TestBed } from '@angular/core/testing' import { OcxContentComponent } from './ocx-content.component' -import { Component } from '@angular/core' import { OcxContentDirective } from '../../directives/ocx-content.directive' -import { By } from '@angular/platform-browser' - -// Mock host component that's used in all tests that require a dynamic ocxContent title change -// Using this mock host allows us to simulate Angular @Input mechanisms -@Component({ - template: ` `, -}) -class TestHostComponent { - title = '' -} +import { TestbedHarnessEnvironment } from '@angular/cdk/testing/testbed'; describe('OcxContentComponent', () => { - let component: TestHostComponent | OcxContentComponent - let fixture: ComponentFixture - const titleElemID = 'ocxContentTitleElement' + let component: OcxContentComponent + let fixture: ComponentFixture + let ocxContentHarness: OcxContentHarness const testComponentTitle = 'My cool title' beforeEach(async () => { await TestBed.configureTestingModule({ - declarations: [OcxContentComponent, OcxContentDirective, TestHostComponent], + declarations: [OcxContentComponent, OcxContentDirective], }).compileComponents() fixture = TestBed.createComponent(OcxContentComponent) component = fixture.componentInstance - fixture.detectChanges() + ocxContentHarness = await TestbedHarnessEnvironment.harnessForFixture(fixture, OcxContentHarness) }) it('should create', () => { expect(component).toBeTruthy() }) - it('should render a ocxContent card with no title by default', () => { - // Check that the title is falsy (in this case '') by default - expect(component.title).toBeFalsy() - fixture.detectChanges() - - // Check that classList of element contains all expected classes + it('should render a ocxContent card with no title by default', async () => { const expectedClasses = ['card'] - expect(Object.keys(fixture.debugElement.children[0].classes)).toEqual(expectedClasses) - - // Check that title element doesn't exist inside of ocxContent card - expect(fixture.debugElement.query(By.css(`#${titleElemID}`))).toBeFalsy() + expect(await ocxContentHarness.getContentClasses()).toEqual(expectedClasses) + expect(await ocxContentHarness.hasTitle()).toEqual(false) }) - it('should render a ocxContent card with a title, when given a title via input', () => { - // Replace default component with custom host component to simulate input behavior - fixture = TestBed.createComponent(TestHostComponent) - component = fixture.componentInstance - fixture.detectChanges() - - // Check that initial rendering without title still works as expected - // Check that the title is falsy (in this case '') by default - expect(component.title).toBeFalsy() - fixture.detectChanges() - - // Check that classList of element contains all expected classes + it('should render a ocxContent card with a title, when given a title via input', async () => { const expectedClasses = ['card'] - expect(Object.keys(fixture.debugElement.children[0].children[0].classes)).toEqual(expectedClasses) + expect(await ocxContentHarness.getContentClasses()).toEqual(expectedClasses) + expect(await ocxContentHarness.hasTitle()).toEqual(false) - // Check that title element doesn't exist inside of ocxContent card - expect(fixture.debugElement.query(By.css(`#${titleElemID}`))).toBeFalsy() - - // Set title to a specific value component.title = testComponentTitle - fixture.detectChanges() - - // Check that title now matches the expected value - expect(component.title).toEqual(testComponentTitle) - // Check that title element has been rendered and contains the expected text content - const titleElement = fixture.debugElement.query(By.css(`#${titleElemID}`)) - expect(titleElement).toBeTruthy() - expect(titleElement.nativeElement.textContent).toEqual(testComponentTitle) + const expectedTitleClasses = ['font-medium', 'text-lg'] + expect(await ocxContentHarness.hasTitle()).toEqual(true) + expect(await ocxContentHarness.getTitle()).toEqual(testComponentTitle) + expect(await ocxContentHarness.getTitleClasses()).toEqual(expectedTitleClasses) }) }) diff --git a/libs/portal-integration-angular/testing/div.harness.ts b/libs/portal-integration-angular/testing/div.harness.ts index 45a66e68..39669eae 100644 --- a/libs/portal-integration-angular/testing/div.harness.ts +++ b/libs/portal-integration-angular/testing/div.harness.ts @@ -24,4 +24,13 @@ export class DivHarness extends ComponentHarness { async getText(): Promise { return await (await this.host()).text() } + + async getClassList() { + const host = await this.host() + const attributeString = await host.getAttribute("class"); + if(attributeString) { + return attributeString.trim().split(" ") + } + return [] + } } diff --git a/libs/portal-integration-angular/testing/ocx-content-container.harness.ts b/libs/portal-integration-angular/testing/ocx-content-container.harness.ts new file mode 100644 index 00000000..e9944d17 --- /dev/null +++ b/libs/portal-integration-angular/testing/ocx-content-container.harness.ts @@ -0,0 +1,25 @@ +import { ComponentHarness } from '@angular/cdk/testing' +import { DivHarness } from './div.harness'; + + +export class OcxContentContainerHarness extends ComponentHarness { + static hostSelector = 'ocx-content-container' + + async getLayoutClasses() { + const div = await this.locatorFor(DivHarness)() + const actualClassList = await div.getClassList() + + return actualClassList; + } + + async getLayout(): Promise<'horizontal' | 'vertical'> { + const layoutClassses = await this.getLayoutClasses(); + return layoutClassses.find((c) => c.endsWith(':flex-row')) ? 'horizontal' : 'vertical' + } + + async getBreakpoint(): Promise<'sm' | 'md' | 'lg' | 'xl' | undefined> { + const layoutClassses = await this.getLayoutClasses(); + const layoutClass = layoutClassses.find((c) => c.endsWith(':flex-row')) + return layoutClass?.split(":")[0] as 'sm' | 'md' | 'lg' | 'xl' | undefined + } +} diff --git a/libs/portal-integration-angular/testing/ocx-content.harness.ts b/libs/portal-integration-angular/testing/ocx-content.harness.ts new file mode 100644 index 00000000..03a05f20 --- /dev/null +++ b/libs/portal-integration-angular/testing/ocx-content.harness.ts @@ -0,0 +1,42 @@ +import { ComponentHarness } from '@angular/cdk/testing' +import { DivHarness } from './div.harness' +import { PHarness } from './p.harness' + +export class OcxContentHarness extends ComponentHarness { + static hostSelector = 'ocx-content' + + async getContentClasses() { + const div = await this.locatorFor(DivHarness)() + const actualClassList = await div.getClassList() + + return actualClassList + } + + async getTitleClasses() { + const p = await this.getTitleHarness() + if (p) { + const actualClassList = await p.getClassList() + return actualClassList + } + return null + } + + async getTitle() { + const p = await this.getTitleHarness() + if (p) { + const titleContent = await p.getText() + return titleContent + } + return null + } + + async getTitleHarness() { + const pHarness = await this.locatorForOptional(PHarness.with({ id: 'ocxContentTitleElement' }))() + return pHarness + } + + async hasTitle(): Promise { + const title = await this.getTitleHarness() + return !!title + } +} diff --git a/libs/portal-integration-angular/testing/p.harness.ts b/libs/portal-integration-angular/testing/p.harness.ts new file mode 100644 index 00000000..fbe7fc91 --- /dev/null +++ b/libs/portal-integration-angular/testing/p.harness.ts @@ -0,0 +1,41 @@ +import { BaseHarnessFilters, ComponentHarness, HarnessPredicate } from '@angular/cdk/testing' + +export interface PHarnessFilters extends BaseHarnessFilters { + class?: string + id?: string +} + +export class PHarness extends ComponentHarness { + static hostSelector = 'p' + + static with(options: PHarnessFilters): HarnessPredicate { + return new HarnessPredicate(PHarness, options) + .addOption('class', options.class, (harness, c) => HarnessPredicate.stringMatches(harness.getByClass(c), c)) + .addOption('id', options.id, (harness, id) => HarnessPredicate.stringMatches(harness.hasId(id), id)) + } + + async getByClass(c: string): Promise { + return (await (await this.host()).hasClass(c)) ? c : '' + } + + async hasId(id: string): Promise { + return (await (await this.host()).matchesSelector('#' + id)) ? id : '' + } + + async checkHasClass(value: string) { + return await (await this.host()).hasClass(value) + } + + async getText(): Promise { + return await (await this.host()).text() + } + + async getClassList() { + const host = await this.host() + const attributeString = await host.getAttribute('class') + if (attributeString) { + return attributeString.trim().split(' ') + } + return [] + } +}