Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 18 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -157,7 +157,7 @@ Angular Testing Library also supports Angular's native bindings API, which provi

```typescript
import { render, screen } from '@testing-library/angular';
import { inputBinding, outputBinding } from '@angular/core';
import { inputBinding, outputBinding, twoWayBinding, signal } from '@angular/core';
import { CounterComponent } from './counter.component';

describe('Counter with Bindings API', () => {
Expand All @@ -182,6 +182,23 @@ describe('Counter with Bindings API', () => {

expect(clickHandler).toHaveBeenCalledWith(1);
});

it('should handle two-way binding with signals', async () => {
const counterSignal = signal(0);

await render(CounterComponent, {
bindings: [twoWayBinding('counter', counterSignal)],
});

expect(screen.getByText('Current Count: 0')).toBeVisible();

const incrementButton = screen.getByRole('button', { name: '+' });
fireEvent.click(incrementButton);

// Two-way binding updates the external signal
expect(counterSignal()).toBe(1);
expect(screen.getByText('Current Count: 1')).toBeVisible();
});
});
```

Expand Down
6 changes: 4 additions & 2 deletions projects/testing-library/src/lib/models.ts
Original file line number Diff line number Diff line change
Expand Up @@ -317,12 +317,14 @@ export interface RenderComponentOptions<ComponentType, Q extends Queries = typeo
* []
*
* @example
* import { inputBinding, outputBinding } from '@angular/core';
* import { inputBinding, outputBinding, twoWayBinding } from '@angular/core';
* import { signal } from '@angular/core';
*
* await render(AppComponent, {
* bindings: [
* inputBinding('value', () => 'test value'),
* outputBinding('click', (event) => console.log(event))
* outputBinding('click', (event) => console.log(event)),
* twoWayBinding('name', signal('initial value'))
* ]
* })
*/
Expand Down
62 changes: 61 additions & 1 deletion projects/testing-library/tests/bindings-support.spec.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Component, input, output, inputBinding, outputBinding } from '@angular/core';
import { Component, input, output, inputBinding, outputBinding, twoWayBinding, signal, model } from '@angular/core';
import { render, screen, aliasedInput } from '../src/public_api';

describe('ATL Bindings API Support', () => {
Expand All @@ -17,6 +17,23 @@ describe('ATL Bindings API Support', () => {
clicked = output<string>();
}

@Component({
selector: 'atl-two-way-test',
template: `
<div data-testid="name-display">{{ name() }}</div>
<input data-testid="name-input" [value]="name()" (input)="name.set($any($event.target).value)" />
<button data-testid="update-button" (click)="updateName()">Update</button>
`,
standalone: true,
})
class TwoWayBindingTestComponent {
name = model<string>('default');

updateName() {
this.name.set('updated from component');
}
}

it('should support inputBinding for regular inputs', async () => {
await render(BindingsTestComponent, {
bindings: [inputBinding('value', () => 'test-value'), inputBinding('greet', () => 'hi there')],
Expand All @@ -39,6 +56,49 @@ describe('ATL Bindings API Support', () => {
expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
});

it('should support inputBinding with writable signal for re-rendering scenario', async () => {
const valueSignal = signal('initial-value');

await render(BindingsTestComponent, {
bindings: [inputBinding('value', valueSignal), inputBinding('greet', () => 'hi there')],
});

expect(screen.getByTestId('value')).toHaveTextContent('initial-value');
expect(screen.getByTestId('greeting')).toHaveTextContent('hi there');

// Update the signal and verify it reflects in the component
valueSignal.set('updated-value');

// The binding should automatically update the component
expect(await screen.findByText('updated-value')).toBeInTheDocument();
});

it('should support twoWayBinding for model signals', async () => {
const nameSignal = signal('initial name');

await render(TwoWayBindingTestComponent, {
bindings: [twoWayBinding('name', nameSignal)],
});

// Verify initial value
expect(screen.getByTestId('name-display')).toHaveTextContent('initial name');
expect(screen.getByTestId('name-input')).toHaveValue('initial name');

// Update from outside (signal change)
nameSignal.set('updated from signal');
expect(await screen.findByDisplayValue('updated from signal')).toBeInTheDocument();
expect(screen.getByTestId('name-display')).toHaveTextContent('updated from signal');

// Update from component - let's trigger change detection after the click
const updateButton = screen.getByTestId('update-button');
updateButton.click();

// Give Angular a chance to process the update and check both the signal and display
// The twoWayBinding should update the external signal
expect(await screen.findByText('updated from component')).toBeInTheDocument();
expect(nameSignal()).toBe('updated from component');
});

it('should warn when mixing bindings with traditional inputs but still work', async () => {
const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
const clickHandler = jest.fn();
Expand Down
Loading