jest is failing to find `bota` and `atob` - javascript

Creating a jest test like:
test("btoa", () => {
expect(btoa("aaa")).toStrictEqual("YWFh");
});
fails with
ReferenceError: btoa is not defined
however, node does define btoa as of node 16, and so the following:
console.log(bota("aaa"))
correctly outputs YWFh.
How can I configure jest to get this test to pass? Clearly something is happening in the jest test runner to not execute in the current node environment, or otherwise is stripping specific built-ins, only I can't seem to find any documentation on how to debug or adjust this.
Update
There are work arounds by writing the encoding manually in "pure js" or depending on something that's similar, but I'm particularly interested in why the jest execution ending is failing to find built-ins that seem to be present in other environments.
This also works fine in other testing frameworks like mocha, so it's clearly related to the jest runner in particular.

Update
After much searching and head scratching as to why btoa/atob are available in node but NOT available in jest running in node, I finally figured it out. Jest runs all tests inside a vm, which is an isolated sandbox environment. The btoa/atob methods are not automatically exposed on the global object inside of a VM. Best explained by example:
const vm = require('vm');
// this works outside the vm - but for legacy reasons only
// you shouldn't be doing this in the first place
btoa('aaa'); // -> "YWFh"
const context = vm.createContext({});
const code = 'btoa("aaa")';
vm.runInContext(code, context); //-> Uncaught ReferenceError: btoa is not defined
Note: The answer described below is still the "solution" - you need to define these methods for use in node, and then you need to expose them using jest's globalSetup.
Original answer
The root of your problem is the fact that NodeJS and web browsers have different APIs. For example, I get this deprecation notice when I try to use btoa in my node application.
The first part of the solution is that you need to provide your own atob/btoa methods for use in NodeJs (see examples here). Then you need to make these available using jest's globalSetup config:
/** Encodes a string as base64 format */
global.btoa = (str: string) => Buffer.from(str, 'binary').toString('base64');
/** Decodes a base64 encoded string */
global.atob = (str: string) => Buffer.from(str, 'base64').toString('binary');
If you don't feel comfortable doing this yourself, there are libraries and tools out there that do it for you (jsdom, phantomjs, testing-library). These libraries essentially replicate the browser APIs in a node environment for doing things like running tests, server-side rendering, etc. I recommend reading about testing web frameworks for code examples and techniques.

Related

NodeJS - Dynamically import built in modules

I'd like to get a built in module (for example Math or path or fs), whether from the global object or require, I thought about doing something like this:
function getModuleByName(name) {
return global[name] || require(name);
}
Is there a way to check that it is indeed a module and not something else? Would this make a security problem?
Is there a way to check that it is indeed a module and not something else?
Other methods but here's an example:
function getModuleByName(name)
{
let module = null;
try {
module = require(name);
} catch (e) {
// Recommend Logging e Somewhere
}
return module;
}
This will graciously fail as null where the module does not exist, or return it.
Would this make a security problem?
Quite possibly, it depends on how it's used. I'd argue it is more of a general design issue however and would blanket say avoid doing it (without any context, you may have a very good reason).
You, like anyone, obviously have a finite amount of modules you could be loading. These modules have all been selected by yourself for your application for specific reasons, or are bundled into your node version natively and are expected parts of your environment.
What you are doing by introducing this functionality is adding the addition of unexpected elements in your environment. If you are using getModuleByName to access a third party library- you should know outright that library is available and as such there's no reason why you can't just require it directly.
--
If you do think your use case warrants this, please let me know what it is as I may never have encountered it before. I have used dynamic imports like the following:
https://javascript.info/modules-dynamic-imports
But that hasn't been for global packages/libraries, but for dynamic reference to modules built internally to the application (i.e. routing to different views, invokation of internal scripts).
These I have protected by ensuring filepaths can't be altered by whitelisting the target directories, making sure each script follows a strict interface per use case and graciously failing where a module doesn't exist (error output "this script does not exist" for the script usage and a 404 view for the routing example).

Getting the entire tree-sitter parse tree inside an Atom package

I am attempting to write an Atom package which would require access to the parse tree generated internally for Atom by tree-sitter. I am unfamiliar with tree-sitter and not overly familiar with Atom packages.
While reading some tutorials for Atom packages in general, I came across a page which looks promising that discusses getting the "Scope descriptor for the given position in buffer coordinates" and on the same page a way to get the current grammar but nothing for getting the entire parse tree. Is this possible using the api provided by Atom's environment? If not how would I go about doing it otherwise? Maybe reparsing the tree from inside the package (though that sounds both more work than it's worth and inefficient)?
For anyone else who might need to do this in the future, Atom exposes atom.workspace which can be used to get various things such as the TextEditors, Grammars, etc. from the active environment. While tracing Atom's source, I found that the GrammarRegistry object which is accessible in the workspace (atom.workspace.grammarRegistry) has a method called languageModeForGrammarAndBuffer which returns a TreeSitterLanguageMode object which in turn has a getter for the tree (.tree). This method in turn needs a grammar and a textBuffer, both of which can be obtained from the workspace as well (the buffer can be gotten from the TextEditorRegistry). Putting that all together, to get the root node of the tree from an Atom package, you can use something like this:
const grammarRegistry = atom.workspace.grammarRegistry;
const grammar = grammarRegistry.treeSitterGrammarsById["source.js"]; // where source.js is the name of the tree-sitter grammar you want
const buffer = atom.workspace.textEditorRegistry.editors.values().next().value.buffer; //assuming you want the first buffer
const root = grammarRegistry.languageModeForGrammarAndBuffer(grammar, buffer).tree.rootNode;
In current versions you can just do editor.getBuffer().getLanguageMode().tree. Note this could be either a TextMateLanguageMode or TreeSitterLanguageMode, depending on Tree Sitter support for the language, and the value of the core useTreeSitterParsers setting. Also none of this appears to be documented API (i.e. stable) yet.

How to fix "Unexpected token $ in JSON" when using VelocityJS to parse VTL

In order to write unit tests for my AppSync resolvers, I'm using the VelocityJS package to parse the VTL inside of my sam template. However, the VTL template isn't being fully parsed.
The code works fine on AWS, it's the unit tests that are erroring.
https://github.com/shepherdwind/velocity.js
#if($util.isNullOrBlank($ctx.result.themePreferences))
#set($themePreverences = {})
$util.qr($themePreverences.put("darkMode", "DEFAULT"))
$util.qr($ctx.result.put("themePreferences", $themePreverences))
#end
$util.toJson($ctx.result)
When using let result = Velocity.parse(template, ctx); the result is the string "$util.toJson($ctx.result)" instead of the JSON value of $ctx.result.
IE:
{ "themePreferences": { "darkMode": "DEFAULT" } }
I believe the reason for the parsing troubles is that util and ctx/context are all AppSync concepts, not VTL ones. As you're using a third party library, they don't know what it means and can't replace them.
You might find Amplify's new mocking and testing tool helpful: https://aws-amplify.github.io/docs/cli-toolchain/quickstart?sdk=js#mocking-and-testing
There's also additional third party libraries that do a good job of covering AppSync local development worth checking out, if the above doesn't fit your use case.

How to manipulate what `shell.which()` returns?

I have a JS/Node based CLI that was initially developed to run on macOS and Linux. I now want to make it work on Windows, but because of complicated and fixed reasons I have to achieve this without changing the app's source code.
A first step was to lie to the program about its process.platform, which works great by messing with its Module (suggested by #estus) and wrapping the original CLI in another CLI that is then actually used on Windows.
Now I stumbled over some code that runs shelljs.which('ruby') and compares the result to a specific string (/usr/bin/ruby) and outputs an error message or even fails the program when it does not match. I don't know how to overcome that yet.
How can I manipulate what shell.which() returns?
One approach that I could take would be to manipulate require('shelljs') to load my own fork of shelljs that returns whatever I want (via using override-require, which I already used to replace child_process with cross-spawn which works much better on Windows). But I want to avoid maintaining my own fork of shelljs of course - it would be much more practical if I could somehow just manipulate shelljs.which.
I created a super small demo project that is similar to the CLI I am using and can be used to experiment with possible solutions: https://github.com/janpio/nodejs-cli-wrongruby - fake.js would be where I would want to manipulate shelljs.which somehow.
With the help of #Berdi in the comments I figured out that, similar to how I can mess with process.platform, I can also mess with the shelljs.which method:
// Manipulate shelljs.which('ruby')
const shelljs = require('shelljs')
var original_which = shelljs.which
var new_which = function(cmd) {
if(cmd == 'ruby') {
return "/usr/bin/ruby"
}
return original_which.call(this, cmd)
}
shelljs.which = new_which
require("./index.js");
(This assumes the original CLI lives in ./index.js)
Here all calls to shelljs.which with the parameter ruby are answered with /usr/bin/ruby, and all the others requests are sent to the actual shelljs.which implementation.

Ignore not declared variables (TypeScript)

I am creating a WebExtension using TypeScript which is later compiled to JavaScript.
My extension depends on one of the APIs the browser (Firefox) offers, specifically the extension API. As an example, I use the getURL() method, which is called like this:
browser.extension.getURL("foo/bar.js");
Of course, TypeScript gives an error "Cannot find name 'browser'". This prevents me from fully compiling the code. I would like to know if there is any way to bypass this. Preferably not only at compile level, but also at the linting level.
I have tried:
Defining browser at the beginning as var browser: any;: breaks the API.
Compiling with --noEmit, --noEmitOnErrors: irrelevant, still complains.
Any suggestions?
If you want to let Typescript know that the variable exists but not actually emit any code for it you can use declare
declare var browser: any;

Categories

Resources