import { Component, signal } from '@angular/core';
import { ComponentFixture, TestBed } from '@angular/core/testing';
import { FormControl, ReactiveFormsModule } from '@angular/forms';
import { TextInputComponent, type TextInputType } from './text-input.component';
@Component({
standalone: true,
imports: [TextInputComponent, ReactiveFormsModule],
template: `
P
S
`,
})
class TextInputHostComponent {
readonly hint = signal('Helpful hint');
readonly error = signal(null);
readonly control = new FormControl('initial', { nonNullable: true });
}
describe('TextInputComponent', () => {
let fixture: ComponentFixture;
let host: TextInputHostComponent;
beforeEach(async () => {
await TestBed.configureTestingModule({
imports: [TextInputHostComponent],
}).compileComponents();
fixture = TestBed.createComponent(TextInputHostComponent);
host = fixture.componentInstance;
fixture.detectChanges();
});
it('should render label, hint, and CVA value', () => {
const label = fixture.nativeElement.querySelector('label');
const input = fixture.nativeElement.querySelector('input');
const hint = fixture.nativeElement.querySelector('p');
expect(label?.textContent?.trim()).toBe('Name');
expect(input?.value).toBe('initial');
expect(hint?.textContent?.trim()).toBe('Helpful hint');
});
it('should replace hint with error and set aria wiring', () => {
host.error.set('Required');
fixture.detectChanges();
const input: HTMLInputElement =
fixture.nativeElement.querySelector('input');
const alert = fixture.nativeElement.querySelector('[role="alert"]');
expect(alert?.textContent?.trim()).toBe('Required');
expect(fixture.nativeElement.textContent).not.toContain('Helpful hint');
expect(input.getAttribute('aria-invalid')).toBe('true');
expect(input.getAttribute('aria-describedby')).toBe(alert.id);
});
it('should propagate user input through ControlValueAccessor', () => {
const input: HTMLInputElement =
fixture.nativeElement.querySelector('input');
input.value = 'Ada';
input.dispatchEvent(new Event('input'));
fixture.detectChanges();
expect(host.control.value).toBe('Ada');
});
it('should project prefix and suffix slots', () => {
const prefix = fixture.nativeElement.querySelector('[data-slot="prefix"]');
const suffix = fixture.nativeElement.querySelector('[data-slot="suffix"]');
expect(prefix?.textContent?.trim()).toBe('P');
expect(suffix?.textContent?.trim()).toBe('S');
});
it('should default to type=text and omit empty autocomplete', () => {
const input: HTMLInputElement =
fixture.nativeElement.querySelector('input');
expect(input.getAttribute('type')).toBe('text');
expect(input.hasAttribute('autocomplete')).toBe(false);
});
});
@Component({
standalone: true,
imports: [TextInputComponent],
template: `
`,
})
class PasswordInputHostComponent {
readonly type = signal('password');
}
describe('TextInputComponent type/autocomplete', () => {
it('should bind password type and autocomplete', async () => {
await TestBed.configureTestingModule({
imports: [PasswordInputHostComponent],
}).compileComponents();
const fixture = TestBed.createComponent(PasswordInputHostComponent);
fixture.detectChanges();
const input: HTMLInputElement =
fixture.nativeElement.querySelector('input');
expect(input.getAttribute('type')).toBe('password');
expect(input.getAttribute('autocomplete')).toBe('current-password');
fixture.componentInstance.type.set('text');
fixture.detectChanges();
expect(input.getAttribute('type')).toBe('text');
});
});