Skip to content

melusc/mock-violentmonkey

Repository files navigation

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

About

No description, website, or topics provided.

Resources

License

Stars

Watchers

Forks

Packages

No packages published

Languages