Saltar al contenido principal
Neural Nativev0.1.0-beta.1Documentación

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().

component.test.ts
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.

terminal
npm install --save-dev @ng-native/testing@0.18.2 vitest@5 @vitest/coverage-v8@5

Crea 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.

vitest.config.mts
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.

test/setup-motion.mjs
// 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.

src/button.test.ts
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.

src/input.test.ts
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.

src/profile-form.test.ts
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.

src/modal.test.ts
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

terminal
# 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 paths

El 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.

Guía de testing de Angular Native ↗