I am have trouble trying to make a unit test for this function. The problem is it using a lib noUiSlider for a range slider and when the test get there , it does not recongnise noUiSlider.set. How do I correctly mock this
TypeError: Cannot read property 'set' of undefined
function popState(){
if (rangeSlider) {
$('.range-reset.' + name).removeClass('hidden');
var element = self.isRangeElement(name).element;
var unit = element.getAttribute('data-unit');
//since noUiSlider accepts no unit,Remove unit from values
unit = new RegExp(unit, 'g');
value = value.replace(unit, '');
value = value.split('-');
***element.noUiSlider.set(value);***
}
}
I have tried this approach it did not work
import { JSDOM } from 'jsdom';
const dom = new JSDOM();
dom.noUiSlider = {
set: jest.fn()
} ;
global.window = dom.window;
And I have tried this as well none work
Object.defineProperty(window.document, 'noUiSlider', {
set: jest.fn(),
});
Unit test case
test('should set range state', () => {
document.body.innerHTML = `<div id="twobuttons-range_0" class="twobuttons-range" data-technicalname="motor" data-unit="mm" data-min="100" data-max="500" data-start="[30,50]"></div>`;
Object.defineProperty(window.document, 'noUiSlider', {
set: jest.fn(),
});
popState();
expect($('#twobuttons-range_0').length).toBe(1);
});
I use it like this and it works:
Inside myComponent.tsx:
import noUiSlider from "nouislider";
....
globalSlider: noUiSlider.noUiSlider;
Inside myComponent.spec.tsx:
component = new myComponent();
...
it("Set value for slider", () => {
component.globalSlider = {
options: undefined, target: undefined, destroy(): void {
}, get(): string | string[] {
return undefined;
}, off(): void {
}, on(): void {
}, reset(): void {
}, updateOptions(): void {
}, set: jest.fn() };
component.setSliderValue("3");
});
Related
I'm having a problem with tests. I'm getting error:
TypeError: Cannot read property 'pipe' of undefined
method in directive:
#HostListener('mouseenter')
onMouseenter(): void {
forkJoin([
this.dateService.formatDate(dateStart).pipe(take(1)),
this.dateService.formatDate(dateEnd).pipe(take(1))
]).subscribe((dates) => {
this.showTooltip(`${dates[0]} - ${dates[1]}`);
});
}
(formatDate returns Observable string)
in test:
fit('should call mouse enter', () => {
directive.onMouseenter();
expect(directive.showTooltip).toHaveBeenCalled();
});
should i mock pipe somehow?
thanks!
test looks like this
fdescribe('TooltipDirective', () => {
let directive: TooltipDirective;
let mockDateService: jasmine.SpyObj<DateService>;
beforeEach(() => {
mockDateService = jasmine.createSpyObj('dateService', ['formatDate']);
directive = new TooltipDirective(null, null, mockDateService);
directive.tooltipData = {dateStart: new Date(), endDate: Date()};
spyOn(directive, 'showTooltip');
});
fit('should show tooltip', () => {
directive.onMouseenter();
expect(directive.showTooltip).toHaveBeenCalled();
});
});
I'm new to VueJs and currently trying to load some data only once and make it globally available to all vue components. What would be the best way to achieve this?
I'm a little bit stuck because the global variables occasionally seem to become null and I can't figure out why.
In my main.js I make three global Vue instance variables:
let globalData = new Vue({
data: {
$serviceDiscoveryUrl: 'http://localhost:40000/api/v1',
$serviceCollection: null,
$clientConfiguration: null
}
});
Vue.mixin({
computed: {
$serviceDiscoveryUrl: {
get: function () { return globalData.$data.$serviceDiscoveryUrl },
set: function (newUrl) { globalData.$data.$serviceDiscoveryUrl = newUrl; }
},
$serviceCollection: {
get: function () { return globalData.$data.$serviceCollection },
set: function (newCollection) { globalData.$data.$serviceCollection = newCollection; }
},
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) { globalData.$data.$clientConfiguration = newConfiguration; }
}
}
})
and in my App.vue component I load all the data:
<script>
export default {
name: 'app',
data: function () {
return {
isLoading: true,
isError: false
};
},
methods: {
loadAllData: function () {
this.$axios.get(this.$serviceDiscoveryUrl)
.then(
response => {
this.$serviceCollection = response.data;
let configurationService = this.$serviceCollection.services.find(obj => obj.key == "ProcessConfigurationService");
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
})
}
},
created: function m() {
this.loadAllData();
}
}
</script>
But when I try to access the $clientConfiguration it seems to be null from time to time and I can't figure out why. For example when I try to build the navigation sidebar:
beforeMount: function () {
let $ = JQuery;
let clients = [];
if (this.$clientConfiguration === null)
console.error("client config is <null>");
$.each(this.$clientConfiguration, function (key, clientValue) {
let processes = [];
$.each(clientValue.processConfigurations, function (k, processValue) {
processes.push(
{
name: processValue.name,
url: '/process/' + processValue.id,
icon: 'fal fa-project-diagram'
});
});
clients.push(
{
name: clientValue.name,
url: '/client/' + clientValue.id,
icon: 'fal fa-building',
children: processes
});
});
this.nav.find(obj => obj.name == 'Processes').children = clients;
The most likely cause is that the null is just the initial value. Loading the data is asynchronous so you'll need to wait for loading to finish before trying to create any components that rely on that data.
You have an isLoading flag, which I would guess is your attempt to wait for loading to complete before showing any components (maybe via a suitable v-if). However, it currently only waits for the first request and not the second. So this:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
}
);
this.isLoading = false;
would need to be:
this.$axios.get(configurationService.address + "/api/v1/clientConfiguration").then(
response2 => {
this.$clientConfiguration = response2.data;
this.isLoading = false;
}
);
If it isn't that initial value that's the problem then you need to figure out what is setting it to null. That should be prety easy, just put a debugger statement in your setter:
$clientConfiguration: {
get: function () { return globalData.$data.$clientConfiguration },
set: function (newConfiguration) {
if (!newConfiguration) {
debugger;
}
globalData.$data.$clientConfiguration = newConfiguration;
}
}
Beyond the problem with the null, if you're using Vue 2.6+ I would suggest taking a look at Vue.observable, which is a simpler way of creating a reactive object than creating a new Vue instance.
Personally I would probably implement all of this by putting a reactive object on Vue.prototype rather than using a global mixin. That assumes that you even need the object to be reactive, if you don't then this is all somewhat more complicated than it needs to be.
I am doing a task where I need to wire up a search field to a simple JS application that displays a few items and the user can search through and filter them.
There are three classes - App, ProductsPanel and Search. Both Search and ProductsPanel are being initialised inside the App class.
The ProductsPanel class holds an array with 10 products.
I want to call a method of ProductsPanel from inside Search that filters through the products. How can I do that?
I've tried using this.productsPanel = new productsPanel() inside the constructor of the first class, but that brings up a new instance which doesn't have the array of all of the products.
Here's the App class:
class App {
constructor() {
this.modules = {
search: {
type: Search,
instance: null
},
filter: {
type: Filter,
instance: null
},
productsPanel: {
type: ProductsPanel,
instance: null
},
shoppingCart: {
type: ShoppingCart,
instance: null
}
};
}
init() {
const placeholders = document.querySelectorAll("#root [data-module]");
for (let i = 0; i < placeholders.length; i++) {
const root = placeholders[i];
const id = root.dataset.module;
const module = this.modules[id];
if (module.instance) {
throw new Error(`module ${id} has already been started`);
}
module.instance = new module.type(root);
module.instance.init();
// console.info(`${id} is running...`);
}
}
}
app = new App();
app.init();
And here are the Search:
export default class Search {
constructor(root) {
this.input = root.querySelector("#search-input");
}
// addEventListener is an anonymous function that encapsulates code that sends paramaters to handleSearch() which actually handles the event
init() {
this.input.addEventListener("input", () => {
this.handleSearch();
});
}
handleSearch() {
const query = this.input.value;
app.modules.productsPanel.instance.performSearch(query);
}
}
And ProductsPanel classes:
export default class ProductsPanel {
constructor(root) {
this.view = new ProductsPanelView(root, this);
this.products = [];
}
init() {
this.products = new ProductsService().products;
this.products.forEach(x => this.view.addProduct(x));
}
performSearch(query) {
query = query.toLowerCase();
this.products.forEach(p => {
if (query === p.name) {
this.view.showProduct(p.id);
} else {
this.view.hideProduct(p.id);
}
});
}
addToCart(id) {
const product = this.products.filter(p => p.id === id)[0];
if (product) {
app.modules.shoppingCart.instance.addProduct(product);
}
}
}
I want to call ProductsPanel's performSearch method but on the instance created by the App class. I have no clue on how I can do that.
Try below custom event handler class
class CustomEventEmitter {
constructor() {
this.eventsObj = {};
}
emit(eName, data) {
const event = this.eventsObj[eName];
if( event ) {
event.forEach(fn => {
fn.call(null, data);
});
}
}
subscribe(eName, fn) {
if(!this.eventsObj[eName]) {
this.eventsObj[eName] = [];
}
this.eventsObj[eName].push(fn);
return () => {
this.eventsObj[eName] = this.events[eName].filter(eventFn => fn !== eventFn);
}
}
}
How to use?
create the object of CustomEventEmitter class
let eventEmitter = new CustomEventEmitter()
Subscribe an event
emitter.subscribe('event: do-action', data => {
console.log(data.message);
});
call the event
emitter.emit('event: do-action',{message: 'My Custom Event handling'});
Hope this helps!
I have a working piece of code as below:
let pageParams = {
data: { todos: [], desc: '' }
}
pageParams.onLoad = function () {
//I am trying to encapsulate this to a standalone function and
// make it generic, instead of hard coding the 'this.addTodo=XXX'
const evProducer = {
start: listener => {
//Here, I am adding a named property function
this.addTodo = ev => {
listener.next(ev.detail.value)
}
},
stop: ()=>{}
}
const input$ = xs.create(evProducer)
input$.compose(debounce(400)).subscribe({
next: val => console.log(val)
})
}
The code works and now I am going to do some refactor work, i.e. move the logic out of this onLoad function. So I move the logic to another module
let xsCreator = {}
xsCreator.fromEvent = function(handler){
const evProducer = {
start: listener => {
handler = ev => listener.next(ev.detail.value)
},
stop: () => {}
}
return xs.create(evProducer)
}
And in the previous onLoad function becomes the following:
pageParams.onLoad = function () {
xs.fromEvent(this.addTodo).subscribe(blablabla)
}
but it does not work. I guess I might use apply/call/bind to make this work, but don't know how to. Anyone can help? Thanks in advance
I've found the solution, I should use Object.defineProperty to add a named property for object.
xsCreator.fromInputEvent = (srcObj, propertyName) => {
const evProducer = {
start: (listener) => {
Object.defineProperty(
srcObj,
propertyName,
{value: ev => listener.next(ev.detail.value)})
},
stop: () => {}
}
return xs.create(evProducer)
}
I’m trying to set up a mock unit test with jasmine by using createSpy(). I’ve getting
TypeError: undefined is not a function (evaluating jasmine.createSpy().andCallFake(function(msg) { return [] })) (line 13).
CODE:
$scope.workInit = function() {
$scope.work_loading = true;
$scope.public_work_loading = true;
var projects = SomeService.getGroups();
var publicProjects = SomeService.getPublicGroupings('G');
…
...
}
TEST:
this.SomeService = {
getGroups: jasmine.createSpy().andCallFake(function(msg) { return [] }),
getPublicGroupings: jasmine.createSpy().andCallFake(function(msg) { return [] }),
}
it('should expect work_loading and public_loading to be false', function () {
this.scope.workInit();
expect($scope.work_loading).toEqual(false);
expect($scope.public_work_loading).toEqual(false);
});
The way you have set up the mock unit test case is correct, the same works for me in this fiddle.
Check the scope in which you've included this part of the code:
this.SomeService = {
getGroups: jasmine.createSpy().andCallFake(function(msg) { return [] }),
getPublicGroupings: jasmine.createSpy().andCallFake(function(msg) { return [] }),