ソースを参照

WIP: migrate to Vue 3

Gerald 3 年 前
コミット
2493bfc6c0
85 ファイル変更1155 行追加1227 行削除
  1. 12 10
      .eslintrc.js
  2. 4 0
      .husky/pre-push
  3. 6 17
      .postcssrc.js
  4. 1 1
      jsconfig.json
  5. 20 29
      package.json
  6. 4 10
      scripts/webpack.conf.js
  7. 5 5
      src/background/index.js
  8. 1 1
      src/background/plugin/events.js
  9. 3 3
      src/background/sync/base.js
  10. 2 2
      src/background/sync/googledrive.js
  11. 2 2
      src/background/sync/onedrive.js
  12. 1 1
      src/background/utils/cache.js
  13. 4 4
      src/background/utils/db.js
  14. 3 3
      src/background/utils/icon.js
  15. 1 1
      src/background/utils/message.js
  16. 1 1
      src/background/utils/notifications.js
  17. 3 3
      src/background/utils/options.js
  18. 1 1
      src/background/utils/patch-db.js
  19. 1 1
      src/background/utils/popup-tracker.js
  20. 6 6
      src/background/utils/preinject.js
  21. 3 3
      src/background/utils/requests-core.js
  22. 3 3
      src/background/utils/requests.js
  23. 3 3
      src/background/utils/script.js
  24. 2 2
      src/background/utils/storage-fetch.js
  25. 2 2
      src/background/utils/tab-redirector.js
  26. 2 2
      src/background/utils/tabs.js
  27. 3 3
      src/background/utils/tester.js
  28. 1 1
      src/background/utils/update.js
  29. 2 2
      src/background/utils/values.js
  30. 1 1
      src/common/download.js
  31. 1 1
      src/common/index.js
  32. 1 1
      src/common/load-script-icon.js
  33. 1 1
      src/common/options.js
  34. 3 2
      src/common/router.js
  35. 1 1
      src/common/storage.js
  36. 2 2
      src/common/tld.js
  37. 8 8
      src/common/ui/code.vue
  38. 4 4
      src/common/ui/externals.vue
  39. 1 1
      src/common/ui/icon.vue
  40. 25 2
      src/common/ui/index.js
  41. 1 1
      src/common/ui/locale-group.vue
  42. 1 1
      src/common/ui/message.vue
  43. 1 1
      src/common/ui/setting-check.vue
  44. 3 3
      src/common/ui/setting-text.vue
  45. 1 10
      src/common/ui/style/index.js
  46. 3 0
      src/common/ui/style/style.css
  47. 2 2
      src/common/ui/toggle-button.vue
  48. 1 1
      src/common/util.js
  49. 8 13
      src/confirm/index.js
  50. 0 4
      src/confirm/style.css
  51. 15 15
      src/confirm/views/app.vue
  52. 1 1
      src/injected/content/safe-globals-content.js
  53. 1 1
      src/injected/content/util-content.js
  54. 2 2
      src/injected/index.js
  55. 1 1
      src/injected/safe-globals-injected.js
  56. 2 2
      src/injected/util/index.js
  57. 1 1
      src/injected/web/safe-globals-web.js
  58. 9 13
      src/options/index.js
  59. 0 4
      src/options/style.css
  60. 8 10
      src/options/utils/index.js
  61. 8 7
      src/options/views/app.vue
  62. 9 9
      src/options/views/edit/index.vue
  63. 3 3
      src/options/views/edit/settings.vue
  64. 6 6
      src/options/views/edit/values.vue
  65. 3 4
      src/options/views/feature.vue
  66. 8 8
      src/options/views/script-item.vue
  67. 74 71
      src/options/views/tab-installed.vue
  68. 11 11
      src/options/views/tab-settings/index.vue
  69. 3 3
      src/options/views/tab-settings/vm-blacklist.vue
  70. 2 2
      src/options/views/tab-settings/vm-css.vue
  71. 6 6
      src/options/views/tab-settings/vm-editor.vue
  72. 12 12
      src/options/views/tab-settings/vm-export.vue
  73. 6 6
      src/options/views/tab-settings/vm-import.vue
  74. 6 6
      src/options/views/tab-settings/vm-sync.vue
  75. 2 2
      src/options/views/tab-settings/vm-template.vue
  76. 9 13
      src/popup/index.js
  77. 4 2
      src/popup/utils/index.js
  78. 11 11
      src/popup/views/app.vue
  79. 15 18
      test/background/script.test.js
  80. 197 154
      test/background/tester.test.js
  81. 47 46
      test/common/index.test.js
  82. 6 8
      test/injected/gm-resource.test.js
  83. 5 7
      test/injected/helpers.test.js
  84. 4 4
      test/mock/polyfill.js
  85. 487 576
      yarn.lock

+ 12 - 10
.eslintrc.js

@@ -62,13 +62,9 @@ module.exports = {
   root: true,
   extends: [
     require.resolve('@gera2ld/plaid/eslint'),
-    require.resolve('@gera2ld/plaid-vue/eslint/vue'),
+    require.resolve('@gera2ld/plaid-common-vue/eslint/vue3-js'),
   ],
-  parserOptions: {
-    ecmaFeatures: {
-      legacyDecorators: true,
-    },
-  },
+  plugins: ['jest'],
   overrides: [{
     // `browser` is a local variable since we remove the global `chrome` and `browser` in injected*
     // to prevent exposing them to userscripts with `@inject-into content`
@@ -129,12 +125,18 @@ module.exports = {
     rules: {
       'vue/multi-word-component-names': 0,
     },
+  }, {
+    files: ['test/**'],
+    env: {
+      'jest/globals': true,
+    },
   }],
   rules: {
-    'import/extensions': ['error', 'ignorePackages', {
-      js: 'never',
-      vue: 'never',
-    }],
+    'prettier/prettier': 'off',
+    // 'import/extensions': ['error', 'ignorePackages', {
+    //   js: 'never',
+    //   vue: 'never',
+    // }],
     // copied from airbnb-base, replaced 4 with 8
     'object-curly-newline': ['error', {
       ObjectExpression: { minProperties: 8, multiline: true, consistent: true },

+ 4 - 0
.husky/pre-push

@@ -0,0 +1,4 @@
+#!/usr/bin/env sh
+. "$(dirname -- "$0")/_/husky.sh"
+
+yarn ci

+ 6 - 17
.postcssrc.js

@@ -1,19 +1,8 @@
-const { readManifest } = require('./scripts/manifest-helper');
-const cfg = require('@gera2ld/plaid/postcss/precss')({});
+const { combineConfigSync } = require('@gera2ld/plaid');
+const base = require('@gera2ld/plaid/postcss/base');
 
-module.exports = async () => {
-  const manifest = await readManifest();
-  const minChrome = parseInt(manifest.minimum_chrome_version);
-  const minFirefox = parseInt(manifest.browser_specific_settings.gecko.strict_min_version);
-  if (minChrome < 76 || minFirefox < 67) {
-    // Disabling `prefers-color-scheme` polyfill because we use our own one
-    cfg.plugins.forEach((p, i) => {
-      if ((p.postcss || {}).postcssPlugin === 'precss') {
-        cfg.plugins[i] = require('precss')({
-          features: { 'prefers-color-scheme-query': false },
-        });
-      }
-    });
-  }
+module.exports = combineConfigSync({}, [base, (cfg) => {
+  cfg.parser = 'postcss-scss';
+  cfg.plugins.unshift('postcss-simple-vars');
   return cfg;
-};
+}]);

+ 1 - 1
jsconfig.json

@@ -2,7 +2,7 @@
   "compilerOptions": {
     "baseUrl": ".",
     "paths": {
-      "#/*": ["./src/*"]
+      "@/*": ["./src/*"]
     }
   },
   "exclude": ["node_modules", "dist"]

+ 20 - 29
package.json

@@ -3,6 +3,7 @@
   "description": "Violentmonkey",
   "version": "2.13.0",
   "scripts": {
+    "prepare": "husky install",
     "dev": "gulp dev",
     "prebuild": "yarn ci",
     "build": "cross-env NODE_ENV=production gulp build",
@@ -14,9 +15,8 @@
     "lint:js": "eslint --ext .js,.vue .",
     "lint:yml": "gulp check",
     "svgo": "plaid svgo",
-    "test": "cross-env BABEL_ENV=test tape -r ./test/mock/register \"test/**/*.test.js\" | tap-summary",
-    "ci": "run-s lint test",
-    "transform": "locky yarn yarn",
+    "test": "cross-env BABEL_ENV=test jest test",
+    "ci": "run-p lint test",
     "bumpVersion": "gulp bump",
     "bump": "run-s ci \"bumpVersion --commit\"",
     "preversion": "run-s ci \"bumpVersion --reset\""
@@ -26,34 +26,29 @@
     "@actions/github": "^5.0.3",
     "@babel/helper-plugin-utils": "^7.18.6",
     "@babel/plugin-syntax-function-bind": "^7.18.6",
-    "@babel/register": "^7.18.6",
-    "@babel/runtime": "^7.18.6",
-    "@gera2ld/locky": "^0.1.1",
-    "@gera2ld/plaid": "~1.5.6",
-    "@gera2ld/plaid-vue": "~1.6.5",
-    "@gera2ld/plaid-webpack": "~1.5.5",
+    "@gera2ld/plaid": "~2.5.6",
+    "@gera2ld/plaid-common-ts": "~2.5.1",
+    "@gera2ld/plaid-test": "^2.5.0",
+    "@gera2ld/plaid-webpack": "~2.5.3",
+    "@gera2ld/plaid-webpack-vue3": "~2.5.5",
     "@types/chrome": "^0.0.191",
     "@types/firefox-webext-browser": "94.0.1",
     "amo-upload": "^0.2.0",
     "cross-env": "^7.0.3",
     "cross-spawn": "^7.0.3",
     "del": "^6.1.1",
-    "eslint-plugin-vue": "^9.3.0",
+    "eslint-plugin-jest": "^26.7.0",
     "fancy-log": "^2.0.0",
     "gulp": "^4.0.2",
     "gulp-plumber": "^1.1.0",
-    "husky": "^4.2.3",
+    "husky": "^8.0.1",
     "js-yaml": "^4.1.0",
     "jsdom": "^20.0.0",
-    "npm-run-all": "^4.1.5",
     "plugin-error": "^2.0.0",
+    "postcss-scss": "^4.0.4",
+    "postcss-simple-vars": "^6.0.3",
     "sharp": "^0.30.7",
-    "tap-summary": "^4.0.0",
-    "tape": "^5.5.3",
-    "terser": "^5.14.1",
-    "terser-webpack-plugin": "^4",
     "through2": "^4.0.2",
-    "vue-eslint-parser": "^9.0.3",
     "vinyl": "^2.2.1",
     "wrapper-webpack-plugin": "2.1.0"
   },
@@ -73,20 +68,16 @@
     "codemirror": "^5.65.6",
     "codemirror-js-mixed": "^0.9.2",
     "tldjs": "^2.3.1",
-    "vue": "^2.7.8",
-    "vue-loader": "^15.10.0",
-    "vue-template-babel-compiler": "^2.0.0",
-    "vue-template-compiler": "^2.7.8",
-    "vueleton": "^1.0.6"
+    "vue": "^3.2.37",
+    "vueleton": "^2.0.0-3"
   },
   "engines": {
-    "node": ">=10"
+    "node": ">=12"
   },
-  "husky": {
-    "hooks": {
-      "pre-commit": "yarn transform -t",
-      "pre-push": "run-p lint test"
-    }
+  "jest": {
+    "setupFiles": [
+      "./test/mock/index.js"
+    ]
   },
   "beta": 23
-}
+}

+ 4 - 10
scripts/webpack.conf.js

@@ -5,7 +5,7 @@ const TerserPlugin = isProd && require('terser-webpack-plugin');
 const deepmerge = isProd && require('deepmerge');
 const { ListBackgroundScriptsPlugin } = require('./manifest-helper');
 const { addWrapperWithGlobals, getCodeMirrorThemes, getUniqIdB64 } = require('./webpack-util');
-const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin');
+// const ProtectWebpackBootstrapPlugin = require('./webpack-protect-bootstrap-plugin');
 const projectConfig = require('./plaid.conf');
 const { getVersion } = require('./version-helper');
 const mergedConfig = shallowMerge(defaultOptions, projectConfig);
@@ -14,13 +14,10 @@ const mergedConfig = shallowMerge(defaultOptions, projectConfig);
 const INIT_FUNC_NAME = `Violentmonkey:${getUniqIdB64()}`;
 const VAULT_ID = '__VAULT_ID__';
 const HANDSHAKE_ID = '__HANDSHAKE_ID__';
-// eslint-disable-next-line import/no-dynamic-require
 const VM_VER = getVersion();
 const WEBPACK_OPTS = {
   node: {
     global: false,
-    process: false,
-    setImmediate: false,
   },
   performance: {
     maxEntrypointSize: 1e6,
@@ -28,10 +25,8 @@ const WEBPACK_OPTS = {
   },
 };
 const MIN_OPTS = {
-  cache: true,
   extractComments: false,
   parallel: true,
-  sourceMap: true,
   terserOptions: {
     compress: {
       // `terser` often inlines big one-time functions inside a small "hot" function
@@ -93,6 +88,7 @@ const skipReinjectionHeader = `{
 const modify = (page, entry, init) => modifyWebpackConfig(
   (config) => {
     Object.assign(config, WEBPACK_OPTS);
+    config.output.publicPath = '/';
     config.plugins.push(new webpack.DefinePlugin({
       ...defsObj,
       // Conditional compilation to remove unsafe and unused stuff from `injected`
@@ -126,12 +122,10 @@ module.exports = Promise.all([
     config.plugins.push(new ListBackgroundScriptsPlugin({
       minify: false, // keeping readable
     }));
-    config.module.rules.find(r => r.loader === 'vue-loader')
-    .options.compiler = require('vue-template-babel-compiler');
   }),
 
   modify('injected', './src/injected', (config) => {
-    config.plugins.push(new ProtectWebpackBootstrapPlugin());
+    // config.plugins.push(new ProtectWebpackBootstrapPlugin());
     addWrapperWithGlobals('injected/content', config, defsObj, getGlobals => ({
       header: () => `${skipReinjectionHeader} { ${getGlobals()}`,
       footer: '}}',
@@ -141,7 +135,7 @@ module.exports = Promise.all([
   modify('injected-web', './src/injected/web', (config) => {
     // TODO: replace WebPack's Object.*, .call(), .apply() with safe calls
     config.output.libraryTarget = 'commonjs2';
-    config.plugins.push(new ProtectWebpackBootstrapPlugin());
+    // config.plugins.push(new ProtectWebpackBootstrapPlugin());
     addWrapperWithGlobals('injected/web', config, defsObj, getGlobals => ({
       header: () => `${skipReinjectionHeader}
         window[INIT_FUNC_NAME] = function (IS_FIREFOX,${HANDSHAKE_ID},${VAULT_ID}) {

+ 5 - 5
src/background/index.js

@@ -1,8 +1,8 @@
-import '#/common/browser';
-import { getActiveTab, makePause, sendCmd } from '#/common';
-import { TIMEOUT_24HOURS, TIMEOUT_MAX } from '#/common/consts';
-import { deepCopy } from '#/common/object';
-import * as tld from '#/common/tld';
+import '@/common/browser';
+import { getActiveTab, makePause, sendCmd } from '@/common';
+import { TIMEOUT_24HOURS, TIMEOUT_MAX } from '@/common/consts';
+import { deepCopy } from '@/common/object';
+import * as tld from '@/common/tld';
 import * as sync from './sync';
 import { commands } from './utils';
 import { getData, getSizes, checkRemove } from './utils/db';

+ 1 - 1
src/background/plugin/events.js

@@ -1,4 +1,4 @@
-import EventEmitter from '#/common/events';
+import EventEmitter from '@/common/events';
 
 export default new EventEmitter([
   'scriptEdit',

+ 3 - 3
src/background/sync/base.js

@@ -1,10 +1,10 @@
 import {
   debounce, normalizeKeys, request, noop, makePause, ensureArray, sendCmd,
-} from '#/common';
-import { TIMEOUT_HOUR } from '#/common/consts';
+} from '@/common';
+import { TIMEOUT_HOUR } from '@/common/consts';
 import {
   forEachEntry, objectSet, objectPick,
-} from '#/common/object';
+} from '@/common/object';
 import {
   getEventEmitter, getOption, setOption, hookOptions,
 } from '../utils';

+ 2 - 2
src/background/sync/googledrive.js

@@ -1,8 +1,8 @@
 // Reference:
 // - https://developers.google.com/drive/v3/reference/files
 // - https://github.com/google/google-api-nodejs-client
-import { getUniqId, noop } from '#/common';
-import { objectGet } from '#/common/object';
+import { getUniqId, noop } from '@/common';
+import { objectGet } from '@/common/object';
 import { dumpQuery, notify } from '../utils';
 import {
   getURI, getItemFilename, BaseService, register, isScriptFile,

+ 2 - 2
src/background/sync/onedrive.js

@@ -1,6 +1,6 @@
 // Reference: https://dev.onedrive.com/README.htm
-import { noop } from '#/common';
-import { objectGet } from '#/common/object';
+import { noop } from '@/common';
+import { objectGet } from '@/common/object';
 import { dumpQuery } from '../utils';
 import {
   getURI, getItemFilename, BaseService, isScriptFile, register,

+ 1 - 1
src/background/utils/cache.js

@@ -1,4 +1,4 @@
-import initCache from '#/common/cache';
+import initCache from '@/common/cache';
 import { commands } from './message';
 
 const cache = initCache({

+ 4 - 4
src/background/utils/db.js

@@ -1,10 +1,10 @@
 import {
   compareVersion, dataUri2text, i18n,
   getFullUrl, getScriptName, isRemote, sendCmd, trueJoin,
-} from '#/common';
-import { INJECT_PAGE, INJECT_AUTO, TIMEOUT_WEEK } from '#/common/consts';
-import { forEachEntry, forEachKey, forEachValue } from '#/common/object';
-import storage from '#/common/storage';
+} from '@/common';
+import { INJECT_PAGE, INJECT_AUTO, TIMEOUT_WEEK } from '@/common/consts';
+import { forEachEntry, forEachKey, forEachValue } from '@/common/object';
+import storage from '@/common/storage';
 import pluginEvents from '../plugin/events';
 import { getNameURI, parseMeta, newScript, getDefaultCustom } from './script';
 import { testScript, testBlacklist } from './tester';

+ 3 - 3
src/background/utils/icon.js

@@ -1,6 +1,6 @@
-import { i18n, noop } from '#/common';
-import { INJECTABLE_TAB_URL_RE } from '#/common/consts';
-import { objectPick } from '#/common/object';
+import { i18n, noop } from '@/common';
+import { INJECTABLE_TAB_URL_RE } from '@/common/consts';
+import { objectPick } from '@/common/object';
 import cache from './cache';
 import { postInitialize } from './init';
 import { commands, forEachTab } from './message';

+ 1 - 1
src/background/utils/message.js

@@ -1,4 +1,4 @@
-import { defaultImage, i18n, noop } from '#/common';
+import { defaultImage, i18n, noop } from '@/common';
 
 export const commands = {};
 

+ 1 - 1
src/background/utils/notifications.js

@@ -1,4 +1,4 @@
-import { i18n, defaultImage, sendTabCmd } from '#/common';
+import { i18n, defaultImage, sendTabCmd } from '@/common';
 import { commands } from './message';
 
 const openers = {};

+ 3 - 3
src/background/utils/options.js

@@ -1,6 +1,6 @@
-import { debounce, ensureArray, initHooks, normalizeKeys } from '#/common';
-import { deepCopy, deepEqual, mapEntry, objectGet, objectSet } from '#/common/object';
-import defaults from '#/common/options-defaults';
+import { debounce, ensureArray, initHooks, normalizeKeys } from '@/common';
+import { deepCopy, deepEqual, mapEntry, objectGet, objectSet } from '@/common/object';
+import defaults from '@/common/options-defaults';
 import { preInitialize } from './init';
 import { commands } from './message';
 

+ 1 - 1
src/background/utils/patch-db.js

@@ -1,5 +1,5 @@
 import { parseMeta } from './script';
-import storage from '#/common/storage';
+import storage from '@/common/storage';
 
 export default () => new Promise((resolve, reject) => {
   console.info('Upgrade database...');

+ 1 - 1
src/background/utils/popup-tracker.js

@@ -1,4 +1,4 @@
-import { getActiveTab, sendTabCmd } from '#/common';
+import { getActiveTab, sendTabCmd } from '@/common';
 import cache from './cache';
 import { getData } from './db';
 import { postInitialize } from './init';

+ 6 - 6
src/background/utils/preinject.js

@@ -1,12 +1,12 @@
-import { getScriptName, getUniqId, sendTabCmd, trueJoin } from '#/common';
+import { getScriptName, getUniqId, sendTabCmd, trueJoin } from '@/common';
 import {
   INJECT_AUTO, INJECT_CONTENT, INJECT_MAPPING, INJECT_PAGE,
   INJECTABLE_TAB_URL_RE, METABLOCK_RE,
-} from '#/common/consts';
-import initCache from '#/common/cache';
-import { forEachEntry, objectPick, objectSet } from '#/common/object';
-import storage from '#/common/storage';
-import ua from '#/common/ua';
+} from '@/common/consts';
+import initCache from '@/common/cache';
+import { forEachEntry, objectPick, objectSet } from '@/common/object';
+import storage from '@/common/storage';
+import ua from '@/common/ua';
 import { getScriptsByURL, ENV_CACHE_KEYS, ENV_REQ_KEYS, ENV_SCRIPTS, ENV_VALUE_IDS } from './db';
 import { extensionRoot, postInitialize } from './init';
 import { commands } from './message';

+ 3 - 3
src/background/utils/requests-core.js

@@ -1,6 +1,6 @@
-import { buffer2string, getUniqId, isEmpty, noop } from '#/common';
-import { forEachEntry } from '#/common/object';
-import ua from '#/common/ua';
+import { buffer2string, getUniqId, isEmpty, noop } from '@/common';
+import { forEachEntry } from '@/common/object';
+import ua from '@/common/ua';
 import { extensionRoot } from './init';
 
 let encoder;

+ 3 - 3
src/background/utils/requests.js

@@ -1,6 +1,6 @@
-import { blob2base64, sendTabCmd, string2uint8array } from '#/common';
-import { forEachEntry, forEachValue, objectPick } from '#/common/object';
-import ua from '#/common/ua';
+import { blob2base64, sendTabCmd, string2uint8array } from '@/common';
+import { forEachEntry, forEachValue, objectPick } from '@/common/object';
+import ua from '@/common/ua';
 import cache from './cache';
 import { commands } from './message';
 import {

+ 3 - 3
src/background/utils/script.js

@@ -1,6 +1,6 @@
-import { getUniqId, encodeFilename } from '#/common';
-import { METABLOCK_RE } from '#/common/consts';
-import { mapEntry } from '#/common/object';
+import { getUniqId, encodeFilename } from '@/common';
+import { METABLOCK_RE } from '@/common/consts';
+import { mapEntry } from '@/common/object';
 import { commands } from './message';
 import { getOption } from './options';
 import cache from './cache';

+ 2 - 2
src/background/utils/storage-fetch.js

@@ -1,5 +1,5 @@
-import { request } from '#/common';
-import storage from '#/common/storage';
+import { request } from '@/common';
+import storage from '@/common/storage';
 
 /** @type { function(url, options, check): Promise<void> } or throws on error */
 storage.cache.fetch = cacheOrFetch({

+ 2 - 2
src/background/utils/tab-redirector.js

@@ -1,5 +1,5 @@
-import { request, noop, i18n, getUniqId } from '#/common';
-import ua from '#/common/ua';
+import { request, noop, i18n, getUniqId } from '@/common';
+import ua from '@/common/ua';
 import cache from './cache';
 import { extensionRoot } from './init';
 import { commands } from './message';

+ 2 - 2
src/background/utils/tabs.js

@@ -1,5 +1,5 @@
-import { getActiveTab, noop, sendTabCmd, getFullUrl } from '#/common';
-import ua from '#/common/ua';
+import { getActiveTab, noop, sendTabCmd, getFullUrl } from '@/common';
+import ua from '@/common/ua';
 import { extensionRoot } from './init';
 import { commands } from './message';
 import { getOption } from './options';

+ 3 - 3
src/background/utils/tester.js

@@ -1,4 +1,4 @@
-import * as tld from '#/common/tld';
+import * as tld from '@/common/tld';
 import cache from './cache';
 import { postInitialize } from './init';
 import { commands } from './message';
@@ -207,8 +207,8 @@ export function testBlacklist(url) {
   let res = blCache[url];
   if (res === undefined) {
     const rule = blacklistRules.find(({ test }) => test(url));
-    res = rule?.reject && rule.text;
-    updateBlacklistCache(url, res || false);
+    res = rule?.reject && rule.text || false;
+    updateBlacklistCache(url, res);
   }
   return res;
 }

+ 1 - 1
src/background/utils/update.js

@@ -1,4 +1,4 @@
-import { getScriptName, i18n, request, compareVersion, sendCmd, trueJoin } from '#/common';
+import { getScriptName, i18n, request, compareVersion, sendCmd, trueJoin } from '@/common';
 import { fetchResources, getScriptById, getScripts, parseScript } from './db';
 import { parseMeta } from './script';
 import { getOption, setOption } from './options';

+ 2 - 2
src/background/utils/values.js

@@ -1,5 +1,5 @@
-import { isEmpty, makePause, sendTabCmd } from '#/common';
-import { forEachEntry, forEachKey, objectSet } from '#/common/object';
+import { isEmpty, makePause, sendTabCmd } from '@/common';
+import { forEachEntry, forEachKey, objectSet } from '@/common/object';
 import { getScript, getValueStoresByIds, dumpValueStores } from './db';
 import { commands } from './message';
 

+ 1 - 1
src/common/download.js

@@ -1,4 +1,4 @@
-import { makePause } from '#/common';
+import { makePause } from '@/common';
 
 export function downloadBlob(blob, name) {
   const url = URL.createObjectURL(blob);

+ 1 - 1
src/common/index.js

@@ -1,6 +1,6 @@
 // SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null
 
-import { browser } from '#/common/consts';
+import { browser } from '@/common/consts';
 import { deepCopy } from './object';
 import { i18n, noop } from './util';
 

+ 1 - 1
src/common/load-script-icon.js

@@ -1,4 +1,4 @@
-import { sendCmdDirectly } from '#/common/index';
+import { sendCmdDirectly } from '@/common/index';
 
 const KEY = 'safeIcon';
 

+ 1 - 1
src/common/options.js

@@ -1,4 +1,4 @@
-import defaults from '#/common/options-defaults';
+import defaults from '@/common/options-defaults';
 import { initHooks, sendCmdDirectly } from '.';
 import { forEachEntry, objectGet, objectSet } from './object';
 

+ 3 - 2
src/common/router.js

@@ -1,4 +1,5 @@
-import { showConfirmation } from '#/common/ui';
+import { reactive } from 'vue';
+import { showConfirmation } from '@/common/ui';
 import { i18n } from './util';
 
 function parse(hash) {
@@ -17,7 +18,7 @@ function parse(hash) {
 }
 
 const stack = [];
-export const route = {};
+export const route = reactive({});
 export const lastRoute = () => stack[stack.length - 1] || {};
 
 updateRoute();

+ 1 - 1
src/common/storage.js

@@ -1,4 +1,4 @@
-import { deepCopy, forEachEntry } from '#/common/object';
+import { deepCopy, forEachEntry } from '@/common/object';
 import { blob2base64, ensureArray } from './util';
 
 /** @type VMCache */

+ 2 - 2
src/common/tld.js

@@ -1,7 +1,7 @@
-import tldjs from 'tldjs';
+import tldjs from 'tldjs/tld';
 // import { fromUserSettings } from 'tldjs';
 // import Trie from 'tldjs/lib/suffix-trie';
-// import { request } from '#/common';
+// import { request } from '@/common';
 
 // let tldjs;
 

+ 8 - 8
src/common/ui/code.vue

@@ -71,13 +71,13 @@ import 'codemirror/addon/hint/show-hint';
 import 'codemirror/addon/hint/javascript-hint';
 import 'codemirror/addon/hint/anyword-hint';
 import CodeMirror from 'codemirror';
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import ToggleButton from '#/common/ui/toggle-button';
-import { debounce, getUniqId, i18n } from '#/common';
-import { deepEqual, forEachEntry, objectPick } from '#/common/object';
-import hookSetting from '#/common/hook-setting';
-import options from '#/common/options';
-import storage from '#/common/storage';
+import Tooltip from 'vueleton/lib/tooltip';
+import ToggleButton from '@/common/ui/toggle-button';
+import { debounce, getUniqId, i18n } from '@/common';
+import { deepEqual, forEachEntry, objectPick } from '@/common/object';
+import hookSetting from '@/common/hook-setting';
+import options from '@/common/options';
+import storage from '@/common/storage';
 
 /* eslint-disable no-control-regex */
 let maxDisplayLength;
@@ -630,7 +630,7 @@ export default {
       }
     });
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.onActive(false);
   },
 };

+ 4 - 4
src/common/ui/externals.vue

@@ -23,7 +23,7 @@
       <vm-code
         v-show="!img"
         class="abs-full"
-        v-model="code"
+        :value="code"
         ref="code"
         readonly
         :cm-options="cmOptions"
@@ -34,9 +34,9 @@
 </template>
 
 <script>
-import { formatByteLength, dataUri2text } from '#/common';
-import VmCode from '#/common/ui/code';
-import storage from '#/common/storage';
+import { formatByteLength, dataUri2text } from '@/common';
+import VmCode from '@/common/ui/code';
+import storage from '@/common/storage';
 
 export default {
   props: ['value', 'cmOptions', 'commands', 'install', 'errors'],

+ 1 - 1
src/common/ui/icon.vue

@@ -3,7 +3,7 @@
 </template>
 
 <script>
-const requireIcon = require.context('#/resources/svg', false, /\.svg$/);
+const requireIcon = require.context('@/resources/svg', false, /\.svg$/);
 requireIcon.keys().map(key => requireIcon(key));
 
 export default {

+ 25 - 2
src/common/ui/index.js

@@ -1,5 +1,6 @@
-import Modal from 'vueleton/lib/modal/bundle';
-import { i18n } from '#/common/util';
+import { createApp } from 'vue';
+import Modal from 'vueleton/lib/modal';
+import { i18n } from '@/common/util';
 import Message from './message';
 
 export function showMessage(message) {
@@ -49,3 +50,25 @@ export function showConfirmation(text, { ok, cancel, input = false } = {}) {
     });
   });
 }
+
+/** @returns {?number} Number of lines + 1 if the last line is not empty */
+export function calcRows(val) {
+  return val && (
+    val.match(/$/gm).length
+      + !val.endsWith('\n')
+  );
+}
+
+export function render(App, el) {
+  const app = createApp(App);
+  Object.assign(app.config.globalProperties, {
+    i18n,
+    calcRows,
+  });
+  if (!el) {
+    el = document.createElement('div');
+    document.body.append(el);
+  }
+  app.mount(el);
+  return app;
+}

+ 1 - 1
src/common/ui/locale-group.vue

@@ -7,7 +7,7 @@
 </template>
 
 <script>
-import { i18n } from '#/common';
+import { i18n } from '@/common';
 
 const SEP = '\x02';
 

+ 1 - 1
src/common/ui/message.vue

@@ -40,7 +40,7 @@ export default {
       });
     }
   },
-  beforeDestroy() {
+  beforeUnmount() {
     const i = dismissers.indexOf(this.dismiss);
     if (i >= 0) dismissers.splice(i, 1);
   },

+ 1 - 1
src/common/ui/setting-check.vue

@@ -44,7 +44,7 @@ export default {
     this.revoke = hookSetting(this.name, val => { this.value = val; });
     this.$watch('value', this.onChange);
   },
-  beforeDestroy() {
+  beforeUnmount() {
     if (this.revoke) this.revoke();
   },
 };

+ 3 - 3
src/common/ui/setting-text.vue

@@ -9,7 +9,7 @@
       :disabled="disabled"
       :title="parsedData.error"
       :placeholder="placeholder"
-      :rows="rows || CalcRows(value)"
+      :rows="rows || calcRows(value)"
       @change="onChange"
     />
     <button v-if="hasSave" v-text="i18n('buttonSave')" @click="onSave"
@@ -21,7 +21,7 @@
 </template>
 
 <script>
-import { getUnloadSentry } from '#/common/router';
+import { getUnloadSentry } from '@/common/router';
 import { deepEqual, objectGet } from '../object';
 import options from '../options';
 import defaults from '../options-defaults';
@@ -96,7 +96,7 @@ export default {
       this.value = handle(this.savedValue);
     });
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.revoke();
     this.toggleUnloadSentry(false);
   },

+ 1 - 10
src/common/ui/style/index.js

@@ -1,5 +1,3 @@
-import Vue from 'vue';
-import { i18n } from '#/common';
 import options from '../../options';
 import './style.css';
 
@@ -11,7 +9,7 @@ let localStorage = {};
 /* Accessing `localStorage` in may throw in Private Browsing mode or if dom.storage is disabled.
  * Since it allows object-like access, we'll map it to a variable with a fallback to a dummy. */
 try {
-  (localStorage = global.localStorage || {}).foo; // eslint-disable-line babel/no-unused-expressions
+  (localStorage = global.localStorage || {}).foo;
 } catch (e) {
   /* keep the dummy object */
 }
@@ -68,13 +66,6 @@ options.hook((changes) => {
   }
 });
 
-Vue.prototype.i18n = i18n;
-/** @returns {?number} Number of lines + 1 if the last line is not empty */
-Vue.prototype.CalcRows = val => val && (
-  val.match(/$/gm).length
-  + !val.endsWith('\n')
-);
-
 if ('ontouchstart' in document) {
   document.documentElement.classList.add('touch');
 }

+ 3 - 0
src/common/ui/style/style.css

@@ -309,6 +309,9 @@ li {
   margin-top: .5em;
   margin-bottom: .5em;
 }
+.h-screen {
+  height: 100vh;
+}
 .h-100 {
   height: 100%;
 }

+ 2 - 2
src/common/ui/toggle-button.vue

@@ -10,10 +10,10 @@
 
 <script>
 export default {
-  props: ['value'],
+  props: ['modelValue'],
   methods: {
     onToggle() {
-      this.$emit('input', !this.value);
+      this.$emit('update:modelValue', !this.value);
     },
   },
 };

+ 1 - 1
src/common/util.js

@@ -1,6 +1,6 @@
 // SAFETY WARNING! Exports used by `injected` must make ::safe() calls and use __proto__:null
 
-import { browser } from '#/common/consts';
+import { browser } from '@/common/consts';
 
 export function i18n(name, args) {
   return browser.i18n.getMessage(name, args) || name;

+ 8 - 13
src/confirm/index.js

@@ -1,20 +1,15 @@
-import Vue from 'vue';
-import '#/common/browser';
-import { i18n } from '#/common';
-import '#/common/handlers';
-import options from '#/common/options';
-import '#/common/ui/favicon';
-import '#/common/ui/style';
+import '@/common/browser';
+import { i18n } from '@/common';
+import '@/common/handlers';
+import options from '@/common/options';
+import { render } from '@/common/ui';
+import '@/common/ui/favicon';
+import '@/common/ui/style';
 import App from './views/app';
 import './style.css';
 
 document.title = `${i18n('labelInstall')} - ${i18n('extName')}`;
 
 options.ready.then(() => {
-  const el = document.createElement('div');
-  document.body.appendChild(el);
-  new Vue({
-    render: h => h(App),
-  })
-  .$mount(el);
+  render(App);
 });

+ 0 - 4
src/confirm/style.css

@@ -1,4 +0,0 @@
-html,
-body {
-  height: 100%;
-}

+ 15 - 15
src/confirm/views/app.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-confirm frame flex flex-col h-100" :class="{ reinstall }">
+  <div class="page-confirm frame flex flex-col h-screen" :class="{ reinstall }">
     <div class="frame-block">
       <div class="flex">
         <div class="image">
@@ -75,7 +75,7 @@
       <vm-externals
         ref="externals"
         v-if="script"
-        v-model="script"
+        :value="script"
         class="abs-full"
         :cm-options="cmOptions"
         :commands="commands"
@@ -86,21 +86,21 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import Icon from '#/common/ui/icon';
+import Tooltip from 'vueleton/lib/tooltip';
+import Icon from '@/common/ui/icon';
 import {
   sendCmdDirectly, request, isRemote, getFullUrl, makePause,
   getLocaleString, trueJoin,
-} from '#/common';
-import { keyboardService } from '#/common/keyboard';
-import initCache from '#/common/cache';
-import storage from '#/common/storage';
-import VmExternals from '#/common/ui/externals';
-import SettingCheck from '#/common/ui/setting-check';
-import { loadScriptIcon } from '#/common/load-script-icon';
-import { deepEqual, objectPick } from '#/common/object';
-import { route } from '#/common/router';
-import ua from '#/common/ua';
+} from '@/common';
+import { keyboardService } from '@/common/keyboard';
+import initCache from '@/common/cache';
+import storage from '@/common/storage';
+import VmExternals from '@/common/ui/externals';
+import SettingCheck from '@/common/ui/setting-check';
+import { loadScriptIcon } from '@/common/load-script-icon';
+import { deepEqual, objectPick } from '@/common/object';
+import { route } from '@/common/router';
+import ua from '@/common/ua';
 
 const KEEP_INFO_DELAY = 5000;
 const RETRY_DELAY = 3000;
@@ -201,7 +201,7 @@ export default {
     ];
     keyboardService.enable();
   },
-  beforeDestroy() {
+  beforeUnmount() {
     if (this.guard) {
       clearInterval(this.guard);
       this.guard = null;

+ 1 - 1
src/injected/content/safe-globals-content.js

@@ -8,7 +8,7 @@
 export const {
   Blob: SafeBlob,
   CustomEvent: SafeCustomEvent,
-  Error, // for #/common e.g. in sendMessage
+  Error, // for @/common e.g. in sendMessage
   MouseEvent: SafeMouseEvent,
   Object, // for minification and guarding webpack Object(import) calls
   Promise: SafePromise,

+ 1 - 1
src/injected/content/util-content.js

@@ -1,5 +1,5 @@
 // eslint-disable-next-line no-restricted-imports
-export { sendCmd } from '#/common';
+export { sendCmd } from '@/common';
 
 /** When looking for documentElement, use '*' to also support XML pages
  * Note that we avoid spoofed prototype getters by using hasOwnProperty, and not using `length`

+ 2 - 2
src/injected/index.js

@@ -1,5 +1,5 @@
-import '#/common/browser'; // eslint-disable-line no-restricted-imports
-import { sendCmd } from '#/common'; // eslint-disable-line no-restricted-imports
+import '@/common/browser'; // eslint-disable-line no-restricted-imports
+import { sendCmd } from '@/common'; // eslint-disable-line no-restricted-imports
 import './content';
 
 // Script installation in Firefox as it does not support `onBeforeRequest` for `file:`

+ 1 - 1
src/injected/safe-globals-injected.js

@@ -4,7 +4,7 @@
  * This file is used by both `injected` and `injected-web` entries.
  * `global` is used instead of WebPack's polyfill which we disable in webpack.conf.js.
  * `export` is stripped in the final output and is only used for our NodeJS test scripts.
- * WARNING! Don't use exported functions from #/common anywhere in injected!
+ * WARNING! Don't use exported functions from @/common anywhere in injected!
  */
 
 const global = (function _() {

+ 2 - 2
src/injected/util/index.js

@@ -8,8 +8,8 @@
 export {
   dumpScriptValue,
   isEmpty,
-} from '#/common';
-export * from '#/common/consts';
+} from '@/common';
+export * from '@/common/consts';
 
 export const fireBridgeEvent = (eventId, msg, cloneInto) => {
   const detail = cloneInto ? cloneInto(msg, document) : msg;

+ 1 - 1
src/injected/web/safe-globals-web.js

@@ -1,5 +1,5 @@
 /* eslint-disable one-var, one-var-declaration-per-line, no-unused-vars,
-   prefer-const, import/no-mutable-exports */
+   prefer-const */
 
 /**
  * `safeCall` is used by our modified babel-plugin-safe-bind.js.

+ 9 - 13
src/options/index.js

@@ -1,10 +1,10 @@
-import Vue from 'vue';
-import '#/common/browser';
-import { formatByteLength, getLocaleString, i18n, sendCmdDirectly, trueJoin } from '#/common';
-import handlers from '#/common/handlers';
-import { loadScriptIcon } from '#/common/load-script-icon';
-import options from '#/common/options';
-import '#/common/ui/style';
+import '@/common/browser';
+import { formatByteLength, getLocaleString, i18n, sendCmdDirectly, trueJoin } from '@/common';
+import handlers from '@/common/handlers';
+import { loadScriptIcon } from '@/common/load-script-icon';
+import options from '@/common/options';
+import { render } from '@/common/ui';
+import '@/common/ui/style';
 import { store } from './utils';
 import App from './views/app';
 
@@ -23,11 +23,7 @@ initialize();
 
 function initialize() {
   initMain();
-  const vm = new Vue({
-    render: h => h(App),
-  })
-  .$mount();
-  document.body.append(vm.$el);
+  render(App);
 }
 
 /**
@@ -122,7 +118,7 @@ function initMain() {
         update.message = '';
         scripts.push(updated);
       } else {
-        Vue.set(scripts, index, updated);
+        scripts[index] = updated;
       }
     },
     RemoveScript(id) {

+ 0 - 4
src/options/style.css

@@ -3,10 +3,6 @@ $tabPadX: 2rem;
 $tabPadY: 3rem;
 $sectionBorder: 1px solid var(--fill-2);
 
-html,
-body {
-  height: 100%;
-}
 aside {
   position: relative;
   z-index: 1000;

+ 8 - 10
src/options/utils/index.js

@@ -1,15 +1,13 @@
-import { route } from '#/common/router';
-import { isHiDPI } from '#/common/ui/favicon';
+import { computed, reactive } from 'vue';
+import { route } from '@/common/router';
+import { isHiDPI } from '@/common/ui/favicon';
 
-export const store = {
+export const store = reactive({
   route,
   scripts: [],
-  get installedScripts() {
-    return store.scripts.filter(script => !script.config.removed);
-  },
-  get removedScripts() {
-    return store.scripts.filter(script => script.config.removed);
-  },
   HiDPI: isHiDPI,
   storageSize: 0,
-};
+});
+
+export const installedScripts = computed(() => store.scripts.filter(script => !script.config.removed));
+export const removedScripts = computed(() => store.scripts.filter(script => script.config.removed));

+ 8 - 7
src/options/views/app.vue

@@ -1,5 +1,5 @@
 <template>
-  <div class="page-options flex h-100">
+  <div class="page-options flex h-screen">
     <aside :class="{ 'show-aside': aside }" v-if="canRenderAside">
       <div class="aside-content">
         <img src="/public/images/icon128.png">
@@ -10,7 +10,7 @@
             :key="tab.name"
             :href="`#${tab.name}`"
             :class="{active: tab === current}"
-            :data-num-scripts="tab.name === 'scripts' && store.installedScripts.length || null"
+            :data-num-scripts="tab.name === 'scripts' && installedScripts.length || null"
             v-text="tab.label"
           />
         </div>
@@ -23,10 +23,10 @@
 </template>
 
 <script>
-import { i18n } from '#/common';
-import Icon from '#/common/ui/icon';
-import { keyboardService } from '#/common/keyboard';
-import { store } from '../utils';
+import { i18n } from '@/common';
+import Icon from '@/common/ui/icon';
+import { keyboardService } from '@/common/keyboard';
+import { store, installedScripts } from '../utils';
 import Installed from './tab-installed';
 import Settings from './tab-settings';
 import About from './tab-about';
@@ -54,6 +54,7 @@ export default {
       // skip rendering the aside when starting in the editor for a new script.
       canRenderAside: name !== SCRIPTS || (tabFunc !== '_new' && !Number(tabFunc)),
       store,
+      installedScripts,
     };
   },
   computed: {
@@ -107,7 +108,7 @@ export default {
     keyboardService.enable();
     this.updateContext();
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.disposeList?.forEach(dispose => {
       dispose();
     });

+ 9 - 9
src/options/views/edit/index.vue

@@ -63,17 +63,17 @@
 import {
   debounce, formatByteLength, getScriptName, i18n, isEmpty,
   sendCmdDirectly, trueJoin,
-} from '#/common';
-import { deepCopy, deepEqual, objectPick } from '#/common/object';
-import { showConfirmation, showMessage } from '#/common/ui';
-import { keyboardService } from '#/common/keyboard';
-import VmCode from '#/common/ui/code';
-import options from '#/common/options';
-import { route, getUnloadSentry } from '#/common/router';
+} from '@/common';
+import { deepCopy, deepEqual, objectPick } from '@/common/object';
+import { showConfirmation, showMessage } from '@/common/ui';
+import { keyboardService } from '@/common/keyboard';
+import VmCode from '@/common/ui/code';
+import options from '@/common/options';
+import { route, getUnloadSentry } from '@/common/router';
 import { store } from '../../utils';
 import VmSettings from './settings';
 import VmValues from './values';
-import VmExternals from '#/common/ui/externals';
+import VmExternals from '@/common/ui/externals';
 import VmHelp from './help';
 
 const CUSTOM_PROPS = {
@@ -339,7 +339,7 @@ export default {
       this.canSave = this.codeDirty || !deepEqual(this.settings, savedSettings);
     },
   },
-  beforeDestroy() {
+  beforeUnmount() {
     store.title = null;
     this.toggleUnloadSentry(false);
     this.disposeList?.forEach(dispose => {

+ 3 - 3
src/options/views/edit/settings.vue

@@ -76,7 +76,7 @@
           </label>
         </td>
         <td>
-          <textarea v-model="custom[name]" spellcheck="false" :rows="CalcRows(custom[name])"/>
+          <textarea v-model="custom[name]" spellcheck="false" :rows="calcRows(custom[name])"/>
         </td>
       </tr>
     </table>
@@ -84,8 +84,8 @@
 </template>
 
 <script>
-import { i18n } from '#/common';
-import { objectGet } from '#/common/object';
+import { i18n } from '@/common';
+import { objectGet } from '@/common/object';
 
 const highlightMetaKeys = str => str.match(/^(.*?)(@[-a-z]+)(.*)/)?.slice(1) || [str, '', ''];
 

+ 6 - 6
src/options/views/edit/values.vue

@@ -89,12 +89,12 @@
 </template>
 
 <script>
-import { dumpScriptValue, formatByteLength, isEmpty, sendCmdDirectly } from '#/common';
-import { handleTabNavigation, keyboardService } from '#/common/keyboard';
-import { deepEqual, mapEntry } from '#/common/object';
-import Icon from '#/common/ui/icon';
-import storage from '#/common/storage';
-import { showMessage } from '#/common/ui';
+import { dumpScriptValue, formatByteLength, isEmpty, sendCmdDirectly } from '@/common';
+import { handleTabNavigation, keyboardService } from '@/common/keyboard';
+import { deepEqual, mapEntry } from '@/common/object';
+import Icon from '@/common/ui/icon';
+import storage from '@/common/storage';
+import { showMessage } from '@/common/ui';
 import { store } from '../../utils';
 
 const PAGE_SIZE = 25;

+ 3 - 4
src/options/views/feature.vue

@@ -5,9 +5,8 @@
 </template>
 
 <script>
-import Vue from 'vue';
-import options from '#/common/options';
-import { objectGet } from '#/common/object';
+import options from '@/common/options';
+import { objectGet } from '@/common/object';
 import { store } from '../utils';
 
 const FEATURES_KEY = 'features';
@@ -15,7 +14,7 @@ store.features = options.get(FEATURES_KEY);
 options.hook((data) => {
   const features = data[FEATURES_KEY];
   if (features) {
-    Vue.set(store, 'features', features);
+    store.features = features;
   }
 });
 options.ready.then(() => reset('sync'));

+ 8 - 8
src/options/views/script-item.vue

@@ -19,10 +19,7 @@
     </div>
     <div class="script-info flex ml-1c">
       <span class="script-order" v-text="script.props.position"/>
-      <!-- eslint-disable-next-line vue/require-component-is -->
-      <component class="script-name ellipsis flex-auto" v-bind="viewTable
-        ? { is: 'a', href: url, tabIndex }
-        : { is: 'span' }">{{script.$cache.name}}</component>
+      <component :is="nameProps.is" class="script-name ellipsis flex-auto" v-bind="nameProps">{{script.$cache.name}}</component>
       <template v-if="canRender">
         <tooltip v-if="author" :content="i18n('labelAuthor') + script.meta.author"
                  class="script-author ml-1c hidden-sm"
@@ -128,10 +125,10 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { getLocaleString, formatTime } from '#/common';
-import Icon from '#/common/ui/icon';
-import { keyboardService, isInput, toggleTip } from '#/common/keyboard';
+import Tooltip from 'vueleton/lib/tooltip';
+import { getLocaleString, formatTime } from '@/common';
+import Icon from '@/common/ui/icon';
+import { keyboardService, isInput, toggleTip } from '@/common/keyboard';
 import enableDragging from '../utils/dragging';
 
 const itemMargin = 8;
@@ -212,6 +209,9 @@ export default {
     url() {
       return `#scripts/${this.script.props.id}`;
     },
+    nameProps() {
+      return this.viewTable ? { is: 'a', href: this.url, tabIndex: this.tabIndex } : { is: 'span' };
+    },
   },
   watch: {
     visible(visible) {

+ 74 - 71
src/options/views/tab-installed.vue

@@ -7,25 +7,27 @@
             :closeAfterClick="true"
             :class="{active: menuNewActive}"
             @stateChange="onStateChange">
-            <tooltip :content="i18n('buttonNew')" placement="bottom" align="start" slot="toggle">
+            <tooltip :content="i18n('buttonNew')" placement="bottom" align="start">
               <a class="btn-ghost" tabindex="0">
                 <icon name="plus"></icon>
               </a>
             </tooltip>
-            <a
-              class="dropdown-menu-item"
-              v-text="i18n('buttonNew')"
-              tabindex="0"
-              @click.prevent="editScript('_new')"
-            />
-            <a class="dropdown-menu-item" v-text="i18n('installFrom', 'OpenUserJS')" href="https://openuserjs.org/" target="_blank" rel="noopener noreferrer"></a>
-            <a class="dropdown-menu-item" v-text="i18n('installFrom', 'GreasyFork')" href="https://greasyfork.org/scripts" target="_blank" rel="noopener noreferrer"></a>
-            <a
-              class="dropdown-menu-item"
-              v-text="i18n('buttonInstallFromURL')"
-              tabindex="0"
-              @click.prevent="installFromURL"
-            />
+            <template #content>
+              <a
+                class="dropdown-menu-item"
+                v-text="i18n('buttonNew')"
+                tabindex="0"
+                @click.prevent="editScript('_new')"
+              />
+              <a class="dropdown-menu-item" v-text="i18n('installFrom', 'OpenUserJS')" href="https://openuserjs.org/" target="_blank" rel="noopener noreferrer"></a>
+              <a class="dropdown-menu-item" v-text="i18n('installFrom', 'GreasyFork')" href="https://greasyfork.org/scripts" target="_blank" rel="noopener noreferrer"></a>
+              <a
+                class="dropdown-menu-item"
+                v-text="i18n('buttonInstallFromURL')"
+                tabindex="0"
+                @click.prevent="installFromURL"
+              />
+            </template>
           </dropdown>
           <tooltip :content="i18n('buttonUpdateAll')" placement="bottom" align="start">
             <a class="btn-ghost" tabindex="0" @click="updateAll">
@@ -47,34 +49,36 @@
           </a>
         </tooltip>
         <dropdown align="right" class="filter-sort">
-          <tooltip :content="i18n('labelSettings')" placement="bottom" slot="toggle">
+          <tooltip :content="i18n('labelSettings')" placement="bottom">
             <a class="btn-ghost" tabindex="0">
               <icon name="cog"/>
             </a>
           </tooltip>
-          <div>
-            <locale-group i18n-key="labelFilterSort">
-              <select :value="filters.sort.value" @change="onOrderChange">
-                <option
-                  v-for="(option, name) in filterOptions.sort"
-                  v-text="option.title"
-                  :key="name"
-                  :value="name">
-                </option>
-              </select>
-            </locale-group>
-          </div>
-          <div v-show="currentSortCompare">
-            <setting-check name="filters.showEnabledFirst"
-                           :label="i18n('optionShowEnabledFirst')" />
-          </div>
-          <div>
-            <setting-check name="filters.showOrder" :label="i18n('labelShowOrder')" />
-          </div>
-          <div class="mr-2c">
-            <setting-check name="filters.viewTable" :label="i18n('labelViewTable')" />
-            <setting-check name="filters.viewSingleColumn" :label="i18n('labelViewSingleColumn')" />
-          </div>
+          <template #content>
+            <div>
+              <locale-group i18n-key="labelFilterSort">
+                <select :modelValue="filters.sort.value" @update:modelValue="onOrderChange">
+                  <option
+                    v-for="(option, name) in filterOptions.sort"
+                    v-text="option.title"
+                    :key="name"
+                    :value="name">
+                  </option>
+                </select>
+              </locale-group>
+            </div>
+            <div v-show="currentSortCompare">
+              <setting-check name="filters.showEnabledFirst"
+                :label="i18n('optionShowEnabledFirst')" />
+            </div>
+            <div>
+              <setting-check name="filters.showOrder" :label="i18n('labelShowOrder')" />
+            </div>
+            <div class="mr-2c">
+              <setting-check name="filters.viewTable" :label="i18n('labelViewTable')" />
+              <setting-check name="filters.viewSingleColumn" :label="i18n('labelViewSingleColumn')" />
+            </div>
+          </template>
         </dropdown>
         <!-- form and id are required for the built-in autocomplete using entered values -->
         <form class="filter-search hidden-xs flex" @submit.prevent>
@@ -89,11 +93,12 @@
                 id="installed-search">
               <icon name="search"></icon>
             </label>
-            <pre
-              class="filter-search-tooltip"
-              slot="content"
-              v-text="searchError || i18n('titleSearchHint')">
-            </pre>
+            <template #content>
+              <pre
+                class="filter-search-tooltip"
+                v-text="searchError || i18n('titleSearchHint')"
+              />
+            </template>
           </tooltip>
           <select v-model="filters.searchScope" @change="onScopeChange">
             <option value="name" v-text="i18n('filterScopeName')"/>
@@ -111,8 +116,8 @@
            ref="scriptList"
            :style="`--num-columns:${numColumns}`"
            :data-columns="numColumns"
-           :data-show-order="filters.showOrder"
-           :data-table="filters.viewTable">
+           :data-show-order="filters.showOrder || null"
+           :data-table="filters.viewTable || null">
         <script-item
           v-for="(script, index) in sortedScripts"
           v-show="!search || script.$cache.show !== false"
@@ -130,7 +135,7 @@
           @update="handleActionUpdate"
           @move="moveScript"
           @scrollDelta="handleSmoothScroll"
-          @tiptoggle.native="showHotkeys = !showHotkeys"
+          @tiptoggle="showHotkeys = !showHotkeys"
         />
       </div>
     </div>
@@ -139,23 +144,23 @@
 </template>
 
 <script>
-import Dropdown from 'vueleton/lib/dropdown/bundle';
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { i18n, sendCmdDirectly, debounce, makePause } from '#/common';
-import options from '#/common/options';
-import { showConfirmation, showMessage } from '#/common/ui';
-import SettingCheck from '#/common/ui/setting-check';
-import hookSetting from '#/common/hook-setting';
-import Icon from '#/common/ui/icon';
-import LocaleGroup from '#/common/ui/locale-group';
-import { forEachKey } from '#/common/object';
-import { setRoute, lastRoute } from '#/common/router';
-import storage from '#/common/storage';
-import { keyboardService, handleTabNavigation } from '#/common/keyboard';
-import { loadData } from '#/options';
+import Dropdown from 'vueleton/lib/dropdown';
+import Tooltip from 'vueleton/lib/tooltip';
+import { i18n, sendCmdDirectly, debounce, makePause } from '@/common';
+import options from '@/common/options';
+import { showConfirmation, showMessage } from '@/common/ui';
+import SettingCheck from '@/common/ui/setting-check';
+import hookSetting from '@/common/hook-setting';
+import Icon from '@/common/ui/icon';
+import LocaleGroup from '@/common/ui/locale-group';
+import { forEachKey } from '@/common/object';
+import { setRoute, lastRoute } from '@/common/router';
+import storage from '@/common/storage';
+import { keyboardService, handleTabNavigation } from '@/common/keyboard';
+import { loadData } from '@/options';
 import ScriptItem from './script-item';
 import Edit from './edit';
-import { store } from '../utils';
+import { store, installedScripts, removedScripts } from '../utils';
 
 const filterOptions = {
   sort: {
@@ -278,6 +283,8 @@ export default {
         limit: step,
       },
       numColumns: null,
+      scripts: installedScripts,
+      trash: removedScripts,
     };
   },
   watch: {
@@ -317,17 +324,11 @@ export default {
       }
       return null;
     },
-    scripts() {
-      return this.store.installedScripts;
-    },
     searchNeedsCodeIds() {
       return this.search
         && ['code', 'all'].includes(filters.searchScope)
         && this.store.scripts.filter(s => s.$cache.code == null).map(s => s.props.id);
     },
-    trash() {
-      return this.store.removedScripts;
-    },
   },
   methods: {
     async refreshUI() {
@@ -362,7 +363,9 @@ export default {
         url = url?.trim();
         if (url) {
           if (!url.includes('://')) url = `https://${url}`;
-          if (new URL(url)) await sendCmdDirectly('ConfirmInstall', { url });
+          // test if URL is valid
+          new URL(url);
+          await sendCmdDirectly('ConfirmInstall', { url });
         }
       } catch (err) {
         if (err) showMessage({ text: err });
@@ -656,7 +659,7 @@ export default {
       ]),
     ];
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.disposeList?.forEach(dispose => {
       dispose();
     });
@@ -684,7 +687,7 @@ export default {
   .vl-dropdown.active .vl-tooltip-wrap {
     display: none;
   }
-  @media (max-width: 500px) { // same size as `hidden-sm` in #/common/ui/style/style.css
+  @media (max-width: 500px) { // same size as `hidden-sm` in @/common/ui/style/style.css
     .vl-dropdown-right .vl-dropdown-menu {
       position: fixed;
       top: auto;

+ 11 - 11
src/options/views/tab-settings/index.vue

@@ -131,16 +131,16 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { debounce, i18n } from '#/common';
-import { INJECT_AUTO, INJECT_PAGE, INJECT_CONTENT } from '#/common/consts';
-import SettingCheck from '#/common/ui/setting-check';
-import { forEachEntry, mapEntry } from '#/common/object';
-import options from '#/common/options';
-import optionsDefaults from '#/common/options-defaults';
-import hookSetting from '#/common/hook-setting';
-import LocaleGroup from '#/common/ui/locale-group';
-import loadZip from '#/common/zip';
+import Tooltip from 'vueleton/lib/tooltip';
+import { debounce, i18n } from '@/common';
+import { INJECT_AUTO, INJECT_PAGE, INJECT_CONTENT } from '@/common/consts';
+import SettingCheck from '@/common/ui/setting-check';
+import { forEachEntry, mapEntry } from '@/common/object';
+import options from '@/common/options';
+import optionsDefaults from '@/common/options-defaults';
+import hookSetting from '@/common/hook-setting';
+import LocaleGroup from '@/common/ui/locale-group';
+import loadZip from '@/common/zip';
 import VmImport from './vm-import';
 import VmExport from './vm-export';
 import VmSync from './vm-sync';
@@ -262,7 +262,7 @@ export default {
     // Preload zip.js when user visits settings tab
     loadZip();
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.revokers.forEach((revoke) => { revoke(); });
   },
 };

+ 3 - 3
src/options/views/tab-settings/vm-blacklist.vue

@@ -10,9 +10,9 @@
 </template>
 
 <script>
-import { sendCmd } from '#/common';
-import { showMessage } from '#/common/ui';
-import SettingText from '#/common/ui/setting-text';
+import { sendCmd } from '@/common';
+import { showMessage } from '@/common/ui';
+import SettingText from '@/common/ui/setting-text';
 
 export default {
   components: {

+ 2 - 2
src/options/views/tab-settings/vm-css.vue

@@ -7,8 +7,8 @@
 </template>
 
 <script>
-import { showMessage } from '#/common/ui';
-import SettingText from '#/common/ui/setting-text';
+import { showMessage } from '@/common/ui';
+import SettingText from '@/common/ui/setting-text';
 
 export default {
   components: {

+ 6 - 6
src/options/views/tab-settings/vm-editor.vue

@@ -20,10 +20,10 @@
 </template>
 
 <script>
-import options from '#/common/options';
-import hookSetting from '#/common/hook-setting';
-import { showMessage } from '#/common/ui';
-import SettingText from '#/common/ui/setting-text';
+import options from '@/common/options';
+import hookSetting from '@/common/hook-setting';
+import { showMessage } from '@/common/ui';
+import SettingText from '@/common/ui/setting-text';
 
 const keyThemeCSS = 'editorTheme';
 const keyThemeNAME = 'editorThemeName';
@@ -64,7 +64,7 @@ export default {
   components: {
     SettingText,
   },
-  beforeDestroy() {
+  beforeUnmount() {
     this.revokers.forEach(revoke => revoke());
     this.revokers = null;
   },
@@ -134,7 +134,7 @@ export default {
       const opts = {};
       Object.entries({
         ...(await import('codemirror')).defaults,
-        ...(await import('#/common/ui/code')).default.data().cmDefaults,
+        ...(await import('@/common/ui/code')).default.data().cmDefaults,
         ...options.get('editor'),
       })
       // sort by keys alphabetically to make it more readable

+ 12 - 12
src/options/views/tab-settings/vm-export.vue

@@ -30,18 +30,18 @@
 </template>
 
 <script>
-import Modal from 'vueleton/lib/modal/bundle';
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import Icon from '#/common/ui/icon';
-import { getScriptName, sendCmdDirectly } from '#/common';
-import { formatDate, DATE_FMT } from '#/common/date';
-import { objectGet } from '#/common/object';
-import options from '#/common/options';
-import ua from '#/common/ua';
-import SettingCheck from '#/common/ui/setting-check';
-import SettingText from '#/common/ui/setting-text';
-import { downloadBlob } from '#/common/download';
-import loadZip from '#/common/zip';
+import Modal from 'vueleton/lib/modal';
+import Tooltip from 'vueleton/lib/tooltip';
+import Icon from '@/common/ui/icon';
+import { getScriptName, sendCmdDirectly } from '@/common';
+import { formatDate, DATE_FMT } from '@/common/date';
+import { objectGet } from '@/common/object';
+import options from '@/common/options';
+import ua from '@/common/ua';
+import SettingCheck from '@/common/ui/setting-check';
+import SettingText from '@/common/ui/setting-text';
+import { downloadBlob } from '@/common/download';
+import loadZip from '@/common/zip';
 import { store } from '../../utils';
 
 /**

+ 6 - 6
src/options/views/tab-settings/vm-import.vue

@@ -18,12 +18,12 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { ensureArray, i18n, sendCmdDirectly } from '#/common';
-import options from '#/common/options';
-import SettingCheck from '#/common/ui/setting-check';
-import loadZipLibrary from '#/common/zip';
-import { showConfirmation, showMessage } from '#/common/ui';
+import Tooltip from 'vueleton/lib/tooltip';
+import { ensureArray, i18n, sendCmdDirectly } from '@/common';
+import options from '@/common/options';
+import SettingCheck from '@/common/ui/setting-check';
+import loadZipLibrary from '@/common/zip';
+import { showConfirmation, showMessage } from '@/common/ui';
 
 const reports = [];
 

+ 6 - 6
src/options/views/tab-settings/vm-sync.vue

@@ -70,12 +70,12 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { noop, sendCmd } from '#/common';
-import options from '#/common/options';
-import SettingCheck from '#/common/ui/setting-check';
-import hookSetting from '#/common/hook-setting';
-import Icon from '#/common/ui/icon';
+import Tooltip from 'vueleton/lib/tooltip';
+import { noop, sendCmd } from '@/common';
+import options from '@/common/options';
+import SettingCheck from '@/common/ui/setting-check';
+import hookSetting from '@/common/hook-setting';
+import Icon from '@/common/ui/icon';
 import { store } from '../../utils';
 
 const SYNC_CURRENT = 'sync.current';

+ 2 - 2
src/options/views/tab-settings/vm-template.vue

@@ -6,8 +6,8 @@
 </template>
 
 <script>
-import { showMessage } from '#/common/ui';
-import SettingText from '#/common/ui/setting-text';
+import { showMessage } from '@/common/ui';
+import SettingText from '@/common/ui/setting-text';
 
 export default {
   components: {

+ 9 - 13
src/popup/index.js

@@ -1,21 +1,17 @@
-import Vue from 'vue';
-import '#/common/browser';
-import { sendCmdDirectly } from '#/common';
-import { INJECT_PAGE } from '#/common/consts';
-import handlers from '#/common/handlers';
-import { loadScriptIcon } from '#/common/load-script-icon';
-import { forEachValue, mapEntry } from '#/common/object';
-import '#/common/ui/style';
+import '@/common/browser';
+import { sendCmdDirectly } from '@/common';
+import { INJECT_PAGE } from '@/common/consts';
+import handlers from '@/common/handlers';
+import { loadScriptIcon } from '@/common/load-script-icon';
+import { forEachValue, mapEntry } from '@/common/object';
+import { render } from '@/common/ui';
+import '@/common/ui/style';
 import App from './views/app';
 import { mutex, store } from './utils';
 
 mutex.init();
 
-const vm = new Vue({
-  render: h => h(App),
-})
-.$mount();
-document.body.append(vm.$el);
+render(App);
 
 Object.assign(handlers, {
   async SetPopup(data, src) {

+ 4 - 2
src/popup/utils/index.js

@@ -1,4 +1,6 @@
-export const store = {
+import { reactive } from 'vue';
+
+export const store = reactive({
   scripts: [],
   frameScripts: [],
   scriptIds: [],
@@ -7,7 +9,7 @@ export const store = {
   injectionFailure: null,
   injectable: true,
   blacklisted: false,
-};
+});
 
 export const mutex = {
   init(delay = 100) {

+ 11 - 11
src/popup/views/app.vue

@@ -41,8 +41,8 @@
     </div>
     <div class="menu" v-if="store.injectable" v-show="store.domain">
       <div class="menu-item menu-area menu-find" :tabIndex="tabIndex">
-        <template v-for="(url, text, i) in findUrls">
-          <a :key="url" target="_blank" :class="{ ellipsis: !i, 'mr-1': !i, 'ml-1': i }"
+        <template v-for="(url, text, i) in findUrls" :key="url">
+          <a target="_blank" :class="{ ellipsis: !i, 'mr-1': !i, 'ml-1': i }"
              :href="url" :data-message="url.split('://')[1]">
             <icon name="search" v-if="!i"/>{{text}}
           </a>
@@ -119,7 +119,7 @@
           </div>
           <div v-if="item.excludesValue != null" class="excludes-menu flex flex-col">
             <textarea v-model="item.excludesValue" spellcheck="false"
-                      :rows="CalcRows(item.excludesValue)"/>
+                      :rows="calcRows(item.excludesValue)"/>
             <div>
               <button v-text="i18n('buttonOK')" @click="onExcludeSave(item)"/>
               <button v-text="i18n('buttonCancel')" @click="onExcludeClose(item)"/>
@@ -183,13 +183,13 @@
 </template>
 
 <script>
-import Tooltip from 'vueleton/lib/tooltip/bundle';
-import { INJECT_AUTO } from '#/common/consts';
-import options from '#/common/options';
-import { getScriptName, i18n, makePause, sendCmd, sendTabCmd } from '#/common';
-import { objectPick } from '#/common/object';
-import Icon from '#/common/ui/icon';
-import { keyboardService, isInput } from '#/common/keyboard';
+import Tooltip from 'vueleton/lib/tooltip';
+import { INJECT_AUTO } from '@/common/consts';
+import options from '@/common/options';
+import { getScriptName, i18n, makePause, sendCmd, sendTabCmd } from '@/common';
+import { objectPick } from '@/common/object';
+import Icon from '@/common/ui/icon';
+import { keyboardService, isInput } from '@/common/keyboard';
 import { mutex, store } from '../utils';
 
 const manifest = browser.runtime.getManifest();
@@ -511,7 +511,7 @@ export default {
     // issue #1520: Firefox + Wayland doesn't autofocus the popup so CSS hover doesn't work
     this.focusBug = !document.hasFocus();
   },
-  beforeDestroy() {
+  beforeUnmount() {
     keyboardService.disable();
     this.disposeList?.forEach(dispose => { dispose(); });
   },

+ 15 - 18
test/background/script.test.js

@@ -1,5 +1,4 @@
-import test from 'tape';
-import { parseMeta } from '#/background/utils/script';
+import { parseMeta } from '@/background/utils/script';
 
 const baseMeta = {
   include: [],
@@ -11,8 +10,8 @@ const baseMeta = {
   resources: {},
 };
 
-test('parseMeta', (t) => {
-  t.deepEqual(parseMeta(`\
+test('parseMeta', () => {
+  expect(parseMeta(`\
 // ==UserScript==
 // @name New Script
 // @namespace Violentmonkey Scripts
@@ -21,7 +20,7 @@ test('parseMeta', (t) => {
 // @match *://*/*
 // @grant none
 // ==/UserScript==
-`), Object.assign({}, baseMeta, {
+`)).toEqual(Object.assign({}, baseMeta, {
     name: 'New Script',
     namespace: 'Violentmonkey Scripts',
     description: 'This is a script',
@@ -29,48 +28,46 @@ test('parseMeta', (t) => {
     match: ['*://*/*'],
     grant: ['none'],
   }));
-  t.deepEqual(parseMeta(`\
+  expect(parseMeta(`\
 // ==UserScript==
 // @name New Script
 // @namespace Violentmonkey Scripts
 // @match *://*/*
 // @noframes
 // ==/UserScript==
-`), Object.assign({}, baseMeta, {
+`)).toEqual(Object.assign({}, baseMeta, {
     name: 'New Script',
     namespace: 'Violentmonkey Scripts',
     match: ['*://*/*'],
     noframes: true,
   }));
-  t.end();
 });
 
-test('parseMetaIrregularities', (t) => {
-  t.deepEqual(parseMeta(`\
+test('parseMetaIrregularities', () => {
+  expect(parseMeta(`\
   // ==UserScript==============
 // @name foo
  // @namespace bar
 // ==/UserScript===================
-  `), {
+  `)).toEqual({
     ...baseMeta,
     name: 'foo',
     namespace: 'bar',
   });
-  t.deepEqual(parseMeta(`\
+  expect(parseMeta(`\
 // ==UserScript==
 //@name foo
-// ==/UserScript==`), baseMeta);
-  t.deepEqual(parseMeta(`\
+// ==/UserScript==`)).toEqual(baseMeta);
+  expect(parseMeta(`\
 //==UserScript==
 // @name foo
-//\t==/UserScript==`), baseMeta);
-  t.deepEqual(parseMeta(`\
+//\t==/UserScript==`)).toEqual(baseMeta);
+  expect(parseMeta(`\
 /*
 //
   ==UserScript==
 // @name foo
 //
 ==/UserScript==
-*/`), baseMeta);
-  t.end();
+*/`)).toEqual(baseMeta);
 });

+ 197 - 154
test/background/tester.test.js

@@ -1,8 +1,7 @@
-import test from 'tape';
-import { testScript, testBlacklist, resetBlacklist } from '#/background/utils/tester';
-import cache from '#/background/utils/cache';
+import { testScript, testBlacklist, resetBlacklist } from '@/background/utils/tester';
+import cache from '@/background/utils/cache';
 
-test.onFinish(cache.destroy);
+afterEach(cache.destroy);
 
 function buildScript(props) {
   return Object.assign({
@@ -16,8 +15,8 @@ function buildScript(props) {
   }, props);
 }
 
-test('scheme', (t) => {
-  t.test('should match all', (q) => {
+describe('scheme', () => {
+  test('should match all', () => {
     {
       const script = buildScript({
         meta: {
@@ -26,9 +25,9 @@ test('scheme', (t) => {
           ],
         },
       });
-      q.ok(testScript('http://www.google.com/', script), 'should match `http | https`');
-      q.ok(testScript('https://www.google.com/', script), 'should match `http | https`');
-      q.notOk(testScript('file:///Users/Gerald/file', script), 'should not match `file`');
+      expect(testScript('http://www.google.com/', script)).toBeTruthy();
+      expect(testScript('https://www.google.com/', script)).toBeTruthy();
+      expect(testScript('file:///Users/Gerald/file', script)).toBeFalsy();
     }
     {
       const script = buildScript({
@@ -38,14 +37,13 @@ test('scheme', (t) => {
           ],
         },
       });
-      q.ok(testScript('http://www.google.com/', script), 'should match `http | https`');
-      q.ok(testScript('https://www.google.com/', script), 'should match `http | https`');
-      q.notOk(testScript('file:///Users/Gerald/file', script), 'should not match `file`');
+      expect(testScript('http://www.google.com/', script)).toBeTruthy();
+      expect(testScript('https://www.google.com/', script)).toBeTruthy();
+      expect(testScript('file:///Users/Gerald/file', script)).toBeFalsy();
     }
-    q.end();
   });
 
-  t.test('should match exact', (q) => {
+  test('should match exact', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -55,18 +53,15 @@ test('scheme', (t) => {
         ],
       },
     });
-    q.ok(testScript('http://www.google.com/', script), 'should match `http`');
-    q.notOk(testScript('https://www.google.com/', script), 'should not match `https`');
-    q.ok(testScript('file:///Users/Gerald/file', script), 'should match `file`');
-    q.ok(testScript('ftp://example.com/file', script), 'should match `ftp`');
-    q.end();
+    expect(testScript('http://www.google.com/', script)).toBeTruthy();
+    expect(testScript('https://www.google.com/', script)).toBeFalsy();
+    expect(testScript('file:///Users/Gerald/file', script)).toBeTruthy();
+    expect(testScript('ftp://example.com/file', script)).toBeTruthy();
   });
-
-  t.end();
 });
 
-test('host', (t) => {
-  t.test('should match domain', (q) => {
+describe('host', () => {
+  test('should match domain', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -74,13 +69,12 @@ test('host', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://docs.google.com/', script), 'should match exact domain name');
-    q.notOk(testScript('https://sub.docs.google.com/', script), 'should not match subdomains');
-    q.notOk(testScript('https://docs.google.com.cn/', script), 'should not match suffixed domains');
-    q.end();
+    expect(testScript('https://docs.google.com/', script)).toBeTruthy();
+    expect(testScript('https://sub.docs.google.com/', script)).toBeFalsy();
+    expect(testScript('https://docs.google.com.cn/', script)).toBeFalsy();
   });
 
-  t.test('should match wildcard', (q) => {
+  test('should match wildcard', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -89,17 +83,20 @@ test('host', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match subdomains');
-    q.ok(testScript('https://a.b.google.com/', script), 'should match subdomains');
-    q.ok(testScript('https://google.com/', script), 'should match specified domain');
-    q.notOk(testScript('https://www.google.com.hk/', script), 'should not match suffixed domains');
-    q.ok(testScript('https://www.example.com/', script), 'should match prefix');
-    q.ok(testScript('https://www.example.com.cn/', script), 'should match prefix');
-    q.ok(testScript('https://www.example.g.com/', script), 'should match prefix');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://a.b.google.com/',
+      'https://google.com/',
+      'https://www.example.com/',
+      'https://www.example.com.cn/',
+      'https://www.example.g.com/',
+    ].forEach(url => {
+        expect(testScript(url, script)).toBeTruthy();
+      });
+    expect(testScript('https://www.google.com.hk/', script)).toBeFalsy();
   });
 
-  t.test('should match tld', (q) => {
+  test('should match tld', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -108,16 +105,23 @@ test('host', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match subdomains');
-    q.ok(testScript('https://www.google.com.cn/', script), 'should match subdomains');
-    q.ok(testScript('https://www.google.jp/', script), 'should match tld');
-    q.ok(testScript('https://www.google.no-ip.org/', script), 'should match a hyphened `no-ip.org` from Public Suffix List');
-    q.notOk(testScript('https://www.google.example.com/', script), 'should not match subdomains');
-    q.notOk(testScript('https://www.dummy.com/', script), '`.tld` should be lowercase');
-    q.end();
+    [
+    'https://www.google.com/', // should match subdomains
+    'https://www.google.com.cn/', // should match subdomains
+    'https://www.google.jp/', // should match tld
+    'https://www.google.no-ip.org/', // should match a hyphened `no-ip.org` from Public Suffix List
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
+    [
+      'https://www.google.example.com/',
+      'https://www.dummy.com/', // `.tld` should be lowercase
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
   });
 
-  t.test('should ignore case', (q) => {
+  test('should ignore case', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -125,15 +129,12 @@ test('host', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://google.COM/', script), 'should ignore case');
-    q.end();
+    expect(testScript('https://google.COM/', script)).toBeTruthy();
   });
-
-  t.end();
 });
 
-test('path', (t) => {
-  t.test('should match any', (q) => {
+describe('path', () => {
+  test('should match any', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -141,12 +142,15 @@ test('path', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match `/`');
-    q.ok(testScript('https://www.google.com/hello/world', script), 'should match any');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com/hello/world',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
   });
 
-  t.test('should match exact', (q) => {
+  test('should match exact', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -154,12 +158,11 @@ test('path', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/a/b/c', script), 'should match exact');
-    q.notOk(testScript('https://www.google.com/a/b/c/d', script), 'should match exact');
-    q.end();
+    expect(testScript('https://www.google.com/a/b/c', script)).toBeTruthy();
+    expect(testScript('https://www.google.com/a/b/c/d', script)).toBeFalsy();
   });
 
-  t.test('should ignore query string and hash', (q) => {
+  test('should ignore query string and hash', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -167,14 +170,17 @@ test('path', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/a', script), 'should match without query and hash');
-    q.ok(testScript('https://www.google.com/a#hash', script), 'should match with hash');
-    q.ok(testScript('https://www.google.com/a?query', script), 'should match with query');
-    q.ok(testScript('https://www.google.com/a?query#hash', script), 'should match with query and hash');
-    q.end();
+    [
+      'https://www.google.com/a', // should match without query and hash
+      'https://www.google.com/a#hash', // should match with hash
+      'https://www.google.com/a?query', // should match with query
+      'https://www.google.com/a?query#hash', // should match with query and hash
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
   });
 
-  t.test('should match query string and hash if existed in rules', (q) => {
+  test('should match query string and hash if existed in rules', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -184,17 +190,24 @@ test('path', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/a', script), 'should match query');
-    q.notOk(testScript('https://www.google.com/b', script), 'should match hash');
-    q.ok(testScript('https://www.google.com/a?query', script), 'should match query');
-    q.ok(testScript('https://www.google.com/a?query#hash', script), 'should match query and ignore hash');
-    q.notOk(testScript('https://www.google.com/b?query#hash', script), 'should match query and hash');
-    q.ok(testScript('https://www.google.com/b#hash', script), 'should match hash');
-    q.ok(testScript('https://www.google.com/c?query#hash', script), 'should match query and hash');
-    q.end();
+    [
+      'https://www.google.com/a?query',
+      'https://www.google.com/a?query#hash',
+      'https://www.google.com/b#hash',
+      'https://www.google.com/c?query#hash',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
+    [
+      'https://www.google.com/a',
+      'https://www.google.com/b',
+      'https://www.google.com/b?query#hash',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
   });
 
-  t.test('should be case-sensitive', (q) => {
+  test('should be case-sensitive', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -203,18 +216,23 @@ test('path', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/a?Query', script), 'query should be case-sensitive');
-    q.notOk(testScript('https://www.google.com/a?query', script), 'query should be case-sensitive');
-    q.ok(testScript('https://www.google.com/b#Hash', script), 'hash should be case-sensitive');
-    q.notOk(testScript('https://www.google.com/b#hash', script), 'hash should be case-sensitive');
-    q.end();
+    [
+      'https://www.google.com/a?Query',
+      'https://www.google.com/b#Hash',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
+    [
+      'https://www.google.com/a?query',
+      'https://www.google.com/b#hash',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
   });
-
-  t.end();
 });
 
-test('include', (t) => {
-  t.test('should include any', (q) => {
+describe('include', () => {
+  test('should include any', () => {
     const script = buildScript({
       meta: {
         include: [
@@ -222,12 +240,15 @@ test('include', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match `http | https`');
-    q.ok(testScript('file:///Users/Gerald/file', script), 'should match `file`');
-    q.end();
+    [
+      'https://www.google.com/',
+      'file:///Users/Gerald/file'
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
   });
 
-  t.test('should include by glob', (q) => {
+  test('should include by glob', () => {
     const script = buildScript({
       meta: {
         include: [
@@ -236,13 +257,16 @@ test('include', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match `/`');
-    q.ok(testScript('https://www.google.com/hello/world', script), 'include by prefix');
-    q.notOk(testScript('https://www.hello.com/', script), 'not include by prefix');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com/hello/world',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
+    expect(testScript('https://www.hello.com/', script)).toBeFalsy();
   });
 
-  t.test('should include by regexp', (q) => {
+  test('should include by regexp', () => {
     const script = buildScript({
       meta: {
         include: [
@@ -251,12 +275,11 @@ test('include', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should ignore the invalid regexp and match target');
-    q.notOk(testScript('https://www.hello.com/', script), 'should not match nontarget');
-    q.end();
+    expect(testScript('https://www.google.com/', script)).toBeTruthy();
+    expect(testScript('https://www.hello.com/', script)).toBeFalsy();
   });
 
-  t.test('should support magic TLD', (q) => {
+  test('should support magic TLD', () => {
     const script = buildScript({
       meta: {
         include: [
@@ -264,14 +287,17 @@ test('include', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.google.com/', script), 'should match `.com`');
-    q.ok(testScript('https://www.google.com.hk/', script), 'should match `.com.hk`');
-    q.ok(testScript('https://www.google.no-ip.org/', script), 'should match a hyphened `no-ip.org` from Public Suffix List');
-    q.notOk(testScript('https://www.google.example.com/', script), 'should not match subdomains');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com.hk/',
+      'https://www.google.no-ip.org/',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
+    expect(testScript('https://www.google.example.com/', script)).toBeFalsy();
   });
 
-  t.test('should ignore case', (q) => {
+  test('should ignore case', () => {
     const script = buildScript({
       meta: {
         include: [
@@ -280,14 +306,17 @@ test('include', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://www.GOOGLE.com/', script), 'should ignore case');
-    q.ok(testScript('https://www.REGEXP.com/', script), 'should ignore case');
-    q.end();
+    [
+      'https://www.GOOGLE.com/',
+      'https://www.REGEXP.com/',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeTruthy();
+    });
   });
 });
 
-test('exclude', (t) => {
-  t.test('should exclude any', (q) => {
+describe('exclude', () => {
+  test('should exclude any', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -298,11 +327,10 @@ test('exclude', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/', script), 'should exclude `http | https`');
-    q.end();
+    expect(testScript('https://www.google.com/', script)).toBeFalsy();
   });
 
-  t.test('should include by glob', (q) => {
+  test('should include by glob', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -314,13 +342,16 @@ test('exclude', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/', script), 'should exclude `/`');
-    q.notOk(testScript('https://www.google.com/hello/world', script), 'exclude by prefix');
-    q.ok(testScript('https://www.hello.com/', script), 'not exclude by prefix');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com/hello/world',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
+    expect(testScript('https://www.hello.com/', script)).toBeTruthy();
   });
 
-  t.test('should support magic TLD', (q) => {
+  test('should support magic TLD', () => {
     const script = buildScript({
       meta: {
         exclude: [
@@ -328,16 +359,19 @@ test('exclude', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/', script), 'should match `.com`');
-    q.notOk(testScript('https://www.google.com.hk/', script), 'should match `.com.hk`');
-    q.notOk(testScript('https://www.google.no-ip.org/', script), 'should match a hyphened `no-ip.org` from Public Suffix List');
-    q.ok(testScript('https://www.google.example.com/', script), 'should not match subdomains');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com.hk/',
+      'https://www.google.no-ip.org/',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
+    expect(testScript('https://www.google.example.com/', script)).toBeTruthy();
   });
 });
 
-test('exclude-match', (t) => {
-  t.test('should exclude any', (q) => {
+describe('exclude-match', () => {
+  test('should exclude any', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -348,11 +382,10 @@ test('exclude-match', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/', script), 'should exclude `http | https`');
-    q.end();
+    expect(testScript('https://www.google.com/', script)).toBeFalsy();
   });
 
-  t.test('should include by glob', (q) => {
+  test('should include by glob', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -364,13 +397,16 @@ test('exclude-match', (t) => {
         ],
       },
     });
-    q.notOk(testScript('https://www.google.com/', script), 'should exclude `/`');
-    q.notOk(testScript('https://www.google.com/hello/world', script), 'exclude by prefix');
-    q.ok(testScript('https://www.hello.com/', script), 'not exclude by prefix');
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com/hello/world',
+    ].forEach(url => {
+      expect(testScript(url, script)).toBeFalsy();
+    });
+    expect(testScript('https://www.hello.com/', script)).toBeTruthy();
   });
 
-  t.test('should ignore case only in host', (q) => {
+  test('should ignore case only in host', () => {
     const script = buildScript({
       meta: {
         match: [
@@ -378,14 +414,13 @@ test('exclude-match', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://google.COM/FOO?BAR#HASH', script), 'should ignore case in host');
-    q.notOk(testScript('https://google.com/foo?bar#hash', script), 'should ignore case in host only');
-    q.end();
+    expect(testScript('https://google.COM/FOO?BAR#HASH', script)).toBeTruthy();
+    expect(testScript('https://google.com/foo?bar#hash', script)).toBeFalsy();
   });
 });
 
-test('custom', (t) => {
-  t.test('should ignore original rules', (q) => {
+describe('custom', () => {
+  test('should ignore original rules', () => {
     const script = buildScript({
       custom: {
         match: [
@@ -398,43 +433,51 @@ test('custom', (t) => {
         ],
       },
     });
-    q.ok(testScript('https://twitter.com/', script), 'should match custom rules');
-    q.notOk(testScript('https://www.google.com/', script), 'should not match original rules');
-    q.end();
+    expect(testScript('https://twitter.com/', script)).toBeTruthy();
+    expect(testScript('https://www.google.com/', script)).toBeFalsy();
   });
 });
 
-test('blacklist', (t) => {
-  t.test('should exclude match rules', (q) => {
+describe('blacklist', () => {
+  test('should exclude match rules', () => {
     resetBlacklist(`\
 # match rules
 *://www.google.com/*
 `);
-    q.ok(testBlacklist('http://www.google.com/'));
-    q.ok(testBlacklist('https://www.google.com/'));
-    q.notOk(testBlacklist('https://twitter.com/'));
-    q.end();
+    [
+      'http://www.google.com/',
+      'https://www.google.com/',
+    ].forEach(url => {
+      expect(testBlacklist(url)).toBeTruthy();
+    });
+    expect(testBlacklist('https://twitter.com/')).toBeFalsy();
   });
 
-  t.test('should exclude domains', (q) => {
+  test('should exclude domains', () => {
     resetBlacklist(`\
 # domains
 www.google.com
 `);
-    q.ok(testBlacklist('http://www.google.com/'));
-    q.ok(testBlacklist('https://www.google.com/'));
-    q.notOk(testBlacklist('https://twitter.com/'));
-    q.end();
+    [
+      'http://www.google.com/',
+      'https://www.google.com/',
+    ].forEach(url => {
+      expect(testBlacklist(url)).toBeTruthy();
+    });
+    expect(testBlacklist('https://twitter.com/')).toBeFalsy();
   });
 
-  t.test('should support @exclude rules', (q) => {
+  test('should support @exclude rules', () => {
     resetBlacklist(`\
 # @exclude rules
 @exclude https://www.google.com/*
 `);
-    q.ok(testBlacklist('https://www.google.com/'));
-    q.ok(testBlacklist('https://www.google.com/whatever'));
-    q.notOk(testBlacklist('http://www.google.com/'));
-    q.end();
+    [
+      'https://www.google.com/',
+      'https://www.google.com/whatever',
+    ].forEach(url => {
+      expect(testBlacklist(url)).toBeTruthy();
+    });
+    expect(testBlacklist('http://www.google.com/')).toBeFalsy();
   });
 });

+ 47 - 46
test/common/index.test.js

@@ -1,41 +1,47 @@
-import test from 'tape';
 import {
   isRemote, compareVersion, debounce, throttle,
-} from '#/common';
+} from '@/common';
 import { mocker } from '../mock';
 
-test('isRemote', (t) => {
-  t.notOk(isRemote());
-  t.notOk(isRemote('file:///tmp/file'));
-  t.notOk(isRemote('data:text/plain,hello,world'));
-  t.ok(isRemote('http://www.google.com'));
-  t.ok(isRemote('https://www.google.com'));
-  t.notOk(isRemote('http://localhost/a.user.js'));
-  t.notOk(isRemote('https://localhost/a.user.js'));
-  t.notOk(isRemote('http://127.0.0.1/a.user.js'));
-  t.notOk(isRemote('http://127.0.0.1:5555/a.user.js'));
-  t.notOk(isRemote('http://192.168.1.32/a.user.js'));
-  t.notOk(isRemote('http://172.16.0.1/a.user.js'));
-  t.notOk(isRemote('http://10.0.0.1/a.user.js'));
-  t.notOk(isRemote('http://[::1]/a.user.js'));
-  t.notOk(isRemote('http://[fe80::6996:2ba9:37e6:8762]/a.user.js'));
-  t.notOk(isRemote('http://[fc00::90:90]/a.user.js'));
-  t.notOk(isRemote('http://example.test/a.user.js'));
-  t.notOk(isRemote('https://example.example/a.user.js'));
-  t.notOk(isRemote('http://example.invalid/a.user.js'));
-  t.notOk(isRemote('https://example.localhost/a.user.js'));
-  t.end();
+test('isRemote', () => {
+  [
+    isRemote(),
+    isRemote('file:///tmp/file'),
+    isRemote('data:text/plain,hello,world'),
+    isRemote('http://localhost/a.user.js'),
+    isRemote('https://localhost/a.user.js'),
+    isRemote('http://127.0.0.1/a.user.js'),
+    isRemote('http://127.0.0.1:5555/a.user.js'),
+    isRemote('http://192.168.1.32/a.user.js'),
+    isRemote('http://172.16.0.1/a.user.js'),
+    isRemote('http://10.0.0.1/a.user.js'),
+    isRemote('http://[::1]/a.user.js'),
+    isRemote('http://[fe80::6996:2ba9:37e6:8762]/a.user.js'),
+    isRemote('http://[fc00::90:90]/a.user.js'),
+    isRemote('http://example.test/a.user.js'),
+    isRemote('https://example.example/a.user.js'),
+    isRemote('http://example.invalid/a.user.js'),
+    isRemote('https://example.localhost/a.user.js'),
+  ].forEach(f => {
+    expect(f).toBeFalsy();
+  });
+  [
+    isRemote('http://www.google.com'),
+    isRemote('https://www.google.com'),
+  ].forEach(t => {
+    expect(t).toBeTruthy();
+  });
 });
 
-test('compareVersion', (t) => {
-  t.equal(compareVersion('1.2.3', '1.2.3'), 0);
-  t.equal(compareVersion('1.2.3', '1.2.0'), 1);
-  t.equal(compareVersion('1.2.3', '1.2.4'), -1);
-  t.equal(compareVersion('1.2.0', '1.2'), 0);
-  t.equal(compareVersion('1.2.1', '1.2'), 1);
-  t.equal(compareVersion('1.1.9', '1.2'), -1);
-  t.equal(compareVersion('1.10', '1.9'), 1);
-  t.deepEqual([
+test('compareVersion', () => {
+  expect(compareVersion('1.2.3', '1.2.3')).toEqual(0);
+  expect(compareVersion('1.2.3', '1.2.0')).toEqual(1);
+  expect(compareVersion('1.2.3', '1.2.4')).toEqual(-1);
+  expect(compareVersion('1.2.0', '1.2')).toEqual(0);
+  expect(compareVersion('1.2.1', '1.2')).toEqual(1);
+  expect(compareVersion('1.1.9', '1.2')).toEqual(-1);
+  expect(compareVersion('1.10', '1.9')).toEqual(1);
+  expect([
     '1.2.3',
     '1.2.3-alpha',
     '1.0.0-x.7.z.92',
@@ -57,7 +63,7 @@ test('compareVersion', (t) => {
     '1.0.0-alpha.beta+build',
     '1.0.0-alpha.1',
     '1.0.0-alpha',
-  ].sort(compareVersion), [
+  ].sort(compareVersion)).toEqual([
     '1.0.0-alpha',
     '1.0.0-alpha',
     '1.0.0-alpha.1',
@@ -80,10 +86,9 @@ test('compareVersion', (t) => {
     '10.5.5',
     '11.3.0',
   ]);
-  t.end();
 });
 
-test('debounce', (t) => {
+test('debounce', () => {
   const log = [];
   const fn = debounce((i) => {
     log.push(i);
@@ -97,11 +102,10 @@ test('debounce', (t) => {
     fn(i);
     mocker.clock.tick(600);
   }
-  t.deepEqual(log, [2, 0, 1, 2]);
-  t.end();
+  expect(log).toEqual([2, 0, 1, 2]);
 });
 
-test('debounce with invalid time', (t) => {
+test('debounce with invalid time', () => {
   for (const time of [undefined, -100]) {
     const log = [];
     const fn = debounce((i) => {
@@ -111,12 +115,11 @@ test('debounce with invalid time', (t) => {
       fn(i);
     }
     mocker.clock.tick(500);
-    t.deepEqual(log, [2]);
+    expect(log).toEqual([2]);
   }
-  t.end();
 });
 
-test('throttle', (t) => {
+test('throttle', () => {
   const log = [];
   const fn = throttle((i) => {
     log.push(i);
@@ -130,11 +133,10 @@ test('throttle', (t) => {
     fn(i);
     mocker.clock.tick(600);
   }
-  t.deepEqual(log, [0, 3, 0, 1, 2]);
-  t.end();
+  expect(log).toEqual([0, 3, 0, 1, 2]);
 });
 
-test('throttle with invalid time', (t) => {
+test('throttle with invalid time', () => {
   for (const time of [undefined, -100]) {
     const log = [];
     const fn = throttle((i) => {
@@ -144,7 +146,6 @@ test('throttle with invalid time', (t) => {
       fn(i);
     }
     mocker.clock.tick(500);
-    t.deepEqual(log, [0]);
+    expect(log).toEqual([0]);
   }
-  t.end();
 });

+ 6 - 8
test/injected/gm-resource.test.js

@@ -1,6 +1,5 @@
-import test from 'tape';
-import { buffer2string } from '#/common';
-import { decodeResource } from '#/injected/content/util-content';
+import { buffer2string } from '@/common';
+import { decodeResource } from '@/injected/content/util-content';
 
 const stringAsBase64 = str => btoa(buffer2string(new TextEncoder().encode(str).buffer));
 
@@ -15,9 +14,8 @@ const RESOURCE_TEXT = 'abcd\u1234\u2345\u3456\u4567\u5678\u6789\u789A\u89AB\u9AB
 const DATA = `text/plain,${stringAsBase64(RESOURCE_TEXT)}`;
 const DATA_URL = `data:${DATA.replace(',', ';base64,')}`;
 
-test('@resource decoding', async (t) => {
-  t.equal(decodeResource(DATA), RESOURCE_TEXT, 'GM_getResourceText');
-  t.equal(await blobAsText(decodeResource(DATA, true)), RESOURCE_TEXT, 'GM_getResourceURL');
-  t.equal(decodeResource(DATA, false), DATA_URL, 'GM_getResourceURL as dataUrl');
-  t.end();
+test('@resource decoding', async () => {
+  expect(decodeResource(DATA)).toEqual(RESOURCE_TEXT);
+  expect(await blobAsText(decodeResource(DATA, true))).toEqual(RESOURCE_TEXT);
+  expect(decodeResource(DATA, false)).toEqual(DATA_URL);
 });

+ 5 - 7
test/injected/helpers.test.js

@@ -1,7 +1,6 @@
-import test from 'tape';
-import { jsonDump } from '#/injected/web/util-web';
+import { jsonDump } from '@/injected/web/util-web';
 
-test('jsonDump', (t) => {
+test('jsonDump', () => {
   const sameChildObj = { foo: 1 };
   // eslint-disable-next-line no-restricted-syntax
   for (const obj of [
@@ -29,12 +28,11 @@ test('jsonDump', (t) => {
       sameChild3: [sameChildObj],
     },
   ]) {
-    t.equal(jsonDump(obj), JSON.stringify(obj));
+    expect(jsonDump(obj)).toEqual(JSON.stringify(obj));
   }
-  t.throws(() => {
+  expect(() => {
     const cyclic = {};
     cyclic.foo = [1, 2, 3, { cyclic }];
     jsonDump(cyclic);
-  }, /Converting circular structure to JSON/, 'circular');
-  t.end();
+  }).toThrow(/Converting circular structure to JSON/);
 });

+ 4 - 4
test/mock/polyfill.js

@@ -32,7 +32,7 @@ for (const k of Object.keys(domProps)) {
 delete domProps.performance;
 Object.defineProperties(global, domProps);
 global.__VAULT_ID__ = false;
-Object.assign(global, require('#/common/safe-globals'));
-Object.assign(global, require('#/injected/safe-globals-injected'));
-Object.assign(global, require('#/injected/content/safe-globals-content'));
-Object.assign(global, require('#/injected/web/safe-globals-web'));
+Object.assign(global, require('@/common/safe-globals'));
+Object.assign(global, require('@/injected/safe-globals-injected'));
+Object.assign(global, require('@/injected/content/safe-globals-content'));
+Object.assign(global, require('@/injected/web/safe-globals-web'));

ファイルの差分が大きいため隠しています
+ 487 - 576
yarn.lock


この差分においてかなりの量のファイルが変更されているため、一部のファイルを表示していません