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

ControlValueAccessor Mock not working on MatInput #305

Closed
3 tasks done
jensweigele opened this issue Feb 26, 2021 · 11 comments · Fixed by #306
Closed
3 tasks done

ControlValueAccessor Mock not working on MatInput #305

jensweigele opened this issue Feb 26, 2021 · 11 comments · Fixed by #306

Comments

@jensweigele
Copy link

jensweigele commented Feb 26, 2021

When having an input element like that:

<input data-testid="inputControl" matInput [formControl]="myControl" required>

with a test setup like that:

MockBuilder()
                .mock(MyModule)
                .keep(MyComponent)
                .keep(ReactiveFormsModule)

I can get a MatInput Mock Instance with:

ngMocks.get(ngMocks.find(['data-testid', 'inputControl']), MatInput)

But this mock is not a "MockControlValueAccessor" (isMockControlValueAccessor is false). I also tried other approaches but did not find a way to get it working. Ticket #73 is about a fix for that, but maybe this fix got lost by the refactoring between the version!?

  • test overrides of interfaces to ensure it is possible
  • Lazy instance on proxy should receive previous calls of proxy
  • ngMocks.touch, .change and so on
@satanTime
Copy link
Member

satanTime commented Feb 26, 2021

Hi, thanks for the report! I'll take a look today.

Might you share how matInput has been imported?

Also is it really .find? ngMocks.find(['data-testid', 'inputControl']), I would assume that it should be .reveal.

@satanTime
Copy link
Member

Also is it on 11.7.0 or 11.8.0? there was a regression before: #302

@jensweigele
Copy link
Author

MatInput is imported in "MyModule" via imports: [MatInputModule] (so I think that should be mocked).
But I also tried to use:

MockBuilder()
                .mock(MyModule)
                .keep(MyComponent)
                .keep(ReactiveFormsModule)
                .mock(MatInput)

(also with mock(MatInputModule)

Also is it really .find? ngMocks.find(['data-testid', 'inputControl']), I would assume that it should be .reveal.

Yes it's find. But it's interesting - the mock-helper.d.ts does not contain a reveal Method (11.7.0)
I just saw that you published a new version some hours again - let me doublecheck with that version.

@satanTime
Copy link
Member

Ah true :) I forgot that it will be transformed to [data-testid="inputControl"], sorry :) a too fresh feature. Good, then I'll investigate how it works on 11.7.0 and 11.8.0. The fix should be here next days and released on the weekend.

@jensweigele
Copy link
Author

I've the same behavior with 11.8.0

@satanTime
Copy link
Member

satanTime commented Feb 26, 2021

thanks, then I'll investigate what I did break again.

@satanTime
Copy link
Member

satanTime commented Feb 26, 2021

So, the thing is that MatInput doesn't implement ControlValueAccessor.
Therefore, I would say that, when isMockControlValueAccessor returns false, it is expected.

  • Do you know a way how to change the value or emit changes of the input via MatInput?
  • What is the logic behind your test?

Before, all mock directives and components implemented ControlValueAccessor, but it was wrong, because it gives a false impression, how we can access real ValueAccessor. Therefore, to respect interfaces between a real instance and its mock a fix was added to apply ControlValueAccessor only if the original declaration implements it.

But still, let's discuss it, maybe I missed something.


In the example you provided, the real ControlValueAccessor is DefaultValueAccessor.

Therefore, to simulate its behavior, it should be mocked.

MockBuilder(MyComponent, MyModule)
  .keep(ReactiveFormsModule)
  .mock(DefaultValueAccessor) // <-- this is the change
);
const instance = ngMocks.get(ngMocks.find(['data-testid', 'inputControl']), DefaultValueAccessor);
expect(isMockControlValueAccessor(instance)).toEqual(true);
import { Component, NgModule } from '@angular/core';
import { DefaultValueAccessor, FormControl, ReactiveFormsModule } from '@angular/forms';
import { MatInput, MatInputModule } from '@angular/material/input';
import { isMockControlValueAccessor, MockBuilder, MockRender, ngMocks } from 'ng-mocks';

@Component({
  selector: 'my',
  template: `
    <input data-testid="inputControl" matInput [formControl]="myControl" required>
  `,
})
class MyComponent {
  public readonly myControl = new FormControl();
}

@NgModule({
  declarations: [MyComponent],
  exports: [MyComponent],
  imports: [ReactiveFormsModule, MatInputModule],
})
class MyModule {}

describe('issue-305', () => {
  beforeEach(() => MockBuilder(MyComponent, MyModule).keep(ReactiveFormsModule).mock(DefaultValueAccessor));

  it('correctly mocks matInput', () => {
    MockRender(MyComponent);

    // MatInput does not implement ControlValueAccessor
    const matInput = ngMocks.get(ngMocks.find(['data-testid', 'inputControl']), MatInput);
    expect(isMockControlValueAccessor(matInput)).toEqual(false);

    // DefaultValueAccessor does implement ControlValueAccessor
    const valueAccessor = ngMocks.get(ngMocks.find(['data-testid', 'inputControl']), DefaultValueAccessor);
    expect(isMockControlValueAccessor(valueAccessor)).toEqual(true);
  });
});

@satanTime
Copy link
Member

Ah, doesn't work in no-ivy. I think the best way would be to provide ngMocks.touch(debugEl) and ngMocks.change(debugEl), so nobody needs to know what is under the hood and how control value accessor is set.

@satanTime
Copy link
Member

satanTime commented Feb 27, 2021

Fixed.

Instead of calling (now it works correctly and it is fine to continue using it)

const instance = ngMocks.get(ngMocks.find(['data-testid', 'inputControl']), DefaultValueAccessor);
if (isMockControlValueAccessor(instance)) {
  instance.__simulateChange(123);
}

you can use the next syntax now

const el = ngMocks.find(['data-testid', 'inputControl']);
ngMocks.change(el, 'desiredValue');

satanTime added a commit that referenced this issue Feb 27, 2021
@satanTime
Copy link
Member

v11.9.0 has been released and contains a fix for the issue. Feel free to reopen the issue or to submit a new one if you meet any problems.

@jensweigele
Copy link
Author

jensweigele commented Mar 1, 2021

Works like a charm! THANKS!

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

Successfully merging a pull request may close this issue.

2 participants