npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2024 – Pkg Stats / Ryan Hefner

mock-violentmonkey

v3.1.0

Published

Mock violentmonkey's globals for testing userscripts

Downloads

87

Readme

mock-violentmonkey

NPM License Dependencies

mock-violentmonkey allows you to mock Violentmonkey's api for testing Violentmonkey userscripts. mock-violentmonkey allows you to have seperated contexts for testing different scenarious without them interfering with each other. It was written with ava in mind but should work with other test runners.

Disclaimer

I've stopped active development on this library. I'll continue to provide updates for dependencies and address any bugs that pop up. As for features, I consider the library complete. If you think there's something crucial missing, please open an issue to discuss potential additions.

API

violentMonkeyContext

This the whole magic. violentMonkeyContext seperates the various testing contexts from each other. This allows you to use GM_setValue and the like and not worry about it affecting other tests.

test(
  'title1',
  violentMonkeyContext(t => {
    // This is seperate
  }),
);

test(
  'title2',
  violentMonkeyContext(t => {
    // from here
  }),
);

violentMonkeyContextMacro

This is similar to violentMonkeyContext, but this makes use of ava macros and, as a result, can only be used with ava.

test('title', violentMonkeyContextMacro(), t => {
  t.is(GM_getValue('a'), 'b');
});

// same as
test(
  'title',
  violentMonkeyContext(t => {
    t.is(GM_getValue('a'), 'b');
  }),
);

tabContext

This allows you to simulate seperate tabs. It is currently only useful for GM_addValueChangeListener and GM_removeValueChangeListener.

test(
  'title',
  violentMonkeyContext(t => {
    GM_addValueChangeListener('key', (key, oldValue, newValue, remote) => {
      console.log(remote);
    });

    GM_setValue('key', 1); // Will log false

    tabContext(() => {
      GM_setValue('key', 2); // Will log true
    });
  }),
);

GM api

✔️ = supported, ❌ = not supported

| Function | Support | Notes | | ------------------------------------------------------------------------------------------------------ | ------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | | GM_info / GM.info | ✔️ | Default value | | GM_getValue / GM.getValue | ✔️ | | | GM_setValue / GM.setValue | ✔️ | | | GM_deleteValue / GM.deleteValue | ✔️ | | | GM_listValues / GM.listValues | ✔️ | | | GM_addValueChangeListener | ✔️ | | | GM_removeValueChangeListener | ✔️ | | | GM_getResourceText | ✔️ | | | GM_getResourceURL / GM.getResourceURL | ✔️ | This returns an object url, which cannot be fetched with regular http libs in node, use GM_getResourceText instead. | | GM_addStyle / GM.addStyle | ✔️ | | | GM_openInTab / GM.openInTab | ✔️ | | | GM_registerMenuCommand | ✔️ | | | GM_unregisterMenuCommand | ✔️ | | | GM_notification / GM.notification | ✔️ | Because Chromium and Firefox's notifications behave slightly different, mock-violentmonkey's implementation allows you to simulate either with Firefox's behaviour by default. More info | | GM_setClipboard / GM.setClipboard | ✔️ | This doesn't actually set the clipboard. | | GM_xmlhttpRequest / GM.xmlHttpRequest | ✔️ | Can be used in combination with setBaseUrl | | GM_download | ✔️ | Can be used in combination with setBaseUrl |

The GM_* and GM.* api is added to the global scope so that userscripts have access to them. With Typescript you can either import them or tell Typescript that they're globals.

If you import GM_info it is not a getter, you have to call it.

import {GM_info} from 'mock-violentmonkey';
test(
  'title',
  violentMonkeyContext(t => {
    console.log(GM_info());
  }),
);
import {ScriptInfo} from 'mock-violentmonkey';

declare const GM_info: ScriptInfo;

test(
  'title',
  violentMonkeyContext(t => {
    console.log(GM_info);
  }),
);

Additional GM api

Additionally, mock-violentmonkey has some helper functions for setting up tests.

update_GM_info

This provides an easy way of updating GM_info / GM.info. The object is mutable but update_GM_info provides an easy way of updating it in one go.

test(
  'title',
  violentMonkeyContext(t => {
    update_GM_info({
      version: '2.0.0', // Version of Violentmonkey
      platform: {
        // Supports deep assignment
        arch: 'arm', // update platform arch
      },
      script: {
        version: '1.5.2', // Version of userscript
        matches: ['https://github.com/*'], // Merges old array and new array
      },
    });

    // Same as
    GM_info.version = '2.0.0';
    GM_info.platform.arch = 'arm';
    GM_info.script.version = '1.5.2';
    GM_info.script.matches.push('https://github.com/*');
  }),
);

setResource

Because mock-violentmonkey can't access the headers you need to make sure to add the resources manually before testing code that requires @resource tags.

type SetResource = (name: string, url: string) => Promise<string>;
// In the userscript
// @resource	example.org https://example.org

// When testing
await setResource('example.org', 'https://example.org/');

triggerMenuCommand

Because there's no UI a different method of triggering menu commands is necessary. To simulate clicking a menu command you can use this command instead.

GM_registerMenuCommand('Open settings', openSettings);
triggerMenuCommand('Open settings'); // Calls `openSettings`

setNotificationTimeout

This sets the time after which the notification will timeout and be removed (like closing a notification). Defaults to 50ms. The notification takes the value at the time of creating the notification.

type SetNotificationTimeout = (timeout: number) => void;

findNotifications

This provides a way of finding all the notifications by their text, image, or title and removing, clicking, or closing all in one go.

Setting any of the selectors to undefined allows any value. That's why findNotifications({}) returns all notifications.

type FindNotifications = (selectors: {
  text?: string;
  image?: string;
  title?: string;
}) => {
  remove: () => void; // Remove notifications that match the selectors
  click: () => void; // Click notifications that match the selectors
  close: () => void; // Close notifications that match the selectors
  count: () => number; // Get count of notifications that match the selectors
};

setNotificationCompat

This allows you to have GM_notification / GM.notification behave like with Firefox / Chromium. See more about the differences here

type SetNotificationCompat = (platform: 'Firefox' | 'Chromium') => void;

getClipboard

This allows you to get the current clipboard value for the current context.

type GetClipbard = () => {data: string; type: string} | undefined;

getTabs

This allows you to see all open tabs and close them or see the resolved options.

If url is undefined, it returns all tabs.

type GetTabs = (url?: string | RegExp) => Tab[];

type Tab = {
  close: () => void;
  url: string;
  options: {
    active: boolean;
    container: number;
    insert: boolean;
    pinned: boolean;
  };
};

enableDomGlobal

Instead of polluting the global namespace, this allows you to only enable whatever is required. Only document and window are added to the global namespace by default.

This should always be used for FormData, because node's implementation of FormData does not work with jsdom's File.

This is not aware of violentmonkey-contexts, so calling it once at the start of the file is enough.

console.log(typeof FormData); // undefined
enableDomGlobal('FormData');
console.log(typeof FormData); // function

setBaseUrl

If your code should behave differently depending on the location, you can set the location with setBaseUrl. If not called, it defaults to http://localhost:5000/. This is also used for relative urls.

This should run before calling GM_xmlhttpRequest, getWindow or other functions reliant on jsdom, ideally at the beginning of the vm context.

test(
  'setBaseUrl before',
  violentMonkeyContext(t => {
    setBaseUrl('https://google.com/');

    console.log(getWindow().location.href); // https://google.com/
  }),
);

test(
  'setBaseUrl after',
  violentMonkeyContext(t => {
    // Initialise the window.
    getWindow();

    // The window is already initialised, changing the baseUrl doesn't do anything
    setBaseUrl('https://google.com/');

    console.log(getWindow().location.href); // http:localhost:5000/
  }),
);

getDownloads

Instead of actually saving files to the disk, GM_download saves them to memory as a buffer and getDownloads provides a way of gaining access to them.

type GetDownloads = () => Record<string, Buffer>;
test('title', violentMonkeyContextMacro(), t => {
  console.log(getDownloads()); // {}

  GM_download({
    url: 'https://example.com/',
    name: 'example-com.html',
    onload: () => {
      console.log(getDownloads());
      /* {
				"example-com.html": <Buffer 3c 21 ...>
			} */
    },
  });
});

getDownload

This is similar to getDownloads but it only returns the corresponding buffer of the passed filename.

type GetDownload = (name: string) => Buffer | undefined;
test('title', violentMonkeyContextMacro(), t => {
  console.log(getDownload('example-com.html')); // undefined

  GM_download({
    url: 'https://example.com/',
    name: 'example-com.html',
    onload: () => {
      console.log(getDownload('example-com.html'));
      // <Buffer 3c 21 ...>
    },
  });
});

License

MIT