Calidad
Testing
Prueba los componentes de Neural Native mediante su API pública.
Herramientas e imports públicos
Estos ejemplos utilizan NeuButton y NeuInput para probar acciones, estados bloqueados y cambios de texto. Localiza los controles por su rol y nombre accesibles y libera cada montaje con cleanup().
import { cleanup, fireEvent, render, screen, userEvent } from '@ng-native/testing';
import { afterEach, expect, test, vi } from 'vitest';
import { NeuButtonComponent } from '@neural-ui/native/button';
import { NeuInputComponent } from '@neural-ui/native/input';
afterEach(cleanup);Configura el entorno de pruebas
Instala estas dependencias en tu aplicación Angular Native 0.18.2. Mantén testing en la misma versión del framework, y Vitest en la misma versión que su paquete de cobertura. No necesitas cambiar app.config.ts.
npm install --save-dev @ng-native/testing@0.18.2 vitest@5 @vitest/coverage-v8@5Crea vitest.config.mts en la raíz del proyecto. El plugin compila los componentes importados de Neural Native; el alias de animaciones utiliza el grafo del framework disponible en Node.
import { ngNative } from '@ng-native/testing/vitest';
import { defineConfig } from 'vitest/config';
import { createRequire } from 'node:module';
import path from 'node:path';
const require = createRequire(import.meta.url);
const animations = path.join(
path.dirname(require.resolve('@ng-native/components/package.json')),
'dist/animations-web.js',
);
export default defineConfig({
plugins: [ngNative({ inline: ['@neural-ui/native'] })],
resolve: {
alias: [{ find: /^@ng-native\/components\/animations$/, replacement: animations }],
},
test: {
include: ['src/**/*.test.ts'],
setupFiles: ['./test/setup-motion.mjs'],
coverage: {
provider: 'v8',
include: ['src/**/*.ts'],
exclude: ['src/**/*.test.ts', 'src/**/*.d.ts'],
reporter: ['text', 'html', 'json-summary'],
thresholds: { lines: 95, statements: 95, functions: 95, branches: 95 },
},
},
});El entorno de pruebas predeterminado expone las APIs Intl de Hermes. Collator, DateTimeFormat y NumberFormat siguen disponibles; APIs como Segmenter y RelativeTimeFormat requieren un polyfill de la aplicación. Cárgalo también en un archivo de setup de Vitest para que las pruebas utilicen el mismo soporte que el dispositivo.
Crea test/setup-motion.mjs con este reloj alternativo. Mantiene los callbacks asíncronos y permite cancelarlos durante la limpieza.
// Node has no display clock. Keep animations asynchronous and cancelable.
globalThis.requestAnimationFrame ??= callback =>
setTimeout(() => callback(performance.now()), 16);
globalThis.cancelAnimationFrame ??= handle => clearTimeout(handle);Prueba NeuButton
Guárdalo como src/button.test.ts. Comprueba que la pulsación emite el output público y que los estados disabled y loading impiden la acción.
import { cleanup, render, screen, userEvent } from '@ng-native/testing';
import { afterEach, expect, test, vi } from 'vitest';
import { NeuButtonComponent } from '@neural-ui/native/button';
afterEach(cleanup);
test('emits the save action once', async () => {
const pressed = vi.fn();
await render(NeuButtonComponent, {
inputs: { label: 'Save changes' },
on: { pressed },
});
await userEvent.setup().press(
screen.getByRole('button', { name: 'Save changes' }),
);
expect(pressed).toHaveBeenCalledOnce();
});
test.each([{ disabled: true }, { loading: true }])(
'does not submit while unavailable: %j',
async state => {
const pressed = vi.fn();
await render(NeuButtonComponent, {
inputs: { label: 'Save changes', ...state },
on: { pressed },
});
await userEvent.setup().press(
screen.getByRole('button', { name: 'Save changes' }),
);
expect(pressed).not.toHaveBeenCalled();
},
);Prueba NeuInput
Guárdalo como src/input.test.ts. La edición debe actualizar el valor y emitir valueChange; el modo readonly debe impedir nuevas modificaciones.
import { cleanup, fireEvent, render, screen } from '@ng-native/testing';
import { afterEach, expect, test, vi } from 'vitest';
import { NeuInputComponent } from '@neural-ui/native/input';
afterEach(cleanup);
test('updates the value model and emits its change', async () => {
const valueChange = vi.fn();
const result = await render(NeuInputComponent, {
inputs: { label: 'Full name' },
on: { valueChange },
});
await fireEvent.changeText(screen.getByLabelText('Full name'), 'Ada');
expect(result.instance.value()).toBe('Ada');
expect(valueChange).toHaveBeenCalledExactlyOnceWith('Ada');
await result.rerender({ inputs: { readonly: true } });
valueChange.mockClear();
await fireEvent.changeText(screen.getByLabelText('Full name'), 'Grace');
expect(result.instance.value()).toBe('Ada');
expect(valueChange).not.toHaveBeenCalled();
});Prueba la validación de formularios
Este test utiliza Signal Forms de Angular con un NeuInput real. Comprueba los valores inválidos, el estado touched, el error visible y si se permite guardar. Utiliza la configuración anterior.
import { Component, computed, signal } from '@angular/core';
import { FormField, email, form, required } from '@angular/forms/signals';
import { NeuButtonComponent } from '@neural-ui/native/button';
import { NeuInputComponent } from '@neural-ui/native/input';
import { cleanup, fireEvent, render, screen, userEvent } from '@ng-native/testing';
import { afterEach, expect, test } from 'vitest';
@Component({
selector: 'app-profile-form-test',
imports: [FormField, NeuInputComponent, NeuButtonComponent],
template: `
<neu-input label="Email" [formField]="fields.email" [error]="error()" />
<neu-button label="Save" [disabled]="fields().invalid()" (pressed)="saved.set(true)" />
`,
})
class ProfileForm {
readonly profile = signal({ email: '' });
readonly fields = form(this.profile, path => {
required(path.email, { message: 'Email is required.' });
email(path.email, { message: 'Enter a valid email.' });
});
readonly saved = signal(false);
readonly error = computed(() => {
const field = this.fields.email();
return field.touched() ? (field.errors()[0]?.message ?? '') : '';
});
}
afterEach(cleanup);
test('validates the field before allowing a save', async () => {
const result = await render(ProfileForm);
const user = userEvent.setup();
const save = screen.getByRole('button', { name: 'Save' });
await user.press(save);
expect(result.instance.saved()).toBe(false);
await fireEvent.changeText(screen.getByLabelText('Email'), 'invalid');
await fireEvent(screen.getByLabelText('Email'), 'blur');
expect(result.instance.fields.email().touched()).toBe(true);
expect(screen.getByRole('alert')).toBeTruthy();
await fireEvent.changeText(screen.getByLabelText('Email'), 'ada@example.com');
expect(result.instance.profile().email).toBe('ada@example.com');
expect(result.instance.fields().valid()).toBe(true);
expect(screen.queryByRole('alert')).toBeNull();
await user.press(screen.getByRole('button', { name: 'Save' }));
expect(result.instance.saved()).toBe(true);
});Espera al estado de un overlay
Utiliza findByRole para un control que aparece al abrir y waitFor para esperar a que desaparezca. Comprueba también el model y el output públicos. Evita esperas fijas; esto comprueba el estado del componente, mientras la apariencia de la animación se revisa en el dispositivo.
import { cleanup, render, screen, userEvent, waitFor } from '@ng-native/testing';
import { afterEach, expect, test, vi } from 'vitest';
import { NeuModalComponent } from '@neural-ui/native/modal';
afterEach(cleanup);
test('closes the modal through its public model and output', async () => {
const closed = vi.fn();
const result = await render(NeuModalComponent, {
inputs: { title: 'Edit profile', open: false, closeLabel: 'Close profile' },
on: { closed },
});
await result.rerender({ inputs: { open: true } });
const close = await screen.findByRole('button', { name: 'Close profile' });
await userEvent.setup().press(close);
expect(result.instance.open()).toBe(false);
expect(closed).toHaveBeenCalledExactlyOnceWith('action');
await waitFor(() => {
expect(screen.queryByRole('button', { name: 'Close profile' })).toBeNull();
});
});Ejecuta tus pruebas
# Run component tests in your Angular Native application
npx vitest run
# Measure your application's source coverage
npx vitest run --coverage
# Open coverage/index.html to inspect uncovered pathsEl informe de cobertura mide el código de tu aplicación. La configuración de ejemplo establece mínimos independientes para líneas, sentencias, funciones y ramas; adáptalos a tu proyecto.
Completa las comprobaciones nativas
El renderer simulado comprueba estado y eventos. Verifica el layout, las animaciones, los gestos, el teclado y los lectores de pantalla en la aplicación instalada en iOS y Android.