Skip to content

Commit

Permalink
Support error state on radio group in Angular and Blazor (#2433)
Browse files Browse the repository at this point in the history
# Pull Request

## 🀨 Rationale

Resolves #2019 by adding Angular and Blazor support for `error-text` and
`error-visible` on the `nimble-radio-group`.

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

- Expose error text and error visible on the radio group in Angular and
Blazor
- Update Angular and Blazor example apps to put a label on the radio
group

## πŸ§ͺ Testing

- Updated unit tests for the components
- Manually verified that error text and error visible can be configured
through both Angular and Blazor

## βœ… 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
mollykreis and rajsite authored Oct 16, 2024
1 parent aa1b0ca commit 280d85a
Show file tree
Hide file tree
Showing 9 changed files with 154 additions and 3 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Expose error state on radio-group",
"packageName": "@ni/nimble-angular",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
{
"type": "minor",
"comment": "Expose error state on radio-group",
"packageName": "@ni/nimble-blazor",
"email": "20542556+mollykreis@users.noreply.github.com",
"dependentChangeType": "patch"
}
Original file line number Diff line number Diff line change
Expand Up @@ -74,6 +74,7 @@
<div class="sub-container">
<div class="container-label">Radio Buttons</div>
<nimble-radio-group>
<span slot="label">Fruit</span>
<nimble-radio name="fruit" value="apple" [(ngModel)]="selectedRadio">Apple</nimble-radio>
<nimble-radio name="fruit" value="banana" [(ngModel)]="selectedRadio">Banana</nimble-radio>
<nimble-radio name="fruit" value="mango" [(ngModel)]="selectedRadio">Mango</nimble-radio>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,5 +38,21 @@ export class NimbleRadioGroupDirective {
this.renderer.setProperty(this.elementRef.nativeElement, 'orientation', value);
}

public get errorText(): string | undefined {
return this.elementRef.nativeElement.errorText;
}

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

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

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

public constructor(private readonly renderer: Renderer2, private readonly elementRef: ElementRef<RadioGroup>) {}
}
Original file line number Diff line number Diff line change
Expand Up @@ -70,11 +70,28 @@ describe('Nimble radio group', () => {
directive.orientation = Orientation.vertical;
expect(nativeElement.orientation).toBe(Orientation.vertical);
});

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

it('can use the directive to set errorText', () => {
directive.errorText = 'new value';
expect(nativeElement.errorText).toBe('new value');
});
});

describe('with template string values', () => {
@Component({
template: '<nimble-radio-group #radioGroup disabled name="foo" orientation="vertical"></nimble-radio-group>'
template: `
<nimble-radio-group #radioGroup
disabled
name="foo"
orientation="vertical"
error-text="error text"
error-visible
></nimble-radio-group>`
})
class TestHostComponent {
@ViewChild('radioGroup', { read: NimbleRadioGroupDirective }) public directive: NimbleRadioGroupDirective;
Expand Down Expand Up @@ -110,18 +127,37 @@ describe('Nimble radio group', () => {
expect(directive.orientation).toBe(Orientation.vertical);
expect(nativeElement.orientation).toBe(Orientation.vertical);
});

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

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

describe('with property bound values', () => {
@Component({
template: '<nimble-radio-group #radioGroup [disabled]="disabled" [name]="name" [orientation]="orientation"></nimble-radio-group>'
template: `
<nimble-radio-group #radioGroup
[disabled]="disabled"
[name]="name"
[orientation]="orientation"
[error-text]="errorText"
[error-visible]="errorVisible"
></nimble-radio-group>`
})
class TestHostComponent {
@ViewChild('radioGroup', { read: NimbleRadioGroupDirective }) public directive: NimbleRadioGroupDirective;
@ViewChild('radioGroup', { read: ElementRef }) public elementRef: ElementRef<RadioGroup>;
public disabled = false;
public name = 'foo';
public orientation: Orientation = Orientation.vertical;
public errorText = 'initial value';
public errorVisible = false;
}

let fixture: ComponentFixture<TestHostComponent>;
Expand Down Expand Up @@ -171,18 +207,49 @@ describe('Nimble radio group', () => {
expect(directive.orientation).toBe(Orientation.horizontal);
expect(nativeElement.orientation).toBe(Orientation.horizontal);
});

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

fixture.componentInstance.errorText = 'new value';
fixture.detectChanges();

expect(directive.errorText).toBe('new value');
expect(nativeElement.errorText).toBe('new value');
});

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

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

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

describe('with attribute bound values', () => {
@Component({
template: '<nimble-radio-group #radioGroup [attr.disabled]="disabled" [attr.name]="name" [attr.orientation]="orientation"></nimble-radio-group>'
template: `
<nimble-radio-group #radioGroup
[attr.disabled]="disabled"
[attr.name]="name"
[attr.orientation]="orientation"
[attr.error-text]="errorText"
[attr.error-visible]="errorVisible"
></nimble-radio-group>`
})
class TestHostComponent {
@ViewChild('radioGroup', { read: NimbleRadioGroupDirective }) public directive: NimbleRadioGroupDirective;
@ViewChild('radioGroup', { read: ElementRef }) public elementRef: ElementRef<RadioGroup>;
public disabled: BooleanValueOrAttribute = null;
public name = 'foo';
public orientation: Orientation = Orientation.vertical;
public errorText = 'initial value';
public errorVisible: BooleanValueOrAttribute = null;
}

let fixture: ComponentFixture<TestHostComponent>;
Expand Down Expand Up @@ -232,5 +299,27 @@ describe('Nimble radio group', () => {
expect(directive.orientation).toBe(Orientation.horizontal);
expect(nativeElement.orientation).toBe(Orientation.horizontal);
});

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

fixture.componentInstance.errorText = 'new value';
fixture.detectChanges();

expect(directive.errorText).toBe('new value');
expect(nativeElement.errorText).toBe('new value');
});

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

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

expect(directive.errorVisible).toBeTrue();
expect(nativeElement.errorVisible).toBeTrue();
});
});
});
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,7 @@
<div class="sub-container">
<div class="container-label">Radio Buttons</div>
<NimbleRadioGroup Name="options" @bind-Value="@SelectedRadio">
<span slot="label">Options</span>
<NimbleRadio Value="1">Option 1</NimbleRadio>
<NimbleRadio Value="2">Option 2</NimbleRadio>
<NimbleRadio Value="3">Option 3</NimbleRadio>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@
<nimble-radio-group disabled=@Disabled
name="@Name"
orientation="@Orientation.ToAttributeValue()"
error-visible="@ErrorVisible"
error-text="@ErrorText"
value="@Value"
@onchange="@(EventCallback.Factory.CreateBinder<string?>(this, __value => CurrentValue = __value, CurrentValue))"
@attributes="AdditionalAttributes">
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,18 @@ public partial class NimbleRadioGroup : NimbleInputBase<string>
[Parameter]
public Orientation? Orientation { get; set; }

/// <summary>
/// Gets or sets whether the error state is displayed
/// </summary>
[Parameter]
public bool? ErrorVisible { get; set; }

/// <summary>
/// Gets or sets an error message describing the error state
/// </summary>
[Parameter]
public string? ErrorText { get; set; }

/// <summary>
/// Gets or sets the child content to be rendered inside the <see cref="NimbleRadioGroup"/>.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,22 @@ public void NimbleRadioGroupName_AttributeIsSet()
Assert.Contains("name", radioGroup.Markup);
}

[Fact]
public void NimbleRadioGroupErrorText_AttributeIsSet()
{
var radioGroup = RenderNimbleRadioGroupWithPropertySet(x => x.ErrorText, "bad value");

Assert.Contains("error-text=\"bad value\"", radioGroup.Markup);
}

[Fact]
public void NimbleRadioGroupErrorVisible_AttributeIsSet()
{
var radioGroup = RenderNimbleRadioGroupWithPropertySet(x => x.ErrorVisible, true);

Assert.Contains("error-visible", radioGroup.Markup);
}

private IRenderedComponent<NimbleRadioGroup> RenderNimbleRadioGroupWithPropertySet<TProperty>(Expression<Func<NimbleRadioGroup, TProperty>> propertyGetter, TProperty propertyValue)
{
var context = new TestContext();
Expand Down

0 comments on commit 280d85a

Please sign in to comment.