From 7f2511a35d0f57942d0c15d437190427d2df641e Mon Sep 17 00:00:00 2001 From: Kevin Buhmann Date: Thu, 23 Jan 2025 09:47:35 -0600 Subject: [PATCH] fix(form-label): prevent id attribute from being removed (#1681) --- .../angular/src/forms/common/label.spec.ts | 60 +++++++++++++++++++ projects/angular/src/forms/common/label.ts | 4 ++ 2 files changed, 64 insertions(+) diff --git a/projects/angular/src/forms/common/label.spec.ts b/projects/angular/src/forms/common/label.spec.ts index c2c7879b01..f759074d5f 100644 --- a/projects/angular/src/forms/common/label.spec.ts +++ b/projects/angular/src/forms/common/label.spec.ts @@ -11,6 +11,7 @@ import { By } from '@angular/platform-browser'; import { ClrIconModule } from '../../icon/icon.module'; import { ClrSignpostModule, ClrSignpostTrigger } from '../../popover'; +import { ClrInput } from '../input/input'; import { ClrInputContainer } from '../input/input-container'; import { ClrLabel } from './label'; import { ControlIdService } from './providers/control-id.service'; @@ -72,6 +73,31 @@ class SignpostTest { }) class DefaultClickBehaviorTest {} +@Component({ + template: ``, +}) +class ExplicitIdTest {} + +@Component({ + template: ` + + + + + `, +}) +class ControlIdTest {} + +@Component({ + template: ` + + + + + `, +}) +class ExplicitControlIdTest {} + export default function (): void { describe('ClrLabel', () => { it("doesn't crash if it is not used in an Angular form", function () { @@ -201,6 +227,40 @@ export default function (): void { expect(label.getAttribute('for')).toBe('updatedFor'); }); + it('leaves the id attribute untouched if it exists (with control id service)', function () { + TestBed.configureTestingModule({ declarations: [ClrLabel, ExplicitIdTest], providers: [ControlIdService] }); + const fixture = TestBed.createComponent(ExplicitIdTest); + fixture.detectChanges(); + const label = fixture.nativeElement.querySelector('label'); + expect(label.getAttribute('id')).toBe('explicit-label'); + }); + + it('leaves the id attribute untouched if it exists (without control id service)', function () { + TestBed.configureTestingModule({ declarations: [ClrLabel, ExplicitIdTest] }); + const fixture = TestBed.createComponent(ExplicitIdTest); + fixture.detectChanges(); + const label = fixture.nativeElement.querySelector('label'); + expect(label.getAttribute('id')).toBe('explicit-label'); + }); + + it('uses the control id when present', function () { + TestBed.configureTestingModule({ declarations: [ClrLabel, ClrInputContainer, ClrInput, ControlIdTest] }); + const fixture = TestBed.createComponent(ControlIdTest); + fixture.detectChanges(); + const input = fixture.nativeElement.querySelector('input'); + const label = fixture.nativeElement.querySelector('label'); + expect(label.getAttribute('id')).toBe(`${input.id}-label`); + }); + + it('uses an explicit control id when present', function () { + TestBed.configureTestingModule({ declarations: [ClrLabel, ClrInputContainer, ClrInput, ExplicitControlIdTest] }); + const fixture = TestBed.createComponent(ExplicitControlIdTest); + fixture.detectChanges(); + const label = fixture.nativeElement.querySelector('label'); + expect(label.id).toBe('explicit-control-label'); + expect(label.getAttribute('for')).toBe('explicit-control'); + }); + it('signposts work inside labels', function () { TestBed.configureTestingModule({ imports: [ClrSignpostModule, ClrIconModule], diff --git a/projects/angular/src/forms/common/label.ts b/projects/angular/src/forms/common/label.ts index 8eea8ede14..4bf23d028a 100644 --- a/projects/angular/src/forms/common/label.ts +++ b/projects/angular/src/forms/common/label.ts @@ -50,6 +50,10 @@ export class ClrLabel implements OnInit, OnDestroy { } ngOnInit() { + // Prevent id attributes from being removed by the `undefined` host binding. + // This happens when a `label` is used outside of a control container and other use cases. + this.idAttr = this.idInput; + // Only add the clr-control-label if it is inside a control container if (this.controlIdService || this.ngControlService) { this.renderer.addClass(this.el.nativeElement, 'clr-control-label');