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

Does shallow-render support ViewChild queries? #64

Closed
ivportilla opened this issue Oct 8, 2018 · 7 comments
Closed

Does shallow-render support ViewChild queries? #64

ivportilla opened this issue Oct 8, 2018 · 7 comments

Comments

@ivportilla
Copy link

Hello,

I have the next component tree:

            AppComponent
                 |
                 |
           ParentComponent
                 |
                 |
            ChildComponent

The ParentComponent has a ViewChild reference to ChildComponent. When i mock ChildComponent by hand in the ParentComponent spec, it renders but never gets the reference to the ViewChild. Is any way to solve this using shallow-render?

If it helps, this repo has an example of the case i explained above: https://github.com/Ivykp/test-viewchild-mock

Thanks!

@getsaf
Copy link
Owner

getsaf commented Oct 9, 2018

Since we use ng-mocks to provide our component mocks, it should be supported as of this PR. However, I have not had a chance to test this and provide examples in the stack-blitz.

If it's giving you trouble, check that your lib is using at least ng-mocks@6.2.3 and it should work. I will leave this issue open until I get a chance to confirm it.

Since this is a big-feature, I plan on releasing another version of shallow-render that requires the newer version of ng-mocks so this feature is guaranteed to work.

@getsaf
Copy link
Owner

getsaf commented Oct 13, 2018

Just cut a new release with support for this!!

Check out the example spec here.

@getsaf getsaf closed this as completed Oct 13, 2018
@ticklemycode
Copy link

I'm actually trying to do something similar and I'm finding that the @ViewChild template ref on a form doesn't include a value prop. value is undefinded.

https://stackblitz.com/edit/github-jc7sw5?file=examples%2Fcomponent-with-view-child.spec.ts

import { ContentChild, Component, NgModule, AfterContentInit, ViewChild } from '@angular/core';
import { Shallow } from 'shallow-render';
import { FormsModule } from '@angular/forms';

import { fakeAsync, tick } from '@angular/core/testing';

////// Module Setup //////
@Component({
  selector: 'my-form',
  template: `
   <form #simpleForm="ngForm">
    <input type="text" name="adminUsername" [(ngModel)]="adminUsernameText" #adminUsername="ngModel">
  </form>`,
})
class FormComponent {
  @ViewChild('simpleForm') myForm: any;
  randomProp: boolean = true;
  adminUsername:string = 'user1';
}

@NgModule({
  declarations: [FormComponent],
  imports: [FormsModule]
})
class FormModule {
  
}
//////////////////////////

describe('form component with @ViewChild', () => {
  let shallow: Shallow<FormComponent>;

  beforeEach(() => {
    shallow = new Shallow(FormComponent, FormModule)
  });

  it('should find reference random prop as true', async () => {
   const {instance, fixture} = await shallow.render('<my-form>');
   expect(instance.randomProp).toBe(true);
  });

  it('should find reference random prop as false', async () => {
   const {instance, fixture} = await shallow.render('<my-form>');
   instance.randomProp = false;
   expect(instance.randomProp).toBe(false);
  });

   it('should find reference to form template ref with value prop', async () => {
   const {instance, fixture} = await shallow.render('<my-form>');
   fixture.detectChanges();
   expect(instance.myForm.value).toBeDefined();
  //  instance.myForm.value = {}
  //  instance.myForm.value.adminUsername = 'user2'
   
  //  expect(instance.myForm.value).toBeDefined();
  //  expect(instance.myForm.value.adminUsername).toBeDefined();
  });
});

@getsaf
Copy link
Owner

getsaf commented Oct 15, 2018

Looks like the issue you're seeing here is that, Shallow mocks ALL modules except for CommonModule by default. This means, Angular's FormsModule is being mocked but your test is expecting stuff from the FormsModule to behave normally. Since most (all?) of the things in the FormsModule are self-contained (eg: nothing inside the module has dependencies outside the forms module), you can/should safely be able to use the real FormsModule in your specs.

If your project uses FormsModule excessively, I'd recommend neverMocking it in your karma setup:

Shallow.neverMock(FormsModule);

If you just need a one-off, you can do it right in your test:

shallow = new Shallow(MyComponent, MyModule).dontMock(FormsModule);

^-- This worked in my fork of your StackBlitz example

@ticklemycode
Copy link

@getsaf, thanks for you help with that scenario and we are indeed using .dontMock(FormsModule). I should have caught that. I actually updated my StackBlitz to better reflect what our actual test is doing.

Updated StackBlitz

import { ContentChild, Component, NgModule, AfterContentInit, ViewChild } from '@angular/core';
import { Shallow } from 'shallow-render';
import { FormsModule } from '@angular/forms';

import { fakeAsync, tick } from '@angular/core/testing';

////// Module Setup //////
@Component({
  selector: 'my-form',
  template: `
   <form #simpleForm="ngForm">
    <input type="text" id="username" name="adminUsername" [(ngModel)]="adminUsernameText" #adminUsername="ngModel">
  </form>`,
})
class FormComponent {
  @ViewChild('simpleForm') myForm: any;
  adminUsername:string = 'user1';
}

@NgModule({
  declarations: [FormComponent],
  imports: [FormsModule]
})
class FormModule {}
//////////////////////////

describe('form component with @ViewChild', () => {
  let shallow: Shallow<FormComponent>;

  const render = fakeAsync(async () => {
    const rendering = await shallow.render();
    tick();
    rendering.fixture.detectChanges();
    return rendering;
  });

  beforeEach(() => {
    shallow = new Shallow(FormComponent, FormModule)
  });

  it('should find reference to form template ref', async () => {
   const {instance} = await render();
   expect(instance.myForm).toBeDefined('myForm should be defined');
  });

  it('should find reference to form template ref with value prop', async () => {
   const {instance, fixture, find} = await render();
   const input = find('#username').nativeElement;
   
   expect(input).toBeDefined('Input should be defined');

   input.value = "user@abc";
   fixture.detectChanges();
   
   expect(instance.myForm.vaule).toBeDefined('myForm.value should be defined');
   expect(instance.myForm.vaule.adminUsername).toEqual('user@abc');
  });
});

@ike18t
Copy link
Collaborator

ike18t commented Oct 16, 2018

Hi @ticklemycode ,

I noticed a few problems with your stackblitz example:

  • Needs to use dontMock or neverMock the FormsModule
  • Needed to dispatchEvent on the input so that ngModel will update
  • "value" was misspelled in the assertions

Here are my updates:
https://stackblitz.com/edit/github-jc7sw5-bq4p3x?file=examples%2Fcomponent-with-view-child.spec.ts

@ticklemycode
Copy link

@ike18t, ah interesting. So anytime we need to get a reference to the a ngForm's value we will need to dispatchEvent on any of the inputs we are interested in? I think that was the main thing we are missing in our real code. Looks like we may have been under the assumption that that was magically happening.

This helps a lot! Thanks for taking a look!

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants