I'm trying to test my component injected into a mock class I created. Although the component works when I try to test its existence it returns null.
Injectable Component:
import { Injectable, ElementRef, Renderer2, RendererFactory2 } from '#angular/core';
#Injectable()
export class NgBackdropComponent {
private renderer: Renderer2;
private appElementRef: ElementRef;
message: string = 'Carregando...';
constructor(rendererFactory: RendererFactory2) {
this.renderer = rendererFactory.createRenderer(null, null);
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
}
show() {
const divSpinnerItem1 = this.renderer.createElement('i');
const divSpinnerItem2 = this.renderer.createElement('i');
const divSpinnerItem3 = this.renderer.createElement('i');
const divSpinner = this.renderer.createElement('div');
this.renderer.addClass(divSpinner, 'spinner');
this.renderer.appendChild(divSpinner, divSpinnerItem1);
this.renderer.appendChild(divSpinner, divSpinnerItem2);
this.renderer.appendChild(divSpinner, divSpinnerItem3);
const spanMensagem = this.renderer.createElement('span');
spanMensagem.innerHTML = this.message;
const div = this.renderer.createElement('div');
this.renderer.addClass(div, 'lock-content');
this.renderer.appendChild(div, divSpinner);
this.renderer.appendChild(div, spanMensagem);
this.renderer.appendChild(this.appElementRef.nativeElement, div);
}
hide() {
const elemento = this.appElementRef.nativeElement.querySelector('.lock-content');
if (elemento) {
elemento.remove();
}
}
}
my testing environment:
import { async, ComponentFixture, TestBed } from '#angular/core/testing';
import { NgBackdropComponent } from './ng-backdrop.component';
import { Component } from '#angular/core';
import { By } from '#angular/platform-browser';
#Component({
template: `
<button (click)="clickButton()"></button>
`
})
class MockNgBackdropComponent {
constructor(private backdrop: NgBackdropComponent) { }
clickButton() {
this.backdrop.message = 'Teste BackDrop aesdas';
this.backdrop.show();
console.log('iniciei backdrop');
}
closeBackdrop() {
this.backdrop.hide();
}
}
describe('NgBackdropComponent', () => {
let component: MockNgBackdropComponent;
let fixture: ComponentFixture<MockNgBackdropComponent>;
beforeEach(async(() => {
TestBed.configureTestingModule({
declarations: [MockNgBackdropComponent],
providers: [NgBackdropComponent]
})
.compileComponents();
}));
beforeEach(() => {
fixture = TestBed.createComponent(MockNgBackdropComponent);
component = fixture.componentInstance;
});
describe('Deve injetar', async () => {
it('Deve ter uma div principal', function () {
const btnClick = fixture.nativeElement.querySelector('button');
btnClick.click();
fixture.detectChanges();
const el = fixture.nativeElement.querySelector('.lock-content');
console.log(el);
expect(el).toBeTruthy();
});
});
});
In testing I create a Mock class where I inject my component.
I do not understand why it can not find the class because it exists.
The reason you can't find it in the component is because you did not create it in the component. If you look at this line in your constructor:
this.appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0))
You are creating it on the document directly in the <body> element. If you search for that in your spec, you will find it there. I created a STACKBLITZ to show you what I mean. Here is the spec from that stackblitz:
it('Deve ter uma div principal', () => {
const btnClick = fixture.nativeElement.querySelector('button');
console.log(btnClick);
btnClick.click();
fixture.detectChanges();
const appElementRef = new ElementRef(<Element>document.getElementsByTagName('body').item(0));
const el = appElementRef.nativeElement.querySelector('.lock-content');
expect(el).toBeTruthy();
});
Adding a little more clarification:
If you console.log(appElementRef) you'll notice that its tagName is body, and note the contents of its nativeElement.innerHTML Here is what that would look like "prettyfied":
<body>
<div class="jasmine_html-reporter">
<div class="jasmine-banner"><a class="jasmine-title" href="http://jasmine.github.io/" target="_blank"></a><span
class="jasmine-version">3.3.0</span></div>
<ul class="jasmine-symbol-summary"></ul>
<div class="jasmine-alert"></div>
<div class="jasmine-results">
<div class="jasmine-failures"></div>
</div>
</div>
<div id="nprogress" style="transition: none 0s ease 0s; opacity: 1;">
<div class="bar" role="bar" style="transform: translate3d(0%, 0px, 0px); transition: all 200ms ease 0s;">
<div class="peg"></div>
</div>
</div>
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
<div class="lock-content">
<div class="spinner">
<i></i>
<i></i>
<i></i>
</div>
<span>Teste BackDrop aesdas</span>
</div>
</body>
Note how the button was created within the div with id="root0"? However, the div with class="lock-content" was created right off the root <body> element, and therefore is not within the div of the component.
In fact, you can see this very clearly when you console.log(fixture.nativeElement) and see that the tagName is "div", its innerHTML is <button></button>, and it has two attributes: id: "root0" and ng-version: "7.0.1". Put that all together and it looks like this:
<div id="root0" ng-version="7.0.1">
<button></button>
</div>
So you can clearly see that you cannot find the div you created in the component because you created it outside the component.
I hope this helps.
I think you should use DebugElement, for example:
it('Deve ter uma div principal', function () {
const btnClick = fixture.debugElement.query(By.css('button'));
btnClick.click();
fixture.detectChanges();
const el = fixture.debugElement.query(By.css('.lock-content'));
console.log(el);
expect(el).toBeTruthy();
});
Follow this link for more information.
Related
I'm working on a personal project with redux. My mapStateToProps function seems to me properly written. but when I try to use it to send an object to my store nothing works.
Here's my function:
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
And my reducer:
const initialState = {
orderList : []
}
console.log(initialState);
export default function rootReducer ( state= initialState, action){
const orderList = [...state.orderList];
let position
switch (action.type){
case ADD_ORDER:
return {
orderList : [...state.orderList, action.payload]
};
case DELETE_ORDER:
position = orderList.indexOf(action.payload)
orderList.splice(position, 1)
return {
orderList
}
default:
return state;
}
console.log(state)
}
My entire component as requested:
import React, { Component } from 'react';
import { NavItem } from 'react-bootstrap';
import menu from './menu';
import { connect } from 'react-redux';
import { addOrder} from '../action'
class getOrder extends Component {
state = {
number: `CMD-${Date.now()}`,
order:[],
total: 0 ,
menu:menu,
isPaid: false
}
addItem = (index) => {
const order = [...this.state.order];
const menu = [...this.state.menu];
let total = this.state.total;
const pizza = menu[index];
console.log(pizza);
let ind = order.findIndex((item) =>
item.article == pizza.name
)
if (ind === -1){
order.push({article: pizza.name, price: pizza.price, volume:1})
total = total + order[order.length-1].price
} else if (ind != -1){
order[ind].volume++
total = total + order[ind].price
}
this.setState({
order:order,
total:total
})
console.log("youpiii");
console.log(this.state.total);
console.log(this.state.order);
}
render() {
const menuDisplay= menu.map( (item) => {
return (
<div>
<img onClick={() => this.addItem(item.number)} src={`${process.env.PUBLIC_URL}${item.picture}`} alt="picture" />
<div className="tagPrice">
<p>{item.name}</p>
<p>{item.price} €</p>
</div>
</div>
)
});
const currentOrder = [...this.state.order]
const orderDisplay = currentOrder.map((item) => {
let price = item.price*item.volume;
console.log(price);
return (
<div>
<h1>{item.volume} × {item.article}</h1>
<p>{price} €</p>
</div>
)
} );
return (
<div className="takeOrder">
<div className="orderban">
<h1>Pizza Reflex</h1>
</div>
<div>
<div className="menuDisplay">
{menuDisplay}
</div>
<div className="orderBoard">
<h1>Détail de la commande N°{this.state.number}</h1>
{orderDisplay}
<div className="total">
<h2>Soit un total de {this.state.total} € </h2>
</div>
<div className="recordOrder">
<button onclick={() => this.props.addOrder(this.state)}>Enregistrer et lancer la commande</button>
</div>
</div>
</div>
</div>
);
}
}
const mapDispatchToProps = dispatch => {
return {
addOrder: (item) => {
dispatch(addOrder(item));
}
}
}
export default connect ( mapDispatchToProps) (getOrder);
Can someone tell me what I've missed ?
Thanks for your help !
What you are missing is more of your code it can not be solved with what you have.
In more details what I need is the this.state , combinedReducer
The easiest fix you can do now is changing yow mapDispatchToProps works better if it is an obj
const mapStateToProps = (state) => {
return {
// here you specified the properties you want to pass yow component fom the state
}
};
const mapDispatchToProps = {action1, action2};
export default connect ( mapDispatchToProps) (getOrder);
connectreceives two params mapStateToProps and mapDispatchToProps,
mapDispatchToProps is optional, but mapStateToProps is mandatory, there for you need to specified, if your are not going to pass anything you need to pass a null value
export default connect (null, mapDispatchToProps) (getOrder);
also avoid exporting components without a name
example
function MyButton () {}
const MyButtonConnect = connect(state, dispatch)(MyButton);
export default MyButtonConnect
I have installed npm i ckeditor4 to my stencil project and I have used it like this. But Im not getting the ckeditor, tell me where to add the script tag I am completely new to stencil
ui-editor.tsx
import { Component, h } from '#stencil/core';
#Component({
tag: 'ui-editor',
styleUrl: 'style.scss',
shadow: true
})
export class UiEditor {
render() {
return (
<div id="editor">
<p>This is the editor content.</p>
</div>
)
}
}
As said in the documentation https://www.npmjs.com/package/ckeditor4 where should I add the scripts
<script src="./node_modules/ckeditor4/ckeditor.js"></script>
<script>
CKEDITOR.replace( 'editor' );
</script>
Try removing the script tag from your index.html file. The following component will automatically add the script tag from unpkg.
Example on webcomponents.dev
import { h, Component, State, Host } from "#stencil/core";
#Component({
tag: "ck-editor"
})
export class CkEditor {
_textarea: HTMLTextAreaElement;
componentWillLoad() {
return this.appendScript();
}
componentDidLoad() {
//#ts-ignore
let editor = CKEDITOR.replace(this._textarea, {
width: "99%",
height: "300px",
});
}
private async submit() {
// #ts-ignore
console.log(
CKEDITOR.instances[
this._textarea.nextSibling.id.replace("cke_", "")
].getData()
);
}
appendScript() {
return new Promise((resolve) => {
if (document.getElementById("ckeditor-script")) {
resolve();
return;
}
const ckeditorScript = document.createElement("script");
ckeditorScript.id = "ckeditor-script";
ckeditorScript.src = "https://unpkg.com/ckeditor4#4.14.1/ckeditor.js";
ckeditorScript.onload = () => resolve();
document.body.appendChild(ckeditorScript);
});
}
render() {
return (
<Host>
<textarea ref={(el) => (this._textarea = el)}></textarea>
<button onClick={() => this.submit()}>Submit</button>
</Host>
);
}
}
You should be able to import ckeditor but I haven't tested to see how that handles in rollup. The project I was recently working on was already loading ckeditor from unpkg so we went that direction instead.
Is there anyway to know how many children a named slot contains? In my Stencil component I have something like this in my render function:
<div class="content">
<slot name="content"></slot>
</div>
What I want to do is style the div.content differently depending on how many children are inside the slot. If there are no children in the slot, then div.content's style.display='none', otherwise, I have a bunch of styles applied to div.content that make the children appear correctly on the screen.
I tried doing:
const divEl = root.querySelector( 'div.content' );
if( divEl instanceof HTMLElement ) {
const slotEl = divEl.firstElementChild;
const hasChildren = slotEl && slotEl.childElementCount > 0;
if( !hasChildren ) {
divEl.style.display = 'none';
}
}
however this is always reporting hasChildren = false even when I have items inserted into the slot.
If you are querying the host element you will get all the slotted content inside of it. That means that the host element's children are going to be all the content that will be injected into the slot.
For example try to use the following code to see it in action:
import {Component, Element, State} from '#stencil/core';
#Component({
tag: 'my-component',
styleUrl: 'my-component.css',
shadow: true
})
export class MyComponent {
#Element() host: HTMLElement;
#State() childrenData: any = {};
componentDidLoad() {
let slotted = this.host.children;
this.childrenData = { hasChildren: slotted && slotted.length > 0, numberOfChildren: slotted && slotted.length };
}
render() {
return (
<div class="content">
<slot name="content"></slot>
<div>
Slot has children: {this.childrenData.hasChildren ? 'true' : 'false'}
</div>
<div>
Number of children: {this.childrenData.numberOfChildren}
</div>
</div>);
}
}
The accepted solution is actually not the correct way to do it. Even the code example is wrong. It is using a named slot name="content". Only elements with slot="content" attribute from the light DOM will be slotted into that slot; hence simply checking this.host.children is not sufficient at all.
Instead, you should work with the slotchange event (which also has the benefit of properly reflecting dynamic changes):
import {Component, Element, State} from '#stencil/core';
export type TSlotInfo = {
hasSlottedElements?: boolean;
numberOfSlottedElements?: number;
}
#Component({
tag: 'my-component',
shadow: true
})
export class MyComponent {
#Element() host: HTMLElement;
#State() slotInfo: TSlotInfo = {};
handleSlotChange = (event: Event) => {
let assignedElementCount = event.currentTarget.assignedElements().length;
this.slotInfo = {
hasSlottedElements: Boolean(assignedElementCount),
numberOfSlottedElements: assignedElementCount,
}
}
render() {
return (
<div class="content">
<slot name="content" onslotchange={this.handleSlotChange}></slot>
<div>
Slot is populated: {this.slotInfo.hasSlottedElements}
</div>
<div>
Number of slotted elements: {this.slotInfo.numberOfSlottedElements}
</div>
</div>);
}
}
I have a service which is publishing objects on an observable, but when I update an array using a subscription, the new object does not display on the list (html):
export class PublicationService {
// ... more code ...
getAllPapersOnState(state: string): Observable<Paper> {
return Observable.create(observer => {
this.WF_SC.deployed().then(instance => {
// State changed watcher to update on-live the observable
const event = instance.AssetStateChanged({});
event.on('data', (data) => {
console.log('StateChanged catched!');
this.getPaper((data['args']['assetAddress'])).then((paper) => observer.next(paper));
});
// TODO Filter by asset type
return instance.findAssetsByState.call(state);
}).then(addresses => {
addresses.forEach((address) => this.getPaper(address).then((paper) => observer.next(paper)));
});
});
}
}
export class HomeComponent implements OnInit, OnDestroy {
papersSubmitted$:Observable<DataCard>;
papersOnReview$:Observable<DataCard>;
papersPublished$:Observable<DataCard>;
constructor(private publicationService: PublicationService) { }
ngOnInit() {
this.papersSubmitted$ = this.publicationService.getAllPapersOnState("Submitted").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', 'Review'))
);
this.papersOnReview$ = this.publicationService.getAllPapersOnState("OnReview").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', 'Accept'))
);
this.papersPublished$ = this.publicationService.getAllPapersOnState("Published").pipe(
map(paper => HomeComponent.paperToCard(paper,'Read', ''))
);
}
// ... more code ...
}
<app-cardlist
[title]="'Published'"
[description]="'Last science papers published. Yay!'"
[collection$]="papersPublished$"
(clickActionCard)="clickActionCardHandlePublished($event)"></app-cardlist>
<app-cardlist
[title]="'On Review'"
[description]="'Last papers on review. On publish way!'"
[collection$]="papersOnReview$"
(clickActionCard)="clickActionCardHandleOnReview($event)"></app-cardlist>
<app-cardlist
[title]="'Submitted'"
[description]="'Last papers submitted for reviewing. Be the first one to check them!'"
[collection$]="papersSubmitted$"
(clickActionCard)="clickActionCardHandleSubmitted($event)"></app-cardlist>
export class CardlistComponent implements OnInit {
#Input() title;
#Input() description;
#Input() collection$: Observable<DataCard>;
items:DataCard[] = [];
subscription:Subscription;
#Output() clickActionCard: EventEmitter<any> = new EventEmitter();
constructor() { }
ngOnInit() {
this.subscription = this.collection$.subscribe((data_card:DataCard) => {
console.log(data_card);
this.items.push(data_card);
console.log(this.items);
})
}
ngOnDestroy() {
this.subscription.unsubscribe();
}
}
<div *ngIf="(items)" class="card-deck">
<div *ngFor="let item of items" class="card" style="width: 18rem;">
<div class="card-body">
<div class="card-body">
<h5 class="card-title">{{item['title']}}</h5>
<h6 class="card-subtitle mb-2 text-muted">{{item['subtitle']}}</h6>
<p class="card-text">{{item['description']}}</p>
{{item['action_1_name']}}
{{item['action_2_name']}}
</div>
</div>
</div>
</div>
The new object published on observable $collection should be displayed on the CardList but nothing is refreshed although console.log(data_card) show the new object.
Repository code: HomeComponent, CardListComponent
I created a Angular2 app that allows the user to upload images. I want to implement a preview option. However, when i try to imperilment it the image doesn't show up. How do i achieve this feature?
UploadComponent.ts
import * as ng from '#angular/core';
//import { UPLOAD_DIRECTIVES } from 'ng2-uploader';
import {UploadService} from '../services/upload.service';
#ng.Component({
selector: 'my-upload',
providers:[UploadService],
template: require('./upload.html')
})
export class UploadComponent {
progress:any;
logo:any;
filesToUpload: Array<File>;
constructor(public us:UploadService){
this.filesToUpload = [];
}
upload() {
this.us.makeFileRequest("http://localhost:5000/api/SampleData/Upload", this.filesToUpload)
.then((result) => {
console.log(result);
}, (error) => {
console.error(error);
});
}
onFileChange(fileInput: any){
this.logo = fileInput.target.files[0];
}
}
Upload.html
<h2>Upload</h2>
<input type="file" (change)="onFileChange($event)" placeholder="Upload image..." />
<button type="button" (click)="upload()">Upload</button>
<img [src]="logo" alt="Preivew">
The way you try it, you don't get the image URL with fileInput.target.files[0], but an object.
To get an image URL, you can use FileReader (documentation here)
onFileChange(fileInput: any){
this.logo = fileInput.target.files[0];
let reader = new FileReader();
reader.onload = (e: any) => {
this.logo = e.target.result;
}
reader.readAsDataURL(fileInput.target.files[0]);
}
filesToUpload: Array<File> = [];
url: any;
image: any;
//file change event
filechange(fileInput: any) {
this.filesToUpload = <Array<File>>fileInput.target.files;
this.image = fileInput.target.files[0]['name'];
this.readurl_file(event);
}
//read url of the file
readurl_file(event) {
if (event.target.files && event.target.files[0]) {
const reader = new FileReader();
reader.onload = (eve: any) => {
this.url = eve.target.result;
};
reader.readAsDataURL(event.target.files[0]);
}
}
<div class="form-group">
<label for="image">Image</label>
<input type="file" class="form-control" (change)="filechange($event)" placeholder="Upload file..." >
</div>
<div class="container">
<img [src]="url">
</div>
Using FileReader is not a good practice. If an image is too large it can crash your browser because onload function load whole image in RAM.
Better approach is to use:
url = URL.createObjectURL($event.target.files[0]);
Then, show it out with DomSanitizer:
this.sanitizer.bypassSecurityTrustUrl(url)
So in ts:
constructor(private sanitizer: DomSanitizer) {}
onFileChange(fileInput: any){
this.url = URL.createObjectURL($event.target.files[0]);
}
get previewUrl(): SafeUrl {
return this.sanitizer.bypassSecurityTrustUrl(this.url);
}
And in html:
<img [src]="previewUrl"/>