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

Feature/accordion #28

Merged
merged 3 commits into from
Dec 22, 2024
Merged
Show file tree
Hide file tree
Changes from all 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,6 +1,9 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';

import { NgxPrimerAccordionContentComponent } from './accordion-content.component';
import { NgxPrimerAccordionItemComponent } from '../accordion-item/accordion-item.component';
import { NgxPrimerAccordionRootComponent } from '../accordion-root/accordion-root.component';
import { NgxPrimerIdGeneratorDirective } from '@ngx-primer/primitive/utilities';

describe('NgxPrimerAccordionContentComponent', () => {
let component: NgxPrimerAccordionContentComponent<unknown>;
Expand All @@ -9,6 +12,10 @@ describe('NgxPrimerAccordionContentComponent', () => {
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [NgxPrimerAccordionContentComponent],
declarations: [
NgxPrimerAccordionItemComponent,
NgxPrimerIdGeneratorDirective,
],
}).compileComponents();

fixture = TestBed.createComponent(NgxPrimerAccordionContentComponent);
Expand All @@ -19,4 +26,52 @@ describe('NgxPrimerAccordionContentComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('should have role attribute set to region', () => {
expect(fixture.nativeElement.getAttribute('role')).toBe('region');
});

it('should have data-orientation attribute', () => {
spyOn(
component.accordionRoot as NgxPrimerAccordionRootComponent<unknown>,
'orientation'
).and.returnValue('vertical');
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-orientation')).toBe(
'vertical'
);
});

it('should have data-expanded attribute', () => {
spyOn(
component.accordionItem as NgxPrimerAccordionItemComponent<unknown>,
'isOpen'
).and.returnValue(true);
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-expanded')).toBe('true');
});

it('should have data-is-open attribute', () => {
spyOn(
component.accordionItem as NgxPrimerAccordionItemComponent<unknown>,
'isOpen'
).and.returnValue(true);
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-is-open')).toBe('true');
});

it('should have aria-labelledby attribute', () => {
const mockTriggerId = 'trigger-id';
if (component.accordionItem) {
const accordionItem =
component.accordionItem as NgxPrimerAccordionItemComponent<unknown>;
(
accordionItem.accordionTrigger as { accordionTriggerId: string }
).accordionTriggerId = mockTriggerId;
}
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('aria-labelledby')).toBe(
mockTriggerId
);
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -17,28 +17,20 @@
import { Component, HostBinding, OnInit, inject } from '@angular/core';

import { CommonModule } from '@angular/common';
import { NgxPrimerAccordionContentContext } from '../../contexts/accordion-content/accordion-content.context';
import { NgxPrimerAccordionContentContextDirective } from '../../directives';
import { NgxPrimerAccordionItemComponent } from '../accordion-item/accordion-item.component';
import { NgxPrimerAccordionItemContext } from '../../contexts/accordion-item/accordion-item.context';
import { NgxPrimerAccordionRootComponent } from '../accordion-root/accordion-root.component';
import { NgxPrimerIdGeneratorDirective } from '@ngx-primer/primitive/utilities';

@Component({
selector: 'ngx-primer-accordion-content',
standalone: true,
imports: [CommonModule],
providers: [NgxPrimerAccordionContentContext],
templateUrl: './accordion-content.component.html',
styleUrl: './accordion-content.component.scss',
hostDirectives: [
{
directive: NgxPrimerIdGeneratorDirective,
inputs: ['ngxPrimerIdAttr'],
},
{
directive: NgxPrimerAccordionContentContextDirective,
},
],
})
export class NgxPrimerAccordionContentComponent<T> implements OnInit {
Expand All @@ -50,15 +42,9 @@ export class NgxPrimerAccordionContentComponent<T> implements OnInit {
public readonly accordionContentId = this.idGenerator?.resolvedId;

protected readonly accordionItemContext = inject(
NgxPrimerAccordionItemContext,
{
optional: true,
}
);

protected readonly accordionContentContext = inject(
NgxPrimerAccordionContentContext,
NgxPrimerAccordionItemComponent,
{
host: true,
optional: true,
}
);
Expand All @@ -80,12 +66,17 @@ export class NgxPrimerAccordionContentComponent<T> implements OnInit {

@HostBinding('attr.data-is-open')
public get dataIsOpenAttr() {
return this.accordionItem.isOpen();
return this.accordionItem?.isOpen();
}

@HostBinding('attr.aria-labelledby')
public get dataAriaLabelledByAttr() {
return this.accordionItem?.accordionTrigger()?.accordionTriggerId;
return this.accordionItem?.accordionTrigger?.accordionTriggerId;
}

@HostBinding('attr.data-value')
public get dataValueAttr() {
return this.accordionItem?.value() as T;
}

ngOnInit(): void {
Expand All @@ -95,23 +86,20 @@ export class NgxPrimerAccordionContentComponent<T> implements OnInit {
protected runInitializationFn(doneFn?: <P>(args?: P) => void): void {
if (doneFn) {
doneFn({
context: this.accordionContentContext,
context: this,
});
}
}

protected get accordionContent() {
return this.accordionContentContext
?.instance as NgxPrimerAccordionContentComponent<T>;
public get accordionItem() {
return this.accordionItemContext;
}

protected get accordionItem() {
return this.accordionItemContext
?.instance as NgxPrimerAccordionItemComponent<T>;
public get accordionRoot() {
return this.accordionItem?.accordionRoot;
}

protected get accordionRoot() {
return this.accordionItem?.accordionRootContext
?.instance as NgxPrimerAccordionRootComponent<T>;
public get accordionTrigger() {
return this.accordionItem?.accordionTrigger;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -19,4 +19,55 @@ describe('NgxPrimerAccordionItemComponent', () => {
it('should create', () => {
expect(component).toBeTruthy();
});

it('should have default value as null', () => {
expect(component.value()).toBeNull();
});

it('should have default disabled state as false', () => {
expect(component.disabled()).toBeFalsy();
});

it('should have default isOpen state as false', () => {
expect(component.isOpen()).toBeFalsy();
});

it('should set data-orientation attribute based on accordionRoot orientation', () => {
spyOn(component, 'accordionRoot').and.returnValue({
orientation: () => 'horizontal',
});
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-orientation')).toBe(
'horizontal'
);
});

it('should set data-is-open attribute based on isOpen state', () => {
spyOn(component, 'isOpen').and.returnValue(true);
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-is-open')).toBe('true');
});

it('should set data-value attribute based on value', () => {
component.value.set('testValue');
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-value')).toBe('testValue');
});

it('should set data-disabled attribute based on accordionRoot disabled state', () => {
spyOn(component, 'accordionRoot').and.returnValue({
disabled: () => true,
});
fixture.detectChanges();
expect(fixture.nativeElement.getAttribute('data-disabled')).toBe('true');
});

it('should focus on accordion trigger when focus method is called', () => {
const focusSpy = jasmine.createSpy('focus');
spyOn(component, 'accordionTrigger').and.returnValue({
focus: focusSpy,
});
component.focus();
expect(focusSpy).toHaveBeenCalled();
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -26,18 +26,14 @@ import {

import { CommonModule } from '@angular/common';
import { NgxPrimerAccordionContentComponent } from '../accordion-content/accordion-content.component';
import { NgxPrimerAccordionItemContext } from '../../contexts/accordion-item/accordion-item.context';
import { NgxPrimerAccordionItemContextDirective } from '../../directives';
import { NgxPrimerAccordionRootComponent } from '../accordion-root/accordion-root.component';
import { NgxPrimerAccordionRootContext } from '../../contexts/accordion-root/accordion-root.context';
import { NgxPrimerAccordionTriggerComponent } from '../accordion-trigger/accordion-trigger.component';
import { NgxPrimerIdGeneratorDirective } from '@ngx-primer/primitive/utilities';

@Component({
selector: 'ngx-primer-accordion-item',
standalone: true,
imports: [CommonModule],
providers: [NgxPrimerAccordionItemContext],
templateUrl: './accordion-item.component.html',
styleUrl: './accordion-item.component.scss',
exportAs: 'ngxPrimerAccordionItemComponent',
Expand All @@ -46,9 +42,6 @@ import { NgxPrimerIdGeneratorDirective } from '@ngx-primer/primitive/utilities';
directive: NgxPrimerIdGeneratorDirective,
inputs: ['ngxPrimerIdAttr'],
},
{
directive: NgxPrimerAccordionItemContextDirective,
},
],
})
export class NgxPrimerAccordionItemComponent<T> implements OnInit {
Expand All @@ -59,20 +52,20 @@ export class NgxPrimerAccordionItemComponent<T> implements OnInit {
optional: true,
});

public readonly accordionItemId = this.idGenerator?.resolvedId;

public readonly accordionRootContext = inject(NgxPrimerAccordionRootContext, {
optional: true,
});
protected readonly accordionItemId = this.idGenerator?.resolvedId;

public readonly accordionItemContext = inject(NgxPrimerAccordionItemContext, {
optional: true,
});
protected readonly accordionRootContext = inject(
NgxPrimerAccordionRootComponent,
{
optional: true,
host: true,
}
);

/**
* Accordion content instance.
*/
public readonly accordionContent = contentChild(
protected readonly accordionContentContext = contentChild(
NgxPrimerAccordionContentComponent,
{
descendants: true,
Expand All @@ -83,7 +76,7 @@ export class NgxPrimerAccordionItemComponent<T> implements OnInit {
/**
* Accordion trigger instance.
*/
public readonly accordionTrigger = contentChild(
protected readonly accordionTriggerContext = contentChild(
NgxPrimerAccordionTriggerComponent,
{
descendants: true,
Expand All @@ -102,8 +95,8 @@ export class NgxPrimerAccordionItemComponent<T> implements OnInit {
alias: 'ngxPrimerAccordionItemDisabled',
});

public readonly isOpen = computed<boolean>(() =>
this.accordionRoot?.isOpen(this.value() as T)
public readonly isOpen = computed<boolean>(
() => this.accordionRoot?.isOpen(this.value() as T) ?? false
);

@HostBinding('attr.data-orientation')
Expand Down Expand Up @@ -169,23 +162,25 @@ export class NgxPrimerAccordionItemComponent<T> implements OnInit {
// ensure context being initalized
setTimeout(() =>
doneFn({
context: this.accordionItemContext,
context: this,
})
);
}
}

protected get accordionItem() {
return this.accordionItemContext
?.instance as NgxPrimerAccordionItemComponent<T>;
public get accordionRoot() {
return this.accordionRootContext;
}

public get accordionContent() {
return this.accordionContentContext();
}

protected get accordionRoot() {
return this.accordionItem?.accordionRootContext
?.instance as NgxPrimerAccordionRootComponent<T>;
public get accordionTrigger() {
return this.accordionTriggerContext();
}

focus() {
this.accordionTrigger()?.focus();
public focus() {
this.accordionTrigger?.focus();
}
}
Loading
Loading