From b815a18ec8566caae0d22bd820926c64830a583c Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Sep 2025 16:35:23 +0000
Subject: [PATCH 1/7] Initial plan
From 6a6d3366ec817bcfffd4fcdcfd5bd02856a235a9 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Sep 2025 16:55:22 +0000
Subject: [PATCH 2/7] Add support for Angular v20 bindings API
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
---
 .../app/examples/23-bindings-api.component.ts | 36 ++++++++++++++++
 projects/testing-library/src/lib/models.ts    | 21 ++++++++++
 .../src/lib/testing-library.ts                | 31 +++++++++++---
 projects/testing-library/src/public_api.ts    |  3 ++
 .../tests/bindings-support.spec.ts            | 41 +++++++++++++++++++
 5 files changed, 126 insertions(+), 6 deletions(-)
 create mode 100644 apps/example-app/src/app/examples/23-bindings-api.component.ts
 create mode 100644 projects/testing-library/tests/bindings-support.spec.ts
diff --git a/apps/example-app/src/app/examples/23-bindings-api.component.ts b/apps/example-app/src/app/examples/23-bindings-api.component.ts
new file mode 100644
index 00000000..eb61ebeb
--- /dev/null
+++ b/apps/example-app/src/app/examples/23-bindings-api.component.ts
@@ -0,0 +1,36 @@
+import { Component, computed, input, model, numberAttribute, output } from '@angular/core';
+import { FormsModule } from '@angular/forms';
+
+@Component({
+  selector: 'atl-bindings-api-example',
+  template: `
+    
{{ greetings() }} {{ name() }} of {{ age() }} years old
+    {{ greetingMessage() }}
+    
+    
+    
+    Current age: {{ age() }}
+  `,
+  standalone: true,
+  imports: [FormsModule],
+})
+export class BindingsApiExampleComponent {
+  greetings = input('', {
+    alias: 'greeting',
+  });
+  age = input.required({ transform: numberAttribute });
+  name = model.required();
+  submitValue = output();
+  ageChanged = output();
+
+  greetingMessage = computed(() => `${this.greetings()} ${this.name()} of ${this.age()} years old`);
+
+  submitName() {
+    this.submitValue.emit(this.name());
+  }
+
+  incrementAge() {
+    const newAge = this.age() + 1;
+    this.ageChanged.emit(newAge);
+  }
+}
diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts
index 318bd2be..41ecca2e 100644
--- a/projects/testing-library/src/lib/models.ts
+++ b/projects/testing-library/src/lib/models.ts
@@ -7,6 +7,7 @@ import {
   Provider,
   Signal,
   InputSignalWithTransform,
+  Binding,
 } from '@angular/core';
 import { ComponentFixture, DeferBlockBehavior, DeferBlockState, TestBed } from '@angular/core/testing';
 import { Routes } from '@angular/router';
@@ -307,6 +308,26 @@ export interface RenderComponentOptions;
 
+  /**
+   * @description
+   * An array of bindings to apply to the component using Angular v20+'s native bindings API.
+   * This provides a more direct way to bind inputs and outputs compared to the `inputs` and `on` options.
+   *
+   * @default
+   * []
+   *
+   * @example
+   * import { inputBinding, outputBinding } from '@angular/core';
+   *
+   * await render(AppComponent, {
+   *   bindings: [
+   *     inputBinding('value', () => 'test value'),
+   *     outputBinding('click', (event) => console.log(event))
+   *   ]
+   * })
+   */
+  bindings?: Binding[];
+
   /**
    * @description
    * A collection of providers to inject dependencies of the component.
diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts
index 46677271..9184a449 100644
--- a/projects/testing-library/src/lib/testing-library.ts
+++ b/projects/testing-library/src/lib/testing-library.ts
@@ -11,6 +11,7 @@ import {
   SimpleChanges,
   Type,
   isStandalone,
+  Binding,
 } from '@angular/core';
 import { ComponentFixture, DeferBlockBehavior, DeferBlockState, TestBed, tick } from '@angular/core/testing';
 import { NavigationExtras, Router } from '@angular/router';
@@ -69,6 +70,7 @@ export async function render(
     componentOutputs = {},
     inputs: newInputs = {},
     on = {},
+    bindings = [],
     componentProviders = [],
     childComponentOverrides = [],
     componentImports,
@@ -192,11 +194,19 @@ export async function render(
     outputs: Partial,
     subscribeTo: OutputRefKeysWithCallback,
   ): Promise> => {
-    const createdFixture: ComponentFixture = await createComponent(componentContainer);
-    setComponentProperties(createdFixture, properties);
-    setComponentInputs(createdFixture, inputs);
-    setComponentOutputs(createdFixture, outputs);
-    subscribedOutputs = subscribeToComponentOutputs(createdFixture, subscribeTo);
+    const createdFixture: ComponentFixture = await createComponent(componentContainer, bindings);
+
+    // Only use traditional input/output setting if no bindings are provided
+    // When bindings are used, they handle inputs and outputs natively
+    if (!bindings || bindings.length === 0) {
+      setComponentProperties(createdFixture, properties);
+      setComponentInputs(createdFixture, inputs);
+      setComponentOutputs(createdFixture, outputs);
+      subscribedOutputs = subscribeToComponentOutputs(createdFixture, subscribeTo);
+    } else {
+      // With bindings, we still need to handle componentProperties for non-input properties
+      setComponentProperties(createdFixture, properties);
+    }
 
     if (removeAngularAttributes) {
       createdFixture.nativeElement.removeAttribute('ng-version');
@@ -335,9 +345,18 @@ export async function render(
   };
 }
 
-async function createComponent(component: Type): Promise> {
+async function createComponent(
+  component: Type,
+  bindings?: Binding[],
+): Promise> {
   /* Make sure angular application is initialized before creating component */
   await TestBed.inject(ApplicationInitStatus).donePromise;
+
+  // Use the new bindings API if available and bindings are provided
+  if (bindings && bindings.length > 0) {
+    return TestBed.createComponent(component, { bindings });
+  }
+
   return TestBed.createComponent(component);
 }
 
diff --git a/projects/testing-library/src/public_api.ts b/projects/testing-library/src/public_api.ts
index f7b89e4f..208f742c 100644
--- a/projects/testing-library/src/public_api.ts
+++ b/projects/testing-library/src/public_api.ts
@@ -5,3 +5,6 @@
 export * from './lib/models';
 export * from './lib/config';
 export * from './lib/testing-library';
+
+// Re-export Angular's binding functions for convenience
+export { inputBinding, outputBinding, twoWayBinding, type Binding } from '@angular/core';
diff --git a/projects/testing-library/tests/bindings-support.spec.ts b/projects/testing-library/tests/bindings-support.spec.ts
new file mode 100644
index 00000000..818d500b
--- /dev/null
+++ b/projects/testing-library/tests/bindings-support.spec.ts
@@ -0,0 +1,41 @@
+import { Component, input, output } from '@angular/core';
+import { render, screen, inputBinding, outputBinding } from '../src/public_api';
+
+describe('ATL Bindings API Support', () => {
+  @Component({
+    selector: 'atl-bindings-test',
+    template: `
+      {{ value() }}
+      {{ greeting() }}
+      
+    `,
+    standalone: true,
+  })
+  class BindingsTestComponent {
+    value = input('default');
+    greeting = input('hello', { alias: 'greet' });
+    clicked = output();
+  }
+
+  it('should support inputBinding for regular inputs', async () => {
+    await render(BindingsTestComponent, {
+      bindings: [inputBinding('value', () => 'test-value'), inputBinding('greet', () => 'hi there')],
+    });
+
+    expect(screen.getByTestId('value')).toHaveTextContent('test-value');
+    expect(screen.getByTestId('greeting')).toHaveTextContent('hi there');
+  });
+
+  it('should support outputBinding for outputs', async () => {
+    const clickHandler = jest.fn();
+
+    await render(BindingsTestComponent, {
+      bindings: [inputBinding('value', () => 'bound-value'), outputBinding('clicked', clickHandler)],
+    });
+
+    const button = screen.getByTestId('emit-button');
+    button.click();
+
+    expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
+  });
+});
From 3dca2ea53838b5001ec50062a281074ae712fc94 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Sep 2025 16:56:35 +0000
Subject: [PATCH 3/7] Add documentation for Angular v20 bindings API
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
---
 README.md | 41 +++++++++++++++++++++++++++++++++++++++++
 1 file changed, 41 insertions(+)
diff --git a/README.md b/README.md
index 848aed06..2fb5f295 100644
--- a/README.md
+++ b/README.md
@@ -151,6 +151,47 @@ describe('Counter', () => {
 });
 ```
 
+### Angular v20+ Bindings API
+
+Angular Testing Library also supports Angular v20's native bindings API, which provides a more direct way to bind inputs and outputs:
+
+```typescript
+import { render, screen, inputBinding, outputBinding } from '@testing-library/angular';
+import { CounterComponent } from './counter.component';
+
+describe('Counter with Bindings API', () => {
+  it('should render counter using bindings', async () => {
+    await render(CounterComponent, {
+      bindings: [inputBinding('counter', () => 5), inputBinding('greeting', () => 'Hello Bindings!')],
+    });
+
+    expect(screen.getByText('Current Count: 5')).toBeVisible();
+    expect(screen.getByText('Hello Bindings!')).toBeVisible();
+  });
+
+  it('should handle outputs with bindings', async () => {
+    const clickHandler = jest.fn();
+
+    await render(CounterComponent, {
+      bindings: [inputBinding('counter', () => 0), outputBinding('counterChange', clickHandler)],
+    });
+
+    const incrementButton = screen.getByRole('button', { name: '+' });
+    fireEvent.click(incrementButton);
+
+    expect(clickHandler).toHaveBeenCalledWith(1);
+  });
+});
+```
+
+The new bindings API provides several benefits:
+
+- **Native Angular Integration**: Uses Angular's official bindings API
+- **Better Performance**: Bindings are handled natively by Angular
+- **Improved Type Safety**: Leverages Angular's built-in type checking
+
+Both approaches are supported and can be used interchangeably based on your preference and Angular version.
+
 [See more examples](https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples)
 
 ## Installation
From ec079c2370f9d111096b9e993430de8d4551f21e Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Wed, 24 Sep 2025 18:34:37 +0000
Subject: [PATCH 4/7] Address review feedback: Remove re-exports, enable mixed
 usage with warnings, remove v20 references
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
---
 README.md                                     |  7 +--
 projects/testing-library/src/lib/models.ts    |  2 +-
 .../src/lib/testing-library.ts                | 33 +++++++++++---
 projects/testing-library/src/public_api.ts    |  3 --
 .../tests/bindings-support.spec.ts            | 45 ++++++++++++++++++-
 5 files changed, 74 insertions(+), 16 deletions(-)
diff --git a/README.md b/README.md
index 2fb5f295..a02dc0f1 100644
--- a/README.md
+++ b/README.md
@@ -151,12 +151,13 @@ describe('Counter', () => {
 });
 ```
 
-### Angular v20+ Bindings API
+### Angular Bindings API
 
-Angular Testing Library also supports Angular v20's native bindings API, which provides a more direct way to bind inputs and outputs:
+Angular Testing Library also supports Angular's native bindings API, which provides a more direct way to bind inputs and outputs:
 
 ```typescript
-import { render, screen, inputBinding, outputBinding } from '@testing-library/angular';
+import { render, screen } from '@testing-library/angular';
+import { inputBinding, outputBinding } from '@angular/core';
 import { CounterComponent } from './counter.component';
 
 describe('Counter with Bindings API', () => {
diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts
index 41ecca2e..0396af80 100644
--- a/projects/testing-library/src/lib/models.ts
+++ b/projects/testing-library/src/lib/models.ts
@@ -310,7 +310,7 @@ export interface RenderComponentOptions(
   ): Promise> => {
     const createdFixture: ComponentFixture = await createComponent(componentContainer, bindings);
 
-    // Only use traditional input/output setting if no bindings are provided
-    // When bindings are used, they handle inputs and outputs natively
-    if (!bindings || bindings.length === 0) {
-      setComponentProperties(createdFixture, properties);
-      setComponentInputs(createdFixture, inputs);
+    // Always apply componentProperties (non-input properties)
+    setComponentProperties(createdFixture, properties);
+
+    // Angular doesn't allow mixing setInput with bindings
+    // So we use bindings OR traditional approach, but not both for inputs
+    if (bindings && bindings.length > 0) {
+      // When bindings are used, warn if traditional inputs/outputs are also specified
+      if (Object.keys(inputs).length > 0) {
+        console.warn(
+          'ATL: You specified both bindings and traditional inputs. ' +
+            'Angular does not allow mixing setInput() with inputBinding(). ' +
+            'Only bindings will be used for inputs. Use bindings for all inputs to avoid this warning.',
+        );
+      }
+      if (Object.keys(subscribeTo).length > 0) {
+        console.warn(
+          'ATL: You specified both bindings and traditional output listeners. ' +
+            'Consider using outputBinding() for all outputs for consistency.',
+        );
+      }
+
+      // Only apply traditional outputs, as bindings handle inputs
       setComponentOutputs(createdFixture, outputs);
       subscribedOutputs = subscribeToComponentOutputs(createdFixture, subscribeTo);
     } else {
-      // With bindings, we still need to handle componentProperties for non-input properties
-      setComponentProperties(createdFixture, properties);
+      // Use traditional approach when no bindings
+      setComponentInputs(createdFixture, inputs);
+      setComponentOutputs(createdFixture, outputs);
+      subscribedOutputs = subscribeToComponentOutputs(createdFixture, subscribeTo);
     }
 
     if (removeAngularAttributes) {
diff --git a/projects/testing-library/src/public_api.ts b/projects/testing-library/src/public_api.ts
index 208f742c..f7b89e4f 100644
--- a/projects/testing-library/src/public_api.ts
+++ b/projects/testing-library/src/public_api.ts
@@ -5,6 +5,3 @@
 export * from './lib/models';
 export * from './lib/config';
 export * from './lib/testing-library';
-
-// Re-export Angular's binding functions for convenience
-export { inputBinding, outputBinding, twoWayBinding, type Binding } from '@angular/core';
diff --git a/projects/testing-library/tests/bindings-support.spec.ts b/projects/testing-library/tests/bindings-support.spec.ts
index 818d500b..b48033d4 100644
--- a/projects/testing-library/tests/bindings-support.spec.ts
+++ b/projects/testing-library/tests/bindings-support.spec.ts
@@ -1,5 +1,5 @@
-import { Component, input, output } from '@angular/core';
-import { render, screen, inputBinding, outputBinding } from '../src/public_api';
+import { Component, input, output, inputBinding, outputBinding } from '@angular/core';
+import { render, screen, aliasedInput } from '../src/public_api';
 
 describe('ATL Bindings API Support', () => {
   @Component({
@@ -38,4 +38,45 @@ describe('ATL Bindings API Support', () => {
 
     expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
   });
+
+  it('should warn when mixing bindings with traditional inputs but still work', async () => {
+    const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
+    const clickHandler = jest.fn();
+    const bindingClickHandler = jest.fn();
+
+    await render(BindingsTestComponent, {
+      bindings: [inputBinding('value', () => 'binding-value'), outputBinding('clicked', bindingClickHandler)],
+      inputs: {
+        ...aliasedInput('greet', 'traditional-greeting'), // This will be ignored due to bindings
+      },
+      on: {
+        clicked: clickHandler, // This should still work alongside bindings
+      },
+    });
+
+    // Only binding should work for inputs
+    expect(screen.getByTestId('value')).toHaveTextContent('binding-value');
+    expect(screen.getByTestId('greeting')).toHaveTextContent('hello'); // Default value, not traditional
+
+    const button = screen.getByTestId('emit-button');
+    button.click();
+
+    // Both binding and traditional handlers should be called for outputs
+    expect(bindingClickHandler).toHaveBeenCalledWith('clicked: binding-value');
+    expect(clickHandler).toHaveBeenCalledWith('clicked: binding-value');
+
+    // Should show warning about mixed usage for inputs
+    expect(consoleSpy).toHaveBeenCalledWith(
+      'ATL: You specified both bindings and traditional inputs. ' +
+        'Angular does not allow mixing setInput() with inputBinding(). ' +
+        'Only bindings will be used for inputs. Use bindings for all inputs to avoid this warning.',
+    );
+
+    expect(consoleSpy).toHaveBeenCalledWith(
+      'ATL: You specified both bindings and traditional output listeners. ' +
+        'Consider using outputBinding() for all outputs for consistency.',
+    );
+
+    consoleSpy.mockRestore();
+  });
 });
From 39fe9330cac17f53a7081e05209ba7de9da957f6 Mon Sep 17 00:00:00 2001
From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com>
Date: Sat, 27 Sep 2025 12:38:30 +0000
Subject: [PATCH 5/7] Add twoWayBinding support and writable signal test cases
Co-authored-by: timdeschryver <28659384+timdeschryver@users.noreply.github.com>
---
 README.md                                     | 19 +++++-
 projects/testing-library/src/lib/models.ts    |  6 +-
 .../tests/bindings-support.spec.ts            | 62 ++++++++++++++++++-
 3 files changed, 83 insertions(+), 4 deletions(-)
diff --git a/README.md b/README.md
index a02dc0f1..ed63d3f8 100644
--- a/README.md
+++ b/README.md
@@ -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', () => {
@@ -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();
+  });
 });
 ```
 
diff --git a/projects/testing-library/src/lib/models.ts b/projects/testing-library/src/lib/models.ts
index 0396af80..b8628bae 100644
--- a/projects/testing-library/src/lib/models.ts
+++ b/projects/testing-library/src/lib/models.ts
@@ -317,12 +317,14 @@ export interface RenderComponentOptions 'test value'),
-   *     outputBinding('click', (event) => console.log(event))
+   *     outputBinding('click', (event) => console.log(event)),
+   *     twoWayBinding('name', signal('initial value'))
    *   ]
    * })
    */
diff --git a/projects/testing-library/tests/bindings-support.spec.ts b/projects/testing-library/tests/bindings-support.spec.ts
index b48033d4..3f718b73 100644
--- a/projects/testing-library/tests/bindings-support.spec.ts
+++ b/projects/testing-library/tests/bindings-support.spec.ts
@@ -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', () => {
@@ -17,6 +17,23 @@ describe('ATL Bindings API Support', () => {
     clicked = output();
   }
 
+  @Component({
+    selector: 'atl-two-way-test',
+    template: `
+      {{ name() }}
+      
+      
+    `,
+    standalone: true,
+  })
+  class TwoWayBindingTestComponent {
+    name = model('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')],
@@ -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();
From 04e96a2610ee108f976c559f969de2d501a9c05f Mon Sep 17 00:00:00 2001
From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com>
Date: Mon, 29 Sep 2025 16:58:18 +0200
Subject: [PATCH 6/7] manual tweaks
---
 README.md                                     | 59 -------------------
 .../src/lib/testing-library.ts                |  5 +-
 ...dings-support.spec.ts => bindings.spec.ts} | 21 ++++---
 3 files changed, 12 insertions(+), 73 deletions(-)
 rename projects/testing-library/tests/{bindings-support.spec.ts => bindings.spec.ts} (85%)
diff --git a/README.md b/README.md
index ed63d3f8..848aed06 100644
--- a/README.md
+++ b/README.md
@@ -151,65 +151,6 @@ describe('Counter', () => {
 });
 ```
 
-### Angular Bindings API
-
-Angular Testing Library also supports Angular's native bindings API, which provides a more direct way to bind inputs and outputs:
-
-```typescript
-import { render, screen } from '@testing-library/angular';
-import { inputBinding, outputBinding, twoWayBinding, signal } from '@angular/core';
-import { CounterComponent } from './counter.component';
-
-describe('Counter with Bindings API', () => {
-  it('should render counter using bindings', async () => {
-    await render(CounterComponent, {
-      bindings: [inputBinding('counter', () => 5), inputBinding('greeting', () => 'Hello Bindings!')],
-    });
-
-    expect(screen.getByText('Current Count: 5')).toBeVisible();
-    expect(screen.getByText('Hello Bindings!')).toBeVisible();
-  });
-
-  it('should handle outputs with bindings', async () => {
-    const clickHandler = jest.fn();
-
-    await render(CounterComponent, {
-      bindings: [inputBinding('counter', () => 0), outputBinding('counterChange', clickHandler)],
-    });
-
-    const incrementButton = screen.getByRole('button', { name: '+' });
-    fireEvent.click(incrementButton);
-
-    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();
-  });
-});
-```
-
-The new bindings API provides several benefits:
-
-- **Native Angular Integration**: Uses Angular's official bindings API
-- **Better Performance**: Bindings are handled natively by Angular
-- **Improved Type Safety**: Leverages Angular's built-in type checking
-
-Both approaches are supported and can be used interchangeably based on your preference and Angular version.
-
 [See more examples](https://github.com/testing-library/angular-testing-library/tree/main/apps/example-app/src/app/examples)
 
 ## Installation
diff --git a/projects/testing-library/src/lib/testing-library.ts b/projects/testing-library/src/lib/testing-library.ts
index 16f5fea8..a8bc1ea3 100644
--- a/projects/testing-library/src/lib/testing-library.ts
+++ b/projects/testing-library/src/lib/testing-library.ts
@@ -205,14 +205,13 @@ export async function render(
       // When bindings are used, warn if traditional inputs/outputs are also specified
       if (Object.keys(inputs).length > 0) {
         console.warn(
-          'ATL: You specified both bindings and traditional inputs. ' +
-            'Angular does not allow mixing setInput() with inputBinding(). ' +
+          '[@testing-library/angular]: You specified both bindings and traditional inputs. ' +
             'Only bindings will be used for inputs. Use bindings for all inputs to avoid this warning.',
         );
       }
       if (Object.keys(subscribeTo).length > 0) {
         console.warn(
-          'ATL: You specified both bindings and traditional output listeners. ' +
+          '[@testing-library/angular]: You specified both bindings and traditional output listeners. ' +
             'Consider using outputBinding() for all outputs for consistency.',
         );
       }
diff --git a/projects/testing-library/tests/bindings-support.spec.ts b/projects/testing-library/tests/bindings.spec.ts
similarity index 85%
rename from projects/testing-library/tests/bindings-support.spec.ts
rename to projects/testing-library/tests/bindings.spec.ts
index 3f718b73..c89e9ee1 100644
--- a/projects/testing-library/tests/bindings-support.spec.ts
+++ b/projects/testing-library/tests/bindings.spec.ts
@@ -1,7 +1,7 @@
 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', () => {
+describe('Bindings API Support', () => {
   @Component({
     selector: 'atl-bindings-test',
     template: `
@@ -34,7 +34,7 @@ describe('ATL Bindings API Support', () => {
     }
   }
 
-  it('should support inputBinding for regular inputs', async () => {
+  it('supports inputBinding for regular inputs', async () => {
     await render(BindingsTestComponent, {
       bindings: [inputBinding('value', () => 'test-value'), inputBinding('greet', () => 'hi there')],
     });
@@ -43,7 +43,7 @@ describe('ATL Bindings API Support', () => {
     expect(screen.getByTestId('greeting')).toHaveTextContent('hi there');
   });
 
-  it('should support outputBinding for outputs', async () => {
+  it('supports outputBinding for outputs', async () => {
     const clickHandler = jest.fn();
 
     await render(BindingsTestComponent, {
@@ -56,7 +56,7 @@ describe('ATL Bindings API Support', () => {
     expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
   });
 
-  it('should support inputBinding with writable signal for re-rendering scenario', async () => {
+  it('supports inputBinding with writable signal for re-rendering scenario', async () => {
     const valueSignal = signal('initial-value');
 
     await render(BindingsTestComponent, {
@@ -73,7 +73,7 @@ describe('ATL Bindings API Support', () => {
     expect(await screen.findByText('updated-value')).toBeInTheDocument();
   });
 
-  it('should support twoWayBinding for model signals', async () => {
+  it('supports twoWayBinding for model signals', async () => {
     const nameSignal = signal('initial name');
 
     await render(TwoWayBindingTestComponent, {
@@ -99,7 +99,7 @@ describe('ATL Bindings API Support', () => {
     expect(nameSignal()).toBe('updated from component');
   });
 
-  it('should warn when mixing bindings with traditional inputs but still work', async () => {
+  it('warns when mixing bindings with traditional inputs but still works', async () => {
     const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
     const clickHandler = jest.fn();
     const bindingClickHandler = jest.fn();
@@ -121,19 +121,18 @@ describe('ATL Bindings API Support', () => {
     const button = screen.getByTestId('emit-button');
     button.click();
 
-    // Both binding and traditional handlers should be called for outputs
+    // Both binding and traditional handlers are called for outputs
     expect(bindingClickHandler).toHaveBeenCalledWith('clicked: binding-value');
     expect(clickHandler).toHaveBeenCalledWith('clicked: binding-value');
 
-    // Should show warning about mixed usage for inputs
+    // Shows warning about mixed usage for inputs
     expect(consoleSpy).toHaveBeenCalledWith(
-      'ATL: You specified both bindings and traditional inputs. ' +
-        'Angular does not allow mixing setInput() with inputBinding(). ' +
+      '[@testing-library/angular]: You specified both bindings and traditional inputs. ' +
         'Only bindings will be used for inputs. Use bindings for all inputs to avoid this warning.',
     );
 
     expect(consoleSpy).toHaveBeenCalledWith(
-      'ATL: You specified both bindings and traditional output listeners. ' +
+      '[@testing-library/angular]: You specified both bindings and traditional output listeners. ' +
         'Consider using outputBinding() for all outputs for consistency.',
     );
 
From c44861e008a804898e07bb4cea9ab5d0a78c416f Mon Sep 17 00:00:00 2001
From: Tim Deschryver <28659384+timdeschryver@users.noreply.github.com>
Date: Mon, 29 Sep 2025 19:12:21 +0200
Subject: [PATCH 7/7] add tests for the example component
---
 .../24-bindings-api.component.spec.ts         | 147 ++++++++++++++++++
 ...ponent.ts => 24-bindings-api.component.ts} |   0
 .../testing-library/tests/bindings.spec.ts    |  10 +-
 3 files changed, 152 insertions(+), 5 deletions(-)
 create mode 100644 apps/example-app/src/app/examples/24-bindings-api.component.spec.ts
 rename apps/example-app/src/app/examples/{23-bindings-api.component.ts => 24-bindings-api.component.ts} (100%)
diff --git a/apps/example-app/src/app/examples/24-bindings-api.component.spec.ts b/apps/example-app/src/app/examples/24-bindings-api.component.spec.ts
new file mode 100644
index 00000000..6c0a0e32
--- /dev/null
+++ b/apps/example-app/src/app/examples/24-bindings-api.component.spec.ts
@@ -0,0 +1,147 @@
+import { signal, inputBinding, outputBinding, twoWayBinding } from '@angular/core';
+import { render, screen } from '@testing-library/angular';
+import { BindingsApiExampleComponent } from './24-bindings-api.component';
+
+test('displays computed greeting message with input values', async () => {
+  await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', () => 'Hello'),
+      inputBinding('age', () => 25),
+      twoWayBinding('name', signal('John')),
+    ],
+  });
+
+  expect(screen.getByTestId('input-value')).toHaveTextContent('Hello John of 25 years old');
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Hello John of 25 years old');
+  expect(screen.getByTestId('current-age')).toHaveTextContent('Current age: 25');
+});
+
+test('emits submitValue output when submit button is clicked', async () => {
+  const submitHandler = jest.fn();
+  const nameSignal = signal('Alice');
+
+  await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', () => 'Good morning'),
+      inputBinding('age', () => 28),
+      twoWayBinding('name', nameSignal),
+      outputBinding('submitValue', submitHandler),
+    ],
+  });
+
+  const submitButton = screen.getByTestId('submit-button');
+  submitButton.click();
+  expect(submitHandler).toHaveBeenCalledWith('Alice');
+});
+
+test('emits ageChanged output when increment button is clicked', async () => {
+  const ageChangedHandler = jest.fn();
+
+  await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', () => 'Hi'),
+      inputBinding('age', () => 20),
+      twoWayBinding('name', signal('Charlie')),
+      outputBinding('ageChanged', ageChangedHandler),
+    ],
+  });
+
+  const incrementButton = screen.getByTestId('increment-button');
+  incrementButton.click();
+
+  expect(ageChangedHandler).toHaveBeenCalledWith(21);
+});
+
+test('updates name through two-way binding when input changes', async () => {
+  const nameSignal = signal('Initial Name');
+
+  await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', () => 'Hello'),
+      inputBinding('age', () => 25),
+      twoWayBinding('name', nameSignal),
+    ],
+  });
+
+  const nameInput = screen.getByTestId('name-input') as HTMLInputElement;
+
+  // Verify initial value
+  expect(nameInput.value).toBe('Initial Name');
+  expect(screen.getByTestId('input-value')).toHaveTextContent('Hello Initial Name of 25 years old');
+
+  // Update the signal externally
+  nameSignal.set('Updated Name');
+
+  // Verify the input and display update
+  expect(await screen.findByDisplayValue('Updated Name')).toBeInTheDocument();
+  expect(screen.getByTestId('input-value')).toHaveTextContent('Hello Updated Name of 25 years old');
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Hello Updated Name of 25 years old');
+});
+
+test('updates computed value when inputs change', async () => {
+  const greetingSignal = signal('Good day');
+  const nameSignal = signal('David');
+  const ageSignal = signal(35);
+
+  const { fixture } = await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', greetingSignal),
+      inputBinding('age', ageSignal),
+      twoWayBinding('name', nameSignal),
+    ],
+  });
+
+  // Initial state
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Good day David of 35 years old');
+
+  // Update greeting
+  greetingSignal.set('Good evening');
+  fixture.detectChanges();
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Good evening David of 35 years old');
+
+  // Update age
+  ageSignal.set(36);
+  fixture.detectChanges();
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Good evening David of 36 years old');
+
+  // Update name
+  nameSignal.set('Daniel');
+  fixture.detectChanges();
+  expect(screen.getByTestId('computed-value')).toHaveTextContent('Good evening Daniel of 36 years old');
+});
+
+test('handles multiple output emissions correctly', async () => {
+  const submitHandler = jest.fn();
+  const ageChangedHandler = jest.fn();
+  const nameSignal = signal('Emma');
+
+  await render(BindingsApiExampleComponent, {
+    bindings: [
+      inputBinding('greeting', () => 'Hey'),
+      inputBinding('age', () => 22),
+      twoWayBinding('name', nameSignal),
+      outputBinding('submitValue', submitHandler),
+      outputBinding('ageChanged', ageChangedHandler),
+    ],
+  });
+
+  // Click submit button multiple times
+  const submitButton = screen.getByTestId('submit-button');
+  submitButton.click();
+  submitButton.click();
+
+  expect(submitHandler).toHaveBeenCalledTimes(2);
+  expect(submitHandler).toHaveBeenNthCalledWith(1, 'Emma');
+  expect(submitHandler).toHaveBeenNthCalledWith(2, 'Emma');
+
+  // Click increment button multiple times
+  const incrementButton = screen.getByTestId('increment-button');
+  incrementButton.click();
+  incrementButton.click();
+  incrementButton.click();
+
+  expect(ageChangedHandler).toHaveBeenCalledTimes(3);
+  expect(ageChangedHandler).toHaveBeenNthCalledWith(1, 23);
+  expect(ageChangedHandler).toHaveBeenNthCalledWith(2, 23); // Still 23 because age input doesn't change
+  expect(ageChangedHandler).toHaveBeenNthCalledWith(3, 23);
+});
diff --git a/apps/example-app/src/app/examples/23-bindings-api.component.ts b/apps/example-app/src/app/examples/24-bindings-api.component.ts
similarity index 100%
rename from apps/example-app/src/app/examples/23-bindings-api.component.ts
rename to apps/example-app/src/app/examples/24-bindings-api.component.ts
diff --git a/projects/testing-library/tests/bindings.spec.ts b/projects/testing-library/tests/bindings.spec.ts
index c89e9ee1..50718f96 100644
--- a/projects/testing-library/tests/bindings.spec.ts
+++ b/projects/testing-library/tests/bindings.spec.ts
@@ -34,7 +34,7 @@ describe('Bindings API Support', () => {
     }
   }
 
-  it('supports inputBinding for regular inputs', async () => {
+  test('supports inputBinding for regular inputs', async () => {
     await render(BindingsTestComponent, {
       bindings: [inputBinding('value', () => 'test-value'), inputBinding('greet', () => 'hi there')],
     });
@@ -43,7 +43,7 @@ describe('Bindings API Support', () => {
     expect(screen.getByTestId('greeting')).toHaveTextContent('hi there');
   });
 
-  it('supports outputBinding for outputs', async () => {
+  test('supports outputBinding for outputs', async () => {
     const clickHandler = jest.fn();
 
     await render(BindingsTestComponent, {
@@ -56,7 +56,7 @@ describe('Bindings API Support', () => {
     expect(clickHandler).toHaveBeenCalledWith('clicked: bound-value');
   });
 
-  it('supports inputBinding with writable signal for re-rendering scenario', async () => {
+  test('supports inputBinding with writable signal for re-rendering scenario', async () => {
     const valueSignal = signal('initial-value');
 
     await render(BindingsTestComponent, {
@@ -73,7 +73,7 @@ describe('Bindings API Support', () => {
     expect(await screen.findByText('updated-value')).toBeInTheDocument();
   });
 
-  it('supports twoWayBinding for model signals', async () => {
+  test('supports twoWayBinding for model signals', async () => {
     const nameSignal = signal('initial name');
 
     await render(TwoWayBindingTestComponent, {
@@ -99,7 +99,7 @@ describe('Bindings API Support', () => {
     expect(nameSignal()).toBe('updated from component');
   });
 
-  it('warns when mixing bindings with traditional inputs but still works', async () => {
+  test('warns when mixing bindings with traditional inputs but still works', async () => {
     const consoleSpy = jest.spyOn(console, 'warn').mockImplementation();
     const clickHandler = jest.fn();
     const bindingClickHandler = jest.fn();