can't test ember component that appends a div to the dom - ember-qunit

I have a ember-cli-addon that adds a component which appends a div with a specific class to the consuming application. I'm trying to test this integration and having difficulty to setup the test.
I have tried to unit test the component as well but that doesn't work quite as expected. Here's what I've tried:
I've copied the component from my addon directory to tests/dummy/components/jquery-backstretch.js to make it available to the dummy test application:
jquery-backstretch.js
import Ember from 'ember';
export default Ember.Component.extend({
tagName: 'jquery-backstretch',
image: null,
selector: 'body',
fade: 0,
duration: 5000,
centeredX: true,
centeredY: true,
setupJquerybackstretch: function() {
var image = this.get('image');
if (! Ember.isEmpty(image)) {
var options = {
fade: this.get('fade'),
centeredX: this.get('centeredX'),
centeredY: this.get('centeredY')
};
var jqbsImage;
if (Ember.typeOf(image) === 'string') {
jqbsImage = 'assets/' + image;
} else if (Ember.isArray(image)) {
options.duration = this.get('duration');
jqbsImage = image.map(function(img) {return 'assets/' + img;});
} else {
Ember.Logger.error('Ember JQuery-Backstretch: Unsupported "image" format.');
}
Ember.$(this.get('selector')).backstretch(jqbsImage, options);
} else {
Ember.Logger.error('Ember JQuery-Backstretch: image not supplied.');
}
}.on('didInsertElement'),
teardownJquerybackstretch: function() {
Ember.$(this.get('selector')).backstretch('destroy');
}.on('willDestroyElement')
});
this causes the component to append the img to the body of the test page and not to #ember-testing-container, changing the selector to #ember-testingn-container puts the img in the right place but the test can't find it:
tests/acceptance/jquery-backstretch.js
import Ember from 'ember';
import {
module,
test
} from 'qunit';
import startApp from '../../tests/helpers/start-app';
var application;
module('Acceptance: JqueryBackstretch', {
beforeEach: function() {
application = startApp();
},
afterEach: function() {
// Ember.run(application, 'destroy');
}
});
test('backstretch added to body tag', function(assert) {
visit('/');
andThen(function() {
assert.equal(find('.backstretch > img').length, 1, 'Backstretch found');
});
});
application.hbs
<h2 id="title">Welcome to Ember.js</h2>
{{jquery-backstretch image="img/emberjs.png"}}
{{outlet}}
the test is not passing, it can't find the image, I also tried to test the component and append it to the DOM then test to see if it's in the DOM but that didn't yield better results.
How can I test this please?

Related

Can't get html element using js file in SPFX

I am trying to build dynamic content from a SharePoint list using SPFX. I'd like to use jQuery to build an accordion view of the data. The issue is that I can't even seem to get the element once the page is rendered.
In my code I am requiring a file called ota.js with the following code:
console.log('Start');
function otaExpand(){
console.log('otaExpand Function Called');
let spListContainer = document.getElementById('spListContainer');
console.log(spListContainer);
}
window.addEventListener("load", otaExpand());
In my ts file this is my render method:
public render(): void {
this.domElement.innerHTML = `
<div>
<div id="spListContainer">TEST</div>
</div>
`;
//this._renderListAsync();
//($('.accordion', this.domElement) as any).accordion();
}
When I review the console, I get my messages, but the element itself comes back as null.
console.log
I am using SharePoint 2019 on premise with the following configuration.
+-- #microsoft/generator-sharepoint#1.10.0
+-- gulp-cli#2.3.0
`-- yo#2.0.6
node --version
v8.17.0
I should also mention I am using TypeScript with no JavaScript framework.
Does anyone know why I can't access this element from my js file?
Thanks!
My overall goal is to call list data and apply an accordion style to it (https://jqueryui.com/accordion), but I can't even get passed capturing the element to change it.
I've tried calling my code from a js file as well as trying to put the code directly in the html. Neither worked.
OK, I finally figured out what I was doing wrong. I was calling my jQuery in the render() method rather than in _renderList where this.domElement actually makes sense.
Here's my code in case anyone wants to avoid the pain I put myself through. This allows you to specify a list in the site and you just need to add the fields you want to display.
import { Version } from '#microsoft/sp-core-library';
import {
BaseClientSideWebPart,
IPropertyPaneChoiceGroupOption,
IPropertyPaneConfiguration,
PropertyPaneChoiceGroup,
PropertyPaneCustomField,
PropertyPaneTextField
} from '#microsoft/sp-webpart-base';
import { escape } from '#microsoft/sp-lodash-subset';
import styles from './GetSpListItemsWebPart.module.scss';
import * as strings from 'GetSpListItemsWebPartStrings';
import {
SPHttpClient,
SPHttpClientResponse
} from '#microsoft/sp-http';
import * as jQuery from 'jquery';
import 'jqueryui';
import { SPComponentLoader } from '#microsoft/sp-loader';
import PropertyPane from '#microsoft/sp-webpart-base/lib/propertyPane/propertyPane/PropertyPane';
export interface IGetSpListItemsWebPartProps {
title: string;
description: string;
listField: string;
}
export interface ISPLists {
value: ISPList[];
}
export interface ISPList {
ID: string;
Title: string;
Website: {
Description : string,
Url : string
};
Description : string;
}
export default class GetSpListItemsWebPart extends BaseClientSideWebPart<IGetSpListItemsWebPartProps> {
private _getListData(): Promise<ISPLists> {
return this.context.spHttpClient.get(this.context.pageContext.web.absoluteUrl + "/_api/web/lists/GetByTitle('" + this.properties.listField + "')/Items",SPHttpClient.configurations.v1)
.then((response: SPHttpClientResponse) => {
return response.json();
});
}
private _renderListAsync(): void {
this._getListData()
.then((response) => {
this._renderList(response.value);
})
.catch(() => {});
}
private _renderList(items: ISPList[]): void {
let listData = `
<h1>${this.properties.title}</h1>
<h2>${this.properties.description}</h2>
<div class="accordion">
`;
items.forEach((item: ISPList) => {
let Description : string;
item.Description ? Description = item.Description : Description = "";
listData += `
<h3> ${item.Title}</h3>
<div>
<table>
<tr>
<td>OTA URL</td>
<td>${item.Website.Description}</td>
</tr>
<tr>
<td>Description</td>
<td>${Description}</td>
</tr>
</table>
</div>
`;
});
listData += '</div>';
this.domElement.innerHTML = listData;
const accordionOptions: JQueryUI.AccordionOptions = {
animate: true,
collapsible: true,
icons: {
header: 'ui-icon-circle-arrow-e',
activeHeader: 'ui-icon-circle-arrow-s'
}
};
jQuery('.accordion', this.domElement).accordion(accordionOptions);
}
public render(): void {
this._renderListAsync();
}
protected getPropertyPaneConfiguration(): IPropertyPaneConfiguration {
return {
pages: [
{
header: {
description: strings.PropertyPaneDescription
},
groups: [
{
groupName: strings.BasicGroupName,
groupFields: [
PropertyPaneTextField('title',{
label: strings.TitleFieldLabel
}),
PropertyPaneTextField('description', {
label: strings.DescriptionFieldLabel
}),
PropertyPaneTextField('listField', {
label: strings.ListFieldLabel
})
]
}
]
}
]
};
}
public constructor() {
super();
SPComponentLoader.loadCss('//code.jquery.com/ui/1.11.4/themes/smoothness/jquery-ui.css');
}
}
Your code from the "ota.js" file is probably called before your HTML is initialized (i.e. before the "render()" function is executed). To make sure this is the case, you could add log to the "render()" function to see when it's called.
In other words, "window.load" event happens long before "render()" function is called. This is how web parts are loaded - dynamically after full load of the page. Or "window.load" does not happen at all - web parts may be loaded by the user when using the page designer, i.e. without page reload.
To fix the issue, you should get the element after it's created, i.e. after the "render()" function creates the element you are trying to get.

How to use pdfjs-dist in vue cli typescript project?

I'm having issues getting pdfjs-dist working in a vue typescript cli project.
As soon as I try to use the pdfjs-dist I get this error
As far as I can guess it's an issue with my vue.config.js Or something else.
I'm struggling to progress past this point and haven't seen many examples with vue cli and webpack. There are some webpack rules people have posted, but I wasn't getting much progress on them.
Module parse failed: Unexpected token (2205:45)
You may need an appropriate loader to handle this file type, currently no loaders are configured to process this file. See https://webpack.js.org/concepts#loaders
| intent: renderingIntent,
| renderInteractiveForms: renderInteractiveForms === true,
> annotationStorage: annotationStorage?.serializable || null
| });
| }
Example
package.json
{
"scripts": {
"serve": "vue-cli-service serve",
"build": "vue-cli-service build",
"watch": "vue-cli-service build --mode development --watch"
},
"dependencies": {
"#types/pdfjs-dist": "^2.7.4",
"pdfjs-dist": "^2.8.335",
}
}
component
<template>
<div class="pdfviewer">
<canvas id="pdfPage"></canvas>
<div class="textLayer" id="text-layer"></div>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import * as PDFJS from "pdfjs-dist";
export default Vue.extend({
name: "PdfViewer",
props: { pdfBase64: String },
methods: {
base64ToUint8Array(base64: string) {
const raw = atob(base64); // convert base 64 string to raw string
const uint8Array = new Uint8Array(raw.length);
for (let i = 0; i < raw.length; i++) {
uint8Array[i] = raw.charCodeAt(i);
}
return uint8Array;
},
async getPdf() {
const container = document.getElementById("pdfPage");
let pdfData = this.base64ToUint8Array(this.pdfBase64);
pdfData = pdfData.replace("data:application/pdf;base64,", "");
const loadingTask = PDFJS.getDocument(pdfData);
loadingTask.promise.then(function(pdf) {
const pageRetrieved = pdf.getPage(1);
pageRetrieved.then(function(page) {
const scale: any = 1;
const viewport = page.getViewport(scale);
const canvas = document.getElementById("pdfPage") as HTMLCanvasElement;
if (canvas) {
const context = canvas.getContext("2d");
canvas.height = viewport.height;
canvas.width = viewport.width;
page.render({ canvasContext: context as any, viewport: viewport });
}
});
})
}
},
mounted() {
// load pdf into canvas
this.getPdf()
}
});
</script>
Seems to be the only issue I had was the current version I was using "pdfjs-dist": "2.0.943" Seems to work just fine. I've now changed it to 2.3.200. Which is the most recent one working with this setting. Also text alignment works on this.
Notes on versions
Must change PDFJS.GlobalWorkerOptions.workerSrc ="https://cdn.jsdelivr.net/npm/pdfjs-dist#2.5.207/build/pdf.worker.min.js"; to match version imported
2.0.943 started at all the way to 2.3.200
2.5.207 won't fail to build, but fails to render the pdf in the canvas
2.7.570 onward fails to build w/ the error mentioned above. I suspect I need some webpack change in vue.config.js
I also had to add a watch
watch: {
src: function(newValue: string | null, oldValue: string | null) {
console.log("src update");
console.log(`Updating from`);
console.log(oldValue);
console.log(`to`);
console.log(newValue);
// TODO: if empty clear canvas
this.getPdf();
}
},
text layer
const txtLayer = document.getElementById(
"text-layer"
) as HTMLDivElement;
txtLayer.style.height = viewport.height + "px";
txtLayer.style.width = viewport.height + "px";
txtLayer.style.top = canvas.offsetTop + "px";
txtLayer.style.left = canvas.offsetLeft + "px";
page.render({
canvasContext: context as any,
viewport: viewport
});
page.getTextContent().then(function(textContent) {
console.log(textContent);
PDFJS.renderTextLayer({
textContent: textContent,
container: txtLayer,
viewport: viewport
});
});

Get link of an image in react-photo-gallery?

I'm still a beginner in reactJS (using nodeJS backend) and I have to create a website to manage my collections. I don't know if what I'm going to ask you is feasible, but it probably is.
So I'm using a react component, react-photo-gallery. It's a component where you can use url links and it mixes them together to create a beautiful gallery.
https://github.com/neptunian/react-photo-gallery
I'm using nodeJS to get the information from the database, where I get the urls of all the pictures. For example I have a collection of cards, and an url of the image which represents the collection. What I want to do is get the link of the picture that I'm clicking on so I can use it in another component.
import React from 'react';
import { render } from 'react-dom';
import Gallery from 'react-photo-gallery';
import Photo from './Photo';
class PhotoGallery extends React.Component {
constructor(props){
super(props);
this.onClick = this.onClick.bind(this);
this.state = {
urlImages: []
};
}
async componentDidMount() {
var getUrlImages = 'http://localhost:3004';
const response = await fetch(getUrlImages+"/getUrlImages");
const newList = await response.json();
this.setState(previousState => ({
...previousState,
urlImages: newList,
}));
}
galleryPhotos() {
if(this.state.urlImages) {
return this.state.urlImages.map(function(urlimage) {
return { src: urlimage.urlimage, width: 2, height: 2 }
})
}
}
onClick() {
alert(this.galleryPhotos().value);
}
render() {
return (
<Gallery axis={"xy"} photos={this.galleryPhotos()} onClick={this.onClick}/>
)
}
}
const photos = [];
export default PhotoGallery;
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/react/15.1.0/react-dom.min.js"></script>
Basically what I want to do is get the source link of the picture in the onClick function. Is that possible?
Thanks in advance!
Check the onClick event.
onClick(event) {
alert(event.target.src)
}
The DEMO
The onClick event of the Gallery component has a number of arguments:
the event
an object containing the selected index and the original photo object
You can use this in your onClick handler:
onClick(e, obj) {
const src = obj.photo.src
// do whatever you need with the src (setState, etc)
}

Setting iframe height to scrollHeight in ReactJS using IframeResizer

The typical solution to the problem doesn't work in in React due to its dynamically generated component structure and event model, as opposed to traditional static HTML. I tried with react-iframe-resizer-super but not found perfect solution.
My code:
import React, {PropTypes} from 'react';
import ReactIframeResizer from 'react-iframe-resizer-super';
class Frame extends React.Component {
constructor() {
super();
}
componentDidUpdate() {
const iframeResizerOptions = {
// log: true,
// autoResize: true,
checkOrigin: false,
// resizeFrom: 'parent',
// heightCalculationMethod: 'max',
// initCallback: () => { console.log('ready!'); },
// resizedCallback: () => { console.log('resized!'); },
};
}
render() {
return (
<div style={{position: 'relative'}}>
<IframeResizer iframeResizerOptions={iframeResizerOptions}>
<iframe scrolling="no" src="https://en.wikipedia.org/wiki/Main_Page" allowfullscreen
style={{width:'100%', height:'100%'}}
}}></iframe>
</IframeResizer>
</div>
);
}
}
Then I got following error:
Uncaught ReferenceError: IframeResizer is not defined
Is there a way in React to set the height of an iframe to the height of its scrollable contents or is there any alternative way to archive this requirement?
I refer following link:
https://www.npmjs.com/package/react-iframe-resizer-super
This question is long decease, but I thought I would add just in case anyone else looking for clarification on using react-iframe-resizer-super + iframe-resizer (JS)
The problem in the code above is a misspelling of the imported component.
import ReactIframeResizer from 'react-iframe-resizer-super';
Should be:
import IframeResizer from 'react-iframe-resizer-super';
As you've used it inside your Frame component.
For those looking for clarification on using the library, here is my dead simple working solution:
Install dependencies on React project containing iFrame yarn add react-iframe-resizer-super iframe-resizer
Include iframeResizer.contentWindow.min.js on the page that you are using as the source of your iFrame.
Usage in React:
DynamicIFrame.jsx
import React from 'react';
import IframeResizer from 'react-iframe-resizer-super';
export const DynamicIFrame = props => {
const { src } = props;
const iframeResizerOptions = {
log: true,
// autoResize: true,
checkOrigin: false,
// resizeFrom: 'parent',
// heightCalculationMethod: 'max',
// initCallback: () => { console.log('ready!'); },
// resizedCallback: () => { console.log('resized!'); },
};
return (
<IframeResizer src={src} iframeResizerOptions={iframeResizerOptions} />
);
};

Toastr is not displaying the way it should

toastr is showing an odd behavior -- it's being displayed in a rather ugly way, and I am not overriding anything. No options are given on how to style, but still I am getting this ugly notification.
This is what it looks like:
I am pulling toastr through requireJS; I don't know if that even matters.
logger.js
define(['durandal/system', 'toastr'], function (system, toastr) {
var logger = {
log: log,
logError: logError
};
return logger;
function log(message, data, source, showToast) {
logIt(message, data, source, showToast, 'info');
}
function logError(message, data, source, showToast) {
logIt(message, data, source, showToast, 'error');
}
function logIt(message, data, source, showToast, toastType) {
source = source ? '[' + source + '] ' : '';
if (data) {
system.log(source, message, data);
} else {
system.log(source, message);
}
if (showToast) {
if (toastType === 'error') {
toastr.error(message);
} else {
toastr.info(message);
}
}
}
});
main.js
requirejs.config({
baseUrl: '../Scripts',
paths: {
'services': '../App/services',
'viewmodels': '../App/viewmodels',
'views': '../App/views',
'config': '../App/config',
'durandal': 'durandal',
'plugins': 'durandal/plugins',
'transitions': 'durandal/transitions',
'text': 'text',
'toastr': 'toastr'
}
});
define('jquery', function () { return jQuery; });
define('knockout', ko);
define('main', ['durandal/system', 'durandal/app', 'durandal/viewLocator', 'plugins/router', 'services/logger'], function (system, app, viewLocator, router, logger) {
//>>excludeStart("build", true);
system.debug(true);
//>>excludeEnd("build");
app.title = 'Prepare to die';
app.configurePlugins({
router: true,
dialog: true,
widget: true
});
app.start().then(function () {
// Router will use conventions for modules
// assuming viewmodels/views folder structure
router.makeRelative({ moduleId: 'viewmodels' });
// Replace 'viewmodels' in the moduleId with 'views' to locate the view.
// look for partial views in a 'views' folder in the root.
viewLocator.useConvention();
// Show the app by setting the root view model for our application with a transition.
app.setRoot('viewmodels/shell', 'entrance');
// Override bad route behavior to write to
// console log and show error toast
router.handleInvalidRoute = function (route, params) {
logger.logError('No route found', route, 'main', true);
};
});
});
shell.js
define(['durandal/system', 'services/logger', 'plugins/router', 'config'],
function (system, logger, router, config) {
var shell = {
activate: activate,
router: router
};
return shell;
function activate() {
logger.log('Application is Loaded!', null, system.getModuleId(shell), true);
router.map(config.routes).buildNavigationModel();
return router.activate();
}
});
shell.html
<div>
<header>
<!-- ko compose: {view: 'navigation'} -->
<!-- /ko -->
</header>
<section id="content" class="main container-fluid">
<!-- ko compose: {model: router.activeItem, afterCompose: router.afterCompose} -->
<!-- /ko -->
</section>
</div>
Just as a sidebar, we use toastr under Durandal and I know from John Papa's writings that he feels that third-party frameworks should be loaded globally, while our own modules should be loaded modularly. Just food for thought. I can tell that switching to a global model for third-party frameworks eliminated a lot of esoteric issues.
A quick work-around fix is to do the following:
toastr.options.toastClass = 'toastr';

Resources