Skip to content

Commit

Permalink
ListOptionGroup integration to Angular/Blazor (#2161)
Browse files Browse the repository at this point in the history
# Pull Request

## 🀨 Rationale

- #791

## πŸ‘©β€πŸ’» Implementation

Standard integration implementations for Angular/Blazor.

## πŸ§ͺ Testing

Standard unit tests for Angular/Blazor. Updated both example apps.

## βœ… Checklist

<!--- Review the list and put an x in the boxes that apply or ~~strike
through~~ around items that don't (along with an explanation). -->

- [ ] I have updated the project documentation to reflect my changes or
determined no changes are needed.

---------

Co-authored-by: Milan Raj <rajsite@users.noreply.github.com>
  • Loading branch information
atmgrifter00 and rajsite authored Jun 6, 2024
1 parent 87422aa commit bcc1362
Show file tree
Hide file tree
Showing 13 changed files with 417 additions and 19 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Angular integration for ListOptionGroup",
"packageName": "@ni/nimble-angular",
"email": "26874831+atmgrifter00@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Blazor integration for ListOptionGroup",
"packageName": "@ni/nimble-blazor",
"email": "26874831+atmgrifter00@users.noreply.github.com",
"dependentChangeType": "patch"
}
1 change: 1 addition & 0 deletions packages/angular-workspace/.eslintrc.js
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ module.exports = {
'action-menu-label',
'aria-label',
'button-label',
'label',
'placeholder',
'text',
'title'
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,8 @@ import { NimbleTextAreaModule, NimbleTextFieldModule, NimbleNumberFieldModule, N
NimbleIconAddModule, NimbleSwitchModule, NimbleToolbarModule, NimbleMenuButtonModule, NimbleComboboxModule, NimbleTooltipModule,
NimbleCardButtonModule, NimbleDialogModule, NimbleRadioGroupModule, NimbleRadioModule, NimbleSpinnerModule,
NimbleAnchorModule, NimbleAnchorButtonModule, NimbleAnchorTabModule, NimbleAnchorTabsModule,
NimbleIconCheckModule, NimbleBannerModule, NimbleAnchorMenuItemModule, NimbleAnchorTreeItemModule, NimbleIconXmarkCheckModule } from '@ni/nimble-angular';
NimbleIconCheckModule, NimbleBannerModule, NimbleAnchorMenuItemModule, NimbleAnchorTreeItemModule, NimbleIconXmarkCheckModule,
NimbleListOptionGroupModule } from '@ni/nimble-angular';
import { NimbleCardModule } from '@ni/nimble-angular/card';
import { NimbleLabelProviderCoreModule } from '@ni/nimble-angular/label-provider/core';
import { NimbleLabelProviderRichTextModule } from '@ni/nimble-angular/label-provider/rich-text';
Expand Down Expand Up @@ -53,6 +54,7 @@ import { HeaderComponent } from './header/header.component';
NimbleNumberFieldModule,
NimbleSelectModule,
NimbleListOptionModule,
NimbleListOptionGroupModule,
NimbleButtonModule,
NimbleTreeViewModule,
NimbleTreeItemModule,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -152,21 +152,36 @@
<div class="container-label">Select</div>
<nimble-select filter-mode="standard" appearance="underline">
<nimble-list-option hidden selected disabled>Select an option</nimble-list-option>
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option-group label="Group 1">
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
</nimble-list-option-group>
<nimble-list-option-group label="Group 2">
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option>Option 4</nimble-list-option>
</nimble-list-option-group>
</nimble-select>
<nimble-select appearance="outline">
<nimble-list-option hidden selected disabled>Select an option</nimble-list-option>
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option-group label="Group 1">
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
</nimble-list-option-group>
<nimble-list-option-group label="Group 2">
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option>Option 4</nimble-list-option>
</nimble-list-option-group>
</nimble-select>
<nimble-select appearance="block">
<nimble-list-option hidden selected disabled>Select an option</nimble-list-option>
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option-group label="Group 1">
<nimble-list-option>Option 1</nimble-list-option>
<nimble-list-option>Option 2</nimble-list-option>
</nimble-list-option-group>
<nimble-list-option-group label="Group 2">
<nimble-list-option>Option 3</nimble-list-option>
<nimble-list-option>Option 4</nimble-list-option>
</nimble-list-option-group>
</nimble-select>
</div>
<div class="sub-container">
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Directive, Input, ElementRef, Renderer2 } from '@angular/core';
import { type BooleanValueOrAttribute, toBooleanProperty } from '@ni/nimble-angular/internal-utilities';
import type { ListOptionGroup, listOptionGroupTag } from '@ni/nimble-components/dist/esm/list-option-group';

export type { ListOptionGroup };
export { listOptionGroupTag };

/**
* Directive to provide Angular integration for the list option group.
*/
@Directive({
selector: 'nimble-list-option-group'
})
export class NimbleListOptionGroupDirective {
public get label(): string | undefined {
return this.elementRef.nativeElement.label;
}

@Input() public set label(value: string | undefined) {
this.renderer.setProperty(this.elementRef.nativeElement, 'label', value);
}

public get hidden(): boolean {
return this.elementRef.nativeElement.hidden;
}

@Input() public set hidden(value: BooleanValueOrAttribute) {
this.renderer.setProperty(this.elementRef.nativeElement, 'hidden', toBooleanProperty(value));
}

public constructor(
private readonly elementRef: ElementRef<ListOptionGroup>,
private readonly renderer: Renderer2,
) { }
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { NgModule } from '@angular/core';
import { CommonModule } from '@angular/common';
import { NimbleListOptionGroupDirective } from './nimble-list-option-group.directive';

import '@ni/nimble-components/dist/esm/list-option';

@NgModule({
declarations: [
NimbleListOptionGroupDirective
],
imports: [CommonModule],
exports: [
NimbleListOptionGroupDirective
]
})
export class NimbleListOptionGroupModule { }
Original file line number Diff line number Diff line change
@@ -0,0 +1,207 @@
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { Component, ElementRef, ViewChild } from '@angular/core';
import type { BooleanValueOrAttribute } from '@ni/nimble-angular/internal-utilities';
import { NimbleListOptionGroupModule } from '../nimble-list-option-group.module';
import type { ListOptionGroup } from '../../../public-api';
import { NimbleListOptionGroupDirective } from '../nimble-list-option-group.directive';

describe('Nimble listbox option group', () => {
describe('module', () => {
beforeEach(() => {
TestBed.configureTestingModule({
imports: [NimbleListOptionGroupModule]
});
});

it('custom element is defined', () => {
expect(customElements.get('nimble-list-option-group')).not.toBeUndefined();
});
});

describe('with no values in template', () => {
@Component({
template: `
<nimble-list-option-group #listOptionGroup></nimble-list-option-group>
`
})
class TestHostComponent {
@ViewChild('listOptionGroup', { read: NimbleListOptionGroupDirective }) public directive: NimbleListOptionGroupDirective;
@ViewChild('listOptionGroup', { read: ElementRef }) public elementRef: ElementRef<ListOptionGroup>;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleListOptionGroupDirective;
let nativeElement: ListOptionGroup;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleListOptionGroupModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('has expected defaults for label', () => {
expect(directive.label).toBeUndefined();
expect(nativeElement.label).toBeUndefined();
});

it('has expected defaults for hidden', () => {
expect(directive.hidden).toBeFalse();
expect(nativeElement.hidden).toBeFalse();
});
});

describe('with template string values', () => {
@Component({
template: `
<nimble-list-option-group #listOptionGroup
hidden
label="foo"
></nimble-list-option-group>
`
})
class TestHostComponent {
@ViewChild('listOptionGroup', { read: NimbleListOptionGroupDirective }) public directive: NimbleListOptionGroupDirective;
@ViewChild('listOptionGroup', { read: ElementRef }) public elementRef: ElementRef<ListOptionGroup>;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleListOptionGroupDirective;
let nativeElement: ListOptionGroup;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleListOptionGroupModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('will use template string values for label', () => {
expect(directive.label).toBe('foo');
expect(nativeElement.label).toBe('foo');
});

it('will use template string values for hidden', () => {
expect(directive.hidden).toBeTrue();
expect(nativeElement.hidden).toBeTrue();
});
});

describe('with property bound values', () => {
@Component({
template: `
<nimble-list-option-group #listOptionGroup
[label]="label"
[hidden]="hidden"
></nimble-list-option-group>
`
})
class TestHostComponent {
@ViewChild('listOptionGroup', { read: NimbleListOptionGroupDirective }) public directive: NimbleListOptionGroupDirective;
@ViewChild('listOptionGroup', { read: ElementRef }) public elementRef: ElementRef<ListOptionGroup>;

public label = '';
public hidden = false;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleListOptionGroupDirective;
let nativeElement: ListOptionGroup;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleListOptionGroupModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('can be configured with property binding for label', () => {
expect(directive.label).toBe('');
expect(nativeElement.label).toBe('');

fixture.componentInstance.label = 'foo';
fixture.detectChanges();

expect(directive.label).toBe('foo');
expect(nativeElement.label).toBe('foo');
});

it('can be configured with property binding for hidden', () => {
expect(directive.hidden).toBeFalse();
expect(nativeElement.hidden).toBeFalse();

fixture.componentInstance.hidden = true;
fixture.detectChanges();

expect(directive.hidden).toBeTrue();
expect(nativeElement.hidden).toBeTrue();
});
});

describe('with property attribute values', () => {
@Component({
template: `
<nimble-list-option-group #listOptionGroup
[attr.label]="label"
[attr.hidden]="hidden">
</nimble-list-option-group>
`
})
class TestHostComponent {
@ViewChild('listOptionGroup', { read: NimbleListOptionGroupDirective }) public directive: NimbleListOptionGroupDirective;
@ViewChild('listOptionGroup', { read: ElementRef }) public elementRef: ElementRef<ListOptionGroup>;

public label = 'foo';
public hidden: BooleanValueOrAttribute = null;
}

let fixture: ComponentFixture<TestHostComponent>;
let directive: NimbleListOptionGroupDirective;
let nativeElement: ListOptionGroup;

beforeEach(() => {
TestBed.configureTestingModule({
declarations: [TestHostComponent],
imports: [NimbleListOptionGroupModule]
});
fixture = TestBed.createComponent(TestHostComponent);
fixture.detectChanges();
directive = fixture.componentInstance.directive;
nativeElement = fixture.componentInstance.elementRef.nativeElement;
});

it('can be configured with attribute binding for label', () => {
expect(directive.label).toBe('foo');
expect(nativeElement.label).toBe('foo');

fixture.componentInstance.label = 'bar';
fixture.detectChanges();

expect(directive.label).toBe('bar');
expect(nativeElement.label).toBe('bar');
});

it('can be configured with attribute binding for hidden', () => {
expect(directive.hidden).toBeFalse();
expect(nativeElement.hidden).toBeFalse();

fixture.componentInstance.hidden = '';
fixture.detectChanges();

expect(directive.hidden).toBeTrue();
expect(nativeElement.hidden).toBeTrue();
});
});
});
2 changes: 2 additions & 0 deletions packages/angular-workspace/nimble-angular/src/public-api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@ export * from './directives/list-option/nimble-combobox-list-option.directive';
export * from './directives/list-option/nimble-list-option.directive';
export * from './directives/list-option/nimble-select-list-option.directive';
export * from './directives/list-option/nimble-list-option.module';
export * from './directives/list-option-group/nimble-list-option-group.directive';
export * from './directives/list-option-group/nimble-list-option-group.module';
export * from './directives/menu/nimble-menu.directive';
export * from './directives/menu/nimble-menu.module';
export * from './directives/menu-button/nimble-menu-button.directive';
Expand Down
Loading

0 comments on commit bcc1362

Please sign in to comment.