How to use setValue and patchValue in testing angular 2 reactive forms - forms

I can't seem to get the correct dependencies in my spec file for testing a new form I've created.
I have 2 passing tests but the 3rd comes to a hault when I use setValue or patchValue to setup my test form data.
Karma in browser gives: TypeError: Cannot read property 'patchValue' of undefined
import { TestBed, async } from '#angular/core/testing';
import { Component, ViewChild } from '#angular/core';
import { MyComponent } from './my.component';
import { NgForm, ReactiveFormsModule, FormGroup, FormControl, FormArray } from '#angular/forms';
describe('My component test: ', () => {
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [ReactiveFormsModule],
providers: [] // which provider for setValue/patchValue?
});
let fixture = TestBed.createComponent(MyComponent);
let myComponent = fixture.debugElement.componentInstance;
});
it('sending form values to the service onSubmit method', () => {
// TypeError: Cannot read property 'patchValue' of undefined
this.jobForm.patchValue({'title': 'a spec title'});
});
The question is: How do I use setValue/patchValue to setup my form object for a test form submission?

If you're using reactive forms, then you should have the FormGroup as a member of the component. This is what you need to access
let component;
let fixture;
beforeEach(() => {
TestBed.configureTestingModule({
declarations: [MyComponent],
imports: [ReactiveFormsModule],
providers: [] // which provider for setValue/patchValue?
});
fixture = TestBed.createComponent(MyComponent);
component = fixture.debugElement.componentInstance;
fixture.detectChanges()
});
it('sending form values to the service onSubmit method', () => {
// assuming the property on the component is named jobForm
const form = component.jobForm;
form.patchValue({'title': 'a spec title'});
});

Related

reference error when I try to get a value from other slice redux toolkit?

I am trying to import a value form other slice that has some user information, any idea why I am getting this nasty error ? I read it is normal to request data from other slices, the error seem to be like the slice cannot find the store... below is my code structure, my store is at the top of my app, does this getState function works in a component only and not in slice to other slice .
import React from 'react';
import ReactDOM from 'react-dom';
import { HashRouter } from 'react-router-dom';
import App from './App';
import './index.css';
// Redux Tool Kit
import { store } from './app/store';
import { Provider } from 'react-redux';
ReactDOM.render(
<React.StrictMode>
<Provider store={store}>
<App />
</Provider>
</React.StrictMode>,
document.getElementById('root')
);
import {
RootState,
store
} from './store';
import {
createSlice,
PayloadAction
} from '#reduxjs/toolkit';
export interface miscState {
dayNumber: true,
dayOfWeek: false,
};
export const miscSlice = createSlice({
name: 'misc',
initialState,
reducers: {
setDisplayDay: (state, action: PayloadAction < {
bool: boolean;type: string
} > ) => {
const {
user,
uid
} = store.getState().global.currentUser;
const setDisplay = async() => {
const docRef = doc(db, colDynamic(user)[0], uid);
await updateDoc(docRef, {
[action.payload.type]: action.payload.bool,
});
};
},
},
});
// Values
export const MiscCurrentState = (state: RootState) => state.misc;
// Action creators are generated for each case reducer function
export const {
setDisplayDay
} = miscSlice.actions;
export default miscSlice.reducer;
import { configureStore } from '#reduxjs/toolkit';
// Global
import globalReducer from './globalSlice';
// Misc
import miscReducer from './miscSlice';
export const store = configureStore({
reducer: {
global: globalReducer,
misc: miscReducer,
},
});
// Infer the `RootState` and `AppDispatch` types from the store itself
export type RootState = ReturnType<typeof store.getState>;
// Inferred type: {posts: PostsState, comments: CommentsState, users: UsersState}
export type AppDispatch = typeof store.dispatch;
In Redux, you are not allowed to access the store from within a reducer. A reducer has to be a pure function that only reads the variables passed into it - so all information you have is the slice's own state and the action being dispatched. You are not allowed to read a global variable, have any kind of side effect or read from the global Redux store to get the data of another slice.

Testing Error in Karma: Can't bind to 'cdkObserveContentDisabled' since it isn't a known property of 'label'

Setting up basic testing with parent component using a child FORM component. Getting the following error,
Failed: Template parse errors:
Can't bind to 'cdkObserveContentDisabled' since it isn't a known property of 'label'. ("m-field-label-wrapper">][cdkObserveContentDisabled]="appearance != 'outline'" [id]="_labelId" [attr.for]="_control.id" [attr."): ng:///DynamicTestModule/MatFormField.html#0:930
Error: Template parse errors:
Can't bind to 'cdkObserveContentDisabled' since it isn't a known property of 'label'
component.spec.ts is
import { CreatepageComponent } from './createpage.component';
import { NavbarComponent } from '../common/navbar/navbar.component';
import { TitleComponent } from '../common/title/title.component';
import { MobileTitleComponent } from '../common/mobile-title/mobile-title.component';
import { FormComponent } from '../common/form/form.component';
import { FooterComponent } from '../common/footer/footer.component';
import { MapComponent } from '../common/map/map.component';
import { SvgComponent } from '../common/svg/svg.component';
import { SvgDefinitionsComponent } from '../common/svg/svg-definitions/svg-definitions.component';
import { LinkComponent } from '../common/link/link.component';
import { DropdownMenuComponent } from '../common/dropdown-menu/dropdown-menu.component';
import { RouterTestingModule } from '#angular/router/testing';
import { RouterModule } from '#angular/router';
import { FormsModule, ReactiveFormsModule } from '#angular/forms';
import { ValidationErrorsComponent } from '../common/form/validation-errors/validation-errors.component';
import {
MatError,
MatFormFieldModule,
MatInputModule,
MatFormField,
MatLabel,
} from '#angular/material';
import { ButtonComponent } from '../common/button/button.component';
describe('CreatepageComponent', () => {
let component: CreatepageComponent;
let fixture: ComponentFixture<CreatepageComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [
CreatepageComponent,
NavbarComponent,
TitleComponent,
MobileTitleComponent,
FormComponent,
FooterComponent,
SvgComponent,
SvgDefinitionsComponent,
LinkComponent,
DropdownMenuComponent,
ValidationErrorsComponent,
MatError,
MatFormField,
ButtonComponent,
MapComponent
],
providers: [
RouterModule,
ReactiveFormsModule,
FormsModule,
MatFormFieldModule,
MatInputModule,
MatLabel
],
imports: [FormsModule, ReactiveFormsModule, RouterTestingModule]
}).compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(CreatepageComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
});
Expected: Karma passes all tests
Actual: Karma responds with errors in any component with the form as a child.
Just ran into this myself. It looks like you need to import ObserversModule from '#angular/cdk/observers' in your spec.ts file, and include it in the 'imports' section under the configureTestingModule function.
import {ObserversModule} from '#angular/cdk/observers'; <== HERE
---------------------------
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [],
imports: [ObserversModule] <== HERE
},)
.compileComponents();
}));
Seen here:
https://material.angular.io/cdk/observers/api

how to test a component that having a form with form controls as #Input()

I have 2 components. I declared a form with form controls in parent component. I am passing this form to child component.
Parent component:
this.partyDetailsForm = new FormGroup({
partySymbol: new FormControl('', []),
partyFullName: new FormControl('', Validators.compose([Validators.required,
CustomValidator.nameValidator])),
partyAbbreviation: new FormControl('', Validators.compose([Validators.required,
CustomValidator.nameValidator]))
});
Html:
now I am trying to test the form in the child component.. but it is not coming...
how can I test it
this error is coming
PhantomJS 2.1.1 (Windows 8.0.0) PartyDetailsComponent should create FAILED
[INFO] TypeError: undefined is not an object
I got this answer...
I am declaring the form in the parent so the form is not available in the
child component so that exception is coming...
The solution is we have to declare a form and pass it to the child component
in spec file #Component... like this
describe('CustomComponent', () => {
// let component: CustomComponent;
// let fixture: ComponentFixture<CustomComponent>;
let component: TestHostComponent;
let fixture: ComponentFixture<TestHostComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [CustomComponent, TestHostComponent],
imports: [FormsModule, ReactiveFormsModule, RouterTestingModule,
HttpModule],
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(TestHostComponent);
component = fixture.componentInstance;
fixture.detectChanges();
});
it('should create', () => {
expect(component).toBeTruthy();
});
#Component({
selector: `host-component`,
template: ` <app-custom [fieldOneName]="'test'" [color]="'red'"
[formName]="custom_Form" [fieldOneDefaultMessage]="'enterUserName'"
[firstFieldType]="'text'"
[fieldOneInvalidMessage]=" 'Person name Start with Alphabeat' "
[fieldOneMandatoryMessage]="'Person Name is Mandatory'"
[label]="'test'" [required]="true">
</app-custom> `
})
class TestHostComponent {
custom_Form: FormGroup
ngOnInit(){
this.custom_Form = new FormGroup({
test: new FormControl('', Validators.compose([Validators.required,
CustomValidator.emailValidator]))
});
}
}
});

Angular 2: Custom Input Component with Validation (reactive/model driven approach)

I have to create a component with custom input element (and more elements inside the component, but its not the problem and not part of the example here) with reactive / model driven approach and validation inside and outside the component.
I already created the component, it works fine, my problem is that both formControl's (inside child and parent) are not in sync when it comes to validation or states like touched. For example if you type in a string with more then 10 characters, the form control inside the form is stil valid.
Plunkr
//our root app component
import {Component, Input} from '#angular/core'
import {
FormControl,
FormGroup,
ControlValueAccessor,
NG_VALUE_ACCESSOR,
Validators
} from '#angular/forms';
#Component({
selector: 'my-child',
template: `
<h1>Child</h1>
<input [formControl]="childControl">
`,
providers: [
{provide: NG_VALUE_ACCESSOR, useExisting: Child, multi: true}
]
})
export class Child implements ControlValueAccessor {
childControl = new FormControl('', Validators.maxLength(10));
writeValue(value: any) {
this.childControl.setValue(value);
}
registerOnChange(fn: (value: any) => void) {
this.childControl.valueChanges.subscribe(fn);
}
registerOnTouched() {}
}
#Component({
selector: 'my-app',
template: `
<div>
<h4>Hello {{name}}</h4>
<form [formGroup]="form" (ngSubmit)="sayHello()">
<my-child formControlName="username"></my-child>
<button type="submit">Register</button>
</form>
{{form.value | json }}
</div>
`
})
export class App {
form = new FormGroup({
username: new FormControl('username', Validators.required)
});
constructor() {
this.name = 'Angular2';
}
sayHello() {
console.log(this.form.controls['username'])
}
}
I have no clue how to solve this problem in a proper way
There exists a Validator interface from Angular to forward the validation to the parent. You need to use it and provide NG_VALIDATORS to the component decorator's providers array with a forwardRef:
{
provide: NG_VALIDATORS,
useExisting: CustomInputComponent,
multi: true
}
your component needs to implement the Validator interface:
class CustomInputComponent implements ControlValueAccessor, Validator,...
and you have to provide implementations of the Validator interfaces' methods, at least of the validate method:
validate(control: AbstractControl): ValidationErrors | null {
return this.formGroup.controls['anyControl'].invalid ? { 'anyControlInvalid': true } : null;
}
there is also another to sync the validators when their inputs change:
registerOnValidatorChange(fn: () => void): void {
this.validatorChange = fn;
}

AngularJS 2 Typescript interface

I have a service for handling users operations and an interface for the user object.
user.service.ts
import {Injectable} from 'angular2/core';
export interface User {
name: string;
email?: string;
picture?: string;
}
#Injectable()
export class UserService {
me: User;
constructor() {
}
setUser(user: User) {
this.me = user;
}
}
In my login component I try to set the user with the profile returned from the login service but I get this error:
Property 'firstName' does not exist on type '{}'.
login.component.ts
import {Component} from 'angular2/core';
import {User, UserService} from './services/user.service';
import {LinkedinService} from './services/linkedin.service';
declare const IN: any;
console.log('`Login` component loaded asynchronously');
#Component({
selector: 'Login',
providers: [
UserService,
LinkedinService
],
template: require('./login.html')
})
export class LoginComponent {
me: User;
constructor(public linkedinService: LinkedinService, public userService: UserService) {
this.me = userService.me;
}
ngOnInit() {
console.log('hello `Login` component');
}
login() {
this.linkedinService.login()
.then(() => this.linkedinService.getMe()
.then(profile => this.userService.setUser({ name: profile.firstName })));
}
}
linkedin.service.ts
import {Injectable} from 'angular2/core';
declare const IN: any;
#Injectable()
export class LinkedinService {
constructor() {
IN.init({
api_key: 'xxxxxxxxxxx',
authorize: true
});
}
login() {
return new Promise((resolve, reject) => {
IN.User.authorize(() => resolve());
});
}
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
}
I'm trying to import the User interface from UserService and use inside the LoginComponent but I don't know what I'm doing wrong. Any idea? I am not sure if I have to use the User interface inside the LoginComponent, is that right?
Narrow in on the code :
.then(() => this.linkedinService.getMe())
.then(profile => this.userService.setUser({ name: profile.firstName })));
The type of profile is driven by the response of this.linkedinService.getMe(). Seems like it is something like Promise<{}>. It does not have the member firstName. Hence the error:
Property 'firstName' does not exist on type '{}'.
Fix
Check to the code / signatures of linkedinService. This has nothing to do with the user.service.ts file that the question contains 🌹
Update
Focus in on the code:
getMe() {
return new Promise((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}
The value returned is driven by what is being passed to resolve. So make sure profile.values[0] has the right type. Alternatively provide the hint to the compiler:
getMe() {
return new Promise<{firstName:string}>((resolve, reject) => {
IN.API.Profile('me').result((profile) => resolve(profile.values[0]));
});
}