question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

Validation fails when running via JsDom in NodeJs

See original GitHub issue

Dear,

we are integrating the powerful Plotly library into the open-source Node-RED iot framework, which is running on NodeJs.

Running Plotly on the NodeJs server (via JsDom) works fine, but calling the validate function fails. A small program to reproduce the problem (remark: the jsdom and plotly.js-dist NPM packages need to be installed):

const jsdom = require('jsdom');
const vm = require('vm');  
const fs = require('fs');

var plotlyServerDom = new jsdom.JSDOM('', { runScripts: 'dangerously'});
 
// Mock a few things that JSDOM doesn't support out-of-the-box
plotlyServerDom.window.HTMLCanvasElement.prototype.getContext = function() { return null; };
plotlyServerDom.window.URL.createObjectURL = function() { return null; };

// Run Plotly inside Jsdom
var plotlyJsPath = require.resolve("plotly.js-dist");
var plotlyJsSource = fs.readFileSync(plotlyJsPath, 'utf-8');
plotlyServerDom.window.eval(plotlyJsSource);

var data = [];
var trace ={};
trace.name = "mytrace"; 
trace.type = "scatter";
trace.x = ["2020-09-06 09:10:49"];
trace.y = [5];
trace.opacity = 1;
data.push(trace);

var layout = {};
layout.title = {};
layout.title.text = "mytitle";

var result = plotlyServerDom.window.Plotly.validate(data, layout);

if (result) {
    console.log(result);
}
else {
    console.log("Validation ok");
}

The result is:

[ { code: ‘object’, container: ‘layout’, trace: null, path: ‘’, astr: ‘’, msg: ‘The layout argument must be linked to an object container’ }, { code: ‘object’, container: ‘data’, trace: 0, path: ‘’, astr: ‘’, msg: ‘Trace 0 in the data argument must be linked to an object container’ } ]

Which means that Plotly doesn’t recognize my input parameters as Javascript objects, which is checked in the isPlainObject function:

image

The second check fails, so the input is not considered as a Javascript object… Seems that the problem is caused by this:

  1. JsDom runs its own vm (virtual machine) in NodeJs.
  2. That vm gets its very own instance of Object.
  3. The prototype of an object created inside the vm will not be the exact equal to the prototype of the Object outside the vm (i.e. in my program).
  4. So the equality in the IF condition will fail …

You can find a prove of this theory in the next program. Here a global function createObject is declared inside the vm, and called from outside the vm. This allows us to create an empty Javascript object inside the vm, return it back to us so we can add data to the object, and then we pass the object to Plotly (which is running in the Jsdom vm):

const jsdom      = require('jsdom');
const vm         = require('vm');  
const fs         = require('fs');

var plotlyServerDom = new jsdom.JSDOM('', { runScripts: 'dangerously'});
 
// Mock a few things that JSDOM doesn't support out-of-the-box
plotlyServerDom.window.HTMLCanvasElement.prototype.getContext = function() { return null; };
plotlyServerDom.window.URL.createObjectURL = function() { return null; };
    
// Script to add global functions (to create a Javascript object) in the server DOM context
const script = new vm.Script(`
    function createObject() {
        return {};
    }
`);

// Execute the script in the VM (of jsDom), so that the global function is added to the VM context
const serverDomVmContext = plotlyServerDom.getInternalVMContext();
script.runInContext(serverDomVmContext);
    
var plotlyJsPath = require.resolve("plotly.js-dist");
var plotlyJsSource = fs.readFileSync(plotlyJsPath, 'utf-8');
plotlyServerDom.window.eval(plotlyJsSource);

var data = [];
var trace = plotlyServerDom.window.createObject();
trace.name = "mytrace"; 
trace.type = "scatter";
trace.x = ["2020-09-06 09:10:49"];
trace.y = [5];
trace.opacity = 1;
data.push(trace);

var layout = plotlyServerDom.window.createObject();
layout.title = plotlyServerDom.window.createObject();
layout.title.text = "mytitle";

var result = plotlyServerDom.window.Plotly.validate(data, layout);

if (result) {
    console.log(result);
}
else {
    console.log("Validation ok");
}

And that works fine, and the validation is succesful.

We could use this latter program as a workaround, but it becomes harder when the number of nested levels increases. Because all objects that we receive from anywhere in the NodeJs application, we need to create an new object for and copy all the properties. Which is of course not a very neat solution …

So we would like to ask if it is possible to remove the second condition from the IF statement, since we think that the first condition is enough:

image

Thanks for your time! Bart Butenaers

Issue Analytics

  • State:closed
  • Created 3 years ago
  • Reactions:1
  • Comments:5 (4 by maintainers)

github_iconTop GitHub Comments

1reaction
bartbutenaerscommented, Sep 16, 2020

Hello @archmoj and @alexcjohnson,

Thanks for the fast and kind feedback! I don’t want to mess up any other projects that are already using Plotly, by changing the IF conditions. So I have tested your workaround:

plotlyServerDom.window.process = {versions: 1};

And that works like a charm. Much cleaner than my own workaround. It is also good from a performance point of view, since I don’t have to copy anymore data from one object to another. How stupid that I haven’t thought about this myself…

So this workaround is more than enough for my use case! Unless you guys still want to update the IF condition for some reason, you can close this issue.

Thanks a lot for your time!! Bart

0reactions
alexcjohnsoncommented, Jan 4, 2021

I’m starting to think it would be OK to just drop the second part of this test - again, the only concern is if someone passes in an object created with new f() or new MyClass(), which isn’t possible for anyone coming in through JSON. It would be possible in Dash using a clientside callback, but you’d really have to work hard to do that! So for the most part this affects JS-only users - whether using plotly.js directly or via some JS framework. And at that point we’re trading off a very hypothetical case where we treat an invalid input as valid for several known problems that require tedious debugging and a hacky workaround.

I did a little digging, and I did find one test that pretty reliably distinguishes a plain object from a more complex constructed object: look for a property that is usually inherited from Object, for example:

Object.getPrototypeOf(obj).hasOwnProperty('hasOwnProperty')

Which you can trick by overriding hasOwnProperty in your class, but perhaps at that point you’re on your own 😏

Anyway either removing the second condition or replacing it with something like the test above could be the way to go.

Read more comments on GitHub >

github_iconTop Results From Across the Web

Nodejs parsing dom error with strict mode - Stack Overflow
For me help reinstall jsdom from "latest" to old version "3.1.2". 1) Delete jsdom folder in node_modules. 2) Change dependencies in package.json to:...
Read more >
JSDOM is not a constructor · Issue #2429 - GitHub
When I run that code in a file named test.js in Node.js version 10, I do not get an error.
Read more >
Web Scraping and Parsing HTML in Node.js with jsdom - Twilio
Using Got to retrieve data to use with jsdom. First let's write some code to grab the HTML from the web page, and...
Read more >
jsdom - npm
Start using jsdom in your project by running `npm i jsdom`. ... which means that any resources included via relative URLs will fail...
Read more >
How I Fixed The Unexpected Token Error In Jest
` Validation Error: Test environment jest-environment-jsdom cannot be found. Make sure the testEnvironment configuration option points to an ...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found