NEURALNG
GUIDE

Testing NeuralNg

Test the behavior your users observe: values, events, roles, keyboard flows, focus and visible state—not NeuralNg’s private DOM arrangement.

Angular TestBedFormsBrowser accessibility

CONSUMER TESTS

Assert the public contract

A useful application test asks whether a person can operate the control and whether your application receives the expected value. Internal class names, generated IDs and exact wrapper counts are not stable selectors unless the component explicitly documents them as public class slots.

Import the same component your application uses

Create a small host component when the behavior spans template binding and application state. Standalone imports keep the test setup identical to production usage.

save-action.spec.ts TypeScript
import { Component, signal } from '@angular/core';
import { TestBed } from '@angular/core/testing';
import { NeuralButton } from '@neural-ng/core/button';

@Component({
  imports: [NeuralButton],
  template: `
    <neural-button label="Save" (clicked)="saved.set(true)" />
  `,
})
class Host {
  readonly saved = signal(false);
}

it('runs the consumer action', async () => {
  const fixture = TestBed.createComponent(Host);
  fixture.detectChanges();

  const button = fixture.nativeElement.querySelector('button');
  button.click();

  expect(fixture.componentInstance.saved()).toBe(true);
});

Read models after user interaction

Trigger the native interaction, then assert the bound Signal or semantic output. Programmatically assigning a model tests application state; clicking, typing or pressing a key tests the user path. Keep both when both contracts matter.

Test through the selected Forms API

For Reactive Forms, assert the FormControl value, disabled state, touched state and validation. Apply the same principle to template-driven and Signal Forms: inspect the public form model instead of reaching into the component instance.

email-field.spec.ts TypeScript
@Component({
  imports: [ReactiveFormsModule, NeuralInput],
  template: `<input neuralInput [formControl]="email" />`,
})
class Host {
  readonly email = new FormControl('', { nonNullable: true });
}

it('keeps the form model in sync', () => {
  const fixture = TestBed.createComponent(Host);
  fixture.detectChanges();
  const input = fixture.nativeElement.querySelector('input');

  input.value = '[email protected]';
  input.dispatchEvent(new Event('input'));

  expect(fixture.componentInstance.email.value).toBe('[email protected]');
});

Dispatch real keyboard events

Composite controls require more than click coverage. Test opening, roving focus, selection, Escape dismissal and focus restoration with the documented key contract. Assert roles and ARIA state rather than a visual highlight class.

select-keyboard.spec.ts TypeScript
const trigger = fixture.nativeElement.querySelector('[role="combobox"]');
trigger.dispatchEvent(new KeyboardEvent('keydown', { key: 'ArrowDown' }));
fixture.detectChanges();

expect(trigger.getAttribute('aria-expanded')).toBe('true');
expect(document.querySelector('[role="listbox"]')).not.toBeNull();

Query appended overlays from document.body

Panels using appendTo="body" are outside the fixture root by design. Query the document by role, close or destroy the host after each test and avoid sharing an open overlay between cases.

overlay.spec.ts TypeScript
import { ComponentFixture, TestBed } from '@angular/core/testing';

let fixture: ComponentFixture<SelectHost>;

afterEach(() => fixture?.destroy());

it('opens an accessible listbox', () => {
  fixture = TestBed.createComponent(SelectHost);
  fixture.detectChanges();

  fixture.nativeElement.querySelector('[role="combobox"]').click();
  fixture.detectChanges();

  const panel = document.body.querySelector('[role="listbox"]');
  expect(panel).not.toBeNull();
});

Control asynchronous time deliberately

Debounce

Advance the configured delay and verify stale remote results do not replace the latest query.

Loading

Assert busy semantics, disabled interaction and the final settled state.

Toast lifetime

Use controlled timers for dismissal and keep persistent messages explicit.

Animations

Wait for the semantic state or reduced-motion path, not an arbitrary sleep.

Combine automation with keyboard inspection

Automated rules catch missing names, invalid relationships and many contrast issues, but they do not prove that a date grid, tree or dialog feels correct with a keyboard and screen reader.

checkout.e2e.ts TypeScript
import AxeBuilder from '@axe-core/playwright';

test('checkout has no detectable accessibility violations', async ({ page }) => {
  await page.goto('/checkout');
  const results = await new AxeBuilder({ page }).analyze();
  expect(results.violations).toEqual([]);
});

Exercise the production rendering path

For SSR applications, test at least one production-rendered route with JavaScript delayed or disabled, then hydrate it and operate the control. This catches browser-global access and server/client identity mismatches that a DOM-only unit test cannot see.

Selector priority

1Accessible role and namebutton named Save, combobox named Country
2Visible label or stable application test IDUse a test ID you own when roles are ambiguous
3Documented structural hookOnly when the public component contract exposes it
AvoidGenerated IDs and private classesThey may change without changing user behavior