Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Bug: Incompatibility of MockComponent with new viewChild signal function #8634

Open
mh-ahs opened this issue Apr 2, 2024 · 28 comments · May be fixed by #8771
Open

Bug: Incompatibility of MockComponent with new viewChild signal function #8634

mh-ahs opened this issue Apr 2, 2024 · 28 comments · May be fixed by #8771
Assignees
Labels
bug Something isn't working

Comments

@mh-ahs
Copy link

mh-ahs commented Apr 2, 2024

Description of the bug

Testing standalone components with nested components that use the new viewChild signal feature is currently not possible if the nested component should be mocked.

I'm not sure if ngMocks can do anything about it or if its an angular-core related problem.

An example of the bug

Link: https://stackblitz.com/edit/github-6eneyd?file=src%2Ftest.spec.ts
I added a "NestedComponent" to the default example and replaced the module-based definitions with a simple standalone definition.

Expected vs actual behavior

Using the Signal implementation leads to
TypeError: Cannot read properties of undefined (reading 'Symbol(SIGNAL)')
Switching the implementation from Signal to Decorator lets test run fine,

@mh-ahs mh-ahs added the bug Something isn't working label Apr 2, 2024
@GipHub123
Copy link

I can confirm this one 👍 Having same problems after the change.

@c-harding
Copy link

Temporary workaround: use MockBuilder#replace

@Component({
  selector: 'app-nested',
  standalone: true,
  template: ``,
})
class NestedComponentStub {
  public readonly anchor = signal(new ElementRef(undefined)).asReadonly();
  public readonly name = input.required<string>();
}

describe('my sandbox', () => {
  beforeEach(() =>
    MockBuilder(TargetComponent).replace(NestedComponent, NestedComponentStub)
  );

@c-harding
Copy link

@satanTime this seems to be a better fix: simply remove the view queries, as they’ll always be null anyway

const generateFinalQueries = (queries: {
[key: string]: Query;
}): [Array<[string, Query & { ngMetadataName?: string }]>, string[]] => {
const final: Array<[string, Query & { ngMetadataName?: string }]> = [];
const scanKeys: string[] = [];
for (const key of Object.keys(queries)) {
const query: Query & { ngMetadataName?: string } = queries[key];
final.push([key, query]);
if (!query.isViewQuery && !isInternalKey(key)) {
scanKeys.push(key);
final.push([`__ngMocksVcr_${key}`, cloneVcrQuery(query)]);
}
}
return [final, scanKeys];
};

  const generateFinalQueries = (queries: { 
    [key: string]: Query; 
  }): [Array<[string, Query & { ngMetadataName?: string }]>, string[]] => { 
    const final: Array<[string, Query & { ngMetadataName?: string }]> = []; 
    const scanKeys: string[] = []; 
   
    for (const key of Object.keys(queries)) { 
      const query: Query & { ngMetadataName?: string } = queries[key]; 
-     final.push([key, query]); 
   
      if (!query.isViewQuery && !isInternalKey(key)) { 
+       final.push([key, query]);
        scanKeys.push(key); 
        final.push([`__ngMocksVcr_${key}`, cloneVcrQuery(query)]); 
      } 
    } 
   
    return [final, scanKeys]; 
  }; 

c-harding added a commit to c-harding/ng-mocks that referenced this issue Apr 16, 2024
ViewChild will always be null, and so there is no point in mocking it.
viewChild.required throws an error in mocked components without this
change.

Fixes help-me-momGH-8634
@c-harding c-harding linked a pull request Apr 16, 2024 that will close this issue
c-harding added a commit to c-harding/ng-mocks that referenced this issue Apr 16, 2024
ViewChild will always be null, and so there is no point in mocking it.
viewChild.required throws an error in mocked components without this
change.

Fixes help-me-momGH-8634
@andreandersson
Copy link

This will be fixed by the same fix as #7976, is my guess.

@renanaragao
Copy link

Good evening, any news?

@mishahrokhola
Copy link

Hello, any updates on this issue?

@LukasMachetanz
Copy link

I guess #8895 is a similar problem.

dmitry-stepanenko added a commit to c-harding/ng-mocks that referenced this issue May 28, 2024
dmitry-stepanenko added a commit to c-harding/ng-mocks that referenced this issue May 28, 2024
dmitry-stepanenko added a commit to c-harding/ng-mocks that referenced this issue May 28, 2024
@LukasMachetanz
Copy link

Any news on this issue? This bug really prevents using the new API.

c-harding added a commit to c-harding/ng-mocks that referenced this issue Jun 4, 2024
@SereetsiKC
Copy link

The fix here is not to mock the component but pass it into your test declarations with you parent component

@satanTime
Copy link
Member

Sorry for the delay. I'll take a look closer next week.

@LukasMachetanz
Copy link

@satanTime, did you already have a chance to look into it?

@pcbowers
Copy link

Another workaround I found until this is fixed:

MockInstance(YourComponent, 'viewChildElement', signal(new ElementRef(document.createElement('div'))));

@LukasMachetanz
Copy link

LukasMachetanz commented Nov 28, 2024

@satanTime, sorry to bother you again, but do you have any updates on this? I guess you have currently limited time to work on a request like this, right? No hard feelings, the effort of this great library is definitely appreciated. :)

@Francesco-Borzi
Copy link

Confirmed. If you try to mock a component that uses a viewChild signal inside, it will give error.

cc @satanTime

@zargham-leanix
Copy link

I can confirm, happening for me as well.

@dankerk
Copy link

dankerk commented Dec 19, 2024

Hey @Francesco-Borzi fancy seeing you here :D

+1 on this issue. Happening on 14.13.1 here.

@Martinspire
Copy link

So @satanTime is this on the list to fix soon as well?

@conner-fallone
Copy link

We are also having this issue.

@DavidACCarvalho
Copy link

I'm having also the same problem

@alixroyere
Copy link

Another workaround I found until this is fixed:

MockInstance(YourComponent, 'viewChildElement', signal(new ElementRef(document.createElement('div'))));

Ok so this workaround works but I had some difficulties to apply it. So here is a more verbose explanation that is working in my case:

describe('my tests', () => {
  // Define a scope to auto-clean mocked instance see https://ng-mocks.sudo.eu/api/MockInstance/#scope
  MockInstance.scope();

  beforeEach(async () => {
       // Put expected TestBed conf WITH the broken MockComponent(MyComponent)
       await TestBed.configureTestingModule [...]
       
       // Override the viewChild element with a viewChild-matching-type mock
       // (can be ElementRef as in the previous answer or Component, etc depending on your code)
       MockInstance(MyComponent, 'myViewChild', signal({} as MyViewChildComponent));
  });

  it('my test', () => {
      // test
  });
});

@tibztibz
Copy link

any news for the resolution of this issue ?

@satanTime
Copy link
Member

in todo

@satanTime
Copy link
Member

Hi all, could you also post as many code examples as you have with the issue and use-cases? So it would be easier for me to cover all of them with one attempt.

@renanaragao
Copy link

My case is the same as the first post

@c-harding
Copy link

Mine too, but with viewChild.required

@tibztibz
Copy link

tibztibz commented Mar 12, 2025

@satanTime the same problem with contentChild too. An exemple :

import { CommonModule } from '@angular/common'
import {
    Component,
    Directive,
    contentChild,
    ElementRef,
    viewChild,
} from '@angular/core'
import { MockBuilder, MockInstance, MockRender, ngMocks } from 'ng-mocks'

@Directive({
    selector: '[uiButtonStart]',
    standalone: true,
})
export class ButtonStartDirective {}


@Component({
    selector: 'button[ui-button]',
    template: `
        @if(buttonStart()) {
            <div class="start">
                <ng-content select="[uiButtonStart]" />
            </div>
        }
        <span #buttonText><ng-content></ng-content></span>
    `,
    standalone: true,
    imports: [CommonModule, ButtonStartDirective],
    host: {
        '[attr.title]': 'viewChildProp()?.textContent',
    }
})
export class ButtonComponent  {
    viewChildProp = viewChild<ElementRef>('buttonText')
    buttonStart = contentChild(ButtonStartDirective, { descendants: true })
}

@Component({
    selector: 'button-edit',
    template : `
       <button ui-button>
            <span uiButtonStart>x</span>
            edit
       </button>
    `,
    standalone: true,
    imports: [CommonModule, ButtonComponent, ButtonStartDirective],
})
export class ButtonEditComponent  {}


describe('ButtonEditComponent', () => {
    ngMocks.faster()
    MockInstance.scope()

    beforeAll(() => MockBuilder(ButtonEditComponent)
        .keep(CommonModule)
        .mock(ButtonComponent)
        .mock(ButtonStartDirective)
    )

    it('test', () => {
        const fixture = MockRender(ButtonEditComponent)
        const component = fixture.point.componentInstance
        expect(component).toBeTruthy()
    })
})

@kheos31
Copy link

kheos31 commented Mar 12, 2025

You should test query signatures :

  • viewChild
  • viewChild.required
  • viewChildren
  • viewChildren.required
  • contentChild
  • contentChild.required
  • contentChildren
  • contentChildren.required

In addition to these signatures, as @tibztibz mentioned in these scenarios, you have additional options for these signatures :

thx @satanTime

@yarychh
Copy link

yarychh commented Mar 13, 2025

my case is: standalone component imports a module (SharedFeatureModule) which has a component that uses signal-based viewChild.

await TestBed.configureTestingModule({ imports: [SomeWrapperComponent, MockModule(SharedFeatureModule)], }).compileComponents();

thx @satanTime

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working
Projects
None yet
Development

Successfully merging a pull request may close this issue.