From 7e1d8adfd466d1263d08dc40ad67bf241f4b0edf Mon Sep 17 00:00:00 2001 From: David LJ Date: Thu, 28 Sep 2023 05:28:40 +0200 Subject: [PATCH] feat(description): allow only item to be expanded (#33) --- .../description/description.component.html | 2 +- .../description/description.component.spec.ts | 63 ++++++++++++++++++- .../description/description.component.ts | 25 +++++++- 3 files changed, 85 insertions(+), 5 deletions(-) diff --git a/src/app/about/description/description.component.html b/src/app/about/description/description.component.html index d5582be5..3fca9043 100644 --- a/src/app/about/description/description.component.html +++ b/src/app/about/description/description.component.html @@ -17,7 +17,7 @@ diff --git a/src/app/about/description/description.component.spec.ts b/src/app/about/description/description.component.spec.ts index fb4f0730..698a241d 100644 --- a/src/app/about/description/description.component.spec.ts +++ b/src/app/about/description/description.component.spec.ts @@ -7,7 +7,7 @@ import { getComponentSelector } from '../../../test/helpers/component-testers'; import { MATERIAL_SYMBOLS_SELECTOR } from '../../../test/helpers/material-symbols'; import { PLATFORM_BROWSER_ID, PLATFORM_SERVER_ID } from '../../../test/helpers/platform-ids'; import { expectIsHidden, expectIsVisible } from '../../../test/helpers/visibility'; -import { DescriptionLine } from '../../metadata'; +import { DescriptionLine, DescriptionLineData } from '../../metadata'; import { COLLAPSIBLE_CONFIG, CollapsibleConfiguration, DescriptionComponent } from './description.component'; @@ -167,9 +167,9 @@ describe('DescriptionComponent', () => { }) describe('when depth is set to configured depth to start a collapsible', () => { - function configureToBeCollapsible(component: DescriptionComponent) { + function configureToBeCollapsible(component: DescriptionComponent, line?: DescriptionLine) { component.depth = fakeConfig.collapsibleStartAtDepth - component.line = fakeLine + component.line = line ?? fakeLine } describe('data', () => { @@ -292,6 +292,63 @@ describe('DescriptionComponent', () => { testShouldBeCollapsed() }) }) + + describe('when expanding a child item', () => { + const fakeLineWithManyChildren = new DescriptionLine( + new DescriptionLineData({symbol: '', html: 'Root'}), [ + new DescriptionLine(new DescriptionLineData({symbol: '', html: 'Child 1'}), [ + new DescriptionLine(), + ]), + new DescriptionLine(new DescriptionLineData({symbol: '', html: 'Child 2'}), [ + new DescriptionLine(), + ]), + new DescriptionLine(new DescriptionLineData({symbol: '', html: 'Child 3'}), [ + new DescriptionLine(), + ]), + ]) + beforeEach(() => { + [fixture, component] = makeSut() + configureToBeCollapsible(component, fakeLineWithManyChildren) + + fixture.detectChanges() + + listElement = fixture.debugElement.query(LIST_SELECTOR) + }) + it('should collapse sibling items', () => { + const listItemElements = listElement.children + expect(listItemElements.length).toBe(fakeLineWithManyChildren.children.length) + + // Everything is collapsed + listItemElements.forEach((listItemElement, index) => { + const child = fakeLineWithManyChildren.children[index] + const childDataElement = listItemElement.query(DATA_CLASS_SELECTOR) + expect(childDataElement.attributes['aria-expanded']) + .withContext(`item ${child.data?.text} is collapsed`) + .toBe(false.toString()) + }) + + // Expand first item and ensure is expanded + const firstListItemDataElement = listItemElements[0].query(DATA_CLASS_SELECTOR) + firstListItemDataElement.triggerEventHandler('click') + fixture.detectChanges() + expect(firstListItemDataElement.attributes['aria-expanded']) + .withContext('first item should now be expanded') + .toBe(true.toString()) + + // Expand second item + const secondListItemDataElement = listItemElements[1].query(DATA_CLASS_SELECTOR) + secondListItemDataElement.triggerEventHandler('click') + fixture.detectChanges() + expect(secondListItemDataElement.attributes['aria-expanded']) + .withContext('second item should now be expanded') + .toBe(true.toString()) + + // Now first item should be collapsed + expect(firstListItemDataElement.attributes['aria-expanded']) + .withContext('first item should finally be magically collapsed') + .toBe(false.toString()) + }) + }) }) }) }) diff --git a/src/app/about/description/description.component.ts b/src/app/about/description/description.component.ts index 399de995..09f6aa9d 100644 --- a/src/app/about/description/description.component.ts +++ b/src/app/about/description/description.component.ts @@ -1,6 +1,15 @@ import { animate, AUTO_STYLE, state, style, transition, trigger } from '@angular/animations'; import { isPlatformBrowser } from '@angular/common'; -import { Component, HostBinding, Inject, InjectionToken, Input, PLATFORM_ID } from '@angular/core'; +import { + Component, + HostBinding, + Inject, + InjectionToken, + Input, + PLATFORM_ID, + QueryList, + ViewChildren, +} from '@angular/core'; import { DomSanitizer } from '@angular/platform-browser'; import { EMPHASIZED_DURATION_MS, TIMING_FUNCTION } from '../../common/animations'; import { DescriptionLine } from '../../metadata'; @@ -26,6 +35,8 @@ import { DescriptionLine } from '../../metadata'; export class DescriptionComponent { @Input({required: true}) public line!: DescriptionLine @Input() public depth: number = 0 + @Input() protected parent?: DescriptionComponent + // 👇 Using `protected` to avoid being marked as unused @HostBinding('class.visibleIfNoScript') protected visibleIfNoScript = true @HostBinding('class.hidden') protected hidden = true @@ -34,6 +45,9 @@ export class DescriptionComponent { private EXPANDED_DEFAULT_JS_ENABLED = false public isExpanded = this.EXPANDED_DEFAULT_NO_JS + @ViewChildren(DescriptionComponent) + private children!: QueryList + constructor( protected sanitizer: DomSanitizer, @Inject(COLLAPSIBLE_CONFIG) protected config: CollapsibleConfiguration, @@ -68,6 +82,15 @@ export class DescriptionComponent { expand() { this.isExpanded = true + this.parent?.collapseAllChildren({except: this}) + } + + collapseAllChildren({except}: {except?: DescriptionComponent} = {}) { + const childrenToCollapse = this.children + .filter((child) => child !== except) + for(const child of childrenToCollapse) { + child.collapse() + } } }